SOC-ESP32S3部分:26-物联网MQTT连云_esp32s3 mqtt
飞书文档https://x509p6c8to.feishu.cn/wiki/IGCawAgqFibop7kO83KcsDFBnNb
ESP-MQTT 是 MQTT 协议客户端的实现,MQTT 是一种基于发布/订阅模式的轻量级消息传输协议。ESP-MQTT 当前支持 MQTT v5.0。
特性
- 支持基于 TCP 的 MQTT、基于 Mbed TLS 的 SSL、基于 WebSocket 的 MQTT 以及基于 WebSocket Secure 的 MQTT
- 通过 URI 简化配置流程
- 多个实例(一个应用程序中有多个客户端)
- 支持订阅、发布、认证、遗嘱消息、保持连接心跳机制以及 3 个服务质量 (QoS) 级别(组成全功能客户端)
应用示例
- protocols/mqtt/tcp 演示了如何通过 TCP 实现 MQTT 通信(默认端口 1883)。
- protocols/mqtt/ssl 演示了如何使用 SSL 传输来实现基于 TLS 的 MQTT 通信(默认端口 8883)。
- protocols/mqtt/ssl_ds 演示了如何使用数字签名外设进行身份验证,以实现基于 TLS 的 MQTT 通信(默认端口 8883)。
- protocols/mqtt/ssl_mutual_auth 演示了如何使用证书进行身份验证实现 MQTT 通信(默认端口 8883)。
- protocols/mqtt/ssl_psk 演示了如何使用预共享密钥进行身份验证,以实现基于 TLS 的 MQTT 通信(默认端口 8883)。
- protocols/mqtt/ws 演示了如何通过 WebSocket 实现 MQTT 通信(默认端口 80)。
- protocols/mqtt/wss 演示了如何通过 WebSocket Secure 实现 MQTT 通信(默认端口 443)。
- protocols/mqtt5 演示了如何使用 ESP-MQTT 库通过 MQTT v5.0 连接到代理。
- protocols/mqtt/custom_outbox 演示了如何自定义 ESP-MQTT 库中的 outbox。
地址
通过 address 结构体的 uri 字段或者 hostname、transport 以及 port 的组合,可以设置服务器地址。也可以选择设置 path,该字段对 WebSocket 连接而言非常有用。
使用 uri 字段的格式为 scheme://hostname:port/path。
- 当前支持 mqtt、mqtts、ws 和 wss 协议
- 基于 TCP 的 MQTT 示例:
- mqtt://mqtt.eclipseprojects.io:基于 TCP 的 MQTT,默认端口 1883
- mqtt://mqtt.eclipseprojects.io:1884:基于 TCP 的 MQTT,端口 1884
- mqtt://username:password@mqtt.eclipseprojects.io:1884:基于 TCP 的 MQTT, 端口 1884,带有用户名和密码
- 基于 SSL 的 MQTT 示例:
- mqtts://mqtt.eclipseprojects.io:基于 SSL 的 MQTT,端口 8883
- mqtts://mqtt.eclipseprojects.io:8884:基于 SSL 的 MQTT,端口 8884
- 基于 WebSocket 的 MQTT 示例:
- ws://mqtt.eclipseprojects.io:80/mqtt
- 基于 WebSocket Secure 的 MQTT 示例:
- wss://mqtt.eclipseprojects.io:443/mqtt
- 最简配置:
const esp_mqtt_client_config_t mqtt_cfg = { .broker.address.uri = \"mqtt://mqtt.eclipseprojects.io\",};esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);esp_mqtt_client_start(client);
验证
为验证服务器身份,对于使用 TLS 的安全链接,必须设置 verification 结构体。 服务器证书可设置为 PEM 或 DER 格式。如要选择 DER 格式,必须设置等效 certificate_len 字段,否则应在 certificate 字段传入以空字符结尾的 PEM 格式字符串。
const esp_mqtt_client_config_t mqtt_cfg = { .broker = { .address.uri = \"mqtts://mqtt.eclipseprojects.io:8883\", .verification.certificate = (const char *)mqtt_eclipse_org_pem_start, },};
客户端凭据
credentials 字段下包含所有客户端相关凭据。
- username:指向用于连接服务器用户名的指针,也可通过 URI 设置
- client_id:指向客户端 ID 的指针,默认为 ESP32_%CHIPID%,其中 %CHIPID% 是十六进制 MAC 地址的最后 3 个字节
认证
可以通过 authentication 字段设置认证参数。客户端支持以下认证方式:
- password:使用密码
- certificate 和 key:进行双向 TLS 身份验证,PEM 或 DER 格式均可
- use_secure_element:使用 ESP32 中的安全元素 (ATECC608A)
- ds_data:使用某些乐鑫设备的数字签名外设
MQTT 客户端可能会发布以下事件:
- MQTT_EVENT_BEFORE_CONNECT:客户端已初始化并即将开始连接至服务器。
- MQTT_EVENT_CONNECTED:客户端已成功连接至服务器。客户端已准备好收发数据。
- MQTT_EVENT_DISCONNECTED:由于无法读取或写入数据,例如因为服务器无法使用,客户端已终止连接。
- MQTT_EVENT_SUBSCRIBED:服务器已确认客户端的订阅请求。事件数据将包含订阅消息的消息 ID。
- MQTT_EVENT_UNSUBSCRIBED:服务器已确认客户端的退订请求。事件数据将包含退订消息的消息 ID。
- MQTT_EVENT_PUBLISHED:服务器已确认客户端的发布消息。消息将仅针对 QoS 级别 1 和 2 发布,因为级别 0 不会进行确认。事件数据将包含发布消息的消息 ID。
- MQTT_EVENT_DATA:客户端已收到发布消息。事件数据包含:消息 ID、发布消息所属主题名称、收到的数据及其长度。对于超出内部缓冲区的数据,将发布多个 MQTT_EVENT_DATA,并更新事件数据的 current_data_offset 和 total_data_len 以跟踪碎片化消息。
- MQTT_EVENT_ERROR:客户端遇到错误。使用事件数据 error_handle 字段中的 error_type,可以发现错误。错误类型决定 error_handle 结构体的哪些部分会被填充。
基于TCP无认证的MQTT客户端
MQTT客户端的实现流程如下
- 配置MQTT服务器参数
- 初始化MQTT客户端
- 设置MQTT事件回调函数
- 启动连接MQTT服务器
- 监听MQTT事件进行业务处理
配置MQTT服务器参数
esp_mqtt_client_config_t描述: 配置MQTT客户端的参数。.broker.address.uri: MQTT代理服务器的URI地址。例如,\"mqtt://mqtt.eclipseprojects.io\"表示连接到Eclipse Mosquitto的公共MQTT代理。
初始化MQTT客户端
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config);功能: 初始化MQTT客户端并返回句柄。参数:config: 指向esp_mqtt_client_config_t结构体的指针,包含MQTT客户端的配置信息。返回值: 返回MQTT客户端的句柄。
设置MQTT事件回调函数
esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event_id, esp_event_handler_t event_handler, void *event_handler_arg);功能: 注册事件处理函数,用于处理MQTT客户端的各种事件。参数:client: MQTT客户端句柄。event_id: 事件ID,ESP_EVENT_ANY_ID表示监听所有事件。event_handler: 自定义的事件处理函数,例如mqtt_event_handler。event_handler_arg: 传递给事件处理函数的用户数据(可选)。返回值:ESP_OK: 注册成功。其他错误码: 注册失败。
启动连接MQTT服务器
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client);功能: 启动MQTT客户端,开始与代理服务器通信。参数:client: MQTT客户端句柄。返回值:ESP_OK: 启动成功。其他错误码: 启动失败
监听MQTT事件进行业务处理
// MQTT事件处理函数,用于处理MQTT客户端的各种事件static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data){ // 获取事件数据和MQTT客户端句柄 esp_mqtt_event_handle_t event = event_data; // 事件数据结构体 esp_mqtt_client_handle_t client = event->client; // MQTT客户端句柄 int msg_id; // 存储消息ID的变量 switch ((esp_mqtt_event_id_t)event_id) { // 根据事件ID执行不同的逻辑 case MQTT_EVENT_CONNECTED: // 当客户端成功连接到MQTT代理时触发 ESP_LOGI(TAG, \"MQTT_EVENT_CONNECTED\"); // 打印连接成功的日志 break; case MQTT_EVENT_DISCONNECTED: // 当客户端与MQTT代理断开连接时触发 ESP_LOGI(TAG, \"MQTT_EVENT_DISCONNECTED\"); // 打印断开连接的日志 break; case MQTT_EVENT_SUBSCRIBED: // 当订阅请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_SUBSCRIBED, msg_id=%d\", event->msg_id); // 打印订阅成功的消息ID break; case MQTT_EVENT_UNSUBSCRIBED: // 当取消订阅请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_UNSUBSCRIBED, msg_id=%d\", event->msg_id); // 打印取消订阅成功的消息ID break; case MQTT_EVENT_PUBLISHED: // 当发布请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_PUBLISHED, msg_id=%d\", event->msg_id); // 打印发布成功的消息ID break; case MQTT_EVENT_DATA: // 当接收到消息时触发 ESP_LOGI(TAG, \"MQTT_EVENT_DATA\"); // 打印接收到消息的日志 break; case MQTT_EVENT_ERROR: // 当发生错误时触发 ESP_LOGI(TAG, \"MQTT_EVENT_ERROR\"); // 打印错误日志 break; default: // 处理其他未定义的事件 ESP_LOGI(TAG, \"Other event id:%d\", event->event_id); // 打印未知事件ID break; }}
例如下方代码实现了连接MQTT服务器mqtt://mqtt.eclipseprojects.io,在接收到连接成功MQTT_EVENT_CONNECTED回调后发布一条消息到主题/topic/qos1,订阅两个主题/topic/qos0和/topic/qos1
msg_id = esp_mqtt_client_publish(client, \"/topic/qos1\", \"data_3\", 0, 1, 0);msg_id = esp_mqtt_client_subscribe(client, \"/topic/qos0\", 0);msg_id = esp_mqtt_client_subscribe(client, \"/topic/qos1\", 1);
在订阅成功后,继续发布一条消息到/topic/qos0
msg_id = esp_mqtt_client_publish(client, \"/topic/qos0\", \"data\", 0, 0, 0);
最终可以在MQTT_EVENT_DATA事件中,打印接收到的数据
ESP_LOGI(TAG, \"MQTT_EVENT_DATA\"); printf(\"TOPIC=%.*s\\r\\n\", event->topic_len, event->topic); printf(\"DATA=%.*s\\r\\n\", event->data_len, event->data);
参考代码如下:
#include #include #include \"freertos/FreeRTOS.h\"#include \"freertos/task.h\"#include \"freertos/event_groups.h\"#include \"esp_eap_client.h\"#include \"esp_netif.h\"#include \"esp_smartconfig.h\"#include \"esp_mac.h\"#include \"esp_system.h\"#include \"esp_wifi.h\"#include \"esp_event.h\"#include \"esp_log.h\"#include \"nvs_flash.h\"#include \"esp_log.h\"#include \"mqtt_client.h\"static const char *TAG = \"mqtt_example\";static EventGroupHandle_t s_wifi_event_group;static const int CONNECTED_BIT = BIT0;static const int ESPTOUCH_DONE_BIT = BIT1;static void smartconfig_example_task(void *parm);static bool is_connect_wifi = false;// MQTT事件处理函数,用于处理MQTT客户端的各种事件static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data){ // 打印事件信息,包括事件所属的基础类型和事件ID ESP_LOGD(TAG, \"Event dispatched from event loop base=%s, event_id=%\" PRIi32 \"\", base, event_id); // 获取事件数据和MQTT客户端句柄 esp_mqtt_event_handle_t event = event_data; // 事件数据结构体 esp_mqtt_client_handle_t client = event->client; // MQTT客户端句柄 int msg_id; // 存储消息ID的变量 switch ((esp_mqtt_event_id_t)event_id) { // 根据事件ID执行不同的逻辑 case MQTT_EVENT_CONNECTED: // 当客户端成功连接到MQTT代理时触发 ESP_LOGI(TAG, \"MQTT_EVENT_CONNECTED\"); // 打印连接成功的日志 // 发布一条QoS=1的消息到\"/topic/qos1\" 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); // 打印发布成功的消息ID // 订阅两个主题:QoS=0的\"/topic/qos0\"和QoS=1的\"/topic/qos1\" msg_id = esp_mqtt_client_subscribe(client, \"/topic/qos0\", 0); ESP_LOGI(TAG, \"sent subscribe successful, msg_id=%d\", msg_id); msg_id = esp_mqtt_client_subscribe(client, \"/topic/qos1\", 1); ESP_LOGI(TAG, \"sent subscribe successful, msg_id=%d\", msg_id); break; case MQTT_EVENT_DISCONNECTED: // 当客户端与MQTT代理断开连接时触发 ESP_LOGI(TAG, \"MQTT_EVENT_DISCONNECTED\"); // 打印断开连接的日志 break; case MQTT_EVENT_SUBSCRIBED: // 当订阅请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_SUBSCRIBED, msg_id=%d\", event->msg_id); // 打印订阅成功的消息ID // 发布一条QoS=0的消息到\"/topic/qos0\" msg_id = esp_mqtt_client_publish(client, \"/topic/qos0\", \"data\", 0, 0, 0); ESP_LOGI(TAG, \"sent publish successful, msg_id=%d\", msg_id); // 打印发布成功的消息ID break; case MQTT_EVENT_UNSUBSCRIBED: // 当取消订阅请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_UNSUBSCRIBED, msg_id=%d\", event->msg_id); // 打印取消订阅成功的消息ID break; case MQTT_EVENT_PUBLISHED: // 当发布请求成功时触发 ESP_LOGI(TAG, \"MQTT_EVENT_PUBLISHED, msg_id=%d\", event->msg_id); // 打印发布成功的消息ID break; case MQTT_EVENT_DATA: // 当接收到消息时触发 ESP_LOGI(TAG, \"MQTT_EVENT_DATA\"); // 打印接收到消息的日志 // 打印消息的主题和内容 printf(\"TOPIC=%.*s\\r\\n\", event->topic_len, event->topic); // 打印主题 printf(\"DATA=%.*s\\r\\n\", event->data_len, event->data); // 打印消息内容 break; case MQTT_EVENT_ERROR: // 当发生错误时触发 ESP_LOGI(TAG, \"MQTT_EVENT_ERROR\"); // 打印错误日志 // 如果错误类型是TCP传输错误,则打印详细的错误信息 if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { ESP_LOGI(TAG, \"Last errno string (%s)\", strerror(event->error_handle->esp_transport_sock_errno)); // 打印最后的错误描述 } break; default: // 处理其他未定义的事件 ESP_LOGI(TAG, \"Other event id:%d\", event->event_id); // 打印未知事件ID break; }}// 启动MQTT客户端的应用程序static void mqtt_app_start(void){ // 配置MQTT客户端参数 esp_mqtt_client_config_t mqtt_cfg = { .broker.address.uri = \"mqtt://mqtt.eclipseprojects.io\", // 设置MQTT代理服务器的URI地址 }; // 初始化MQTT客户端并获取句柄 esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); // 注册事件处理函数,监听所有MQTT事件,并将事件传递给`mqtt_event_handler` esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); // 启动MQTT客户端,开始与代理服务器通信 esp_mqtt_client_start(client);}static void http_get_task(void *pvParameters){ while (1) { if (is_connect_wifi) { mqtt_app_start(); while(1){ vTaskDelay(1000 / portTICK_PERIOD_MS); } } vTaskDelay(1000 / portTICK_PERIOD_MS); }}static void 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) { // WiFi 站点模式启动后,创建 SmartConfig 任务 xTaskCreate(smartconfig_example_task, \"smartconfig_example_task\", 4096, NULL, 3, NULL); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { is_connect_wifi = false; // WiFi 断开连接时,重新连接并清除连接标志位 esp_wifi_connect(); xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { // 获取到 IP 地址后,设置连接标志位 xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT); is_connect_wifi = true; } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) { // SmartConfig 扫描完成事件 ESP_LOGI(TAG, \"Scan done\"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) { // SmartConfig 找到信道事件 ESP_LOGI(TAG, \"Found channel\"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { // SmartConfig 获取到 SSID 和密码事件 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; uint8_t ssid[33] = {0}; uint8_t password[65] = {0}; uint8_t rvd_data[33] = {0}; 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)); 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) { // 如果使用的是 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 0) { // 如果配置过,就直接连接wifi ESP_LOGI(TAG, \"alrealy set, SSID is :%s,start connect\", myconfig.sta.ssid); esp_wifi_connect(); } else { // 如果没有配置过,就进行配网操作 ESP_LOGI(TAG, \"have no set, start to config\"); ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); // 支持APP ESPTOUCH和微信AIRKISS smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); } while (1) { // 等待连接标志位或 SmartConfig 完成标志位 uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); if (uxBits & CONNECTED_BIT) { // 连接到 AP 后的日志 ESP_LOGI(TAG, \"WiFi Connected to ap\"); // 联网成功后,可以关闭线程 vTaskDelete(NULL); } if (uxBits & ESPTOUCH_DONE_BIT) { // SmartConfig 完成后的日志 ESP_LOGI(TAG, \"smartconfig over\"); // 停止 SmartConfig esp_smartconfig_stop(); // 删除 SmartConfig 任务 vTaskDelete(NULL); } }}void app_main(void){ // 初始化 NVS 闪存 ESP_ERROR_CHECK( nvs_flash_init()); // 初始化网络接口 ESP_ERROR_CHECK(esp_netif_init()); // 创建事件组 s_wifi_event_group = xEventGroupCreate(); // 创建默认事件循环 ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认的 WiFi 站点模式网络接口 esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); assert(sta_netif); // 初始化 WiFi 配置 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 注册事件处理函数 ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); // 设置 WiFi 模式为站点模式并启动 WiFi ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); xTaskCreate(&http_get_task, \"http_get_task\", 9192, NULL, 5, NULL);}
运行结果如下
这个实验需要先完成WiFi配网哦,具体看WiFi章节24-WiFi配网
基于TLS的MQTT客户端
涂鸦服务器参数生成
参考:https://developer.tuya.com/cn/docs/iot/Protocol-Access?id=Kb3kq5nl2291v
创建产品,并生成产品相关参数,例如下方我的参数
ProductID:tbt2jeegdaywip9lDeviceID:262cdb7f29dfc4bfdc8aqyDeviceSecret:JbDQ6Wi9b0tADfcm--------------------Client ID:tuyalink_262cdb7f29dfc4bfdc8aqy服务器地址:m1.tuyacn.com端口: 8883*用户名:262cdb7f29dfc4bfdc8aqy|signMethod=hmacSha256,timestamp=1739879739,secureMode=1,accessType=1*密码:262f87274dc4febcd0b6a7ef9d6b2d73634edf7801b4d6d62b402f8e031769caSSL/TLS: true证书类型: CA signed serverSSL安全: 开启--------------------
涂鸦下载根证书
https://developer.tuya.com/cn/docs/iot/MQTT-protocol?id=Kb65nphxrj8f1
[Go Daddy Root Certificate Authority - G2.cer]
把文件改名为root_ca.cer,方便操作,然后通过openssl把二进制编码的的证书转换为pem,方便程序读取
openssl x509 -inform der -in root_ca.cer -out tuya_ca.pemopenssl x509:这是 OpenSSL 中用于处理 X.509 证书的子命令。-inform der:指定输入证书的格式为 DER(二进制编码),通常 .cer 文件是 DER 格式。-in root_ca.cer:指定输入的 .cer 证书文件路径。如果文件名包含空格,需要使用反斜杠 \\ 进行转义。-out tuya_ca.pem:指定输出的 .pem 证书文件路径和文件名。
最终得到pem格式的证书
[tuya_ca.pem]
放到工程main内,修改demo06/main/CMakeLists.txt导入
idf_component_register( SRCS \"main.c\" INCLUDE_DIRS \".\" EMBED_TXTFILES tuya_ca.pem )
最终实现如下:
#include #include #include \"freertos/FreeRTOS.h\"#include \"freertos/task.h\"#include \"freertos/event_groups.h\"#include \"esp_eap_client.h\"#include \"esp_netif.h\"#include \"esp_smartconfig.h\"#include \"esp_mac.h\"#include \"esp_system.h\"#include \"esp_wifi.h\"#include \"esp_event.h\"#include \"esp_log.h\"#include \"nvs_flash.h\"#include \"esp_log.h\"#include \"mqtt_client.h\"static const char *TAG = \"mqtt_example\";static EventGroupHandle_t s_wifi_event_group;static const int CONNECTED_BIT = BIT0;static const int ESPTOUCH_DONE_BIT = BIT1;static void smartconfig_example_task(void *parm);static bool is_connect_wifi = false;extern const uint8_t tuya_ca_pem_start[] asm(\"_binary_tuya_ca_pem_start\");extern const uint8_t tuya_ca_pem_end[] asm(\"_binary_tuya_ca_pem_end\");static void log_error_if_nonzero(const char *message, int error_code){ if (error_code != 0) { ESP_LOGE(TAG, \"Last error %s: 0x%x\", message, error_code); }}static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data){ ESP_LOGD(TAG, \"Event dispatched from event loop base=%s, event_id=%\" PRIi32 \"\", base, event_id); esp_mqtt_event_handle_t event = event_data; esp_mqtt_client_handle_t client = event->client; int msg_id; switch ((esp_mqtt_event_id_t)event_id) { case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, \"MQTT_EVENT_CONNECTED\"); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, \"MQTT_EVENT_DISCONNECTED\"); break; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG, \"MQTT_EVENT_SUBSCRIBED, msg_id=%d\", event->msg_id); break; case MQTT_EVENT_UNSUBSCRIBED: ESP_LOGI(TAG, \"MQTT_EVENT_UNSUBSCRIBED, msg_id=%d\", event->msg_id); break; case MQTT_EVENT_PUBLISHED: ESP_LOGI(TAG, \"MQTT_EVENT_PUBLISHED, msg_id=%d\", event->msg_id); break; case MQTT_EVENT_DATA: ESP_LOGI(TAG, \"MQTT_EVENT_DATA\"); printf(\"TOPIC=%.*s\\r\\n\", event->topic_len, event->topic); printf(\"DATA=%.*s\\r\\n\", event->data_len, event->data); break; case MQTT_EVENT_ERROR: ESP_LOGI(TAG, \"MQTT_EVENT_ERROR\"); if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { log_error_if_nonzero(\"reported from esp-tls\", event->error_handle->esp_tls_last_esp_err); log_error_if_nonzero(\"reported from tls stack\", event->error_handle->esp_tls_stack_err); log_error_if_nonzero(\"captured as transport\'s socket errno\", event->error_handle->esp_transport_sock_errno); ESP_LOGI(TAG, \"Last errno string (%s)\", strerror(event->error_handle->esp_transport_sock_errno)); } break; default: ESP_LOGI(TAG, \"Other event id:%d\", event->event_id); break; }}static void mqtt_app_start(void){ esp_mqtt_client_config_t mqtt_cfg = { .broker.address.uri = \"mqtts://m1.tuyacn.com:8883\", .credentials.client_id = \"tuyalink_262cdb7f29dfc4bfdc8aqy\", .credentials.username = \"262cdb7f29dfc4bfdc8aqy|signMethod=hmacSha256,timestamp=1739879739,secureMode=1,accessType=1\", .credentials.authentication.password = \"262f87274dc4febcd0b6a7ef9d6b2d73634edf7801b4d6d62b402f8e031769ca\", .broker.verification.certificate = (const char *)tuya_ca_pem_start }; esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */ esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); esp_mqtt_client_start(client);}static void http_get_task(void *pvParameters){ while (1) { if (is_connect_wifi) { mqtt_app_start(); while(1){ vTaskDelay(1000 / portTICK_PERIOD_MS); } } vTaskDelay(1000 / portTICK_PERIOD_MS); }}static void 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) { // WiFi 站点模式启动后,创建 SmartConfig 任务 xTaskCreate(smartconfig_example_task, \"smartconfig_example_task\", 4096, NULL, 3, NULL); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { is_connect_wifi = false; // WiFi 断开连接时,重新连接并清除连接标志位 esp_wifi_connect(); xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { // 获取到 IP 地址后,设置连接标志位 xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT); is_connect_wifi = true; } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) { // SmartConfig 扫描完成事件 ESP_LOGI(TAG, \"Scan done\"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) { // SmartConfig 找到信道事件 ESP_LOGI(TAG, \"Found channel\"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { // SmartConfig 获取到 SSID 和密码事件 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; uint8_t ssid[33] = {0}; uint8_t password[65] = {0}; uint8_t rvd_data[33] = {0}; 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)); 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) { // 如果使用的是 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 0) { // 如果配置过,就直接连接wifi ESP_LOGI(TAG, \"alrealy set, SSID is :%s,start connect\", myconfig.sta.ssid); esp_wifi_connect(); } else { // 如果没有配置过,就进行配网操作 ESP_LOGI(TAG, \"have no set, start to config\"); ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); // 支持APP ESPTOUCH和微信AIRKISS smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); } while (1) { // 等待连接标志位或 SmartConfig 完成标志位 uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); if (uxBits & CONNECTED_BIT) { // 连接到 AP 后的日志 ESP_LOGI(TAG, \"WiFi Connected to ap\"); // 联网成功后,可以关闭线程 vTaskDelete(NULL); } if (uxBits & ESPTOUCH_DONE_BIT) { // SmartConfig 完成后的日志 ESP_LOGI(TAG, \"smartconfig over\"); // 停止 SmartConfig esp_smartconfig_stop(); // 删除 SmartConfig 任务 vTaskDelete(NULL); } }}void app_main(void){ // 初始化 NVS 闪存 ESP_ERROR_CHECK( nvs_flash_init()); // 初始化网络接口 ESP_ERROR_CHECK(esp_netif_init()); // 创建事件组 s_wifi_event_group = xEventGroupCreate(); // 创建默认事件循环 ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认的 WiFi 站点模式网络接口 esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); assert(sta_netif); // 初始化 WiFi 配置 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 注册事件处理函数 ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); // 设置 WiFi 模式为站点模式并启动 WiFi ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); xTaskCreate(&http_get_task, \"http_get_task\", 9192, NULL, 5, NULL);}