SOC-ESP32S3部分:26-物联网MQTT连云

发布于:2025-06-04 ⋅ 阅读:(39) ⋅ 点赞:(0)

飞书文档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 <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)
{
    // 打印事件信息,包括事件所属的基础类型和事件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 < 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)
    {
        // 如果配置过,就直接连接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: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.pem

openssl 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)
    {
        // 如果配置过,就直接连接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);
}

网站公告

今日签到

点亮在社区的每一天
去签到