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

news2025/6/5 11:03:07

飞书文档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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2397783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

制造业的未来图景:超自动化与劳动力转型的双重革命

市场现状&#xff1a;传统制造业的转型阵痛 当前全球制造业正站在历史性变革的十字路口。埃森哲对552位工厂经理的全球调研显示&#xff0c;60%的受访者将劳动力转型视为首要战略任务​​&#xff0c;而63%的工厂正在加速部署自动化技术[1]。超过​75%的工厂经理​​认为&…

【Unity】相机 Cameras

1 前言 主要介绍官方文档中相机模块的内容。 关于“9动态分辨率”&#xff0c;这部分很多API文档只是提了一下&#xff0c;具体细节还需要自己深入API才行。 2 摄像机介绍 Unity 场景在三维空间中表示游戏对象。由于观察者的屏幕是二维屏幕&#xff0c;Unity 需要捕捉视图并将…

如何在 Solana 上发币,并创建初始流动性让项目真正“动”起来?

在 Solana 上发行代币如今已不再是技术门槛&#xff0c;而是市场策略和执行效率的较量。如果你只是简单发了一个代币&#xff0c;却没为它建立流动性和市场机制&#xff0c;那么它就只是一个“死币”。 本文将带你一步步理解&#xff0c;如何从发币到建立流动性池&#xff0c;…

核心机制:滑动窗口

TCP 协议 1.确认应答 可靠传输的核心机制 2.超时重传 可靠传输的核心机制 3.连接管理 TCP/网络 最高的面试题 三次握手,建立连接(必须是 三次) 四次挥手,断开连接(可能是 三次) 核心机制四:滑动窗口 算法中的"滑动窗口" 出自 TCP 前面的三个…

苹果电脑深度清理,让老旧Mac重焕新生

在日常使用苹果电脑的过程中&#xff0c;随着时间推移&#xff0c;系统内会积累大量冗余数据&#xff0c;导致电脑运行速度变慢、磁盘空间紧张。想要让设备恢复流畅&#xff0c;苹果电脑深度清理必不可少。那么&#xff0c;如何进行苹果电脑深度清理呢&#xff1f;接下来为你详…

微服务面试(分布式事务、注册中心、远程调用、服务保护)

1.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c;例如&#xff1a; 跨数据源的分布式事务跨服务的分布式事务综合情况 我们之前解决分布式事务问题是直接使用Seata框架的AT模式&#xff0c;但是解决分布式事务…

高性能MYSQL(三):性能剖析

一、性能剖析概述 &#xff08;一&#xff09;关于性能优化 1.什么是性能&#xff1f; 我们将性能定义为完成某件任务所需要的时间度量&#xff0c;换句话说&#xff0c;性能即响应时间&#xff0c;这是一个非常重要的原则。 我们通过任务和时间而不是资源来测量性能。数据…

mysql(十四)

目录 多表查询 1.准备工作 2--创建表格 3--插入数据 2.笛卡尔积查询 3.内连接查询 1--隐式内连接 格式 查询 2--显示内连接&#xff08;Inner join .. on &#xff09; 格式 查询 4.外连接查询 1--左外连接查询&#xff08;LEFT OUTER JOIN .. ON &#xff09; 格式 查询 2-- 右…

工业物联网中的事件驱动采样架构及优化

论文标题 Event-Based Sampling Architecture and Optimization for Industrial Internet of Things 工业物联网中的事件驱动采样架构及优化 作者信息 Tejas Thosani Process Control Systems, Micron Technology Inc., Manassas, USA tthosanimicron.com Andres Prado Esp…

基于 HT for Web 的轻量化 3D 数字孪生数据中心解决方案

一、技术架构&#xff1a;HT for Web 的核心能力 图扑软件自主研发的 HT for Web 是基于 HTML5 的 2D/3D 可视化引擎&#xff0c;核心技术特性包括&#xff1a; 跨平台渲染&#xff1a;采用 WebGL 技术&#xff0c;支持 PC、移动端浏览器直接访问&#xff0c;兼容主流操作系统…

JavaScript 性能优化:从入门到实战

在当今快节奏的互联网时代&#xff0c;用户对网页和应用的加载速度与响应性能要求越来越高。JavaScript 作为网页交互的核心语言&#xff0c;其性能表现直接影响用户体验。本文将用简单易懂的语言&#xff0c;带你了解 JavaScript 性能优化的实用技巧&#xff0c;帮助你的代码跑…

启动metastore时报错MetaException(message:Version information not found in metastore

把hdfs清空重新安装了一下&#xff0c;hive的mysql元数据库删除掉之后重建之后一直启动报错 metastore.RetryingHMSHandler (RetryingHMSHandler.java:<init>(83)) - HMSHandler Fatal error: MetaException(message:Version information not found in metastore.) 后来…

MyBatisPlus(1):快速入门

我们知道&#xff0c;MyBatis是一个优秀的操作数据库的持久层框架&#xff08;优秀持久层框架——MyBatis&#xff09;&#xff0c;其基于底层的JDBC进行高度封装&#xff0c;极大的简化了开发。但是对于单表操作而言&#xff0c;我们需要重复地编写简单的CRUD语句。这其实是不…

京东热点缓存探测系统JDhotkey架构剖析

热点探测使用场景 MySQL 中被频繁访问的数据 &#xff0c;如热门商品的主键 IdRedis 缓存中被密集访问的 Key&#xff0c;如热门商品的详情需要 get goods$Id恶意攻击或机器人爬虫的请求信息&#xff0c;如特定标识的 userId、机器 IP频繁被访问的接口地址&#xff0c;如获取用…

【Elasticsearch】ILM(Index Lifecycle Management)策略详解

ILM&#xff08;Index Lifecycle Management&#xff09;策略详解 1.什么是 ILM 策略&#xff1f;2.ILM 解决的核心业务问题3.ILM 生命周期阶段3.1 Hot&#xff08;热阶段&#xff09;3.2 Warm&#xff08;温阶段&#xff09;3.3 Cold&#xff08;冷阶段&#xff09;3.4 Delete…

linux 后记

Linux Server 下载一个Server的版本&#xff0c;就是那种只有命令行的 学会这个就可以去租一个aliyun服务器&#xff0c;挺便宜的 如果在aliyun买服务器的话就不用管镜像源 但是如果是自己的虚拟机就必须设置镜像源&#xff0c;上网搜索阿里的镜像源&#xff0c;然后手动输入&…

【笔记】在 MSYS2 MINGW64 环境中安装构建工具链(CMake、GCC、Make)

&#x1f4dd; 在 MSYS2 MINGW64 环境中安装构建工具链&#xff08;CMake、GCC、Make&#xff09; ✅ 目标说明 记录在 MSYS2 的 MINGW64 工具链环境中&#xff0c;成功安装用于 C/C 构建的常用开发工具。 包括&#xff1a; GCC 编译器Make 构建系统CMake 跨平台构建工具基础开…

PyTorch -TensorBoard的使用 (一)

设置环境 新建python文件 .py 安装Tensorboard 在终端进行安装 显示安装成功 两个logs&#xff0c;出现这种情况怎么解决 所有的logs文件删掉delete&#xff0c;重新运行 add_image 不满足要求 Opencv-numpy 安装Opencv add_image 用法示例 &#xff08;500&#xff0c;375&am…

Redis最佳实践——性能优化技巧之数据结构选择

Redis在电商应用中的数据结构选择与性能优化技巧 一、电商核心场景与数据结构选型矩阵 应用场景推荐数据结构内存占用读写复杂度典型操作商品详情缓存Hash低O(1)HGETALL, HMSET购物车管理Hash中O(1)HINCRBY, HDEL用户会话管理Hash低O(1)HSETEX, HGET商品分类目录Sorted Set高O…

网络安全方向在校生有哪些证书适合考取?

工作7年得出结论&#xff1a;网络安全&#xff0c;考任何证书都没有用&#xff0c;实力才是根本。我是2021年考的 CISSP&#xff0c;报了培训班&#xff0c;花了1万一千块钱&#xff0c;签的保障班还是服务班不记得了&#xff0c;大概意思就是你放心去考&#xff0c;考不过可以…