跳转到内容

出生与死亡消息

本节介绍 MQTT 的出生消息(Birth Message)和死亡消息(Death Message)机制。学习完成后,您将能够:

  • 理解出生消息和死亡消息的概念和用途
  • 在 ESP32 客户端中配置出生消息
  • 在 Node-RED 中处理设备上下线事件
  • 构建设备在线状态监控系统

在开始本节之前,请确保:

  • 理解保留消息机制
  • 理解遗嘱消息
  • Mosquitto Broker 已运行

出生消息是客户端成功连接到 Broker 后立即发布的消息,用于宣告设备上线。

特点

  • 在 CONNECT 成功并收到 CONNACK 后发布
  • 通常使用保留消息,以便新订阅者获取
  • 包含设备标识、状态、能力信息
Client (ESP32) Broker
│ │
│─── CONNECT ──────────────────→│
│←── CONNACK (Accepted) ────────│
│ │
│─── PUBLISH (Birth Message) ──→│
│ Topic: devices/esp32_001 │
│ Payload: {"status":"online"}│
│ Retain: true │
│ │
│ [客户端进入正常通信状态] │

死亡消息是客户端意外断开连接时,由 Broker 代为发布的消息。

特点

  • 在 CONNECT 时注册(作为 Last Will)
  • 在客户端意外断开时由 Broker 触发
  • 通常清除或更新保留的在线状态
Client (ESP32) Broker
│ │
│─── 正常通信 ─────────────────→│
│─── ... ─────────────────────→│
│ │
│ [突然断电/断网] │
│✗ │
│ │
│ [Broker 检测到连接超时] │
│ │
│ [Broker 代为发布死亡消息] │
│ │
│ ┌─────────────────┤
│ │ Topic: devices/ │
│ │ esp32_001 │
│ │ Payload: │
│ │ {"status": │
│ │ "offline"} │
│ │ Retain: true │
│ └─────────────────┤
│ │
│ [订阅者收到死亡消息]│
#include <WiFi.h>
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient mqttClient(espClient);
#define DEVICE_ID "esp32_001"
#define STATUS_TOPIC "devices/esp32_001/status"
#define BIRTH_TOPIC "devices/esp32_001/birth"
void connectMQTT() {
// 配置 MQTT 服务器
mqttClient.setServer(MQTT_BROKER, 1883);
mqttClient.setCallback(mqttCallback);
// 准备遗嘱消息(死亡消息)
// Last Will: 设备意外断开时 Broker 发布离线状态
const char* willTopic = STATUS_TOPIC;
const char* willPayload = "{\"status\":\"offline\",\"device\":\"" DEVICE_ID "\"}";
int willQos = 1;
bool willRetain = true;
// 尝试连接(注册 Last Will)
if (mqttClient.connect(
DEVICE_ID, // Client ID
"", // Username
"", // Password
willTopic, // Will Topic
willQos, // Will QoS
willRetain, // Will Retain
willPayload // Will Message
)) {
Serial.println("MQTT Connected");
// 发布出生消息(设备上线)
String birthPayload = "{\"status\":\"online\",\"device\":\"" DEVICE_ID "\",\"ip\":\""
+ WiFi.localIP().toString() + "\"}";
mqttClient.publish(
STATUS_TOPIC,
birthPayload.c_str(),
true // retain
);
// 也可以发布到单独的 birth Topic
mqttClient.publish(
BIRTH_TOPIC,
"{\"event\":\"birth\",\"timestamp\":" + String(millis()) + "}",
false
);
}
}
void loop() {
if (!mqttClient.connected()) {
connectMQTT();
}
mqttClient.loop();
}
// Node-RED Function 节点:处理设备状态变化
// 订阅所有设备的状态 Topic
// Topic: devices/+/status
var topic = msg.topic;
var payload = msg.payload;
// 提取设备 ID
var topicParts = topic.split('/');
var deviceId = topicParts[1]; // devices/{deviceId}/status
// 解析状态
var status = payload.status;
var eventType = "unknown";
if (status === "online") {
eventType = "device_online";
node.log("Device ONLINE: " + deviceId);
} else if (status === "offline") {
eventType = "device_offline";
node.warn("Device OFFLINE: " + deviceId);
}
// 添加事件信息
msg.event = {
type: eventType,
device_id: deviceId,
timestamp: Date.now(),
ip: payload.ip || "unknown"
};
// 更新设备状态(Flow 上下文)
var devices = flow.get("device_status") || {};
devices[deviceId] = {
status: status,
last_seen: Date.now(),
ip: payload.ip || "unknown"
};
flow.set("device_status", devices);
return msg;
// 在 Node-RED 中查询所有设备状态
// 通过 HTTP Endpoint 提供给前端
// HTTP GET /api/devices/status
var devices = flow.get("device_status") || {};
msg.payload = {
total: Object.keys(devices).length,
online: Object.values(devices).filter(d => d.status === "online").length,
offline: Object.values(devices).filter(d => d.status === "offline").length,
devices: devices
};
return msg;
// 当重要设备离线时触发告警
// 接在设备状态变化节点之后
var deviceId = msg.event.device_id;
var criticalDevices = ["esp32_camera_001", "esp32_sensor_main"];
if (msg.event.type === "device_offline") {
if (criticalDevices.includes(deviceId)) {
// 发送告警
msg.alert = {
severity: "critical",
message: "关键设备离线: " + deviceId,
timestamp: Date.now()
};
return msg;
}
}
return null; // 非关键设备离线,不触发告警
Terminal window
# 终端 1: 订阅所有设备状态
mosquitto_sub -h localhost -t "devices/+/status" -v
# 终端 2: 启动 ESP32 设备
# 预期看到:
# devices/esp32_001/status {"status":"online","device":"esp32_001"}
# 断开设备电源或网络
# 预期看到(有延迟):
# devices/esp32_001/status {"status":"offline","device":"esp32_001"}
  • 推荐: Birth 消息使用 Retain=true,方便新订阅者获取
  • 推荐: Birth 消息包含设备 IP、固件版本等能力信息
  • 推荐: Death 消息使用 Retain=true 覆盖在线状态
  • 推荐: 在 Node-RED 中维护设备状态缓存
  • 避免: Birth/Death Topic 与数据 Topic 混用
  • 避免: 完全依赖 Death 消息(非 100% 可靠)
  • 避免: Birth 消息中包含敏感信息

本节要点总结:

  1. 出生消息:设备连接时主动宣告上线
  2. 死亡消息:通过 Last Will 在设备异常断开时宣告离线
  3. 实现方式:CONNECT 时注册遗嘱,连接成功后发布出生消息
  4. 状态管理:使用保留消息确保新订阅者获取最新状态
  5. 业务应用:设备在线仪表板、离线告警、设备生命周期管理