From 2ccf0804a8f3f372a384945f0beb1bf92f683b91 Mon Sep 17 00:00:00 2001 From: wangfq Date: Thu, 20 Aug 2026 11:53:36 +0800 Subject: [PATCH] =?UTF-8?q?docs(vd=5F960):=20MQTT=20=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E5=B9=B6=E5=85=A5=20Loop=20=E8=BF=9C=E7=A8=8B=20OTA=20?= =?UTF-8?q?=E2=86=92=20V1.08=20(ROADMAP=20P1.4=20=E2=91=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DLD960_IoT_MQTT协议.md V1.07→V1.08: 命令表加 ota_begin/ota_data/ota_end/ ota_abort/ota_flash/ota_status/ota_report; §4.19~4.24 命令详情; §5.5 ota_report; event_report 扩展 type=ota_error; §2.2 OTA 细分错误码 (err_code); 修订记录 - DLD960_MQTT_OTA协议.md V1.01 (按修改意见): Slot A/B 100KB / 维持 Loop 现状 / 会话期间静默 / 非阻塞 tick 驱动 - README 协议索引 + 技术规格书 §5.1 协议矩阵同步 V1.08 (CRLF 保持) --- README.md | 2 +- docs/DLD960_IoT_MQTT协议.md | 295 ++++++++++++++++++++++++++++++++++++ docs/DLD960_技术规格书.md | 4 +- 3 files changed, 298 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc572b8..9e70308 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ DLD960 是一款基于环形线圈检测原理的四通道车辆检测器,支 | [DLD960Loop_串口通信协议.md](docs/DLD960Loop_串口通信协议.md) | V1.05 | Loop MCU ↔ DBN MCU | 0x7F 帧、0xC0 传感主动上报、variation 3B 有符号 | | [DLD960_串口通信协议.md](docs/DLD960_串口通信协议.md) | V1.01 | 整机 TTL 串口 | 设备管理、参数配置、数据上报 | | [DLD960_TCP_JSON协议.md](docs/DLD960_TCP_JSON协议.md) | V1.03 | 以太网 TCP :5960 | 鉴权 + 命令 + event_report 客户端必答 + 脱机日志(事件/快照流) | -| [DLD960_IoT_MQTT协议.md](docs/DLD960_IoT_MQTT协议.md) | V1.07 | 云平台 MQTT | 双主题 `{sn}/srv`+`{sn}/dev`、initialize、event_report 平台必答、设备时钟同步、脱机日志(事件/快照流) | +| [DLD960_IoT_MQTT协议.md](docs/DLD960_IoT_MQTT协议.md) | V1.08 | 云平台 MQTT | 双主题 `{sn}/srv`+`{sn}/dev`、initialize、event_report 平台必答、设备时钟同步、脱机日志(事件/快照流)、Loop 远程 OTA(ota_* 命令,先存后刷) | | [DLD960_BLE协议.md](docs/DLD960_BLE协议.md) | V1.02 | 蓝牙 BLE | 帧格式 + 分包 + 脱机日志(OFFLOG_STAT/QUERY/CLEAR)+ 传感快照(SNAP_STAT/QUERY/CLEAR,0x28/0x29/0x2A),与 MQTT/TCP 同语义 | | [DLD960硬件资源.md](docs/DLD960硬件资源.md) | — | 硬件 | 双 MCU IO 分配、继电器、指示灯、拨码 | diff --git a/docs/DLD960_IoT_MQTT协议.md b/docs/DLD960_IoT_MQTT协议.md index 2ab50f8..81dd5b6 100644 --- a/docs/DLD960_IoT_MQTT协议.md +++ b/docs/DLD960_IoT_MQTT协议.md @@ -96,6 +96,19 @@ dld960/{dev_serial}/{direction} | 5 | 内部错误 | | 6 | 数据超长 | +### OTA 细分错误码(V1.08) + +顶层 `code` 保持通用语义,`ota_*` 命令的细分错误经响应 `data.err_code` 表达: + +| err_code | 场景 | 顶层 code | +|----------|------|-----------| +| 1 | 单片 CRC 失败 / 乱序缺片(`data.offset` 指示续传点) | 1 | +| 2 | 会话状态不允许(未 begin / 非 downloading 收 ota_data) | 3 | +| 3 | 安全窗口拒绝(有车压线圈) | 3 | +| 4 | 版本冲突(同版本且非 force) | 1 | +| 5 | 全镜像 CRC 不匹配(`data.crc_ok=false`) | 5 | +| 6 | 暂存区写失败(SPI 异常/满) | 5 | + ## 2.3 设备时钟同步(`ts` 语义) 设备无 RTC/SNTP 时间源,上电后本地时钟为**上电秒数**(从 0 递增)。为使上行数据带真实 Unix 时间: @@ -140,9 +153,16 @@ dld960/{dev_serial}/{direction} | `log_stat` | 查询脱机日志统计(事件/快照流) | srv→dev | — | | `log_query` | 分页拉取脱机日志(事件/快照流) | srv→dev | — | | `log_clear` | 清除脱机日志(事件/快照流,审计留痕) | srv→dev | — | +| `ota_begin` | 开启 OTA 会话 / 断点续传定位 | srv→dev | — | +| `ota_data` | OTA 分片下发(256B/片,单片 CRC32) | srv→dev | — | +| `ota_end` | 结束 OTA 下载,全镜像 CRC32 复核 | srv→dev | — | +| `ota_abort` | 中止 OTA 会话,释放暂存 | srv→dev | — | +| `ota_flash` | 触发本地 ISP 刷写(仅 ready 态) | srv→dev | — | +| `ota_status` | 查询 OTA 状态(含进度) | srv→dev | — | | `initialize` | 设备上电初始化登陆 | dev→srv | — | | `loop_data` | 线圈传感数据上报 | dev→srv | 0xC0 | | `event_report` | 事件上报(**平台须应答**,见 §5.3) | dev→srv | — | +| `ota_report` | OTA 进度/结果主动上报 | dev→srv | — | | `heartbeat` | 设备心跳 | dev→srv | — | --- @@ -798,6 +818,246 @@ dld960/{dev_serial}/{direction} --- +## 4.19 开启 OTA 会话 `ota_begin` + +> Topic: `dld960/{sn}/srv` +> 依据《DLD960_MQTT_OTA协议.md》设计稿 V1.01(ROADMAP P1.4 ①:Loop MCU 远程 OTA,先存后刷)。 +> 能力探测:老固件(V1.07 及以下)无 `ota_*` 命令,收到回 `code=4`;平台下发前用 `ota_status` 或 `dev_info_query.soft_ver` 判断。 + +**请求:** + +```json +{ + "msg_id": 401, + "cmd": "ota_begin", + "ts": 1719000000, + "data": { + "target": "loop", + "size": 46864, + "crc32": 305419896, + "version": "1.1.0", + "force": false + } +} +``` + +| data 字段 | 类型 | 说明 | +|-----------|------|------| +| `target` | string | `loop`(当前支持);`dbn` 预留 | +| `size` | uint32 | 镜像 bin 字节数(≤ 98304 = 96KB;Slot 数据区 100KB 预留 4KB 边界余量) | +| `crc32` | uint32 | 全镜像 CRC32(十进制,算法见 §4.19.1) | +| `version` | string | 目标固件版本(写入元数据,审计用;bootloader 不校验版本) | +| `force` | bool | `true` = 覆盖现有暂存镜像 / 忽略版本冲突(默认 false) | + +**设备行为:** +1. 读暂存元数据:若已有镜像且 `size+crc32` 与本次一致 → + - `state=ready` → 返回 `offset=size`(平台可直接 `ota_flash`) + - `state=downloading` → 返回 `offset=received`(断点续传) +2. 不一致 → 分配 Slot(A 当前 / B 回滚),写元数据 `state=downloading, received=0`,返回 `offset=0` +3. `force=false` 且目标版本 == 当前运行版本 → 回 `code=1, err_code=4`(防重复刷写,可 force 绕过) + +**响应 data:** + +```json +{ + "msg_id": 401, + "cmd": "ota_begin", + "ts": 1719000001, + "code": 0, + "msg": "success", + "data": { + "target": "loop", + "slot": "a", + "offset": 0, + "received": 0, + "size": 46864, + "crc32": 305419896, + "state": "downloading" + } +} +``` + +### 4.19.1 CRC32 算法(必须双方一致) + +标准 **CRC-32/ISO-HDLC**:poly `0x04C11DB7`(reflected `0xEDB88320`),init `0xFFFFFFFF`,refin/refout true,xorout `0xFFFFFFFF`。平台侧 Python `zlib.crc32()` / `binascii.crc32()` 即此算法;设备侧查表法。全镜像 CRC = offset 0 ~ size-1 连续;单片 CRC = 仅该 256B。 + +--- + +## 4.20 OTA 分片下发 `ota_data` + +> Topic: `dld960/{sn}/srv` +> 单片大小 **256B** 是协议常量,不接受协商;hex 512 字符 + JSON 外壳 ≈ 640B < 设备接收缓冲 1024B。 + +**请求:** + +```json +{ + "msg_id": 402, + "cmd": "ota_data", + "ts": 1719000002, + "data": { + "target": "loop", + "offset": 0, + "crc32": 2524764894, + "data": "6a6173646f6e...(512 hex 字符 = 256B)" + } +} +``` + +| data 字段 | 类型 | 说明 | +|-----------|------|------| +| `target` | string | 同 `ota_begin` | +| `offset` | uint32 | 本片在镜像中的绝对偏移(**256 对齐**,首片 0) | +| `crc32` | uint32 | 本片 256B 的 CRC32(十进制) | +| `data` | string | 256B 原始字节小写 hex,512 字符 | + +**设备行为:** +1. `offset == received`(顺序片)→ 单片 CRC32 校验 → 写 W25Qxx 暂存(256B 页对齐)→ `received += 256`(≥size 截断为 size)→ `code=0` +2. `offset < received`(重复片,平台重发)→ **幂等回 `code=0`**,不重写 +3. `offset > received`(缺片/乱序)→ `code=1, err_code=1` + `data.offset=received`(指示平台从该处续传;协议不要求乱序重组) +4. 单片 CRC 失败 → `code=1, err_code=1`(平台重发本片;连续失败平台可 `ota_abort`) +5. 会话未开始 / 状态非 downloading → `code=3, err_code=2` +6. `data` 非 512 hex / offset 非 256 对齐 / 超 size → `code=1` 参数错误 + +**响应:** 标准成功/失败 + data 回显 `{offset, received}`。 + +--- + +## 4.21 结束下载 `ota_end` + +> Topic: `dld960/{sn}/srv` + +**请求:** + +```json +{ + "msg_id": 403, + "cmd": "ota_end", + "ts": 1719000003, + "data": { + "target": "loop", + "crc32": 305419896 + } +} +``` + +**设备行为:** +1. `received != size` → `code=1` + `data.offset=received`(不完整,续传) +2. `received == size` → 读回暂存区全镜像计算 CRC32,与 ota_begin 声明值比对 + - 一致 → 元数据 `state=ready, last_result=0` → `code=0, data={crc_ok:true}` + - 不一致 → 元数据 `state=downloading`(保留已下载数据,可重发错片)→ `code=5, err_code=5, data={crc_ok:false}` + +--- + +## 4.22 中止会话 `ota_abort` + +> Topic: `dld960/{sn}/srv` + +```json +{ + "msg_id": 404, + "cmd": "ota_abort", + "ts": 1719000004, + "data": { "target": "loop" } +} +``` + +设备行为:元数据 `state=aborted`,Slot 标记可覆盖;正在刷写时 abort → 停止发送后续 A7 块(Loop 端由 bootloader 超时复位回 APP 兜底)。响应标准成功。 + +--- + +## 4.23 触发刷写 `ota_flash` + +> Topic: `dld960/{sn}/srv` +> ⚠ **会车安全关键命令**:平台应确认现场允许(无车压线圈、非高峰)再下发。 + +**请求:** + +```json +{ + "msg_id": 405, + "cmd": "ota_flash", + "ts": 1719000005, + "data": { + "target": "loop", + "slot": "a", + "force": false + } +} +``` + +| data 字段 | 类型 | 说明 | +|-----------|------|------| +| `slot` | string | `a` / `b`(缺省 = 当前 ready 的槽) | +| `force` | bool | `true` = 跳过安全窗口检查(高风险,平台授权) | + +**设备行为(同步检查 → 异步刷写):** + +1. **安全窗口检查**(force=false 时):4 通道 Loop 有车(VD_FLAG 任一置位)→ `code=3, err_code=3`(有车,拒绝;平台提示"车辆离开后重试")。刷写期间会阻断检测与继电器控制 → 平台建议低峰执行 +2. 元数据非 ready → `code=3`(先 `ota_end` 完成校验) +3. 通过 → 立即回 `code=0`(异步),进入刷写: + - 写 offlog 事件日志:`固件升级开始`(target/slot/version/size) + - **暂停事件上报与脱机日志(会话期间)**:MQTT `event_report` 暂停发送(入队积压,16 深溢出丢最旧,会话结束恢复后补发);offlog/快照落盘暂停("升级开始"日志在暂停前写入、"升级结果"在恢复后补记) + - 维持 Loop 现状:复位进 bootloader 后的 GPIO/继电器状态与现网 BLE OTA 升级一致,不额外干预 + - 发 `9F 01 00 01 A5 A7` 启动帧 → Loop APP 写 flag 复位 → bootloader 回 pre_ok + - 发 A6 地址帧(`0x08003400` 4 字节大端)→ addr_ok + - 从 W25Qxx 暂存读镜像,按 ≤254B/块发 A7(**非阻塞 tick 驱动**:每轮主循环发送 1~2 块并检查 ACK,绝不阻塞主循环,保证刷写窗口内 MQTT PINGREQ/心跳/IWDG 喂狗正常);停等 ACK,1s 超时重发 ×3 + - 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP +4. 进度经 `ota_report` 上行(§5.5);失败重试 ×3 仍失败 → 元数据 `state=flash_failed` + `event_report{type:ota_error}` 告警(平台必答) + +**响应:** `code=0` 仅表示已启动,不代表刷写成功——结果以 `ota_report` / `ota_status` 为准。 + +--- + +## 4.24 查询状态 `ota_status` + +> Topic: `dld960/{sn}/srv` + +**请求:** + +```json +{ "msg_id": 406, "cmd": "ota_status", "ts": 1719000006 } +``` + +**响应 data:** + +```json +{ + "target": "loop", + "state": "flashing", + "slot": "a", + "size": 46864, + "received": 46864, + "crc32": 305419896, + "version": "1.1.0", + "progress": { "sent": 42112, "total": 46864 }, + "last_result": 0, + "last_error": 0 +} +``` + +| data 字段 | 类型 | 说明 | +|-----------|------|------| +| `state` | string | `idle` / `downloading` / `ready` / `flashing` / `flash_failed` / `aborted` | +| `progress.sent` | uint32 | 刷写阶段已送 Loop 的字节数 | +| `last_result` | uint32 | 上次刷写结果:0=无/成功,非 0=错误码 | +| `last_error` | uint32 | 上次失败细分错误码 | + +**设备状态机:** + +``` + ota_begin(新会话) ota_data×N ota_end(CRC✓) + IDLE ─────────────────▶ DOWNLOADING ────────────▶ READY + ▲ │ ▲ │ + │ ota_abort │ │ ota_end(CRC✗) │ ota_flash(安全检查✓) + │ / flash_failed │ └──────────────┐ ▼ + └────────────────────────┴─────────────────┴───── FLASHING ──成功──▶ (Loop 重启) ──▶ IDLE(清槽/保留) + │ + └──失败×3──▶ FLASH_FAILED ──ota_abort/ota_begin──▶ IDLE +``` + +--- + # 5 设备主动上报 ## 5.1 设备上电登陆信息 `initialize` @@ -967,6 +1227,7 @@ dld960/{dev_serial}/{direction} | `car_leave` | 车辆离开 | 通过时间 (×50ms) | | `loop_cut` | 线圈断开 | 0 | | `loop_restore` | 线圈恢复 | 断开持续时长 (×50ms) | +| `ota_error` | OTA 刷写失败告警(重试×3 仍失败,V1.08) | 0x1001=启动帧无响应 / 0x1002=地址帧错误 / 0x1003=数据块 ACK 超限 / 0x1004=全镜像校验失败 / 0x1005=安全窗口拒绝后强制失败 | **平台应答(srv topic,收到后必须立即回复):** @@ -1040,6 +1301,39 @@ dld960/{dev_serial}/{direction} | `net_status` | bool | 以太网连接状态 | | `iot_status` | bool | MQTT 连接状态 | +## 5.5 OTA 进度/结果上报 `ota_report` + +> Topic: `dld960/{sn}/dev` · QoS 1 +> 用途:OTA 会话与刷写进度/结果主动推送(V1.08)。进度可丢,结果可经 `ota_status` 兜底查询(设备侧元数据持久化 `last_result`)。 + +**上报:** + +```json +{ + "msg_id": 501, + "cmd": "ota_report", + "ts": 1719000007, + "data": { + "target": "loop", + "stage": "flashing", + "progress": { "sent": 42112, "total": 46864 }, + "code": 0, + "msg": "" + } +} +``` + +| data 字段 | 说明 | +|-----------|------| +| `target` | 目标:`loop`(当前支持) | +| `stage` | `begin`(会话开启)/ `downloading`(片落盘)/ `ready`(校验通过)/ `flashing`(刷写中)/ `done`(刷写成功,Loop 已重启)/ `failed`(刷写失败) | +| `progress` | `sent`/`total` 字节(刷写阶段) | +| `code` | stage 相关结果码 | + +**上报节奏**:`begin`/`ready`/`done`/`failed` 各 1 次;`flashing` 阶段按进度节流(建议每 64 块或每 8KB 一次,避免刷写期间消息风暴)。`done`/`failed` 设备侧重发 3 次(间隔 5s,同 `msg_id`/`ts`),平台去重窗口建议 10 分钟(与 `event_report` 同策略)。 + +**会话期间静默**(V1.08):OTA 会话期间(`ota_begin` ~ 结束)设备暂停 `event_report` 发送(入队积压,结束后补发)与 offlog/快照落盘("升级开始"日志暂停前写入、"升级结果"恢复后补记),保证刷写窗口内 MQTT 保活与 IWDG 喂狗不受影响。 + --- # 修订记录 @@ -1054,4 +1348,5 @@ dld960/{dev_serial}/{direction} | V1.05 | 2026-07-15 | 增加**设备时钟同步**(§2.3,方案B):设备无 RTC,`initialize` 上线后平台经 `report_config` 命令下发 Unix `ts`,设备据此校准,之后上行 `ts` 为真实 Unix 时间;校准前为上电秒数 | wangfq | | V1.06 | 2026-08-04 | 增加**脱机事件日志**命令:`log_stat`(统计/分页定位)、`log_query`(按全局序号分页,count≤4)、`log_clear`(清除+审计留痕);§2.3 补充日志双时间戳语义(`ts_ms` 相对 + `unix_ts` 已同步,0=未同步,锚点回算规则) | wangfq | | V1.07 | 2026-08-18 | `log_stat` / `log_query` / `log_clear` 增加**快照流**支持(`stream=snapshot`,与 BLE 0x28/0x29/0x2A 同语义):快照统计 capacity 随芯片动态(48064~449472)、快照分页 count≤1(4 通道记录 JSON ~810B 超发送缓冲,实测修正;BLE 原始通道仍 ≤2)、快照清除审计留痕;`capacity`/`count` 类型修正为 uint32(W25Q256 事件流 130944 超 16bit) | wangfq | +| V1.08 | 2026-08-20 | 增加 **Loop MCU 远程 OTA**(ROADMAP P1.4 ①,先存后刷):命令 `ota_begin` / `ota_data` / `ota_end` / `ota_abort` / `ota_flash` / `ota_status`(srv→dev)+ `ota_report`(dev→srv);单片 256B + 单片/全镜像 CRC32(ISO-HDLC,§4.19.1);断点续传(`ota_begin` 返回 offset);Slot A/B 双槽回滚(镜像 ≤96KB);`ota_flash` 安全窗口检查 + 非阻塞 tick 驱动刷写(刷写窗口内 MQTT 保活/IWDG 不受影响);会话期间暂停 `event_report` 发送与脱机日志落盘;`event_report` 扩展 `type=ota_error` 失败告警;§2.2 补 OTA 细分错误码(err_code);老固件兼容(`ota_*` 回 code=4) | wangfq | diff --git a/docs/DLD960_技术规格书.md b/docs/DLD960_技术规格书.md index 29cac30..8f9a670 100644 --- a/docs/DLD960_技术规格书.md +++ b/docs/DLD960_技术规格书.md @@ -106,7 +106,7 @@ DLD960 是一款基于环形线圈(LC 振荡)检测原理的四通道车辆 |------|------|------|------| | DLD960 串口通信协议 | V1.01 | TTL | 设备管理、参数配置、数据上报 | | DLD960 TCP JSON 协议 | V1.03 | ETH :5960 | 密码鉴权 + 18 条命令;event_report 客户端必答(5s×3 重发);脱机日志 log_stat/log_query/log_clear(事件/快照流,stream 区分) | -| DLD960 IoT MQTT 协议 | V1.07 | ETH → Broker | 双主题 `dld960/{sn}/srv`+`/dev`;initialize 上线、loop_data 三档调度、event_report 平台必答、设备时钟同步、脱机日志 log_stat/log_query/log_clear(事件/快照流,stream 区分) | +| DLD960 IoT MQTT 协议 | V1.08 | ETH → Broker | 双主题 `dld960/{sn}/srv`+`/dev`;initialize 上线、loop_data 三档调度、event_report 平台必答、设备时钟同步、脱机日志 log_stat/log_query/log_clear(事件/快照流,stream 区分);Loop 远程 OTA(ota_begin/ota_data/ota_end/ota_abort/ota_flash/ota_status + ota_report,256B/片 CRC32,先存后刷断点续传,Slot A/B 双槽回滚) | | DLD960Loop 串口协议 | V1.05 | MCU 间(内部) | 0x7F 帧、0xC0 传感上报(variation 3B 有符号) | | DLD960 BLE 协议 | V1.02 | 蓝牙 BLE | 帧格式 + 分包;脱机日志 OFFLOG_STAT/QUERY/CLEAR + 传感快照 SNAP_STAT/QUERY/CLEAR(0x28/0x29/0x2A),与 MQTT/TCP 同语义 | @@ -186,6 +186,6 @@ DLD960 是一款基于环形线圈(LC 振荡)检测原理的四通道车辆 | 版本 | 修订时间 | 修订说明 | 修订人 | |------|----------|----------|--------| -| V1.02 | 2026-08-19 | 配套整机发布 V1.02.03→V1.02.04:UART2 RX 0x9F OTA 透传机制说明(§2/§5/§7)、协议矩阵与版本配套矩阵更新、已知约束补充 OTA 无自动退出;存储适配(SPI 识别去厂商代码 + factory 写入恢复) | wangfq | +| V1.02 | 2026-08-19 | 配套整机发布 V1.02.03→V1.02.04:UART2 RX 0x9F OTA 透传机制说明(§2/§5/§7)、协议矩阵与版本配套矩阵更新、已知约束补充 OTA 无自动退出;存储适配(SPI 识别去厂商代码 + factory 写入恢复);2026-08-20 协议矩阵 MQTT 同步 V1.08(Loop 远程 OTA 协议设计先行,固件待实现,Slot A/B 100KB) | wangfq | | V1.01 | 2026-08-18 | 配套整机发布 V1.02.01:版本矩阵更新、UART2 RX DMA 通信可靠性说明、已知约束补充 | wangfq | | V1.00 | 2026-07-16 | 初始版本,配套整机发布 V1.0.0 | wangfq |