MQTT消息传输
MQTT消息传输
本节介绍 IoT 按钮的 MQTT 消息传输——将按键按下事件发布到 MQTT Broker 并接收确认。学习完本节后,您将能够:
- 在 ESP32 上配置 PubSubClient 库以使用 MQTT
- 使用合适的负载结构发布按键事件
- 优雅处理 MQTT 连接失败
- 使用适当的 QoS 设置确保消息传递
开始本节前,请确保:
- 已完成 04-07. 按键按下时WiFi连接
- 理解 MQTT 主题和 QoS(参见第 06 章)
- MQTT Broker 正在运行(Mosquitto 或 EMQX)
按钮消息架构
Section titled “按钮消息架构”IoT 按钮在按下时发布一条简单消息:
发布者(按钮) Broker 订阅者 │ │ │ │ ──► 主题:"factory/button/01" │ │ 负载:{"button":"01", │ │ "action":"toggle", │ │ "battery":3.85} │ │ │──► 订阅 ──────► Node-RED │ │ │ │ │ │ │ ◄──(QoS 1 确认) │对于工单按钮系统,使用一致的主题层级:
factory/button/{button_id}/{action}
示例:- factory/button/01/press — 按钮 01 被按下- factory/button/02/press — 按钮 02 被按下- factory/button/status/01 — 按钮 01 状态(电量、在线)主题设计考虑:
- 每个按钮唯一:每个按钮在主题中有唯一 ID
- 基于操作:最后一级表示操作类型
- 可通配符订阅:Node-RED 可订阅
factory/button/#
{ "button_id": "BTN-001", "action": "toggle", "timestamp": 1715901234, "battery_voltage": 3.85, "battery_percent": 85, "rssi": -65, "firmware": "v1.0"}步骤 1:安装并配置 PubSubClient
Section titled “步骤 1:安装并配置 PubSubClient”#include <WiFi.h>#include <PubSubClient.h>
// MQTT Broker 配置const char* MQTT_BROKER = "192.168.1.100"; // Mosquitto 服务器 IPconst int MQTT_PORT = 1883;const char* MQTT_USER = ""; // 无认证时留空const char* MQTT_PASS = "";const char* MQTT_TOPIC = "factory/button/01/press";
// 按钮标识const char* BUTTON_ID = "BTN-001";
WiFiClient wifiClient;PubSubClient mqttClient(wifiClient);步骤 2:连接到 MQTT Broker
Section titled “步骤 2:连接到 MQTT Broker”bool connectMQTT() { mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
// 生成唯一的客户端 ID String clientId = "iot_button_"; clientId += String((uint32_t)ESP.getEfuseMac(), HEX);
Serial.print("正在连接 MQTT Broker:"); Serial.println(MQTT_BROKER);
if (mqttClient.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) { Serial.println("MQTT 已连接"); return true; } else { Serial.print("MQTT 连接失败,状态码:"); Serial.println(mqttClient.state()); return false; }}步骤 3:发布带电池状态的按键按下消息
Section titled “步骤 3:发布带电池状态的按键按下消息”#include <ArduinoJson.h>
float readBatteryVoltage() { int adcValue = analogRead(BATTERY_ADC_PIN); return (adcValue / 4095.0) * 3.3 * 2; // 分压器 ×2}
bool publishButtonPress() { // 创建 JSON 负载 StaticJsonDocument<256> doc; doc["button_id"] = BUTTON_ID; doc["action"] = "toggle";
// 添加电池状态 float voltage = readBatteryVoltage(); doc["battery_voltage"] = voltage; doc["battery_percent"] = (int)((voltage - 3.3) / (4.2 - 3.3) * 100); doc["rssi"] = WiFi.RSSI(); doc["timestamp"] = time(nullptr);
// 序列化为字符串 char payload[256]; serializeJson(doc, payload); Serial.print("正在发布:"); Serial.println(payload);
// 使用 QoS 1 发布(至少一次传递) bool success = mqttClient.publish(MQTT_TOPIC, payload, true);
if (success) { Serial.println("发布成功"); } else { Serial.println("发布失败"); }
return success;}步骤 4:完整的按压-睡眠流程
Section titled “步骤 4:完整的按压-睡眠流程”void performButtonAction() { // 步骤 1:连接 WiFi(来自 04-07 节) if (!connectWiFiWithRetry(2)) { Serial.println("WiFi 失败 - 无法发布"); return; }
// 步骤 2:连接 MQTT if (!connectMQTT()) { Serial.println("MQTT 失败 - 进入睡眠"); WiFi.disconnect(true); return; }
// 步骤 3:发布消息 publishButtonPress();
// 步骤 4:让 MQTT 刷新 mqttClient.loop(); delay(100);
// 步骤 5:干净断开 mqttClient.disconnect(); WiFi.disconnect(true);
Serial.println("按键操作完成");}
void setup() { Serial.begin(115200); delay(100);
// 按键被按下(从深度睡眠唤醒) performButtonAction();
// 重新进入睡眠 Serial.println("进入深度睡眠..."); Serial.flush(); esp_deep_sleep_start();}步骤 5:MQTT 消息验证
Section titled “步骤 5:MQTT 消息验证”要验证按钮消息是否被接收,可使用 MQTT Explorer 或 mosquitto_sub:
# 订阅所有按钮主题mosquitto_sub -h 192.168.1.100 -t "factory/button/#" -v
# 预期输出:# factory/button/01/press {"button_id":"BTN-001","action":"toggle","battery_voltage":3.85,...}- 按下按钮时发布 MQTT 消息
- MQTT Broker 收到消息
- 负载包含正确的 JSON 结构
- 消息中包含电池电压
- 设备在睡眠前干净断开 MQTT
问题 1:MQTT 连接失败
Section titled “问题 1:MQTT 连接失败”症状:mqttClient.connect() 返回 false
MQTT 状态码:
| 状态码 | 含义 | 解决方案 |
|---|---|---|
| -4 | 连接超时 | 检查 Broker IP 和网络 |
| -3 | 连接丢失 | Broker 未运行 |
| -2 | 连接失败 | Broker 地址错误 |
| -1 | 已断开 | 连接前的正常状态 |
| 1 | 协议错误 | 检查 MQTT 版本兼容性 |
| 2 | 客户端 ID 被拒绝 | 检查是否有重复 |
| 3 | 服务器不可用 | Broker 过载或未运行 |
| 4 | 用户名/密码错误 | 检查凭据 |
| 5 | 未授权 | 检查 Broker ACL |
解决方案:
void debugMQTTState() { int state = mqttClient.state(); switch (state) { case MQTT_CONNECT_UNAUTHORIZED: Serial.println("MQTT:未授权 - 请检查凭据"); break; case MQTT_CONNECT_TIMEOUT: Serial.println("MQTT:超时 - 请检查 Broker IP 和端口"); break; case MQTT_CONNECT_FAILED: Serial.println("MQTT:连接失败 - Broker 是否在运行?"); break; default: Serial.print("MQTT 状态:"); Serial.println(state); }}问题 2:Node-RED 未收到消息
Section titled “问题 2:Node-RED 未收到消息”症状:按钮报告发布成功,但 Node-RED 未触发
可能原因:
- 按钮和 Node-RED MQTT In 节点之间的主题不匹配
- MQTT In 节点订阅了错误主题
- QoS 不匹配(按钮发布 QoS 1,Node-RED 订阅 QoS 0)
解决方案:在检查 Node-RED 之前,先使用 MQTT Explorer 或 mosquitto_sub 验证。
- ✅ 对按钮消息使用 QoS 1——确保传递且不会过度增加开销
- ✅ 在每条消息中包含电池电压以进行健康监测
- ✅ 基于 MAC 地址使用唯一的客户端 ID以避免冲突
- ✅ 在深度睡眠前干净断开 MQTT
- ✅ 包含固件版本以进行更新跟踪
- ❌ 不要发布过大的负载——保持在 512 字节以下以确保可靠性
- ❌ 避免为按键按下使用保留消息(每次按下都是新事件)
- MQTT 在 WiFi 连接后发布——顺序执行,而非并行
- JSON 负载包含按钮 ID、操作、电池电量、RSSI 和时间戳
- QoS 1 确保传递,且无 QoS 2 的额外开销
- 总活跃时间约 4-5 秒(WiFi 2-3s + MQTT 1s + 清理)
- 电池电压监测对主动维护至关重要