跳转到内容

MQTT消息传输

MQTT消息传输

本节介绍 IoT 按钮的 MQTT 消息传输——将按键按下事件发布到 MQTT Broker 并接收确认。学习完本节后,您将能够:

  • 在 ESP32 上配置 PubSubClient 库以使用 MQTT
  • 使用合适的负载结构发布按键事件
  • 优雅处理 MQTT 连接失败
  • 使用适当的 QoS 设置确保消息传递

开始本节前,请确保:

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"
}
#include <WiFi.h>
#include <PubSubClient.h>
// MQTT Broker 配置
const char* MQTT_BROKER = "192.168.1.100"; // Mosquitto 服务器 IP
const 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);
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;
}
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();
}

要验证按钮消息是否被接收,可使用 MQTT Explorer 或 mosquitto_sub:

Terminal window
# 订阅所有按钮主题
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

症状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);
}
}

症状:按钮报告发布成功,但 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 字节以下以确保可靠性
  • 避免为按键按下使用保留消息(每次按下都是新事件)
  1. MQTT 在 WiFi 连接后发布——顺序执行,而非并行
  2. JSON 负载包含按钮 ID、操作、电池电量、RSSI 和时间戳
  3. QoS 1 确保传递,且无 QoS 2 的额外开销
  4. 总活跃时间约 4-5 秒(WiFi 2-3s + MQTT 1s + 清理)
  5. 电池电压监测对主动维护至关重要