《ESP32从0到1》之MQTT与阿里iot通信(中)


文章内容

基于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 clientint 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物联网后台的数据上报

🌹欢迎关注、订阅、收藏🌹
文章中有不清晰或者有疑问、错误的地方,请务必留言提醒、讨论。非常感谢!!!

欢迎关注并留言

文章同步发表于公众号"IT搬砖客",欢迎关注并留言

### 回答1: ESP32可以通过阿里云提供的MQTT协议与阿里云进行通信,具体步骤如下: 1. 在阿里云上创建一个MQTT实例,并获取到AccessKey和SecretKey。 2. 在ESP32上安装MQTT客户端库,例如PubSubClient。 3. 在ESP32代码配置WiFi连接信息和阿里MQTT服务器地址、端口、AccessKey、SecretKey等相关信息。 4. 使用MQTT客户端库连接阿里MQTT服务器,并订阅需要的主题或发布消息。 5. 在阿里云上配置相应的规则引擎,用于处理ESP32发送的消息或发送指令到ESP32。 通过以上步骤,ESP32阿里云即可相互通信。具体实现过程可以参考阿里云官方文档和MQTT客户端库的示例代码。 ### 回答2: ESP32 是一款小型的、低功耗的开发板,可以用于物联网应用。它具有Wi-Fi和蓝牙功能,适用于与云平台进行通信阿里云是一家全球领先的云计算平台提供商,提供了丰富的服务和解决方案。 ESP32可以与阿里云相互通信,实现物联网设备与云平台之间的数据传输和控制。具体的步骤如下: 1. ESP32首先需要连接到Wi-Fi网络。通过ESP32的Wi-Fi功能,可以将设备连接到本地无线网络或者移动网络,确保设备与互联网相连。 2. 在阿里云平台上创建一个账号和设备。通过阿里云的控制台,可以注册一个账号,并创建一个设备,获取设备的唯一标识符(Device ID)和设备密钥(Device Secret)。 3. 在ESP32上安装相应的物联网开发框架,例如Arduino IDE或者MicroPython。这些开发框架提供了与阿里通信所需的库和示例代码。 4. 在ESP32的代码,使用阿里云提供的SDK或者库,初始化设备并设置与云平台的连接参数,例如设备ID和设备密钥。 5. 通过调用相应的API,ESP32可以向阿里云发送数据,或者从阿里云接收控制指令。例如,可以使用MQTT协议通过发布订阅方式实现数据传输。 6. 在阿里云平台上,可以配置规则引擎(Rule Engine)或者函数计算(Function Compute)等服务,对设备上报的数据进行处理和分析,实现更复杂的业务逻辑。 通过以上步骤,ESP32可以与阿里云平台相互通信,实现设备与云的数据交互和远程控制。这种通信方式为物联网应用提供了强大的支持,使得设备能够与云端实时交互,实现远程监控、数据分析和远程控制等功能。 ### 回答3: ESP32是一款高度集成的微控制器,具备Wi-Fi和蓝牙通信功能。而阿里云是一个领先的云计算平台,提供云服务和物联网解决方案。通过ESP32阿里云的相互通信,可以实现智能家居、车联网、工业自动化等各种物联网应用。 首先,ESP32可以使用其内置的Wi-Fi模块连接到阿里云服务器,实现与云平台的通信ESP32通过Wi-Fi模块建立与阿里云平台的连接,将传感器数据或其他设备信息发送到阿里云服务器。 其次,阿里云平台提供了物联网平台IoT Hub,可以创建设备连接,具备设备管理、数据通信、数据存储等功能。ESP32可以通过IoT Hub提供的API进行设备注册,获取设备凭证,从而连接到阿里云的物联网平台。 一旦ESP32成功连接到阿里云平台,可以使用MQTT或者HTTP协议进行数据的上传和下载。使用MQTT协议,ESP32可以向阿里云发送传感器数据,也可以接收来自阿里云的指令控制设备。使用HTTP协议,可以通过HTTP请求与阿里云平台进行数据交互。 另外,阿里云提供了物联网套件Link Kit,其包括设备端SDK和云端SDK。ESP32可以使用物联网套件的设备端SDK,结合阿里云的云端SDK,进行更便捷的通信和云平台管理。 综上所述,通过ESP32阿里云的相互通信,可以实现设备与云平台之间的数据交互、远程控制等功能,开发更多智能化的物联网应用。同时,阿里云提供了丰富的云服务,可以为ESP32提供设备管理、数据存储、数据分析等功能,进一步提升系统的可靠性和灵活性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT搬砖客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值