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

article/2025/6/26 22:45:06

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

http://www.hkcw.cn/article/liUeByQesS.shtml

相关文章

樊振东德国看球笑容满面 现身欧冠决赛现场

樊振东德国看球笑容满面 现身欧冠决赛现场!5月31日,樊振东在社交媒体上晒照发文:Guten Morgen, Mnchen! 早上好,慕尼黑!樊振东将作为特邀嘉宾现身欧冠决赛现场,观看巴黎圣日耳曼对阵国际米兰的比赛。这场2024至2025赛季欧洲足坛的巅峰对决将于北京时间6月1日凌晨在慕尼黑…

财经观察:一颗大豆的全球化博弈

【环球时报综合报道】编者的话:美国农业部出口检验报告显示,截至2025年5月15日的一周,美国大豆出口检验量较一周前减少50%,为21.78万吨,远低于市场普遍预期的42.5万吨。外媒分析称,受美国发起的关税战影响,美国大豆出口下降,巴西、阿根廷等国正在抓紧拓展中国大豆市场。…

(16)课35:分组时可以指定多个列名,以进行更细致的分组。窗口函数的练习例题:例题一学生成绩,

&#xff08;84&#xff09;分组时可以指定多个列名&#xff0c;一言也是这么说的 &#xff1a; 测试一下 &#xff1a; &#xff08;85&#xff09;练习一 &#xff1a; 针对该表完成第二个例题 &#xff1a; &#xff08;86&#xff09; 例题二 &#xff1a; &#xff08;8…

聚类分析 | MATLAB实现基于SOM自组织特征映射聚类可视化

聚类分析 | MATLAB实现基于SOM自组织特征映射聚类可视化 目录 聚类分析 | MATLAB实现基于SOM自组织特征映射聚类可视化效果一览 完整代码&#xff1a;[聚类分析 | MATLAB实现基于SOM自组织特征映射聚类可视化](https://blog.csdn.net/kjm13182345320/article/details/148350983…

【C++】23. unordered_map和unordered_set的使用

1. unordered_set系列的使用 1.1 unordered_set和unordered_multiset参考文档 https://legacy.cplusplus.com/reference/unordered_set/ 1.2 unordered_set类的详细介绍 • unordered_set的声明模板如下&#xff1a; template<class Key,class Hash std::hash<Ke…

意识之谜 | 粒子组合与单一意识的诞生

注&#xff1a;本文为“脑意识”相关合辑。 略作重排&#xff0c;未做探析。 从粒子到意识&#xff1a;人类大脑单意识诞生之谜 一、单个粒子无意识的本质原因 单个粒子作为物质的基本单元&#xff0c;仅遵循物理化学规律进行运动和相互作用&#xff0c;其行为具有机械性与确…

【更新至2023年】2000-2023年上市公司绿色专利申请授权面板数据

2000-2023年上市公司绿色专利申请授权面板数据 1、时间&#xff1a;2000-2023年 2、来源&#xff1a;国家知识产权局、WPIO清单 3、指标&#xff1a;年份、股票代码、股票简称、行业名称、行业代码、省份、城市、区县、区县代码、上市状态、绿色专利申请总量、绿色发明专利申…

第4章:操作系统

操作系统目的是&#xff1a;为了填补人与机器之间的鸿沟&#xff0c;即建立用户与计算机之间的接口&#xff0c;而为裸机配置的一种系统软件。 系统软件&#xff1a;编辑程序、汇编程序、编译程序、数据库管理系统等 操作系统在计算机系统中的地位&#xff1a; 程序与进程 程…

可靠性方框图绘制说明

下载安装绘制软件&#xff08;Isograph&#xff09;&#xff1a; Isograph Reliability Workbench 14.0 链接&#xff1a;https://pan.baidu.com/s/1owsSJWLtkqAQhbpWdK8keA?pwd49iu 提取码&#xff1a;49iu 制作可靠性方框图 创建RBD&#xff08;可靠性方框图&#xff…

Nougat:用于学术文档的神经光学理解

摘要 科学知识主要以PDF形式存储于书籍和科学期刊中。然而&#xff0c;PDF格式会导致语义信息的丢失&#xff0c;尤其是在数学表达式方面。我们提出了Nougat&#xff08;Neural Optical Understanding for Academic Documents&#xff09;&#xff0c;这是一种Visual transfor…

移除3D对象的某些部分点云

1&#xff0c;目的 移除3D对象指定区域的点云。效果 2&#xff0c;原理。 通过投影剔除指定区域外的点云数据。 3&#xff0c;主要的算子。 3.1&#xff0c;gen_image_gray_ramp 是 Halcon 中用于生成‌线性灰度渐变图像‌的算子 功能概述‌ 数学原理‌ 生成的图像灰度值…

达芬奇(DaVinci Resolve)下载安装教程

目录 一、软件介绍 二、软件下载 2.1 浏览器访问官网 2.2 进行软件包下载 三、软件安装 3.1 解压软件包 3.2 安装软件 四、基本配置 4.1 语言设置 4.2 其他设置 一、软件介绍 达芬奇&#xff08;DaVinci Resolve&#xff09;视频调色软件。DaVinci Resolve汇集剪辑、…

教室门口160cm是小学生低头线 高中版引发热议

教室门口160cm是小学生低头线!5月27日,山东济宁某高中教室内发生了一件有趣的事情。课间休息时,一位老师注意到学生们普遍较高,旁边正好站着一个身高182厘米的男生,于是老师以他为参照,在教室门口贴上一条胶带作为身高线。结果发现,不少男生经过时都需要低头,这一现象迅…

印尼力邀中企深化产业下游化 共创可持续发展

在印尼谈到与中国的经贸合作,“下游化”是最常提到的词。印尼国土辽阔,镍矿资源的储量和产量皆名列全球第一。坐拥全球绿色能源转型的关键金属,印尼吸引了大量全球投资者。单纯出口原始矿藏对当地就业帮助甚少,且由于附加值低,对印尼财政增收贡献有限。为此,印尼近10年来…

《天下第一楼》迎第600场演出 经典传承再续辉煌

6月2日晚,由何冀平编剧,夏淳、顾威、闫锐导演的北京人艺经典话剧《天下第一楼》在首都剧场迎来第600场演出。北京人艺院长冯远征当天再次强调了“深入生活、体验生活”对创作者的重要性。这部作品讲述了上个世纪初北京城饮食界老字号饭庄“福聚德”在时代变迁中的兴衰史。自1…

2022年上半年软件设计师下午试题

答案&#xff1a; 试题一&#xff1a; 试题二&#xff1a; 试题三&#xff1a; 试题四&#xff1a; 试题五: 试题六&#xff1a;

前端高频面试题2:JavaScript/TypeScript

1.什么是类数组对象 一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象&#xff0c;类数组对象和数组类似&#xff0c;但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果&#xff0c;还有一个函数也可以被看作是类数组对象&#xff…

花卉目标检测数据集介绍(共 12 类,10490 张图像)

在计算机视觉与智能农业快速发展的背景下&#xff0c;基于深度学习的花卉识别与检测技术正被广泛应用于植物分类、智能园艺、自动监测与生态研究等多个领域。为了推动花卉类目标检测任务的发展&#xff0c;本文介绍一个包含 12 种常见花卉类别的目标检测数据集&#xff0c;总计…

linux学习第18天(fork函数)

pid_t fork(void)&#xff1a;创建一个子进程 成功&#xff1a;父进程返回子进程pid 子进程返回0&#xff08;创建成功&#xff09; 失败&#xff1a;-1 getpid/getppid 举个例子&#xff0c;顺便演示getpid和getppid 子进程只能执行fork之后的代码&#xff0c;并且同时争夺c…

Pycharm的终端无法使用Anaconda命令行问题详细解决教程

很多初学者在Windows系统上安装了Anaconda后&#xff0c;在PyCharm终端中运行Conda命令时&#xff0c;会遇到以下错误&#xff1a; conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保…