> 技术文档 > SOC-ESP32S3部分:26-物联网MQTT连云_esp32s3 mqtt

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 字段或者 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); // 打印订阅成功的消息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配网

基于TLSMQTT客户端

涂鸦服务器参数生成

参考: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);}

漳州理工网