当前位置: 首页 > news >正文

SOC-ESP32S3部分:26-物联网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 字段或者 hostnametransport 以及 port 的组合,可以设置服务器地址。也可以选择设置 path,该字段对 WebSocket 连接而言非常有用。

使用 uri 字段的格式为 scheme://hostname:port/path

  • 当前支持 mqttmqttswswss 协议
  • 基于 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_offsettotal_data_len 以跟踪碎片化消息。
  • MQTT_EVENT_ERROR:客户端遇到错误。使用事件数据 error_handle 字段中的 error_type,可以发现错误。错误类型决定 error_handle 结构体的哪些部分会被填充。

基于TCP无认证的MQTT客户端

MQTT客户端的实现流程如下

  1. 配置MQTT服务器参数
  2. 初始化MQTT客户端
  3. 设置MQTT事件回调函数
  4. 启动连接MQTT服务器
  5. 监听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); // 打印订阅成功的消息IDbreak;case MQTT_EVENT_UNSUBSCRIBED: // 当取消订阅请求成功时触发ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); // 打印取消订阅成功的消息IDbreak;case MQTT_EVENT_PUBLISHED: // 当发布请求成功时触发ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); // 打印发布成功的消息IDbreak;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); // 打印未知事件IDbreak;}
}

例如下方代码实现了连接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 <string.h>
#include <stdlib.h>
#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)
{// 打印事件信息,包括事件所属的基础类型和事件IDESP_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); // 打印发布成功的消息IDbreak;case MQTT_EVENT_UNSUBSCRIBED: // 当取消订阅请求成功时触发ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); // 打印取消订阅成功的消息IDbreak;case MQTT_EVENT_PUBLISHED: // 当发布请求成功时触发ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); // 打印发布成功的消息IDbreak;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); // 打印未知事件IDbreak;}
}// 启动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 < 33; i++){printf("%02x ", rvd_data[i]);}printf("\n");}// 断开当前 WiFi 连接,设置新的 WiFi 配置并重新连接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){// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);}
}static void smartconfig_example_task(void *parm)
{EventBits_t uxBits;wifi_config_t myconfig = {0};ESP_LOGI(TAG, "creat smartconfig_example_task");// 获取wifi配置信息esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);if (strlen((char *)myconfig.sta.ssid) > 0){// 如果配置过,就直接连接wifiESP_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和微信AIRKISSsmartconfig_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");// 停止 SmartConfigesp_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 模式为站点模式并启动 WiFiESP_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配网

基于TLSMQTT客户端

涂鸦服务器参数生成

参考:https://developer.tuya.com/cn/docs/iot/Protocol-Access?id=Kb3kq5nl2291v

创建产品,并生成产品相关参数,例如下方我的参数

ProductID:tbt2jeegdaywip9l
DeviceID:262cdb7f29dfc4bfdc8aqy
DeviceSecret:JbDQ6Wi9b0tADfcm
--------------------
Client ID:tuyalink_262cdb7f29dfc4bfdc8aqy
服务器地址:m1.tuyacn.com
端口: 8883
*用户名:262cdb7f29dfc4bfdc8aqy|signMethod=hmacSha256,timestamp=1739879739,secureMode=1,accessType=1
*密码:262f87274dc4febcd0b6a7ef9d6b2d73634edf7801b4d6d62b402f8e031769ca
SSL/TLS: true
证书类型: CA signed server
SSL安全: 开启
--------------------

涂鸦下载根证书

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 <string.h>
#include <stdlib.h>
#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 < 33; i++){printf("%02x ", rvd_data[i]);}printf("\n");}// 断开当前 WiFi 连接,设置新的 WiFi 配置并重新连接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){// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);}
}static void smartconfig_example_task(void *parm)
{EventBits_t uxBits;wifi_config_t myconfig = {0};ESP_LOGI(TAG, "creat smartconfig_example_task");// 获取wifi配置信息esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);if (strlen((char *)myconfig.sta.ssid) > 0){// 如果配置过,就直接连接wifiESP_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和微信AIRKISSsmartconfig_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");// 停止 SmartConfigesp_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 模式为站点模式并启动 WiFiESP_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);
}

相关文章:

  • Spring Cloud 2025 正式发布啦
  • MapReduce(期末速成版)
  • uniapp-商城-77-shop(8.2-商品列表,地址信息添加,级联选择器picker)
  • 每日一道面试题---ArrayList的自动扩容机制(口述版本)
  • Unity中应对高速运动的物体,碰撞组件失效的问题?
  • 计算机视觉---深度学习框架(Backbone、Neck、Head)
  • uniapp中view标签使用范围
  • Java实习面试题
  • 第十天:Java反射
  • vscode 插件 eslint, 检查 js 语法
  • 导入典籍数据
  • Kotlin 中 companion object 扩展函数和普通函数区别
  • 【Delphi】实现在多显示器时指定程序运行在某个显示器上
  • 使用 OpenCV (C/C++) 通过二值化增强车牌识别
  • 如何选择合适的哈希算法以确保数据安全?
  • AJAX对于XML和JSON的处理
  • 腾讯云 Python3.12.8 通过yum安装 并设置为默认版本
  • dify应用探索
  • 基于ubuntu和树莓派环境对游戏进行移植
  • imx6ull(0):烧录、启动
  • 商业计划书ppt免费模板下载/aso应用优化
  • 网站开发 界面/网络营销的企业有哪些
  • 合肥网站建设网页设计/网络推广app是违法的吗
  • 做h5的网站eup/成都网络营销公司
  • 小米网站建设/怎么收录网站
  • 青岛logo设计公司排名/大地seo