ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

《WiFi 嵌入式物联网开发全套实战》| 第 19 章 ESP32 WiFi + MQTT 物联网上云完整工程

《WiFi 嵌入式物联网开发全套实战》| 第 19 章 ESP32 WiFi + MQTT 物联网上云完整工程 专栏《WiFi 嵌入式物联网开发全套实战》 专栏定位嵌入式 Linux/ESP32 WiFi 从原理→驱动→配网→协议→稳定性→抓包调试→量产优化全套工业实战适配物联网设备、智能家居、工控网关、无线透传设备、4GWiFi 双模设备 点赞 收藏 关注嵌入式 WiFi 量产实战全套教程持续更新本章前言经过前面章节我们掌握了 ESP32‑STA 稳定联网、Socket 底层 TCP/UDP 通信能力。在物联网领域直接使用裸 TCP 做设备‑云交互会面临大量工作自定义协议设计、消息应答、重传、QoS 消息等级、订阅发布模型、设备鉴权、断线消息缓存。MQTT 是物联网行业事实标准协议基于 TCP 实现采用发布‑订阅模型非常适合带宽有限、网络不稳定的嵌入式 WiFi 设备。ESP‑IDF 内置esp‑mqtt组件封装完整 MQTT 客户端能力。网上很多 MQTT Demo 仅仅实现简单连接发布订阅量产后暴露出各类问题WiFi 波动不会自动重连、QoS 消息丢失、客户端 ID 重复导致服务端踢下线、证书加密配置错误、心跳保活参数不合理、离线消息处理、内存泄漏、事件回调内执行耗时任务引发栈溢出。本章从 MQTT 协议基础讲起讲解esp‑mqtt组件架构、核心参数、事件回调机制实现工业级可直接编译完整工程支持明文 / SSL 加密连接、发布订阅、QoS0/QoS1/QoS2、断线自动重连、WiFi 状态联动、错误码解析、抓包调试同时梳理量产大量真实踩坑案例。前置阅读第 14 章 STA 掉线恢复、第 18 章 Socket TCP 通信原理。19.1 MQTT 协议基础19.1.1 发布‑订阅模型BrokerMQTT 服务器云平台、本地 EMQX mosquitto消息中转枢纽Publisher发布者向指定 Topic 推送消息Subscriber订阅者订阅感兴趣 TopicBroker 向其推送对应消息设备之间不直接点对点通信全部经过 Broker 转发解耦生产者与消费者。19.1.2 QoS 服务质量等级QoS说明适用场景QoS 0最多一次消息发出去不确认可能丢包上报高频非关键数据传感器实时采样QoS 1至少一次保证消息到达可能重复接收设备控制指令、状态上报业务层需要做去重QoS 2恰好一次握手流程最多开销最大支付、重要告警等绝对不能重复也不能丢失场景19.1.3 KeepAlive 心跳Keep‑alive 时间设备告诉 Broker 多久发送一次心跳 PINGREQ。Broker 超过 1.5 倍 keepalive 时间没有收到设备心跳判定设备离线触发 Will 遗嘱消息。19.1.4 Will 遗嘱消息设备异常断电、网络异常断开没有发送正常 DISCONNECT 报文Broker 主动向订阅者推送预先设置的遗嘱消息上报设备离线状态智能家居、工控设备非常实用。19.1.5 MQTT 连接关键参数Client ID客户端标识同一个 Broker 下不能重复ID 重复会导致旧连接被服务端强制踢掉量产建议使用设备 MAC 地址作为 ClientID。username /password账号密码鉴权Clean Session清除会话标识true连接成功清除历史会话离线期间未接收消息全部丢弃false保留会话断线重连之后补发 QoS1/QoS2 离线消息19.2 ESP‑IDF esp‑mqtt 组件架构说明ESP‑IDF 官方esp‑mqtt是基于 Socket 封装的 MQTT 客户端组件内部已经实现TCP 连接管理、MQTT 报文编解码自动重连逻辑、心跳 PING 报文自动发送QoS 消息队列缓存TLS/SSL 加密传输事件驱动回调机制重要工程约束MQTT 事件回调函数中禁止执行耗时操作不要做 vTaskDelay、大量运算耗时业务丢到 FreeRTOS 任务队列处理否则会阻塞 mqtt 内部事件线程引发断连。esp‑mqtt 自带重连但必须和 WiFi STA 状态联动WiFi 断开时建议停止 mqtt 客户端WiFi 恢复后再启动避免无效重连消耗资源。不要在回调内部阻塞等待 mqtt 消息发送完成。19.2.1 MQTT 重要事件枚举事件含义MQTT_EVENT_CONNECTED成功连接 Broker可以发布订阅MQTT_EVENT_DISCONNECTED连接断开MQTT_EVENT_SUBSCRIBED订阅 Topic 成功MQTT_EVENT_UNSUBSCRIBED取消订阅成功MQTT_EVENT_PUBLISHED发布消息完成QoS0 才会上报MQTT_EVENT_DATA收到订阅 Topic 下发的业务消息MQTT_EVENT_ERROR发生错误解析 error_handle 获取错误类型、errno、TLS 错误码19.3 实战工程ESP32 MQTT 工业级完整代码ESP‑IDF5.x功能清单STA 自动重连 WiFiMQTT 明文 / SSL 可切换连接成功自动订阅指令 topic定时上报设备状态接收云端下发指令事件回调转发到业务任务处理遗嘱消息配置错误事件打印详细错误信息WiFi 断网联动停止 mqtt联网恢复重启客户端ClientID 使用设备 MAC避免 ID 冲突。#include stdio.h #include string.h #include inttypes.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/event_groups.h #include freertos/queue.h #include esp_wifi.h #include esp_event.h #include esp_log.h #include nvs_flash.h #include esp_netif.h #include esp_mac.h #include mqtt_client.h /* 用户配置区 */ #define WIFI_SSID Your_Wifi_SSID #define WIFI_PASSWORD Your_Wifi_Password /* MQTT配置明文示例 mqtt://xxx:1883加密 ssl://xxx:8883 */ #define MQTT_BROKER_URI mqtt://192.168.1.100:1883 #define MQTT_USERNAME #define MQTT_PASSWORD #define MQTT_KEEPALIVE 60 #define MQTT_TOPIC_SUB device/cmd /* 订阅云端下发控制指令 */ #define MQTT_TOPIC_PUB device/status /* 发布设备状态上报 */ #define MQTT_TOPIC_WILL device/online /* 遗嘱消息topic */ #define WIFI_CONNECTED_BIT BIT0 static EventGroupHandle_t wifi_event_group; static esp_mqtt_client_handle_t mqtt_client NULL; static QueueHandle_t mqtt_msg_queue NULL; static const char *TAG MQTT_DEMO; /* MQTT消息队列结构体把回调收到的数据抛到业务任务避免回调阻塞 */ typedef struct { char topic[128]; char payload[256]; int payload_len; } mqtt_msg_t; /** * brief 获取设备MAC作为clientID防止重复ID被broker踢下线 */ static void get_mac_str(char *mac_buf, int buf_len) { uint8_t mac[6]; esp_read_mac(mac, ESP_MAC_WIFI_STA); snprintf(mac_buf, buf_len, esp32_%02x%02x%02x%02x%02x%02x, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } /** * brief WiFi事件回调 */ static void wifi_event_handler(void* arg, esp_event_base_t base, int32_t id, void* data) { if(base WIFI_EVENT id WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGW(TAG,WiFi STA断开); xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); if(mqtt_client ! NULL) { esp_mqtt_client_stop(mqtt_client); } esp_wifi_connect(); } else if(base IP_EVENT id IP_EVENT_STA_GOT_IP) { ESP_LOGI(TAG,WiFi获取IP成功启动MQTT客户端); xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); if(mqtt_client ! NULL) { esp_mqtt_client_start(mqtt_client); } } } /** * brief MQTT事件回调禁止耗时操作消息丢队列 */ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { esp_mqtt_event_handle_t event event_data; switch ((esp_mqtt_event_id_t)event_id) { case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG,MQTT_EVENT_CONNECTED 连接Broker成功); esp_mqtt_client_subscribe(mqtt_client, MQTT_TOPIC_SUB, 1); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGW(TAG,MQTT_EVENT_DISCONNECTED MQTT连接断开); break; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG,MQTT_EVENT_SUBSCRIBED 订阅topic成功); break; case MQTT_EVENT_DATA: { mqtt_msg_t msg {0}; int topic_len event-topic_len; if(topic_len sizeof(msg.topic)-1) topic_len sizeof(msg.topic)-1; memcpy(msg.topic, event-topic, topic_len); int pay_len event-data_len; if(pay_len sizeof(msg.payload)-1) pay_len sizeof(msg.payload)-1; memcpy(msg.payload, event-data, pay_len); msg.payload_len pay_len; /* 发送到业务任务队列 */ xQueueSend(mqtt_msg_queue, msg, pdMS_TO_TICKS(10)); break; } case MQTT_EVENT_ERROR: { ESP_LOGE(TAG,MQTT_EVENT_ERROR); if(event-error_handle-error_type MQTT_ERROR_TYPE_TCP_TRANSPORT) { ESP_LOGE(TAG,Tcp transport error errno %d, event-error_handle-esp_transport_sock_errno); } break; } default: break; } } /** * brief MQTT业务任务处理从队列收到的云端下发消息 */ static void mqtt_business_task(void *arg) { mqtt_msg_t msg_buf; while(1) { if(xQueueReceive(mqtt_msg_queue, msg_buf, portMAX_DELAY)) { msg_buf.topic[sizeof(msg_buf.topic)-1] \0; msg_buf.payload[msg_buf.payload_len] \0; ESP_LOGI(TAG,Recv topic:%s payload:%s, msg_buf.topic, msg_buf.payload); /* ----------------业务逻辑写在这里---------------- */ // 例如解析JSON指令控制外设 } } } /** * brief MQTT状态上报任务定时上报设备状态 */ static void mqtt_publish_task(void *arg) { while(1) { xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); if(mqtt_client esp_mqtt_client_get_state(mqtt_client) MQTT_CLIENT_STATE_CONNECTED) { const char *payload {\dev_status\:\online\}; int msg_id esp_mqtt_client_publish(mqtt_client, MQTT_TOPIC_PUB, payload, strlen(payload), 1, 0); ESP_LOGI(TAG,publish status msg_id%d, msg_id); } vTaskDelay(pdMS_TO_TICKS(5000)); } } /** * brief MQTT客户端初始化 */ static void mqtt_client_init(void) { char client_id[32] {0}; get_mac_str(client_id, sizeof(client_id)); esp_mqtt_client_config_t mqtt_cfg { .broker.address.uri MQTT_BROKER_URI, .credentials.username MQTT_USERNAME, .credentials.password MQTT_PASSWORD, .credentials.client_id client_id, .session.keepalive MQTT_KEEPALIVE, .session.clean_session true, /* 遗嘱消息配置设备异常断开推送离线消息 */ .last_will.topic MQTT_TOPIC_WILL, .last_will.msg {\dev_status\:\offline\}, .last_will.qos 1, .last_will.retain false, }; mqtt_client esp_mqtt_client_init(mqtt_cfg); esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); } static void wifi_sta_init(void) { wifi_event_group xEventGroupCreate(); esp_netif_init(); esp_event_loop_create_default(); esp_netif_create_default_wifi_sta(); wifi_init_config_t wcfg WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(wcfg); esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL, NULL); esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL, NULL); wifi_config_t sta_cfg { .sta { .ssid WIFI_SSID, .password WIFI_PASSWORD, } }; esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, sta_cfg); esp_wifi_start(); esp_wifi_connect(); } void app_main(void) { nvs_flash_init(); wifi_sta_init(); mqtt_msg_queue xQueueCreate(8, sizeof(mqtt_msg_t)); mqtt_client_init(); xTaskCreate(mqtt_business_task, mqtt_biz, 4096, NULL, 5, NULL); xTaskCreate(mqtt_publish_task, mqtt_pub, 4096, NULL, 4, NULL); }19.4 代码关键要点解析ClientID 使用设备 MAC 地址避免多设备 client_id 相同导致 Broker 踢掉旧连接量产高频问题。事件回调绝不执行业务逻辑MQTT_EVENT_DATA 收到消息拷贝数据压入 FreeRTOS 队列业务处理放在独立任务防止回调阻塞 mqtt 内部线程导致断连。WiFi 联动启停 MQTT 客户端WiFi 断开调用esp_mqtt_client_stop()WiFi 恢复再esp_mqtt_client_start()避免网络不可用时疯狂重连占用资源。esp‑mqtt 自带重连但 WiFi 底层链路已经失效的情况下自带重连机制效率很差。遗嘱消息 Last‑Will设备异常断电、网络掉电没有发送正常 DISCONNECTBroker 自动推送离线状态设备正常重启连接时遗嘱不会触发。publish 返回 msg_idQoS0 消息msg_id0 代表消息入队成功不等于已经发送完成QoS0 msg_id 永远为 0。19.5 SSL/TLS 加密 MQTT 配置接入公有云必备接入阿里云、腾讯云等公有 MQTT 云平台必须 SSL 加密通信。Broker URI 修改为ssl://xxx:8883mqtt_cfg 填入 CA 证书、客户端证书密钥.broker.address.uri ssl://xxx:8883, .broker.verification.certificate (const char *)ca_crt_start;证书文件通过 component build 把 PEM 证书编译进固件注意证书时间设备系统时间不正确会直接 SSL 握手失败。19.6 Wireshark 抓包调试 MQTTMQTT 明文端口 1883Wireshark 过滤条件mqttSSL 加密报文密文无法直接解析只能看 TCP 握手排查点CONNECT 报文查看 clientID、keepalive、will 标志位CONNACK返回码 0 连接成功非 0 代表鉴权失败、ID 非法PINGREQ/PINGRESP心跳报文观察心跳周期是否符合 keepalive 设置PUBLISH / PUBACK观察 QoS 交互流程。19.7 量产高频坑点与故障现象坑点 1设备不定时被 Broker 踢下线现象运行一段时间收到MQTT_EVENT_DISCONNECTED很快自动重连根因多设备 ClientID 重复keepalive 设置过大网络波动丢包回调阻塞内部线程无法发送 PING 心跳。解决MAC 生成唯一 ClientIDkeepalive 建议 30‑60s回调禁止耗时操作。坑点 2WiFi 重连成功MQTT 迟迟不恢复根因只依赖 esp‑mqtt 内部重连没有联动 WiFi 事件 stop/start 客户端网络底层已经失效内部重连一直失败。解决WiFi_DISCONNECT 事件执行esp_mqtt_client_stop()WiFi GOT_IP 之后执行esp_mqtt_client_start()。坑点 3QoS1 消息丢失或者重复上报根因clean_sessiontrue断线会话全部清除解决需要离线消息缓存设置clean_sessionfalseBroker 支持持久会话。坑点 4公有云 SSL 连接握手失败现象MQTT_EVENT_ERROR TLS transport 错误根因设备 RTC 时间不对证书校验失败CA 证书不对端口错误网络不通。解决开启 SNTP 同步系统时间核对证书、端口。坑点 5MQTT 事件回调中调用大量 log、vTaskDelay现象随机断连无明确报错日志根因阻塞 mqtt 内部事件线程心跳 PING 报文无法发出Broker 判定设备离线断开。解决回调只做数据拷贝、入队业务交给别的任务处理。坑点 6遗嘱消息不触发现象设备正常重启看不到遗嘱消息只有异常断电断网才触发。根因设备正常断开会发送 DISCONNECT 报文Broker 会丢弃遗嘱消息属于协议标准行为。19.8 menuconfig 关键配置Component config → ESP‑MQTT调大 mqtt 输出缓冲区LWIPsocket 数量保证≥16使用 TLS 加密mbedTLS 组件栈大小适当调大。19.9 本章小结MQTT 是 WiFi 物联网设备对接云平台的核心协议本章梳理 MQTT 协议核心概念讲解 esp‑mqtt 组件使用约束给出工业级完整可编译工程实现发布订阅、遗嘱消息、WiFi 联动启停、队列解耦回调业务同时讲解抓包调试手段整理量产大量真实故障与解决方案。MQTT 上层所有云平台阿里云、腾讯云、EMQX 私有部署都是基于这套基础做鉴权与业务封装。掌握本章之后设备已经具备完整上云能力。 点赞 收藏 关注嵌入式 WiFi 量产实战全套教程持续更新
返回列表