# DLD960 MQTT 远程 OTA 协议(Loop MCU 先存后刷) > 文档版本 V1.01(设计稿·按修改意见修订)· 2026-08-20 · 适用范围:并入《DLD960 IoT 接口协议(MQTT + JSON)》V1.08 后生效 > 依据:`docs/ROADMAP.md` P1.4 ① —— Loop MCU (AT32F421) 远程 OTA,v1.2.x 落地 > 设计原则:**下载阶段纯 MQTT 分片 → W25Qxx 暂存;刷写阶段纯本地 ISP 透传(复用 BLE OTA 0x9F 状态机)**;传输层无关,4G 通道原样复用。 --- # 1 背景与目标 ## 1.1 现状基线 | 项 | 说明 | |----|------| | 双 MCU 架构 | DBN = CH32V208(通信 MCU,跑 MQTT/BLE/TCP)+ Loop = AT32F421(地感 MCU,跑检测算法) | | 现有 OTA 通道 | BLE → DBN 透传 → UART2 0x9F ISP → Loop bootloader(2026-08-19 修复闭环,V1.02.03) | | 本轮目标 | MQTT → DBN 分片下载到 W25Qxx 暂存区 → 校验后**本地** ISP 刷写 Loop | | 暂存介质 | DBN 外挂 W25Qxx(SPI1),OTA 镜像暂存区 **0x010000 起 512KB**(`SNAP_FIXED_SIZE = 参数区64KB + OTA区512KB`,已在 snapshot.c 分区模型预留) | ## 1.2 为什么"先存后刷"(相对 BLE 流式直透) | 维度 | 流式直透(BLE 现状) | 先存后刷(本设计) | |------|---------------------|-------------------| | 网络抖动影响 | 任一分片丢失 → 停等卡死,全流程重来 | 下载阶段断网无所谓,**断点续传** | | 刷写窗口 | 与传输时间重合(网络慢则窗口长) | 下载完成后刷写纯本地,窗口短且可控 | | 校验 | 依赖传输层(BLE 无 CRC) | 单片 CRC32 + 全镜像 CRC32 双重校验 | | 回滚 | 无 | 暂存区 Slot A/B 双槽,保留上一版重刷 | | 安全窗口 | 无检查 | 刷写前检查无车压线圈;继电器维持 Loop 现状(与 BLE OTA 一致) | ## 1.3 范围 - 本版只做 **Loop (AT32F421) 远程 OTA**(ROADMAP P1.4 ①) - DBN (CH32V208) 自身 OTA(ROADMAP P1.4 ②):需 bootloader 改造,**单独评审,不阻塞本设计**;协议命令预留 `target:"dbn"` 位 - 传输层:本版 MQTT;4G(UART1 AT 透传)复用同一分片下载协议(`target` 字段扩展即可) --- # 2 总体架构与数据流 ``` ┌─────────┐ MQTT (dld960/{sn}/srv) ┌──────────────┐ UART2 192000 ┌─────────────┐ │ 平台 │ ── ota_begin / ota_data ─▶ │ DBN (CH32V208) │ ── 0x9F ISP ──▶ │ Loop (AT32) │ │ │ ◀─ 响应 / ota_report ───── │ │ ◀─ ACK ──────── │ bootloader │ └─────────┘ │ W25Qxx 暂存 │ └─────────────┘ │ 0x010000 512KB │ └──────────────┘ 阶段1 下载:平台 MQTT 分片 → DBN 写 W25Qxx(单片 CRC32 校验) 阶段2 校验:ota_end 全镜像 CRC32 复核,state → ready 阶段3 刷写:ota_flash → DBN 从 W25Qxx 读出 → 0x9F A5/A6/A7 逐块透传 → Loop bootloader 写 flash ``` **镜像语义**:平台下发的是 **bin 原始字节**(hex 编码传输),即 AT32F421 0x08003400 起的 APP 代码映像(hex→bin 转换由平台侧完成,设备不解析 Intel HEX)。刷写时 A6 地址固定 `0x08003400`(=`APP_START_ADDR`),A7 数据按 bin 顺序发送。 --- # 3 关键参数 ## 3.1 协议参数(平台 ↔ 设备契约) | 参数 | 值 | 依据 | |------|-----|------| | 下载单片原始字节 | **256B** | 2 的幂 + W25Qxx 页(256B)对齐;hex 512 字符 + JSON 外壳 ≈ 640B < `IOT_MQTT_RECV_BUF_LEN=1024` | | 单片 CRC | **CRC32**(256B 独立计算) | 与全镜像同算法,查表实现复用 | | 全镜像 CRC | **CRC32**(offset 0 ~ size-1 连续) | 标准 CRC-32/ISO-HDLC | | 镜像上限 | **96KB** | Slot 数据区 100KB 预留 4KB 边界余量;Loop APP 区 0x08003400~0x08010000 = 51KB 硬上限绰绰有余,100KB 槽为 DBN (CH32V208) 自身镜像(~100KB APP 区)预留 | | 下载片序号语义 | offset 绝对字节偏移(256 对齐) | 支持乱序/重复片幂等处理 | | 断点续传 | ota_begin 返回已接收字节数(片对齐) | 设备持久化 received 到元数据 | ## 3.2 刷写参数(设备内部实现约束,平台无需感知) | 参数 | 值 | 依据 | |------|-----|------| | A7 数据块 | **≤254B**(实现取 248B) | bootloader `LEN` 为 uint8 → DATA = LEN-1 ≤ 254(iap.c `CMD_9F_DATA_LEN`) | | 块间超时 | 1s(可配) | 0x9F 停等协议,超时重发该块 | | 刷写调度 | **非阻塞 tick 驱动** | 刷写状态机挂主循环轮询,每轮最多发送 1~2 块并检查 ACK,绝不阻塞主循环 → 刷写窗口内 MQTT PINGREQ/心跳/IWDG 喂狗节奏完全不受影响 | | 刷写重试 | 每块失败重发 ×3,整体失败重试 ×3 | ROADMAP 安全底线 | | 刷写速率 | 248B/帧 ≈ 13ms + ACK,~20ms/帧 | 64KB ≈ 265 帧 ≈ **6~8s 窗口**(Loop 典型 40~50KB 更快) | | 升级触发 | DBN 发 `9F 01 00 01 A5 A7` → Loop APP 写 `IAP_UPGRADE_FLAG_9F=0x444C4439` → 复位 | 复用 BLE OTA 启动帧(dbn_ble_srv.c check_pkg 同款) | ## 3.3 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 字节;单片 CRC = 仅该 256B - JSON 中 crc32 字段以**十进制无符号数**传输(与现有 uint32 字段风格一致) --- # 4 暂存区布局(设备内部实现,命令接口不依赖) ``` 0x010000 ┌─────────────────────────┐ │ OTA 元数据扇区 (4KB) │ SlotA/SlotB 头 + 会话状态 + 审计 0x011000 ├─────────────────────────┤ │ Slot A 数据区 (100KB) │ 当前新镜像(bin 原始字节) 0x02A000 ├─────────────────────────┤ │ Slot B 数据区 (100KB) │ 上一版镜像(回滚重刷) 0x043000 ├─────────────────────────┤ │ 预留 308KB │ DBN 自身镜像 / 4G 扩展 0x090000 └─────────────────────────┘ ``` - Slot A/B 各 **100KB 数据区**(`0x011000`/`0x02A000` 起,4KB 对齐),镜像上限 96KB(预留 4KB 边界余量) - 100KB 槽位容量同时覆盖 DBN (CH32V208) 自身镜像(APP 区约 100KB),为 P1.4 ② 预留 - 4KB 边界余量:防止镜像写满后越界擦到下一槽;元数据/数据区起始均 4KB 扇区对齐 元数据结构(建议,实现可调,不进入协议字段): ```c typedef struct { /* 64B */ uint32_t magic; /* 'DLD9' */ uint32_t state; /* idle/downloading/ready/flashing/flash_failed/aborted */ uint8_t target; /* 0=loop, 1=dbn(预留) */ uint8_t slot; /* 0=A, 1=B */ uint16_t rsv; uint32_t size; /* 镜像字节数 */ uint32_t crc32; /* 全镜像 CRC32 */ uint32_t received; /* 已下载字节数(片对齐,断点续传依据) */ uint32_t last_result; /* 上次刷写结果码 */ uint32_t begin_ts; /* 会话开始(同步后 Unix 秒,0=未同步) */ char version[16]; /* 目标固件版本字符串(审计) */ uint32_t flash_cnt; /* 刷写尝试次数 */ uint32_t rsv2; } OtaMeta; ``` **掉电恢复**:元数据每次 ota_begin / 每片落盘 / ota_end / 刷写结果后回写。重启后: - state=downloading → 平台 ota_begin 时返回已接收 offset 续传 - state=ready → 平台 ota_begin 直接确认可刷写,或 ota_flash 直接触发 - state=flashing → 视为上次刷写中断(掉电/异常),可重试 ota_flash --- # 5 命令详表(新增,并入 MQTT 协议 V1.08) | cmd | 方向 | 说明 | |-----|------|------| | `ota_begin` | srv→dev | 开启 OTA 会话 / 断点续传定位 | | `ota_data` | srv→dev | 分片下发(256B/片,单片 CRC32) | | `ota_end` | srv→dev | 结束下载,全镜像 CRC32 复核 | | `ota_abort` | srv→dev | 中止会话,释放暂存 | | `ota_flash` | srv→dev | 触发本地 ISP 刷写(仅 state=ready) | | `ota_status` | srv→dev | 查询 OTA 状态(含进度) | | `ota_report` | dev→srv | 刷写进度/结果主动上报(QoS 1) | | `event_report` | dev→srv | 扩展 `type=ota_error`:重试 ×3 仍失败告警(**平台必答**,复用 §5.3 ACK 闭环) | > 兼容:老固件(V1.07 及以下)收到 `ota_*` → 现有 default 分支回 `code=4 unsupported command`。平台下发前可用 `ota_status` 或 `dev_info_query.soft_ver` 探测能力,避免每命令必报错。 ## 5.1 开启会话 `ota_begin` > Topic: `dld960/{sn}/srv` **请求:** ```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) | | `crc32` | uint32 | 全镜像 CRC32(十进制) | | `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`(防重复刷写,可 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" } } ``` ## 5.2 分片下发 `ota_data` > Topic: `dld960/{sn}/srv` **请求:** ```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` + `data.offset=received`(指示平台从该处续传;协议不要求乱序重组,降低设备复杂度) 4. 单片 CRC 失败 → 回 `code=1` + `data.err_code=1`(平台重发本片;连续失败由平台策略控制,可 abort) 5. 会话未开始 / 状态非 downloading → `code=3`(先 `ota_begin`) 6. `data` 非 512 hex 字符 / offset 非 256 对齐 / 超 size → `code=1` 参数错误 **响应:** 标准成功/失败 + data 回显 `{offset, received}`。 > 单片大小 256B 是协议常量,**不接受协商**(避免双方尺寸漂移)。若未来单片加大需协议升版。 ## 5.3 结束下载 `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, data={crc_ok:false}`(msg 注明 CRC 不匹配) ## 5.4 中止会话 `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 兜底)。响应标准成功。 ## 5.5 触发刷写 `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` + `data.err_code=1`(有车,拒绝;平台可提示"车辆离开后重试") - 刷写期间会阻断检测与继电器控制 → 建议平台在低峰执行 2. 元数据非 ready → `code=3`(先 `ota_end` 完成校验) 3. 通过 → 立即回 `code=0`(异步),进入刷写: - 写 offlog 事件日志:`固件升级开始`(target/slot/version/size) - **暂停事件上报与脱机日志(会话期间)**:MQTT `event_report` 暂停发送(入队积压,16 深溢出丢最旧,会话结束恢复后补发);offlog/快照落盘暂停——理由:刷写期间 Loop 复位不产生检测事件,DBN 让出 SPI 总线/主循环给刷写状态机,保证保活与喂狗 - 维持 Loop 现状:复位进 bootloader 后的 GPIO/继电器状态与现网 BLE OTA 升级完全一致(该通道已验证可用),不额外干预 - 发 `9F 01 00 01 A5 A7` 启动帧 → Loop APP 写 flag 复位 → bootloader 回 pre_ok - 发 A6 地址帧(`0x08003400` 4 字节大端)→ addr_ok - 从 W25Qxx 读镜像,按 248B/块发 A7(停等 ACK,1s 超时重发 ×3) - 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP 4. 进度经 `ota_report` 上行(见 §6);失败重试 ×3 仍失败 → 元数据 `state=flash_failed` + `event_report{type:ota_error}` 告警(平台必答) **响应:** 标准成功/失败(`code=0` 仅表示已启动,不代表刷写成功——结果以 `ota_report`/`ota_status` 为准)。 ## 5.6 查询状态 `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 ``` --- # 6 主动上报 `ota_report` > Topic: `dld960/{sn}/dev` · QoS 1 > 用途:刷写进度与结果主动推送(进度可丢,结果可经 `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 字段 | 说明 | |-----------|------| | `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 同策略)。 ## 6.1 失败告警(扩展 event_report) 刷写整体失败(重试 ×3 仍失败)时,设备经 `event_report` 上报(**平台必答**,复用 §5.3 ACK + 重发机制): ```json { "msg_id": 502, "cmd": "event_report", "ts": 1719000008, "data": { "events": [ { "type": "ota_error", "ch": 0, "value": 1003 } ] } } ``` | type | value | 说明 | |------|-------|------| | `ota_error` | 0x1000 起 | `0x1001`=启动帧无响应 / `0x1002`=地址帧错误 / `0x1003`=数据块 ACK 超限 / `0x1004`=全镜像校验失败 / `0x1005`=安全窗口拒绝后强制失败 | --- # 7 安全设计(会车产品底线) | # | 措施 | 说明 | |---|------|------| | 1 | **升级窗口检查** | `ota_flash` 前检查 4 通道无车;有车 → `code=3`,平台提示延迟。`force=true` 可跳过(高风险,需平台权限控制) | | 2 | **维持 Loop 现状** | 刷写期间 Loop 行为与现网 BLE OTA 升级一致(复位进 bootloader → ISP 刷写 → 复位跑新 APP),继电器 GPIO 状态按当前硬件实测状态接受,不额外干预(BLE 通道已验证可用) | | 3 | **双重 CRC** | 单片 CRC32(传输层抓错)+ 全镜像 CRC32(落盘完整性);刷写前读回复核 | | 4 | **失败可重入** | 下载失败 → 断点续传;刷写失败 → 重试 ×3 → 保留镜像可重刷;AT32 ISP bootloader 兜底,不会真砖 | | 5 | **版本回滚** | Slot A/B 双槽;平台可 `ota_begin` 指定槽刷旧版(`force=true` 覆盖) | | 6 | **审计留痕** | 升级开始/结果写 offlog 事件日志(含目标版本、slot、结果码);平台侧 version 字段归档 | | 7 | **能力探测** | 老固件 `ota_*` 回 `code=4`;平台按 `ota_status`/`dev_info_query.soft_ver` 判断,不盲目下发 | | 8 | **幂等与防呆** | 重复片幂等 ACK;offset 乱序拒绝并要求续传;同版本默认拒绝重刷(force 绕过) | | 9 | **会话期间静默** | OTA 会话期间(begin ~ 结束)暂停 MQTT `event_report` 发送(队列积压,结束后补发)与 offlog/快照落盘("升级开始"日志在暂停前写入、"升级结果"在恢复后补记);避免刷写窗口与上报/日志抢 SPI 总线与主循环 | --- # 8 错误码约定 顶层 `code` 沿用通用语义(0 成功 / 1 参数 / 2 密码 / 3 忙 / 4 不支持 / 5 内部 / 6 超长),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 | --- # 9 平台侧实现要点(edc_server / DBNMQTTool) 1. **hex↔bin**:`data` hex 解码(512 hex = 256B);bin 由 Intel HEX 转换(固定 0x08003400 偏移),转换在平台完成 2. **CRC32**:`zlib.crc32(bin)` 全镜像;单片 `zlib.crc32(chunk)` 与设备逐字节一致(ISO-HDLC 即 Python 内置) 3. **分片循环**:`for offset in range(0, size, 256)` 顺序下发,收到 `code=1 + data.offset` 时从该处续传;单片失败重发 ≤3 次后 abort 4. **断点续传**:会话中断后重新 `ota_begin`,读 `data.offset` 续传 5. **结果确认**:刷写启动后轮询 `ota_status`(或订阅 `ota_report`);`stage=done` 后经 dev_info_query 核对 Loop 版本(需 Loop 侧支持版本上报,见 §10 待办) 6. **告警闭环**:收到 `event_report{type:ota_error}` 先落库后应答 7. DBNMQTTool 增加 OTA 页签(工具先行惯例:先模拟分片下发,固件后到) --- # 10 待板上验证项(实现前必须闭环) | # | 项 | 影响 | |---|-----|------| | 1 | DBN UART2 TX 缓冲容量(能否容纳 254B A7 帧;现 BLE 透传块 ≤94B) | 刷写块大小 | | 2 | W25Qxx 写 256B 页 + 4KB 扇区擦除在 MQTT 接收回调内的耗时(阻塞窗口 vs MQTT 保活) | 单片落盘是否需移主循环 | | 3 | DBN RAM 预算:镜像块缓冲(248B)+ CRC 查表(1KB)是否挤占现有栈余量(历史 .bss 事故) | 内存方案 | | 4 | Loop APP 当前固件实际 bin 大小(验证 100KB 槽余量) | 槽位容量 | | 5 | 刷写期间 MQTT 保活验证(设计已保证非阻塞 tick 驱动,PINGREQ/心跳/IWDG 喂狗不受阻塞;板级抓包确认 6~8s 窗口内无 PINGREQ 超时断连、IWDG 不复位) | 刷写中断安全 | --- # 11 与现有协议的关系 | 协议文档 | 变更 | |----------|------| | `DLD960_IoT_MQTT协议.md` | 本设计并入后升 **V1.08**:§3 命令表 + §4.19~4.24(ota_* 详情)+ §5.5(ota_report)+ §5.3 事件类型表补 `ota_error` + §8 错误码补 err_code 约定 + 修订记录 | | `DLD960_TCP_JSON协议.md` | 本版**不扩展**(ROADMAP 先走 MQTT);后续按"复用命令 + stream/字段"模式补(与 log_* 扩展同套路) | | `DLD960_BLE协议.md` | 不变(BLE OTA 维持流式透传现状;本地刷写状态机与 BLE 透传共用 `lup_feed_byte_ota`) | | `README.md` / `DLD960_技术规格书.md` | 协议矩阵同步 V1.08;规格书补 OTA 分区/安全约束(并入主文档时执行) | **固件代码落点(协议拍板后动):** - `iot_mqtt_srv.c`:命令分发链加 6 个 ota_* 分支(现有 if-else 链) - 新增 `ota_srv.c/h`:会话状态机 + W25Qxx 暂存读写 + CRC32 查表 - `usart_biz.c` / `loop_uart_proto.c`:本地刷写状态机(复用 `lup_feed_byte_ota` 解析 + `g_flag_counter_ota` 门控,注意退出机制——现 BLE 透传 flag 无退出点,本地刷写必须自管) - `offlog.c`:补"固件升级开始/结果"事件类型 - 单测:`tests/test_ota_srv.c`(gcc 提取+嵌入模式,mock SPI/MQTT/Loop) --- # 修订记录 | 版本 | 时间 | 说明 | |------|------|------| | V1.00 | 2026-08-20 | 设计稿:Loop MCU MQTT 远程 OTA 先存后刷协议(依据 ROADMAP P1.4 ①) | | V1.01 | 2026-08-20 | 按修改意见修订:①Slot A/B 容量 60KB→**100KB**(镜像上限 96KB,为 DBN 镜像预留)②删除"继电器 GPIO 默认态"待验证项,维持 Loop 现状(与 BLE OTA 行为一致,不额外干预)③新增"OTA 会话期间静默"约束:暂停 MQTT `event_report` 发送(队列积压结束后补发)+ offlog/快照落盘(开始/结果日志除外)④刷写调度明确**非阻塞 tick 驱动**,刷写窗口内 MQTT 保活(PINGREQ/心跳/IWDG 喂狗)不受影响 |