首页 > 其他分享 >《ESP32从0到1》之MQTT与阿里iot通信(中)

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

时间:2024-09-13 20:48:57浏览次数:13  
标签:wifi ssid err ESP ESP32 iot MQTT password event

文章目录


文章内容

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

标签:wifi,ssid,err,ESP,ESP32,iot,MQTT,password,event
From: https://blog.csdn.net/u013534357/article/details/142219691

相关文章

  • 让ESP32-C3系列的卓越表现助力智能生活:四博智联全新推出的ESPC3-20E模块
    让ESP32-C3系列的卓越表现助力智能生活:四博智联全新推出的ESPC3-20E模块在智能生活和物联网领域,稳定的无线连接和低功耗设计已经成为推动技术发展的关键。作为行业领先的企业,四博智联凭借创新的产品设计,推出了全新的ESPC3-20EWi-Fi和蓝牙5.0双模模块。该模块结合了高性能......
  • MQTT 是什么以及它的工作原理
    平时学习MQTT或调试设备,推荐一款MQTT工具:MQTTAssistantMQTT是什么以及它的工作原理 #本文介绍了MQTT协议。MQTT代表消息队列遥测传输(MessageQueuingTelemetryTransport),是一种适用于物联网设备之间通信的简单消息传递协议。什么是MQTT? #MQTT代表消息队列......
  • 【花雕学编程】Arduino动手做(230)---ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记
    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来——小小的......
  • 使用WebSocket协议实现在ESP32上音频接收播放
     主要目的:学习WebSocket通讯协议和ESP32开发所需配置:Pycharm,python3.12,ESP32S3N16R8, 扬声器(8欧,2W), 功放模块:MAX98357I2SAMP。一、介绍 1、WebSocket协议 WebSocket是一种网络通信协议,位于OSI模型的应用层。它提供了在单个TCP连接上进行全双工通信的能力,使得客......
  • MQTT 协议概述
    目录一、概述二、协议模型1、组成部分2、客户端3、服务器三、MATT通信过程1、连接服务器2、订阅主题3、发布消息4、取消订阅5、断开连接四、MQTT数据包结构1、MQTT固定头2、MQTT可变头3.Payload消息体五、示例演示一、概述MQTT(MessageQueuingTelemetryTr......
  • MQTT是什么?
    1.MQTT是什么?         MQTT协议全称是(MessageQueuingTelemetryTransport),即消息队列遥测传输协议。是一种基于发布/订阅(Publish/Subscribe)模式的轻量级通讯协议,并且该协议构建于TCP/IP协议之上,我们知道TCP协议本身就具有高可靠性的特点,因此基于其上的MQTT协议同样......
  • 模块化IoT平台助力自动售货机大规模部署
    上半年全球知名的自动售货机供应商找到SECO赛柯寻求大批量的智能人机界面(HMI)解决方案。客户希望这些售货机不仅能够提供饮料,还能具备更多智能化功能。面对客户复杂的需求,SECO赛柯交出了一份满分答卷,不仅满足了客户独特的需求,还为其带来了显著的运营效率提升。客户需求● 大......
  • Desthiobiotin-CO-NH-C2-NH2|脱硫生物素-CO-NH-C2-NH2
    基本信息英文名称:Desthiobiotin-CO-NH-C2-NH2中文名称:脱硫生物素-CO-NH-C2-NH2结构式:CASnumber:N/A检测图谱:1HNMR外观:白色实心分子式(Molecularformula):C12H24N4O2分子量(Molecularweight):256.35外形:淡黄色油状溶解性:溶于大部分有机溶剂,溶于水储存条件:-20℃,......
  • Desthiobiotin-PEG3-Amine|cas:2237234-71-6|脱硫生物素-三聚乙二醇-胺
    基本信息中文名称:脱硫生物素-三聚乙二醇-胺英文名称:Desthiobiotin-PEG3-Amine结构式:CAS号:2237234-71-6分子式(Molecularformula):C18H36N4O5分子量(Molecularweigh):388.5存储时间:一年描述脱硫生物素-三聚乙二醇-胺(Desthiobiotin-PEG3-Amine)是一种化学合成的生物素衍生物......
  • Vue2 - 详细实现聊天室IM即时通讯及聊天界面,支持发送图片视频、消息已读未读等,集成mqt
    前言如果您需要Vue3版本,请访问在vue2|nuxt2项目开发中,详解手机移动端H5网页在线1v1聊天功能(仿腾讯云IM功能),技术栈为MQTT通讯协议+后端Node服务端+数据库设计+vue前端聊天界面,超详细前后端完整流程及示例源代码,vue2聊天即时通讯IM实时接收和发送消息,可发送文字、图......