← 返回教程库

用 AI 搭一套 MQTT 收发:能跑的 ESP-IDF 工作流

最后更新 2026-06-22
L3 · 联网与 IoT ⏱ 约 15 分钟 🟢 软件/低风险
你将学到
  • 学会把板型、ESP-IDF 版本、esp-mqtt 组件、Broker、topic、数据格式一次说清,让 AI 产出能跑的事件驱动 MQTT 代码
  • 拿到一段完整可烧录的 ESP32-S3 esp_mqtt_client 发布 + 订阅工程,以及它"应该看到什么"的预期日志
  • 会把 idf.py monitor 日志喂给 AI 排连不上 / clientID 冲突 / 订阅不到 / 事件回调没触发这类常见故障
  • 用 MQTTX 客户端独立验证收发,分清哪些参数 AI 可能编、必须自己核

你已经把 MQTT 的原理啃下来了——Broker 中转、设备只认 topic、发布订阅互不相识。可一旦上手用 ESP-IDF 写,问题就来了:工程 flash 进去,idf.py monitor 卡在 MQTT_EVENT_BEFORE_CONNECT 不往下走,要么连上了发命令死活进不了 MQTT_EVENT_DATA 回调,要么板子每隔几秒抛一次 MQTT_EVENT_DISCONNECTED。原理懂了,落到能跑的代码之间,隔着一堆 ESP-IDF 特有的碎活——esp_mqtt_client_config_t 怎么填、事件回调怎么注册、payload 在 event->data 里怎么读、menuconfig 里 esp-mqtt 组件要不要动。

这正是该让 AI 上场的活。ESP-IDF 的 esp-mqtt 比 Arduino 那套样板多——事件枚举、回调签名、配置结构体嵌套,新手光看官方 example 就头大。但这些恰恰是 AI copilot 最擅长抹平的:你说清楚要什么,它把这堆样板一次性铺好,你只管判断对不对、亲手验证。这一篇不重复讲 MQTT 是什么(没看过原理的先补 MQTT 入门那节),只讲一件事:怎么把一个完整的 esp-mqtt 收发工程,用 AI 加速搭起来并跑通。读完你手里会有一段能直接 flash 的代码、一套排错的提问法、一个能独立验证收发的工具。AI 帮你出初稿、帮你看日志,你负责把关——这是 ESP-IDF 时代做硬件用 AI 最该有的分工。


先把上下文喂够:esp-mqtt 代码的"五件套"

AI 写 ESP-IDF MQTT 代码翻车,几乎都不是它笨,是你没说清。最常见的翻车是它默认给你一段 Arduino PubSubClient 的代码——WiFi.h + client.loop() 轮询那套,跟你的 ESP-IDF 工程根本不是一个世界,一编译全是找不到头文件。你得在提示词里把"我用 ESP-IDF、不要 Arduino"摆到最前面。

写 esp-mqtt,要一次说清这五件套

  • 板型 + 框架版本:ESP32-S3 开发板,ESP-IDF 5.x,用官方 esp-mqtt 组件esp_mqtt_client),不要 Arduino、不要 PubSubClient。这一句最关键——不点明,AI 大概率默认给 Arduino。
  • 联网前提:明确告诉它 WiFi 已经用 wifi_init_sta() 事件驱动骨架连上了(见 WiFi 联网那节),MQTT 代码接在拿到 IP 之后跑,别让它再塞一套联网逻辑。
  • Broker 地址:练手就用公共测试 Broker,URI 写成 mqtt://broker.emqx.io:1883(EMQX 官方免费,谁都能连);正式点可以用 EMQX 自建或巴法云这类国内平台。这一项后面会专门强调:地址和鉴权信息 AI 最容易编
  • topic 设计:发什么用哪个、收什么用哪个,写清楚。比如发温湿度用 qifudev/demo/sensor、收控灯命令用 qifudev/demo/led
  • 要发什么数据 / 收到怎么处理:连上后订阅 led,每 5 秒往 sensor 发一次温湿度;收到 on 点亮 GPIO2、收到 off 关掉。

把这些塞进一段提示词,AI 出来的代码通常能直接 idf.py build flash

一段能照搬的好提示词

把方括号里的内容换成你自己的,直接发给 AI:

"我用 ESP32-S3 开发板,ESP-IDF 5.x,MQTT 用官方 esp-mqtt 组件(esp_mqtt_client),事件驱动写法——不要 Arduino、不要 PubSubClient、不要 client.loop() 轮询。WiFi 我已经用 wifi_init_sta() 事件驱动骨架连上并拿到 IP 了,你只写 MQTT 部分,接在联网之后调用。Broker 用公共测试服,URI 是 mqtt://broker.emqx.io:1883。帮我写:用 esp_mqtt_client_config_t 配好 URI,esp_mqtt_client_init + esp_mqtt_client_register_event + esp_mqtt_client_start;在事件回调里——MQTT_EVENT_CONNECTED 时订阅 qifudev/demo/ledMQTT_EVENT_DATA 时按 event->data / event->data_len 读 payload,收到 on 点亮 GPIO2 的板载 LED、收到 off 关掉。再起一个 FreeRTOS 任务,每 5 秒 esp_mqtt_client_publish 一次温湿度(先用假数据 25.0℃ / 60% 占位,我后面接 DHT11)。关键行加中文注释,topic 字符串提成宏。"

这段话把五件套全说全了,最值钱的是开头那串"不要 Arduino"的硬约束,和"WiFi 已经连上、你只写 MQTT"的边界——后者能挡住 AI 把一大坨重复的联网代码塞进来。下面就是它该产出的代码。


AI 产出的完整 esp-mqtt 收发代码

这是一段把"发布 + 订阅"凑齐的骨架,几乎所有 ESP-IDF MQTT 设备都长这个样:配置 → 初始化 → 注册回调 → 启动,剩下全在事件回调里接招。它假设你已经按 WiFi 那节wifi_init_sta() 跑通、拿到 IP 了,这段接在它后面。引脚按板载 LED(多数 ESP32-S3 是 GPIO2,按你的板子改),温湿度先用假数据占位。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "mqtt_client.h"          // esp-mqtt 组件的头文件

#define LED_GPIO   GPIO_NUM_2                  // 板载 LED,多数 ESP32-S3 在 GPIO2
#define BROKER_URI "mqtt://broker.emqx.io:1883" // 公共测试 Broker,URI 形式
#define TOPIC_PUB  "qifudev/demo/sensor"        // 发布:上报温湿度
#define TOPIC_SUB  "qifudev/demo/led"           // 订阅:收控灯命令

static const char *TAG = "mqtt";
static esp_mqtt_client_handle_t s_client;       // 句柄,发布任务里要用

// 每 5 秒上报一次的发布任务,替代 Arduino loop 里的 millis 计时
static void publish_task(void *arg) {
    while (1) {
        char buf[48];
        float temp = 25.0, humi = 60.0;         // 假数据,后面换成 DHT11 真读数
        snprintf(buf, sizeof(buf), "temp=%.1f humi=%.1f", temp, humi);
        esp_mqtt_client_publish(s_client, TOPIC_PUB, buf, 0, 0, 0); // QoS0,len 传 0 让它自己算
        vTaskDelay(pdMS_TO_TICKS(5000));        // 睡 5 秒,不占 CPU
    }
}

// esp-mqtt 的事件全从这一个回调进来,靠 event_id 分流——这就是事件驱动
static void mqtt_event_handler(void *arg, esp_event_base_t base, int32_t event_id, void *data) {
    esp_mqtt_event_handle_t event = data;
    switch ((esp_mqtt_event_id_t) event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "已连上 Broker");
        esp_mqtt_client_subscribe(s_client, TOPIC_SUB, 0); // 连上后立刻订阅
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "掉线,esp-mqtt 会自动重连");        // 不用自己写重连循环
        break;
    case MQTT_EVENT_DATA: {                                 // 收到订阅消息走这里
        // payload 不带结尾 \0,必须按 data_len 读
        ESP_LOGI(TAG, "收到 [%.*s]: %.*s",
                 event->topic_len, event->topic, event->data_len, event->data);
        if (event->data_len == 2 && strncmp(event->data, "on", 2) == 0)
            gpio_set_level(LED_GPIO, 1);                    // 开灯
        else if (event->data_len == 3 && strncmp(event->data, "off", 3) == 0)
            gpio_set_level(LED_GPIO, 0);                    // 关灯
        break;
    }
    case MQTT_EVENT_ERROR:
        ESP_LOGE(TAG, "MQTT 出错,看 error_handle 里的 reason");
        break;
    default:
        break;
    }
}

// 联网成功后调用它:配置、初始化、注册回调、启动
void mqtt_app_start(void) {
    gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);

    esp_mqtt_client_config_t cfg = {
        .broker.address.uri = BROKER_URI,       // ESP-IDF 5.x 的嵌套写法
    };
    s_client = esp_mqtt_client_init(&cfg);
    // 把所有 MQTT 事件挂到上面那个回调(ESP_EVENT_ANY_ID 表示全收)
    esp_mqtt_client_register_event(s_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(s_client);            // 启动,连接在后台进行

    xTaskCreate(publish_task, "pub", 4096, NULL, 5, NULL); // 起定时上报任务
}

mqtt_app_start()app_main 里、wifi_init_sta() 拿到 IP 之后调用就行:

void app_main(void) {
    ESP_ERROR_CHECK(nvs_flash_init());
    wifi_init_sta();        // 见 l3-wifi,阻塞到连上 WiFi
    mqtt_app_start();       // 联网成功,启动 MQTT
}

那行假数据 temp = 25.0, humi = 60.0,等你想接真传感器时,照DHT11 接线那节读出来替换即可,publish_task 的发送逻辑一行都不用改——这正是把发布和读数解耦的好处。

本篇代码为参考实现,需结合你所用的最新 ESP-IDF 文档自校,尤其是 esp_mqtt_client_config_t 的字段在 4.x→5.x 之间从扁平结构改成了 .broker.address.uri 这种嵌套写法,AI 给的若是老版扁平写法(.uri = ...)会编译报错,以官方 esp-mqtt 文档为准。

你应该看到什么

idf.py build flash monitor 后,日志会按顺序滚出来(中间夹着一堆 WiFi、esp-mqtt 组件自己打的底层日志,正常):先是 WiFi 那节的 拿到 IP: 192.168.x.x,紧接着是 已连上 Broker。从这一刻起,板子每 5 秒往 qifudev/demo/sensor 发一条 temp=25.0 humi=60.0,同时竖着耳朵等 qifudev/demo/led 上的命令。等会儿我们用 MQTTX 往 led 这个 topic 发 onMQTT_EVENT_DATA 回调触发,日志立刻打出 收到 [...]: on,板载灯随即亮起。

要是日志卡在连不上、或者发命令进不了 MQTT_EVENT_DATA,别急——往下看怎么让 AI 帮你查。


esp-mqtt 比 PubSubClient 省在哪:自动重连和事件分流

如果你之前看过 Arduino 的 PubSubClient 写法,会发现这段 ESP-IDF 代码里没有 reconnect() 那个 while 循环,也没有 client.loop()。这不是漏了,是 esp-mqtt 把这两件事都包进组件内部了:

  • 断线自动重连:PubSubClient 要你自己在主循环里查 client.connected()、断了手动重连重订阅。esp-mqtt 不用——掉线它后台自己重连,重连成功会再抛一次 MQTT_EVENT_CONNECTED,你的回调里那句 subscribe 又会跑一遍,订阅自动补回来。你只管在 MQTT_EVENT_CONNECTED 里订阅,重连这事它替你扛。
  • 没有 client.loop():PubSubClient 的收发心跳全靠主循环每轮调 client.loop() 推进,漏了就掉线。esp-mqtt 是事件驱动的,收发跑在组件自己的后台任务里,消息到了直接抛 MQTT_EVENT_DATA 进你的回调,根本没有"主循环喂一口"这回事。

让 AI 写代码时,这恰恰是它容易踩的混淆点:它可能把 PubSubClient 的肌肉记忆带过来,给你的 esp-mqtt 代码里硬塞一个手写 reconnect 循环,或者在某个任务里调一个根本不存在的 client.loop()核对 AI 产出时,看到这两样就该警觉——esp-mqtt 里它们都是多余甚至错误的。


用 MQTTX 验证收发:别只信串口日志

代码自己跟自己玩,不算真跑通。你得有个"第三方"来发命令、看上报——这就是 MQTTX 干的事。它是 EMQX 出的桌面 MQTT 客户端,免费,Windows/Mac/Linux 都有,比手机 App 更适合调试。

用它验证两件事,几分钟搞定:

  1. 新建连接:打开 MQTTX,点 + 新建连接。Host 填 broker.emqx.io,Port 填 1883,Client ID 随便(点一下自动生成也行),其余默认,连接。
  2. 订阅看上报:连上后,订阅 qifudev/demo/sensor。如果板子在跑,每 5 秒你就能看到一条 temp=25.0 humi=60.0 滚进来——说明板子的 publish 通了
  3. 发命令控灯:在发布框里 topic 填 qifudev/demo/led,payload 填 on,发送。盯着你的板子,LED 应该亮;再发 off,灭。同时 monitor 日志会打出 收到 [...]: on——说明板子的 subscribe 和 MQTT_EVENT_DATA 回调也通了

两个方向都通,整套收发就闭环了。MQTTX 的价值在于它独立于你的代码:如果 MQTTX 能收到上报、板子却收不到命令,问题一定在订阅端(topic 拼错,或者 MQTT_EVENT_CONNECTED 里压根没调 subscribe),跟你的发布逻辑无关——一下就把故障范围缩到一半。


用 AI 调 esp-mqtt bug:把 monitor 日志原样喂过去

esp-mqtt 的坑高度集中,而且 idf.py monitor 的日志里几乎都带着线索——esp-mqtt 组件自己会打很多 MQTT_ 前缀的诊断行。调错的正确姿势不是自己干瞪眼,而是把 monitor 完整输出连同代码一起发给 AI,让它对着日志判断。喂的时候记得带一句"这是 ESP-IDF esp-mqtt,不是 Arduino",免得 AI 又往 PubSubClient 上想。

举几个真实场景,看怎么喂:

  • 连不上:日志卡在 MQTT_EVENT_BEFORE_CONNECT 后就没下文,或反复 MQTT_EVENT_ERROR。把 MQTT_EVENT_ERROR 那段连同你的 BROKER_URI 发给 AI,它会让你在回调里把 event->error_handle->esp_transport_sock_errno 打出来看——多半是网络层连不上(DNS 解析失败或端口被封),公司网、校园网常封 1883 端口。
  • clientID 冲突:两块板子同时跑,都莫名其妙反复抛 MQTT_EVENT_DISCONNECTED 又重连。把"两块板用了同一段代码"这个事实告诉 AI,它会指出 esp-mqtt 默认拿 MAC 拼 clientID,本该不撞——但你要是手动设了 .credentials.client_id 成同一个值,就会撞。Broker 用 ID 区分设备,ID 一样后连的会把先连的踢下线。解法是别写死 client_id,或给每块板不同值。
  • 订阅不到:连上了(日志有 已连上 Broker)、上报也正常,可 MQTTX 发命令进不了 MQTT_EVENT_DATA。把回调函数发给 AI,它多半会发现 esp_mqtt_client_subscribe 漏在了 MQTT_EVENT_CONNECTED 之外(比如塞进了 mqtt_app_start 里、在还没连上时就调了,根本没生效),或者收发两端 topic 差了一个字符。
  • QoS 该不该上:你担心命令偶尔丢。问 AI"我这个控灯场景,esp_mqtt_client_publishsubscribe 最后那个 QoS 参数要不要从 0 改 1",它会告诉你练手 QoS 0 够用,等到"丢一条就出事"(比如远程门锁)再考虑——别一上来就过度设计。

给 AI 喂日志有个要点:别只描述"连不上",把 esp-mqtt 打的那几行带 MQTT_EVENT_ 和错误码的原文贴过去。这些是组件给的诊断,AI 看一眼就知道是网络层、协议层还是订阅没生效,比你转述十句都准。更系统的排错思路见让 AI 帮你 debug 硬件


手写 vs 让 AI 写:快,但有一类参数必须你自己核

让 AI 写 esp-mqtt,比你翻官方文档、对着 example 抠那个嵌套的 esp_mqtt_client_config_t、记一长串 MQTT_EVENT_ 枚举名快得多。事件回调骨架、payload 按 data_len 读、MQTT_EVENT_CONNECTED 里补订阅这些样板,它写得又快又对,照搬基本没问题。ESP-IDF 这套样板比 Arduino 厚,恰恰是 AI 帮你省时间最明显的地方。

但有一类东西,AI 给的你一个字都不能盲信,必须自己核——那就是连接信息:Broker 的 URI、端口、用户名密码、topic 前缀。原因很实在:这些值 AI 没法验证,它只能"按最常见的猜"。你让它连巴法云,它可能编一个看着像模像样、其实根本不存在的 URI;你让它配鉴权,它可能往 .credentials.username 里填一个想当然的格式。代码能编译,但一连就 MQTT_EVENT_ERROR,你还以为是代码 bug,查半天才发现是地址是编的。

划条清晰的线:

  • 逻辑代码(事件回调分流、MQTT_EVENT_CONNECTED 里订阅、发布任务、payload 按 data_len 读)——AI 写,你看懂就行,跑通即过。
  • 连接参数.broker.address.uri、端口、.credentials 里的账号密码 token、topic)——一律以官方文档 / 你自己的平台后台为准,AI 写的只当占位符。

这跟做硬件用 AI 的总原则一脉相承:AI 编的"事实性信息"必须自己核实。地址、引脚、寄存器值这类硬事实,它最容易一本正经地编。养成习惯,把 AI 给的连接信息当成"待验证草稿",去官方文档核一遍再用。


故障排查表

现象 最可能的原因 怎么办
日志卡在 MQTT_EVENT_BEFORE_CONNECT、反复 MQTT_EVENT_ERROR 网络出不去 / DNS 失败 / 1883 被封 在回调里打 error_handle 的 errno;确认 WiFi 真连上了(l3-wifi);公司校园网常封 1883
日志有 已连上 Broker,MQTTX 发命令进不了 MQTT_EVENT_DATA subscribe 没放在 MQTT_EVENT_CONNECTED 里,或 topic 拼错 esp_mqtt_client_subscribe 必须在 MQTT_EVENT_CONNECTED 分支调;收发两端 topic 一字不差核对
两块板反复 MQTT_EVENT_DISCONNECTED 又重连 手动设了相同的 .credentials.client_id,撞车 别写死 client_id(默认用 MAC 不撞),或给每块板不同值
编译报 esp_mqtt_client_config_t 没有 .uri 字段 AI 给了 4.x 旧扁平写法 5.x 改用 .broker.address.uri 嵌套写法
收到的内容是乱码 / 多带一截 event->data 当带 \0 的字符串了 event->data_len 读(%.*sstrncmp),别 strcmp
AI 给的代码里有 client.loop() / 手写 reconnect AI 把 PubSubClient 习惯带进来了 esp-mqtt 自动重连、无需 loop,删掉;提示词里强调"不要 Arduino"
AI 给的 Broker URI 连不上 地址 / 鉴权是 AI 编的 以平台官方文档为准,别信 AI 给的连接信息

把这张表连同你的 monitor 日志一起发给 AI,让它对照定位,往往一轮就能锁定问题。


两个变体:让代码更像真项目

跑通基础版后,加这两样,离能用的项目就近了。两个都可以直接让 AI 帮你改——记得提示词里仍旧带上"ESP-IDF esp-mqtt、不要 Arduino"。

变体一:加遗嘱消息 LWT

设备突然断电、断网,Broker 不会立刻知道,你那边还以为它在线。**遗嘱消息(LWT, Last Will and Testament)**就是给这种情况兜底:连接时先告诉 Broker "我要是没打招呼就掉了,你替我往 qifudev/demo/status 发一条 offline"。这样订阅 status 的一端,设备一掉线马上能收到通知。

让 AI 改:

"用 ESP-IDF esp-mqtt 给我的 esp_mqtt_client_config_t 加遗嘱:在 .session.last_will 里设置 topic 为 qifudev/demo/status、msg 为 offline、retain 为 true、qos 0;设备在 MQTT_EVENT_CONNECTED 回调里主动 publish 一条 online 到同一 topic。"

注意核对一点:last_will 同样是 5.x 的嵌套结构(.session.last_will.topic 这种),AI 给的若是老版扁平字段名会编译报错。

变体二:JSON 载荷

上面发的是 temp=25.0 humi=60.0 这种自定义字符串,收的一端得自己切割解析,多加几个字段就乱。换成 JSON 更规整、更通用,几乎所有平台和前端都认。ESP-IDF 自带 cJSON 组件,不用额外装:

"用 ESP-IDF 自带的 cJSON,把我 publish_task 里的 payload 改成 JSON:构造 {\"temp\":25.0,\"humi\":60.0}esp_mqtt_client_publishqifudev/demo/sensor,记得 cJSON_Delete 释放,别内存泄漏。"

AI 会帮你 cJSON_CreateObject、加字段、cJSON_PrintUnformatted 序列化再发送。注意核对一点:它有没有在发完后 cJSON_Deletefree 那个序列化出来的字符串——cJSON 用堆内存,忘了释放,跑久了必崩,这是个高频坑。


动手挑战

代码跑通、MQTTX 验过了,再往前走一步,把"会发会收"用起来:

让 AI 帮你加一路订阅,控制一个继电器。 继电器能开关 220V 的真实家电(台灯、风扇),比板载 LED 有用得多。你要做的:

  1. 接好继电器模块(信号脚接一个空闲 GPIO,比如 GPIO5),接线和注意事项照继电器那节来——这一步关乎用电安全,必须自己确认接线,别让 AI 替你拍板。
  2. 把现有代码发给 AI(带上"ESP-IDF esp-mqtt"),让它在 MQTT_EVENT_CONNECTED 里再订阅一个新 topic qifudev/demo/relay,并在 MQTT_EVENT_DATA 回调里按 event->topic 区分是哪个 topic 的消息——收到 relayon 闭合继电器、off 断开。提示词照前面的好提问法写清楚 GPIO 和 topic。
  3. 用 MQTTX 往 relayon,听继电器"咔哒"一声,接着的台灯亮起——你就真正用 MQTT 远程控制了一件实物电器。

这里有个 AI 容易写漏的点你要盯:原代码回调只认 payload、没区分 topic;加了第二路后,必须在 MQTT_EVENT_DATA 里先比对 event->topic(同样按 event->topic_len 读)才知道这条消息该控灯还是控继电器。卡住了?把代码和 monitor 日志一起发给 AI,对照前面的排查表定位。


小结 · 下一步

  • 你掌握了喂 AI 写 esp-mqtt 代码的五件套:板型 + ESP-IDF 版本、esp-mqtt 组件、Broker URI、topic 设计、收发数据——尤其要在开头把"不要 Arduino、不要 PubSubClient"摆清楚,AI 出的代码才不会跑偏。
  • 你有了一段完整可烧录的 ESP32-S3 esp-mqtt 收发工程:事件回调里订阅控灯、定时任务里发布,断线靠组件自动重连,还知道"应该看到什么"。
  • 你搞清了 esp-mqtt 比 PubSubClient 省在哪(自动重连、无 client.loop()、事件分流),也学会把 monitor 日志原样喂给 AI 排连不上、clientID 冲突、订阅没生效、5.x 配置字段变更这类故障。
  • 你拎得清哪些能交给 AI(事件回调逻辑),哪些必须自己核(连接信息别信 AI 编的)。

下一步,把这套收发接进一个真正能远程用的"云通道"。一条顺手的路是上巴法云这类国内 IoT 平台——它本质也是托管的 MQTT Broker,但加了鉴权和微信小程序控制,配合你刚学的工作流,让 AI 帮你把 .broker.address.uri 和 topic 改成平台的格式(记得自己核地址),很快就能做出一个手机随时能控的设备。原理再回顾,随时翻 MQTT 入门

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明