MQTT 通信协议与规范
1. 核心设计原则
1.1 设备标识体系
YunStack-IoT 采用双 ID 体系:
| ID 类型 | 来源 | 格式 | 用途 |
|---|---|---|---|
| Physical ID | 设备硬件 | MAC 地址 (如 A1B2C3D4E5F6) | 设备首次认证、固件层标识 |
| Logical ID (did) | 平台分配 | UUID (如 d1a2b3c4-...) | 所有业务逻辑、数据关联 |
映射关系: 后端维护 Physical ID ↔ Logical ID 映射表。设备损坏更换时,只需更新映射,历史数据保留。
1.2 安全设计原则
- 零信任:不信任任何来自设备的输入,必须进行格式和范围校验。
- 权限隔离:
- 小程序端指令不直接发给 MQTT Broker,而是发给后端 API,由后端鉴权后代发。
- 设备仅拥有其所属 Topic 的 Pub/Sub 权限(通过 EMQX ACL 控制)。
2. 设备注册与认证流程
2.1 首次接入流程
设备上电 → 读取预置 ProductKey + DeviceSecret
→ MQTT CONNECT (ClientId=MAC, Username=ProductKey, Password=DeviceSecret)
→ EMQX 直连 PostgreSQL 认证(查 device 表验证凭证)
→ 认证通过,设备连接成功
→ 后端 MQTT Client 收到设备首次属性上报
→ 后端查找或创建 Device 记录,分配 Logical ID
→ 后端下发 Logical ID 给设备 (通过 config Topic)2.2 凭证说明
| 凭证 | 来源 | 说明 |
|---|---|---|
| ProductKey (pid) | 固件预置 | 产品标识,同一批次共享 |
| DeviceSecret | 固件预置 / 动态生成 | 设备密钥,每台设备唯一 |
| Logical ID (did) | 平台分配 | 首次连接后由平台下发 |
| ClientId | MAC 地址 | MQTT 连接标识 |
当前状态: 四路继电器固件 v2.0 用 MAC 地址作为设备标识,尚未实现 ProductKey 和 Logical ID 分配机制。详见下方兼容性说明。
2.3 固件预置参数
// 固件中需预置 (生产时烧录)
#define DEVICE_PRODUCT_KEY "p_relay_4ch" // 产品标识
#define DEVICE_SECRET "uuid-xxxx" // 设备密钥 (每台唯一)
// 运行时获取
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA); // Physical ID3. MQTT Topic 规范 (标准)
平台标准采用类阿里云 IoT 的 Topic 结构:
| 用途 | Topic 模板 | 权限 | 方向 |
|---|---|---|---|
| 属性上报 | sys/{pid}/{did}/thing/event/property/post | Pub | 设备 → 云端 |
| 属性设置 | sys/{pid}/{did}/thing/service/property/set | Sub | 云端 → 设备 |
| 属性设置回复 | sys/{pid}/{did}/thing/service/property/set_reply | Pub | 设备 → 云端 |
| 事件上报 | sys/{pid}/{did}/thing/event/{eventId}/post | Pub | 设备 → 云端 |
| 服务调用 | sys/{pid}/{did}/thing/service/{serviceId}/call | Sub | 云端 → 设备 |
| 配置下发 | sys/{pid}/{did}/thing/config/set | Sub | 云端 → 设备 |
| 配置回复 | sys/{pid}/{did}/thing/config/set_reply | Pub | 设备 → 云端 |
| OTA 升级通知 | sys/{pid}/{did}/thing/ota/upgrade | Sub | 云端 → 设备 |
| OTA 进度上报 | sys/{pid}/{did}/thing/ota/progress | Pub | 设备 → 云端 |
参数说明:
{pid}: Product ID (产品标识,如p_relay_4ch){did}: Device ID (逻辑设备 ID,平台分配的 UUID){eventId}: 事件标识符 (如over_current,device_restart){serviceId}: 服务标识符 (如restart,factory_reset)
3.1 后端订阅 (共享订阅)
后端使用共享订阅保证多实例负载均衡:
$share/group1/sys/+/+/thing/event/#
$share/group1/sys/+/+/thing/service/+/set_reply
$share/group1/sys/+/+/thing/config/set_reply
$share/group1/sys/+/+/thing/ota/progress3.2 Config 配置下发 Payload
下发(云端 → 设备)
Topic: sys/{pid}/{did}/thing/config/set
{
"msgId": "uuid-1234",
"params": {
"logicalId": "d1a2b3c4-e5f6-7890-abcd-ef1234567890",
"productKey": "p_relay_4ch",
"topics": {
"propertyPost": "sys/p_relay_4ch/d1a2b3c4-.../thing/event/property/post",
"propertySet": "sys/p_relay_4ch/d1a2b3c4-.../thing/service/property/set",
"propertySetReply": "sys/p_relay_4ch/d1a2b3c4-.../thing/service/property/set_reply"
}
}
}回复(设备 → 云端)
Topic: sys/{pid}/{did}/thing/config/set_reply
{
"msgId": "uuid-1234",
"code": 200,
"msg": "success"
}| 字段 | 说明 |
|---|---|
logicalId | 平台分配给设备的逻辑 ID(UUID),设备收到后写入 NVS |
productKey | 产品标识,设备用于拼接后续通信 Topic |
topics | 设备后续通信所需的完整 Topic 列表 |
code | 200 表示配置保存成功,非 200 表示失败 |
3.3 OTA 进度上报 Payload
Topic: sys/{pid}/{did}/thing/ota/progress
{
"msgId": "uuid-5678",
"progress": 50,
"desc": "downloading"
}desc 字段枚举值:
| desc 值 | 含义 | progress 取值 |
|---|---|---|
downloading | 正在下载固件 | 0-99(百分比) |
flashing | 下载完成,正在写入固件分区 | 100 |
done | 写入完成,准备重启 | 100 |
verify_failed | MD5 校验失败 | 当前下载进度值 |
设备上报流程:downloading(每 10% 上报一次)→ flashing(下载完成后)→ done(写入完成后设备重启)。若 MD5 校验失败则上报 verify_failed,云端记录失败日志。
4. 当前固件兼容性说明
4.1 四路继电器 (v2.0) 现状
当前继电器固件使用简化版 Topic,与标准规范不完全一致:
| 用途 | 标准 Topic | 固件实际 (v2.0) |
|---|---|---|
| 状态上报 | sys/{pid}/{did}/thing/event/property/post | device/{MAC}/relay/status |
| 控制指令 | sys/{pid}/{did}/thing/service/property/set | device/{MAC}/relay/control |
| 设备标识 | Logical ID (UUID) | MAC 地址 |
4.2 Payload 差异
标准格式:
{"msgId":"abc","params":{"relay_1":true,"relay_2":false}}固件当前格式:
{"relay0":1,"relay1":0,"relay2":1,"relay3":0}4.3 迁移计划 (v0.2)
- 后端同时订阅标准 Topic 和兼容 Topic,处理两种格式
- 固件 v3.0 升级到标准 Topic 和 Payload 格式
- 旧固件设备通过后端转换层继续工作
- 转换层在
MqttHandler中实现 Topic/Payload 映射
AI 编码指引: 写后端 MQTT 代码时,订阅标准 Topic (
sys/+/+/thing/...);同时注册兼容 Topic 处理器做格式转换。新设备固件直接按标准协议开发。
5. 场景通信示例:四路继电器模块
为了让硬件开发同学更好地理解,我们以“四路继电器模块”为例,还原真实的使用场景。
设备特性:
- 输入:4 个物理按键 (Btn1 ~ Btn4),支持本地控制继电器。
- 输出:4 路继电器 (Relay1 ~ Relay4),控制灯光或电源。
- 通信:ESP32 Wi-Fi + MQTT。
5.1 场景一:用户远程控制 (App -> Device)
场景描述:用户在小程序上点击“打开继电器 1”。
- 订阅 (初始化):设备开机连接 MQTT 后,必须订阅 Topic
sys/{pid}/{did}/thing/service/property/set,监听云端指令。 - 下发:云端收到小程序请求,向该 Topic 发布消息:json
{ "msgId": "12345", "params": { "relay_1": true } } - 执行:设备收到消息,解析出
relay_1为true,驱动 GPIO 输出高电平,继电器吸合。 - 回复:设备向
sys/{pid}/{did}/thing/service/property/set_reply回复执行结果:json{ "msgId": "12345", "code": 200, "msg": "success" } - 同步:同时,设备应触发一次属性上报(见场景二),确保云端影子状态最新。
5.2 场景二:本地按键控制 (Local -> Cloud)
场景描述:用户走到设备前,按下了“按键 1”,灯亮了。此时云端的小程序界面也应该同步变为“开启”状态。
- 动作:设备检测到按键 1 被按下,执行本地逻辑,将继电器 1 吸合。
- 上报:设备主动向
sys/{pid}/{did}/thing/event/property/post发布当前最新状态:json{ "msgId": "abcde", "params": { "relay_1": true, "relay_2": false, "relay_3": false, "relay_4": false } } - 同步:云端收到消息后,更新数据库中的设备状态,并推送给正在观看的小程序用户。
5.3 场景三:OTA 固件升级
场景描述:开发者发布了新固件 V1.1.0,修复了某个 Bug。
- 通知:云端向
sys/{pid}/{did}/thing/ota/upgrade发布升级信息:json{ "version": "1.1.0", "url": "https://oss.iotforgex.com/firmware/v1.1.0.bin", "size": 102400, "md5": "a1b2c3d4..." } - 下载:设备收到消息,暂停业务逻辑,开始通过 HTTP/HTTPS 下载固件。
- 进度:下载过程中,设备每隔 10% 向
sys/{pid}/{did}/thing/ota/progress上报进度:json{ "progress": 50, "desc": "downloading" } - 完成:下载完成并校验通过后,设备重启并运行新固件。重启后上报一次版本号。
