# MQTT 与嵌入式通信协议 本文档说明后端服务与嵌入式设备之间的 MQTT Topic、通信方向、Payload 类型和 JSON 数据格式。 当前配置位置: - 后端订阅配置:`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 第一段 `{deviceNo}` | | Topic 变量 | 文档中的 `{deviceNo}` 替换为真实设备编号,例如 `/01/publish/register` | | JSON 编码 | UTF-8 | | 时间格式 | `yyyy-MM-dd HH:mm:ss` | 后端会订阅以下 Topic: ```yaml - /+/publish/finish/schedule - /+/publish/register - /+/publish/power - /+/publish/ack - /+/publish/finish/key - /+/publish/error - /+/subscriber/cmd ``` ## Topic 总览 | 功能名称 | 通信方向 | 订阅名称 / Topic | Payload 类型 | 后端处理 | | --- | --- | --- | --- | --- | | 排程任务完成上报 | 设备发布,后端订阅 | `/{deviceNo}/publish/finish/schedule` | JSON | 当前记录日志 | | 设备注册 | 设备发布,后端订阅 | `/{deviceNo}/publish/register` | JSON | 注册或更新设备 | | 电量上报 | 设备发布,后端订阅 | `/{deviceNo}/publish/power` | JSON | 更新设备电量 | | 命令应答 ACK | 设备发布,后端订阅 | `/{deviceNo}/publish/ack` | JSON 或纯文本 | 清理待确认命令 | | 按键浇水完成上报 | 设备发布,后端订阅 | `/{deviceNo}/publish/finish/key` | JSON | 当前记录日志 | | 硬件故障上报 | 设备发布,后端订阅 | `/{deviceNo}/publish/error` | JSON | 当前记录日志 | | 下发命令 | 后端发布,设备订阅 | `/{deviceNo}/subscriber/cmd` | JSON | 设备执行命令 | ## 设备注册 | 项目 | 内容 | | --- | --- | | 功能名称 | 设备注册 | | 通信方向 | 设备发布,后端订阅 | | Topic | `/{deviceNo}/publish/register` | | Payload 类型 | JSON | 后端以 Topic 中的 `{deviceNo}` 作为设备编号,payload 中的 `deviceNo` 即使传入也会被 Topic 覆盖。 示例: ```json { "deviceName": "一号浇水设备", "status": "1", "workStatus": "0", "powerLevel": "86", "wifiName": "office-wifi", "deviceEm": "WATER-EM-01", "deviceSn": "SN202606100001", "fwVer": "1.0.0", "macAddress": "AA:BB:CC:DD:EE:FF", "bindToken": "123456" } ``` 字段说明: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | deviceName | string | 否 | 设备名称 | | status | string | 否 | 设备状态:`1` 在线,`0` 离线,`2` 到期,`3` 故障;为空时后端默认 `2` | | workStatus | string | 否 | 工作状态:`0` 休息,`1` 工作 | | powerLevel | string | 否 | 电量 | | wifiName | string | 否 | WiFi 名称 | | deviceEm | string | 否 | 设备型号 | | deviceSn | string | 否 | 设备序列号 | | fwVer | string | 否 | 固件版本 | | macAddress | string | 否 | MAC 地址 | | bindToken | string | 否 | 设备绑定令牌 | ## 电量上报 | 项目 | 内容 | | --- | --- | | 功能名称 | 电量上报 | | 通信方向 | 设备发布,后端订阅 | | Topic | `/{deviceNo}/publish/power` | | Payload 类型 | JSON | 示例: ```json { "powerLevel": "86" } ``` 字段说明: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | powerLevel | string | 是 | 电量值,后端当前按字符串保存 | ## 命令应答 ACK | 项目 | 内容 | | --- | --- | | 功能名称 | 命令应答 | | 通信方向 | 设备发布,后端订阅 | | Topic | `/{deviceNo}/publish/ack` | | Payload 类型 | JSON,兼容纯文本 | 推荐 JSON: ```json { "commandId": "9f4f1f2e8c6a4e1f8b7f3a1d2c0b9e11", "status": "1", "message": "receive" } ``` 字段说明: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | commandId | string | 是 | 后端下发命令时携带的命令 ID,用于清理待 ACK 命令 | | status | string | 否 | 执行状态,建议 `1` 成功,`0` 失败 | | message | string | 否 | 设备返回消息 | 兼容格式: ```text receive ``` 说明:如果设备只返回纯文本,例如 `receive`,后端会查找该设备当前待确认命令。若只有一条 pending 命令,则按该命令完成 ACK;若没有或有多条 pending 命令,只记录日志,不清理命令。嵌入式侧建议优先使用 JSON ACK,并回传 `commandId`。 ## 下发命令 | 项目 | 内容 | | --- | --- | | 功能名称 | 下发命令 | | 通信方向 | 后端发布,设备订阅 | | Topic | `/{deviceNo}/subscriber/cmd` | | Payload 类型 | JSON | 后端下发手动开关设备命令示例: ```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 到: ```text /{deviceNo}/publish/ack ``` 推荐 ACK: ```json { "commandId": "9f4f1f2e8c6a4e1f8b7f3a1d2c0b9e11", "status": "1", "message": "receive" } ``` ## 排程任务完成上报 | 项目 | 内容 | | --- | --- | | 功能名称 | 排程任务完成上报 | | 通信方向 | 设备发布,后端订阅 | | Topic | `/{deviceNo}/publish/finish/schedule` | | Payload 类型 | JSON | | 当前后端行为 | 写入浇水记录 | 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 示例: ```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: ```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 | 否 | 故障发生时间 | ## 嵌入式侧实现要点 1. 设备启动后发布注册消息到 `/{deviceNo}/publish/register`。 2. 设备定时或电量变化时发布电量到 `/{deviceNo}/publish/power`。 3. 设备订阅自己的命令 Topic:`/{deviceNo}/subscriber/cmd`。 4. 设备收到命令后立即返回 ACK 到 `/{deviceNo}/publish/ack`,推荐返回 JSON 并携带 `commandId`。 5. 排程或按键浇水完成后,分别发布到 `/{deviceNo}/publish/finish/schedule` 或 `/{deviceNo}/publish/finish/key`。 6. 硬件异常时发布到 `/{deviceNo}/publish/error`。