Files
water/docs/mqtt-embedded-communication.md
yuhaiming 70e2356f22 fix(app): 修复 AppController 安全与查询问题
- 加强异常处理、类型安全和图片上传校验
- 优化设备相关查询,避免重复访问数据源
- 补充并记录并发测试与审查修复实施计划
2026-07-17 08:20:44 +08:00

12 KiB
Raw Blame History

MQTT 与嵌入式通信协议

本文档说明后端服务与嵌入式设备之间的 MQTT Topic、通信方向、Payload 类型和 JSON 数据格式。

Topic 标识变更v5.6 起):除首次注册回复 registerDeviceNo 仍按 MAC 地址 下发外,其余 MQTT 下行命令 Topic 第一段统一使用设备编号 deviceNo。后端上行处理通过 DeviceIdentityResolver 兼容 MAC / 设备编号两种入参;下行命令请按 deviceNo 订阅 subscriber/cmdsubscriber/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 统一使用 deviceNoLWT 离线 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"
}

后端收到 offline0 后立即标记设备离线。online1 不会标记在线,设备上线通过注册成功或电量心跳确认。

兼容设备可发送 {"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 星期几,17;当前后端不入库
startTime string 开始时间,支持 yyyy-MM-dd HH:mm:ssyyyy-MM-dd HH:mm
endTime string 结束时间,支持 yyyy-MM-dd HH:mm:ssyyyy-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 取“消息接收时间”和“预计结束时间”中较早的时间,durationMinstartTime 到实际入库 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 星期几,17;当前后端不入库
startTime string 开始时间,支持 yyyy-MM-dd HH:mm:ssyyyy-MM-dd HH:mm
endTime string 结束时间,支持 yyyy-MM-dd HH:mm:ssyyyy-MM-dd HH:mm
durationMin number 预计持续时间,单位分钟;大于 0 时后端按 startTime + durationMin 计算预计结束时间
triggerType string 设备触发来源;当前后端忽略该字段,按键完成记录固定入库为 1

后端先计算预计结束时间:durationMin > 0 时使用 startTime + durationMin,否则使用 payload 中的 endTime。实际入库的 endTime 取“消息接收时间”和“预计结束时间”中较早的时间,durationMinstartTime 到实际入库 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 故障级别,例如 warnerror
time string 故障发生时间

嵌入式侧实现要点

  1. 设备启动后使用 MAC 发布注册消息到 /{mac}/publish/register,取得服务端分配的 deviceNo。
  2. MQTT 连接时配置离线遗嘱 /{deviceNo}/publish/statuspayload 为 {"status":"offline"}retain=false
  3. 设备至少每 5 分钟发布一次电量到 /{deviceNo}/publish/power,电量未变化也要上报。
  4. 设备订阅自己的命令 Topic/{deviceNo}/subscriber/cmd/{deviceNo}/subscriber/schedule
  5. 设备收到命令后立即返回 ACK 到 /{deviceNo}/publish/ack,推荐返回 JSON 并携带 commandId
  6. 排程或按键浇水完成后,分别发布到 /{deviceNo}/publish/finish/schedule/{deviceNo}/publish/finish/key
  7. 硬件异常时发布到 /{deviceNo}/publish/error