From 0861a1c1f82631980503eee08438ca72fa84b3ce Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 15 Jul 2026 11:55:21 +0800 Subject: [PATCH] =?UTF-8?q?proto:=20event=5Freport=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0/=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=BF=85?= =?UTF-8?q?=E7=AD=94=E7=A1=AE=E8=AE=A4=E6=9C=BA=E5=88=B6=20(MQTT=20V1.04?= =?UTF-8?q?=20/=20TCP=20V1.01)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 事件为不可再生关键数据, QoS/TCP送达不代表业务层落库, 引入应用层闭环: - 应答格式: 回显 msg_id + code/msg, 不带 data - 设备重发: 5s 超时, 同 msg_id/原始 ts, 最多 3 次, 失败留队列 待重连或下一事件合并上报 (MQTT ≤500B / TCP ≤4096B 拆包) - 平台/客户端: 先落库后应答; msg_id 十分钟窗口去重, 重复包答 code=0 不重复入库 - 附正常/应答丢失重发时序图; 命令详表与交互示例同步 --- docs/DLD960_IoT_MQTT协议.md | 58 +++++++++++++++++++++++++++++++++++-- docs/DLD960_TCP_JSON协议.md | 36 ++++++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/docs/DLD960_IoT_MQTT协议.md b/docs/DLD960_IoT_MQTT协议.md index 5c1d314..4c55c6f 100644 --- a/docs/DLD960_IoT_MQTT协议.md +++ b/docs/DLD960_IoT_MQTT协议.md @@ -119,7 +119,7 @@ dld960/{dev_serial}/{direction} | `report_config` | 设置主动上报 | srv→dev | 0xC5 | | `initialize` | 设备上电初始化登陆 | dev→srv | — | | `loop_data` | 线圈传感数据上报 | dev→srv | 0xC0 | -| `event_report` | 事件上报 | dev→srv | — | +| `event_report` | 事件上报(**平台须应答**,见 §5.3) | dev→srv | — | | `heartbeat` | 设备心跳 | dev→srv | — | --- @@ -748,9 +748,14 @@ dld960/{dev_serial}/{direction} ## 5.3 事件上报 `event_report` -> Topic: `dld960/{sn}/dev` +> 上报 Topic: `dld960/{sn}/dev` +> 应答 Topic: `dld960/{sn}/srv` +> QoS: 1(建议) +> **⚠️ 本指令要求平台必须应答**(区别于 `loop_data` / `heartbeat` 的单向上报) -设备检测到事件时主动上报,非周期性。 +设备检测到事件时主动上报,非周期性。事件(进车/出车/线圈断开等)是**不可再生的关键数据**,MQTT QoS 仅保证 broker 收到,不代表平台业务层已处理入库,因此引入**应用层应答 + 设备重发**机制闭环确认。 + +**设备上报(dev topic):** ```json { @@ -786,6 +791,52 @@ dld960/{dev_serial}/{direction} | `loop_cut` | 线圈断开 | 0 | | `loop_restore` | 线圈恢复 | 断开持续时长 (×50ms) | +**平台应答(srv topic,收到后必须立即回复):** + +```json +{ + "msg_id": 101, + "cmd": "event_report", + "ts": 1719000201, + "code": 0, + "msg": "success" +} +``` + +| 字段 | 说明 | +|------|------| +| `msg_id` | **必须回显**设备上报的 `msg_id`,设备以此匹配确认 | +| `cmd` | 固定 `"event_report"` | +| `code` | 0 = 已接收并处理;非 0 = 处理失败(设备视同未确认,进入重发) | + +**设备端确认与重发机制:** + +1. 发出 `event_report` 后启动确认定时器,**5s** 内未收到 `msg_id` 匹配且 `code=0` 的应答 → 重发; +2. 重发使用**相同的 `msg_id` 与原始 `ts`**(首次事件发生时间,不随重发刷新),平台以此去重; +3. 最多重发 **3 次**(含首发共 4 次)。仍未确认 → 事件保留在待发队列,待 **MQTT 重连成功或下一次事件触发**时合并上报; +4. 等待确认期间新产生的事件,合并进下一包的 `events` 数组(使用新 `msg_id`)。单包长度受发布缓冲限制(≤500B),超出时拆包分批; +5. 待发队列建议深度 **≥16 条事件**,溢出时丢弃最旧事件(先保新鲜数据)。 + +**平台端要求:** + +1. 收到 `event_report` **先落库、后应答**,应答即承诺数据已持久化; +2. 按 `(dev_serial, msg_id)` 在近期窗口(建议 10 分钟)内去重——重复包为设备未收到应答所致,**直接应答 `code=0`,不重复入库**(设备重启后 `msg_id` 从头递增,去重必须限定时间窗口); +3. 应答报文应控制在最小集(不带 `data`),减轻设备下行解析负担。 + +**时序(正常 / 应答丢失重发):** + +``` +设备 平台 + │── event_report(id=101) ──▶│ 落库 + │◀─ ack(id=101, code=0) ────│ 确认, 事件出队 + │ + │── event_report(id=102) ──▶│ 落库 + │ ✗ 应答丢失 │ + │ [5s 超时] │ + │── event_report(id=102) ──▶│ (dev_serial,102) 命中去重 → 不入库 + │◀─ ack(id=102, code=0) ────│ 确认, 事件出队 +``` + ## 5.4 设备心跳 `heartbeat` > Topic: `dld960/{sn}/dev` @@ -822,4 +873,5 @@ dld960/{dev_serial}/{direction} | V1.01 | 2026-07-07 | Topic 压缩为双主题(`{sn}/srv` + `{sn}/dev`),消息类型由 `cmd` 字段区分 | wangfq | | V1.02 | 2026-07-09 | 缩写相关字段 | wangfq | | V1.03 | 2026-07-09 | 增加设备上电初始化指令 | wangfq | +| V1.04 | 2026-07-15 | `event_report` 增加**平台必答**机制:应答格式(回显 `msg_id`)、设备 5s 超时重发(同 `msg_id`/`ts`,最多 3 次)、待发队列合并上报、平台去重与先落库后应答要求 | wangfq | diff --git a/docs/DLD960_TCP_JSON协议.md b/docs/DLD960_TCP_JSON协议.md index 983c076..7822ead 100644 --- a/docs/DLD960_TCP_JSON协议.md +++ b/docs/DLD960_TCP_JSON协议.md @@ -121,7 +121,7 @@ Client Device (DLD960) | `report_config` | 设置主动上报 | 是 | 0xC5 | | 主动推送 | | | | | `loop_data` | 线圈传感数据(设备→客户端) | — | 0xC0 | -| `event_report` | 事件上报(设备→客户端) | — | — | +| `event_report` | 事件上报(设备→客户端,**客户端须应答**,见 §5.2) | — | — | --- @@ -367,6 +367,12 @@ Client Device (DLD960) ## 5.2 事件上报 `event_report` +> 方向:设备 → 客户端;**⚠️ 客户端必须应答**(区别于 `loop_data` 的单向推送) + +设备检测到事件时主动推送,非周期性。事件(进车/出车/线圈断开等)是**不可再生的关键数据**,TCP 送达仅保证客户端进程收到,不代表业务层已处理入库,因此引入**应用层应答 + 设备重发**机制闭环确认(与 IoT MQTT 协议 V1.04 §5.3 机制对称)。 + +**设备推送:** + ```json {"msg_id":101,"cmd":"event_report","ts":1719000200,"data":{"events":[{"type":"car_enter","ch":2,"value":0},{"type":"car_leave","ch":2,"value":350}]}} ``` @@ -378,6 +384,32 @@ Client Device (DLD960) | `loop_cut` | 线圈断开 | 0 | | `loop_restore` | 线圈恢复 | 断开持续时长 (×5ms) | +**客户端应答(收到后必须立即回复):** + +```json +{"msg_id":101,"cmd":"event_report","ts":1719000201,"code":0,"msg":"success"} +``` + +| 字段 | 说明 | +|------|------| +| `msg_id` | **必须回显**设备推送的 `msg_id`,设备以此匹配确认 | +| `cmd` | 固定 `"event_report"` | +| `code` | 0 = 已接收并处理;非 0 = 处理失败(设备视同未确认,进入重发) | + +**设备端确认与重发机制:** + +1. 推送 `event_report` 后启动确认定时器,**5s** 内未收到 `msg_id` 匹配且 `code=0` 的应答 → 重发; +2. 重发使用**相同的 `msg_id` 与原始 `ts`**(首次事件发生时间,不随重发刷新),客户端以此去重; +3. 最多重发 **3 次**(含首发共 4 次)。仍未确认 → 事件保留在待发队列,待**连接恢复(重新鉴权通过)或下一次事件触发**时合并上报; +4. 连接断开期间产生的事件同样进入待发队列;等待确认期间新产生的事件合并进下一包 `events` 数组(使用新 `msg_id`),单帧不超过帧长限制(4096 字节); +5. 待发队列建议深度 **≥16 条事件**,溢出时丢弃最旧事件(先保新鲜数据)。 + +**客户端要求:** + +1. 收到 `event_report` **先落库、后应答**,应答即承诺数据已持久化; +2. 按 `msg_id` 在近期窗口(建议 10 分钟)内去重——重复帧为设备未收到应答所致,**直接应答 `code=0`,不重复入库**(设备重启后 `msg_id` 从头递增,去重必须限定时间窗口;TCP 为点对点连接,无需 `dev_serial` 维度); +3. 应答帧保持最小集(不带 `data`),且与请求同为**单行紧凑 JSON + `\n` 结尾**。 + --- # 6 交互示例 @@ -402,6 +434,7 @@ Client Device (DLD960) <<< {"msg_id":100,"cmd":"loop_data","ts":1719000012,"data":{...}} <<< {"msg_id":101,"cmd":"loop_data","ts":1719000017,"data":{...}} <<< {"msg_id":102,"cmd":"event_report","ts":1719000020,"data":{"events":[{"type":"car_enter","ch":1,"value":0}]}} +>>> {"msg_id":102,"cmd":"event_report","ts":1719000021,"code":0,"msg":"success"} ``` --- @@ -421,3 +454,4 @@ Client Device (DLD960) | 版本 | 修订时间 | 修订说明 | 修订人 | |------|----------|----------|--------| | V1.00 | 2026-06-22 | 初始版本,基于串口协议 V1.01 | wangfq | +| V1.01 | 2026-07-15 | `event_report` 增加**客户端必答**机制:应答格式(回显 `msg_id`)、设备 5s 超时重发(同 `msg_id`/`ts`,最多 3 次)、待发队列合并上报、客户端去重与先落库后应答要求(与 MQTT 协议 V1.04 对称) | wangfq |