Skip to content

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)平台分配首次连接后由平台下发
ClientIdMAC 地址MQTT 连接标识

当前状态: 四路继电器固件 v2.0 用 MAC 地址作为设备标识,尚未实现 ProductKey 和 Logical ID 分配机制。详见下方兼容性说明。

2.3 固件预置参数

c
// 固件中需预置 (生产时烧录)
#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 ID

3. MQTT Topic 规范 (标准)

平台标准采用类阿里云 IoT 的 Topic 结构:

用途Topic 模板权限方向
属性上报sys/{pid}/{did}/thing/event/property/postPub设备 → 云端
属性设置sys/{pid}/{did}/thing/service/property/setSub云端 → 设备
属性设置回复sys/{pid}/{did}/thing/service/property/set_replyPub设备 → 云端
事件上报sys/{pid}/{did}/thing/event/{eventId}/postPub设备 → 云端
服务调用sys/{pid}/{did}/thing/service/{serviceId}/callSub云端 → 设备
配置下发sys/{pid}/{did}/thing/config/setSub云端 → 设备
配置回复sys/{pid}/{did}/thing/config/set_replyPub设备 → 云端
OTA 升级通知sys/{pid}/{did}/thing/ota/upgradeSub云端 → 设备
OTA 进度上报sys/{pid}/{did}/thing/ota/progressPub设备 → 云端

参数说明

  • {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/progress

3.2 Config 配置下发 Payload

下发(云端 → 设备)

Topic: sys/{pid}/{did}/thing/config/set

json
{
  "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

json
{
  "msgId": "uuid-1234",
  "code": 200,
  "msg": "success"
}
字段说明
logicalId平台分配给设备的逻辑 ID(UUID),设备收到后写入 NVS
productKey产品标识,设备用于拼接后续通信 Topic
topics设备后续通信所需的完整 Topic 列表
code200 表示配置保存成功,非 200 表示失败

3.3 OTA 进度上报 Payload

Topic: sys/{pid}/{did}/thing/ota/progress

json
{
  "msgId": "uuid-5678",
  "progress": 50,
  "desc": "downloading"
}

desc 字段枚举值:

desc 值含义progress 取值
downloading正在下载固件0-99(百分比)
flashing下载完成,正在写入固件分区100
done写入完成,准备重启100
verify_failedMD5 校验失败当前下载进度值

设备上报流程:downloading(每 10% 上报一次)→ flashing(下载完成后)→ done(写入完成后设备重启)。若 MD5 校验失败则上报 verify_failed,云端记录失败日志。


4. 当前固件兼容性说明

4.1 四路继电器 (v2.0) 现状

当前继电器固件使用简化版 Topic,与标准规范不完全一致:

用途标准 Topic固件实际 (v2.0)
状态上报sys/{pid}/{did}/thing/event/property/postdevice/{MAC}/relay/status
控制指令sys/{pid}/{did}/thing/service/property/setdevice/{MAC}/relay/control
设备标识Logical ID (UUID)MAC 地址

4.2 Payload 差异

标准格式:

json
{"msgId":"abc","params":{"relay_1":true,"relay_2":false}}

固件当前格式:

json
{"relay0":1,"relay1":0,"relay2":1,"relay3":0}

4.3 迁移计划 (v0.2)

  1. 后端同时订阅标准 Topic 和兼容 Topic,处理两种格式
  2. 固件 v3.0 升级到标准 Topic 和 Payload 格式
  3. 旧固件设备通过后端转换层继续工作
  4. 转换层在 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”。

  1. 订阅 (初始化):设备开机连接 MQTT 后,必须订阅 Topic sys/{pid}/{did}/thing/service/property/set,监听云端指令。
  2. 下发:云端收到小程序请求,向该 Topic 发布消息:
    json
    {
      "msgId": "12345",
      "params": {
        "relay_1": true
      }
    }
  3. 执行:设备收到消息,解析出 relay_1true,驱动 GPIO 输出高电平,继电器吸合。
  4. 回复:设备向 sys/{pid}/{did}/thing/service/property/set_reply 回复执行结果:
    json
    {
      "msgId": "12345",
      "code": 200,
      "msg": "success"
    }
  5. 同步:同时,设备应触发一次属性上报(见场景二),确保云端影子状态最新。

5.2 场景二:本地按键控制 (Local -> Cloud)

场景描述:用户走到设备前,按下了“按键 1”,灯亮了。此时云端的小程序界面也应该同步变为“开启”状态。

  1. 动作:设备检测到按键 1 被按下,执行本地逻辑,将继电器 1 吸合。
  2. 上报:设备主动向 sys/{pid}/{did}/thing/event/property/post 发布当前最新状态:
    json
    {
      "msgId": "abcde",
      "params": {
        "relay_1": true,
        "relay_2": false,
        "relay_3": false,
        "relay_4": false
      }
    }
  3. 同步:云端收到消息后,更新数据库中的设备状态,并推送给正在观看的小程序用户。

5.3 场景三:OTA 固件升级

场景描述:开发者发布了新固件 V1.1.0,修复了某个 Bug。

  1. 通知:云端向 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..."
    }
  2. 下载:设备收到消息,暂停业务逻辑,开始通过 HTTP/HTTPS 下载固件。
  3. 进度:下载过程中,设备每隔 10% 向 sys/{pid}/{did}/thing/ota/progress 上报进度:
    json
    {
      "progress": 50,
      "desc": "downloading"
    }
  4. 完成:下载完成并校验通过后,设备重启并运行新固件。重启后上报一次版本号。