12 KiB
MQTT 与嵌入式通信协议
本文档说明后端服务与嵌入式设备之间的 MQTT Topic、通信方向、Payload 类型和 JSON 数据格式。
Topic 标识变更(v5.6 起):除首次注册回复
registerDeviceNo仍按 MAC 地址 下发外,其余 MQTT 下行命令 Topic 第一段统一使用设备编号 deviceNo。后端上行处理通过DeviceIdentityResolver兼容 MAC / 设备编号两种入参;下行命令请按deviceNo订阅subscriber/cmd或subscriber/schedule。业务字段(payload 中的deviceNo、数据库主键、API 入参)仍是设备编号,未变更。
当前配置位置:
- 后端订阅配置:
water-admin/src/main/resources/application.yml - 后端上行消息分发:
water-modules/water-app/src/main/java/org/dromara/app/mqtt - 后端命令下发:
water-common/water-common-mqtt/src/main/java/org/dromara/mqtt/DeviceMqttCommandPublisher.java
基础约定
| 项目 | 说明 |
|---|---|
| Broker | mqtt.broker-url |
| QoS | mqtt.qos,当前默认 1 |
| 设备标识 | 首次注册 Topic 使用设备 MAC;注册完成后的上、下行业务 Topic 统一使用 deviceNo;LWT 离线 Topic 兼容 MAC |
| Topic 变量 | 文档中的 {deviceNo} 指服务端分配的设备编号,{mac} 指设备 MAC 地址 |
| JSON 编码 | UTF-8 |
| 时间格式 | yyyy-MM-dd HH:mm:ss |
后端会订阅以下 Topic:
- /+/publish/finish/schedule
- /+/publish/register
- /+/publish/status
- /+/publish/power
- /+/publish/ack
- /+/publish/finish/key
- /+/publish/error
- /+/subscriber/cmd
Topic 总览
| 功能名称 | 通信方向 | 订阅名称 / Topic | Payload 类型 | 后端处理 |
|---|---|---|---|---|
| 排程任务完成上报 | 设备发布,后端订阅 | /{deviceNo}/publish/finish/schedule |
JSON | 当前记录日志 |
| 设备注册 | 设备发布,后端订阅 | /{mac}/publish/register |
JSON | 注册、标记上线并下发 deviceNo |
| 设备离线遗嘱 | 设备发布,后端订阅 | /{deviceNo}/publish/status |
JSON | 收到 offline 后立即标记离线 |
| 电量及在线心跳 | 设备发布,后端订阅 | /{deviceNo}/publish/power |
JSON | 更新设备电量并刷新 10 分钟在线心跳 |
| 命令应答 ACK | 设备发布,后端订阅 | /{deviceNo}/publish/ack |
JSON 或纯文本 | 清理待确认命令 |
| 按键浇水完成上报 | 设备发布,后端订阅 | /{deviceNo}/publish/finish/key |
JSON | 当前记录日志 |
| 硬件故障上报 | 设备发布,后端订阅 | /{deviceNo}/publish/error |
JSON | 当前记录日志 |
| 下发命令 | 后端发布,设备订阅 | /{deviceNo}/subscriber/cmd |
JSON | 设备执行命令 |
设备注册
| 项目 | 内容 |
|---|---|
| 功能名称 | 设备注册 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{mac}/publish/register |
| Payload 类型 | JSON |
后端以 Topic 中的设备标识作为解析入口,payload 中的 deviceNo 即使传入也会被 Topic 覆盖。
示例:
{
"deviceName": "一号浇水设备",
"powerLevel": "86",
"deviceEm": "WATER-EM-01",
"deviceSn": "SN202606100001",
"fwVer": "1.0.0",
"macAddress": "AA:BB:CC:DD:EE:FF"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| deviceName | string | 否 | 设备名称 |
| powerLevel | string | 否 | 电量 |
| deviceEm | string | 否 | 设备型号 |
| deviceSn | string | 否 | 设备序列号 |
| fwVer | string | 否 | 固件版本 |
| macAddress | string | 否 | MAC 地址 |
电量上报
| 项目 | 内容 |
|---|---|
| 功能名称 | 电量上报 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/power |
| Payload 类型 | JSON |
示例:
{
"deviceName": "Waterer_01",
"powerLevel": "86",
"charging": 1
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| powerLevel | string | 是 | 电量值,后端当前按字符串保存 |
注册成功后后端会立即将设备标记为在线。之后收到包含 powerLevel 的有效消息,会把在线心跳有效期刷新为 600 秒。设备必须周期上报,即使电量没有变化也要发送,建议每 5 分钟一次;连续 10 分钟未收到电量或 ACK 刷新时将被判定为离线。ACK 仍保留现有的在线缓存刷新逻辑。
设备离线遗嘱
| 项目 | 内容 |
|---|---|
| 功能名称 | 设备离线遗嘱 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/status,注册前可使用 /{mac}/publish/status |
| Payload 类型 | JSON 或纯文本 |
推荐将以下消息配置为 MQTT LWT,并设置 retain=false:
{
"status": "offline"
}
后端收到 offline 或 0 后立即标记设备离线。online 或 1 不会标记在线,设备上线通过注册成功或电量心跳确认。
兼容设备可发送 {"deviceMac":"AA:BB:CC:DD:EE:FF","offline":"true"},服务端会使用 deviceMac 解析设备并标记离线。
命令应答 ACK
| 项目 | 内容 |
|---|---|
| 功能名称 | 命令应答 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/ack |
| Payload 类型 | JSON,兼容纯文本 |
推荐 JSON:
{
"commandId": "9f4f1f2e8c6a4e1f8b7f3a1d2c0b9e11",
"status": "1",
"message": "receive"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| commandId | string | 是 | 后端下发命令时携带的命令 ID,用于清理待 ACK 命令 |
| status | string | 否 | 执行状态,建议 1 成功,0 失败 |
| message | string | 否 | 设备返回消息 |
兼容格式:
receive
说明:如果设备只返回纯文本,例如 receive,后端会查找该设备当前待确认命令。若只有一条 pending 命令,则按该命令完成 ACK;若没有或有多条 pending 命令,只记录日志,不清理命令。嵌入式侧建议优先使用 JSON ACK,并回传 commandId。
下发命令
| 项目 | 内容 |
|---|---|
| 功能名称 | 下发命令 |
| 通信方向 | 后端发布,设备订阅 |
| Topic | /{deviceNo}/subscriber/cmd |
| Payload 类型 | JSON |
后端下发手动开关设备命令示例:
{
"deviceNo": "01",
"workStatus": "1",
"startTime": "2026-06-10 14:35:31",
"durationMin": 20,
"commandId": "9f4f1f2e8c6a4e1f8b7f3a1d2c0b9e11",
"commandType": "switchDevice"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| deviceNo | string | 是 | 设备编号 |
| workStatus | string | 是 | 0 关闭浇水,1 开始浇水 |
| startTime | string | 否 | 开始时间,格式 yyyy-MM-dd HH:mm:ss |
| durationMin | number | 是 | 持续时间,单位分钟,必须大于 0 |
| commandId | string | 是 | 后端自动生成,设备 ACK 必须原样返回 |
| commandType | string | 是 | 命令类型,手动开关设备为 switchDevice |
设备收到命令后,应发布 ACK 到:
/{deviceNo}/publish/ack
推荐 ACK:
{
"commandId": "9f4f1f2e8c6a4e1f8b7f3a1d2c0b9e11",
"status": "1",
"message": "receive"
}
设备编号下发
| 项目 | 内容 |
|---|---|
| 功能名称 | 设备编号下发 |
| 通信方向 | 后端发布,设备订阅 |
| Topic | /{deviceMac}/subscriber/cmd |
| Payload 类型 | JSON |
说明:这是首次注册后的唯一例外,后端按设备 MAC 回传设备编号;其余下行命令都使用 deviceNo 作为 Topic 第一段。
排程任务完成上报
| 项目 | 内容 |
|---|---|
| 功能名称 | 排程任务完成上报 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/finish/schedule |
| Payload 类型 | JSON |
| 当前后端行为 | 写入浇水记录 |
JSON 示例:
{
"deviceNo": "dcda0cfa289c",
"startWeek": 5,
"startTime": "2026-06-05 14:37",
"durationMin": 3,
"triggerType": "mqtt on"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| deviceNo | string | 否 | 设备编号;后端以 Topic 中的 {deviceNo} 为准,不一致时记录 warn |
| startWeek | number | 否 | 星期几,1 到 7;当前后端不入库 |
| startTime | string | 是 | 开始时间,支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm |
| endTime | string | 否 | 结束时间,支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm |
| durationMin | number | 否 | 预计持续时间,单位分钟;大于 0 时后端按 startTime + durationMin 计算预计结束时间 |
| triggerType | string | 否 | 设备触发来源;当前后端忽略该字段,排程完成记录固定入库为 0 |
| scheduleId | number | 否 | 后端忽略 payload 中的该字段,按 AppSchedulingDevice.deviceNo 查询排程 ID;查不到时写入 0 |
后端先计算预计结束时间:durationMin > 0 时使用 startTime + durationMin,否则使用 payload 中的 endTime。实际入库的 endTime 取“消息接收时间”和“预计结束时间”中较早的时间,durationMin 按 startTime 到实际入库 endTime 重新计算。
按键浇水完成上报
| 项目 | 内容 |
|---|---|
| 功能名称 | 按键浇水完成上报 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/finish/key |
| Payload 类型 | JSON |
| 当前后端行为 | 写入浇水记录 |
JSON 示例:
{
"deviceNo": "dcda0cfa289c",
"startWeek": 5,
"startTime": "2026-06-05 17:43",
"durationMin": 0,
"triggerType": "key"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| deviceNo | string | 否 | 设备编号;后端以 Topic 中的 {deviceNo} 为准,不一致时记录 warn |
| startWeek | number | 否 | 星期几,1 到 7;当前后端不入库 |
| startTime | string | 是 | 开始时间,支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm |
| endTime | string | 否 | 结束时间,支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm |
| durationMin | number | 否 | 预计持续时间,单位分钟;大于 0 时后端按 startTime + durationMin 计算预计结束时间 |
| triggerType | string | 否 | 设备触发来源;当前后端忽略该字段,按键完成记录固定入库为 1 |
后端先计算预计结束时间:durationMin > 0 时使用 startTime + durationMin,否则使用 payload 中的 endTime。实际入库的 endTime 取“消息接收时间”和“预计结束时间”中较早的时间,durationMin 按 startTime 到实际入库 endTime 重新计算。按键完成记录的 scheduleId 固定为 0。
硬件故障上报
| 项目 | 内容 |
|---|---|
| 功能名称 | 硬件故障上报 |
| 通信方向 | 设备发布,后端订阅 |
| Topic | /{deviceNo}/publish/error |
| Payload 类型 | JSON |
| 当前后端行为 | 记录日志,暂未入库 |
建议 JSON:
{
"errorCode": "E001",
"errorType": "pump",
"message": "pump blocked",
"level": "error",
"time": "2026-06-10 10:00:00"
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| errorCode | string | 建议 | 故障编码 |
| errorType | string | 否 | 故障类型 |
| message | string | 建议 | 故障描述 |
| level | string | 否 | 故障级别,例如 warn、error |
| time | string | 否 | 故障发生时间 |
嵌入式侧实现要点
- 设备启动后使用 MAC 发布注册消息到
/{mac}/publish/register,取得服务端分配的 deviceNo。 - MQTT 连接时配置离线遗嘱
/{deviceNo}/publish/status,payload 为{"status":"offline"},retain=false。 - 设备至少每 5 分钟发布一次电量到
/{deviceNo}/publish/power,电量未变化也要上报。 - 设备订阅自己的命令 Topic:
/{deviceNo}/subscriber/cmd和/{deviceNo}/subscriber/schedule。 - 设备收到命令后立即返回 ACK 到
/{deviceNo}/publish/ack,推荐返回 JSON 并携带commandId。 - 排程或按键浇水完成后,分别发布到
/{deviceNo}/publish/finish/schedule或/{deviceNo}/publish/finish/key。 - 硬件异常时发布到
/{deviceNo}/publish/error。