文章目录
文章内容
基于MQTT->tcp结合wifi->smart_config示例工程,读懂程序,最终实现MQTT与阿里iot平台通信。
中篇:整合MQTT->tcp和wifi->smart_config示例,实现app快速配网,wifi连接成功后,定时发布MQTT主题,实现数据定时上报功能(依旧结合MQTTX实现测试);同时增加逻辑,配网成功后保存wifi参数,下次开机时实现自动联网。
硬件
● 一款 ESP32 开发板(本专辑中均使用ESP32-LyraT-Mini V1.2开发板)
● USB 数据线 (A 转 Micro-B)
● 电脑(Windows)
增加定时器,实现定时发布MQTT主题
仿照default_even_loop
,在main文件夹下增加一个event_source.h
头文件,用于声明和定义周期性计时器事件源相关内容。
app_main.c添加头文件
app_main
函数中去掉一些不必要的LOG,仿照default_even_loop的app_main
内容,增加定时器相关代码。
将计时器启动、超时、结束的事件处理程序移植过来,并按照需求修改内容。主要修改的处理程序是timer_expiry_handler
。其逻辑是:10s计时到了之后,清空计数变量,然后发布主题。重复循环,实现定时10s发布主题。
// 当循环执行计时器到期事件时执行的处理程序。
//此处理程序跟踪计时器过期的次数。当达到设定的到期次数时,处理程序会停止计时器并发送计时器停止事件。
static void timer_expiry_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
{
static int count = 0;
count++;
if (count >= TIMER_EXPIRIES_COUNT_10s) {
count=0;
//发布主题
msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
}
ESP_LOGI(TAG, "TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed %d out of %d times", count, TIMER_EXPIRIES_COUNT_10s);
}
mqtt_event_handler
中的MQTT_EVENT_CONNECTED
事件处理程序中,去掉之前的发布主题部分程序,添加启动定时器。将esp_mqtt_client_handle_t client
和int msg_id
从局部变量改成公共变量,以便计时器事件处理程序中调用。
//启动计时器
static void toStartTimer(void)
{
//此函数将启动计时器,计时器将在每“周期”微秒触发一次。
ESP_ERROR_CHECK(esp_timer_start_periodic(TIMER, TIMER_PERIOD));
//发布计时器启动事件
ESP_LOGI(TAG, "TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop");
ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_STARTED, NULL, 0, portMAX_DELAY));
}
运行测试下:
移植smart_config程序
tcp程序的app_main函数中去掉原先的esp_netif_init()
、example_connect()
,添加smart_config主程序的initialise_wifi();
将initialise_wifi()
、smartconfig_example_task()
和event_handler()
(为了便于识别理解,函数名改为wifi_event_handler
)移植到tcp程序中,主要改动内容:
● 引入头文件#include "esp_smartconfig.h
● initialise_wifi()
中注释掉esp_netif_create_default_wifi_sta();
● mqtt_app_start()
放到wifi_event_handler->IP_EVENT_STA_GOT_IP
事件处理程序中,只有网络连接成功了才运行MQTT部分程序
最终程序逻辑
● 上电,进入app_main();
● 初始化nvsflash
● 创建默认事件循环
● 将计时器处理程序注册到默认循环中
● 创建周期性计时器事件源
● 初始化wifi
● 启动smartconfig任务
● 收到配置的ssid和密码后连接wifi
● 连接wifi成功(获取到IP地址)后启动MQTT连接
● MQTT与服务器连接成功后,订阅主题,启动计时器
● 计时器每10秒发布一次主题内容
运行测试
保存ssid和password
基于storage->nvs_rw_blob示例程序,实现对ssid和password的断电保存。
说明:nvs不支持对结构的保存,因此对于ssid和password用字符串方式分别保存和读取
。
readWifiConfig()
:从nvs中读取wifi的ssid和password。
esp_err_t readWifiConfig(void)
{
nvs_handle_t my_handle;
esp_err_t err;
// Open
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
//读取ssid
size_t required_size = 0; // value will default to 0, if not set yet in NVS
// obtain required memory space to store blob being read from NVS
err = nvs_get_blob(my_handle, "last_ssid", NULL, &required_size);//如果读取到的是0则表示还没有保存
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
printf("last_ssid_size:%d\n",required_size);
if (required_size == 0) {
printf("Nothing saved yet!\n");
} else {
char* run_time = malloc(required_size);
err = nvs_get_blob(my_handle, "last_ssid", run_time, &required_size);
if (err != ESP_OK) {
free(run_time);
return err;
}
for (int i = 0; i < required_size / sizeof(char); i++) {
last_ssid[i]=run_time[i];
printf("%c", run_time[i]);
}
printf("\n");
free(run_time);
}
//读取password
required_size = 0; // value will default to 0, if not set yet in NVS
// obtain required memory space to store blob being read from NVS
err = nvs_get_blob(my_handle, "last_password", NULL, &required_size);//如果读取到的是0则表示还没有保存
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
printf("last_password_size:%d\n",required_size);
if (required_size == 0) {
printf("Nothing saved yet!\n");
} else {
char* run_time = malloc(required_size);
err = nvs_get_blob(my_handle, "last_password", run_time, &required_size);
if (err != ESP_OK) {
free(run_time);
return err;
}
for (int i = 0; i < required_size / sizeof(char); i++) {
last_password[i]=run_time[i];
printf("%c",run_time[i]);
}
printf("\n");
free(run_time);
}
// Close
nvs_close(my_handle);
return ESP_OK;
}
writeWifiConfig()
: wifi连接成功后将配网获取到的ssid和password写入nvs中。
esp_err_t writeWifiConfig(char *ssid,size_t ssidlen,char *password,size_t passwordlen)
{
nvs_handle_t my_handle;
esp_err_t err;
// Open
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
// printf("write last_ssid %s %d\n", ssid,ssidlen);
err = nvs_set_blob(my_handle, "last_ssid", ssid, ssidlen);
if (err != ESP_OK) return err;
// printf("write last_password %s %d\n",password,passwordlen);
err = nvs_set_blob(my_handle, "last_password", password, passwordlen);
if (err != ESP_OK) return err;
// Commit
// printf("Commit\n");
err = nvs_commit(my_handle);
if (err != ESP_OK) return err;
// Close
//printf("close handle\n");
nvs_close(my_handle);
return ESP_OK;
}
在IP_EVENT_STA_GOT_IP
事件处理程序中将ssid和password传递过去,调用writeWifiConfig
保存。
上电自动配网
修改逻辑,上电后从nvs_flash中读取ssid和password,如果没有保存则运行smart_config
任务;如果有保存,则配网然后连接wifi。
● 定义一个公共变量
uint8_t wifi_nvs_flash_flg=0;// =0 表示没有保存wifi ssid和password; =1 表示有保存,直接配置联网
● main函数中读取nvs_flash数据时判断其返回值,如果是ESP_OK
则表示有保存ssid和password,则wifi_nvs_flash_flg=1;
void app_main(void)
{
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// NVS partition was truncated and needs to be erased
// Retry nvs_flash_init
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
ESP_ERROR_CHECK(esp_event_loop_create_default());//创建默认事件循环
//定时器相关
//esp_event_handler_instance_register:将事件处理程序的实例注册到默认循环中。
//TIMER_EVENTS:.h中定义的Timer事件库
//TIMER_EVENT_STARTED:特定的事件,timer启动事件
//timer_started_handler:绑定的事件处理程序
//NULL,传递的参数到事件处理程序可以是NULL
//instance = NULL:与注册的事件处理程序和数据相关的事件处理进程实例对象可以为NULL
//绑定了计时器启动、超时、停止处理事件
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STARTED, timer_started_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_EXPIRY, timer_expiry_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STOPPED, timer_stopped_handler, NULL, NULL));
//创建周期性计时器事件源
esp_timer_create_args_t timer_args = {
.callback = &timer_callback,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &TIMER));
err = readWifiConfig();
if( err == ESP_OK)
{
wifi_nvs_flash_flg=1;
}
initialise_wifi();
}
● 主要修改集中在wifi_event_handler
函数中,设置成sta模式启动wifi后,判断是否有保存的ssid和password,如果有则直接配网、联网,如果没有则运行smart_config
任务。wifi事件和IP事件中都要判断上前是不是用的保存的ssid和password以便于与smart_config
触发的事件处理区分开。
char ssid[33] = { 0 };
char password[65] = { 0 };
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
if(wifi_nvs_flash_flg==1)
{
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config_t));
memcpy(wifi_config.sta.ssid, last_ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, last_password, sizeof(wifi_config.sta.password));
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
esp_wifi_connect();
}
else{ //启用smart_config
ESP_LOGI(TAG, "run smartconfig_example_task");
xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
if(wifi_nvs_flash_flg!=1)
{
xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
}
else
{
ESP_LOGI(TAG, "own wificonfig_disconnect");
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
if(wifi_nvs_flash_flg!=1)
{
xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
//保存正确联网的ssid和password
writeWifiConfig(ssid,sizeof(ssid),password,sizeof(password));
}
else
{
ESP_LOGI(TAG, "own IP_EVENT_STA_GOT_IP");
}
mqtt_app_start();
} else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
ESP_LOGI(TAG, "Scan done");
} else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
ESP_LOGI(TAG, "Found channel");
} else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
ESP_LOGI(TAG, "Got SSID and password");
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config_t));
memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
#ifdef CONFIG_SET_MAC_ADDRESS_OF_TARGET_AP
wifi_config.sta.bssid_set = evt->bssid_set;
if (wifi_config.sta.bssid_set == true) {
ESP_LOGI(TAG, "Set MAC address of target AP: "MACSTR" ", MAC2STR(evt->bssid));
memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
}
#endif
uint8_t rvd_data[33] = { 0 };
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
ESP_LOGI(TAG, "SSID:%s", ssid);
ESP_LOGI(TAG, "PASSWORD:%s", password);
if (evt->type == SC_TYPE_ESPTOUCH_V2) {
ESP_ERROR_CHECK( esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)) );
ESP_LOGI(TAG, "RVD_DATA:");
for (int i=0; i<33; i++) {
printf("%02x ", rvd_data[i]);
}
printf("\n");
}
ESP_ERROR_CHECK( esp_wifi_disconnect() );
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
esp_wifi_connect();
} else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
}
}
最终运行测试
上电后自动获取上一次保存的ssid和password直接实现联网,获取IP后启动MQTT程序,自动上报主题。
补充说明
程序上还是有逻辑漏洞,比如说网络参数变更后,之前保存的ssid和password不适用了,无法正常联网,此时就需要重新启动smart_config配网任务。这个坑后面做实际应用时再慢慢填。
另之前调试smart_config示例时发现一旦wifi配置错误会导致程序进入一个wifi死循环,既无法成功联网又无法重新配网,只能重启。解决办法:应用层面上增加计时器,时间到了之后若还没有联网成功则断开wifi连接,重新启动smart_config重新配网。
详见:https://blog.csdn.net/u013534357/article/details/142167495
《ESP32从0到1》之MQTT与阿里iot通信(下)预告:实现MQTT与阿里iot物联网后台的数据上报
标签:wifi,ssid,err,ESP,ESP32,iot,MQTT,password,event From: https://blog.csdn.net/u013534357/article/details/142219691