- 三阶段: MQTT 分片下载→W25Qxx 暂存(0x010000 512KB)→本地 0x9F ISP 透传刷写 - 7 命令: ota_begin/ota_data/ota_end/ota_abort/ota_flash/ota_status/ota_report + event_report 扩展 type=ota_error 告警 - 关键参数: 单片 256B(hex, <RECV_BUF 1024), 刷写块 248B(0x9F LEN uint8 上限 254B), 镜像上限 60KB, CRC32=ISO-HDLC(zlib.crc32 兼容), Slot A/B 双槽回滚 - 安全底线: 升级窗口检查(有车拒绝)/继电器安全侧/双重 CRC/失败重试×3/审计留痕 - 6 项待板上验证(继电器默认态/DBN TX 缓冲/SPI 写阻塞/RAM 预算/bin 大小/MQTT 保活) 协议先行, 未动固件代码; 待设计拍板后并入 MQTT 协议 V1.08
22 KiB
DLD960 MQTT 远程 OTA 协议(Loop MCU 先存后刷)
文档版本 V1.00(设计稿)· 2026-08-20 · 适用范围:并入《DLD960 IoT 接口协议(MQTT + JSON)》V1.08 后生效 依据:
docs/ROADMAP.mdP1.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 双槽,保留上一版重刷 |
| 安全窗口 | 无检查 | 刷写前检查无车压线圈、继电器强制断开 |
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 |
| 镜像上限 | 60KB | Loop APP 区 0x08003400~0x08010000 = 51KB 硬上限;暂存 Slot 数据区 60KB 留余量 |
| 下载片序号语义 | 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 停等协议,超时重发该块 |
| 刷写重试 | 每块失败重发 ×3,整体失败重试 ×3 | ROADMAP 安全底线 |
| 刷写速率 | 248B/帧 ≈ 13ms + ACK,~20ms/帧 | 64KB ≈ 265 帧 ≈ 6~8s 窗口 |
| 升级触发 | 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(reflected0xEDB88320),init0xFFFFFFFF,refin/refout true,xorout0xFFFFFFFF - 平台侧 Python
zlib.crc32()/binascii.crc32()即此算法,直接可用;设备侧查表法实现 - 全镜像 CRC = 从 offset 0 连续计算 size 字节;单片 CRC = 仅该 256B
- JSON 中 crc32 字段以十进制无符号数传输(与现有 uint32 字段风格一致)
4 暂存区布局(设备内部实现,命令接口不依赖)
0x010000 ┌─────────────────────────┐
│ OTA 元数据扇区 (4KB) │ SlotA/SlotB 头 + 会话状态 + 审计
0x010400 ├─────────────────────────┤
│ Slot A 数据区 (60KB) │ 当前新镜像(bin 原始字节)
0x020400 ├─────────────────────────┤
│ Slot B 数据区 (60KB) │ 上一版镜像(回滚重刷)
0x030400 ├─────────────────────────┤
│ 预留 384KB │ DBN 自身镜像 / 4G 扩展
0x090000 └─────────────────────────┘
元数据结构(建议,实现可调,不进入协议字段):
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
请求:
{
"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 字节数(≤ 61440) |
crc32 |
uint32 | 全镜像 CRC32(十进制) |
version |
string | 目标固件版本(写入元数据,审计用;bootloader 不校验版本) |
force |
bool | true = 覆盖现有暂存镜像 / 忽略冲突(默认 false) |
设备行为:
- 读元数据:若已有镜像且
size+crc32与本次一致 →- state=ready → 返回
offset=size(平台可直接ota_flash) - state=downloading → 返回
offset=received(续传)
- state=ready → 返回
- 不一致 → 分配 Slot(A 当前 / B 回滚),写元数据
state=downloading, received=0,返回offset=0 force=false且目标版本 == 当前运行版本 → 返回code=1(防重复刷写,可 force 绕过)
响应 data:
{
"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
请求:
{
"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 字符 |
设备行为:
offset == received(顺序片)→ 单片 CRC32 校验 → 写 W25Qxx(256B 页对齐)→received += 256(≥size 时截断为 size)→ 回code=0offset < received(重复片,平台重发)→ 幂等直接回code=0,不重写offset > received(缺片/乱序)→ 回code=1+data.offset=received(指示平台从该处续传;协议不要求乱序重组,降低设备复杂度)- 单片 CRC 失败 → 回
code=1+data.err_code=1(平台重发本片;连续失败由平台策略控制,可 abort) - 会话未开始 / 状态非 downloading →
code=3(先ota_begin) data非 512 hex 字符 / offset 非 256 对齐 / 超 size →code=1参数错误
响应: 标准成功/失败 + data 回显 {offset, received}。
单片大小 256B 是协议常量,不接受协商(避免双方尺寸漂移)。若未来单片加大需协议升版。
5.3 结束下载 ota_end
Topic:
dld960/{sn}/srv
请求:
{
"msg_id": 403,
"cmd": "ota_end",
"ts": 1719000003,
"data": {
"target": "loop",
"crc32": 305419896
}
}
设备行为:
received != size→code=1+data.offset=received(不完整,续传)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
{
"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⚠ 会车安全关键命令:平台应确认现场允许(无车压线圈、非高峰)再下发。
请求:
{
"msg_id": 405,
"cmd": "ota_flash",
"ts": 1719000005,
"data": {
"target": "loop",
"slot": "a",
"force": false
}
}
| data 字段 | 类型 | 说明 |
|---|---|---|
slot |
string | a / b(缺省 = 当前 ready 的槽) |
force |
bool | true = 跳过安全窗口检查(高风险,平台授权) |
设备行为(同步检查 → 异步刷写):
- 安全窗口检查(force=false 时):
- 4 通道 Loop 有车(VD_FLAG 任一置位)→
code=3+data.err_code=1(有车,拒绝;平台可提示"车辆离开后重试") - 刷写期间会阻断检测与继电器控制 → 建议平台在低峰执行
- 4 通道 Loop 有车(VD_FLAG 任一置位)→
- 元数据非 ready →
code=3(先ota_end完成校验) - 通过 → 立即回
code=0(异步),进入刷写:- 写 offlog 事件日志:
固件升级开始(target/slot/version/size) - 下发 0x7F 命令尝试关闭 Loop 全部继电器(失败不阻塞——bootloader 复位后 GPIO 默认态=断开安全侧,待板上验证)
- 发
9F 01 00 01 A5 A7启动帧 → Loop APP 写 flag 复位 → bootloader 回 pre_ok - 发 A6 地址帧(
0x080034004 字节大端)→ addr_ok - 从 W25Qxx 读镜像,按 248B/块发 A7(停等 ACK,1s 超时重发 ×3)
- 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP
- 写 offlog 事件日志:
- 进度经
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
请求:
{ "msg_id": 406, "cmd": "ota_status", "ts": 1719000006 }
响应 data:
{
"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)
上报:
{
"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 + 重发机制):
{
"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 | 继电器安全侧 | 刷写前尝试 0x7F 关闭全部继电器;bootloader 复位后 GPIO 默认态应为断开(常开安全侧)——待板上验证 |
| 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 绕过) |
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)
- hex↔bin:
datahex 解码(512 hex = 256B);bin 由 Intel HEX 转换(固定 0x08003400 偏移),转换在平台完成 - CRC32:
zlib.crc32(bin)全镜像;单片zlib.crc32(chunk)与设备逐字节一致(ISO-HDLC 即 Python 内置) - 分片循环:
for offset in range(0, size, 256)顺序下发,收到code=1 + data.offset时从该处续传;单片失败重发 ≤3 次后 abort - 断点续传:会话中断后重新
ota_begin,读data.offset续传 - 结果确认:刷写启动后轮询
ota_status(或订阅ota_report);stage=done后经 dev_info_query 核对 Loop 版本(需 Loop 侧支持版本上报,见 §10 待办) - 告警闭环:收到
event_report{type:ota_error}先落库后应答 - DBNMQTTool 增加 OTA 页签(工具先行惯例:先模拟分片下发,固件后到)
10 待板上验证项(实现前必须闭环)
| # | 项 | 影响 |
|---|---|---|
| 1 | Loop 复位后继电器 GPIO 默认态是否断开(安全侧) | 安全底线 2 |
| 2 | DBN UART2 TX 缓冲容量(能否容纳 254B A7 帧;现 BLE 透传块 ≤94B) | 刷写块大小 |
| 3 | W25Qxx 写 256B 页 + 4KB 扇区擦除在 MQTT 接收回调内的耗时(阻塞窗口 vs MQTT 保活) | 单片落盘是否需移主循环 |
| 4 | DBN RAM 预算:镜像块缓冲(248B)+ CRC 查表(1KB)是否挤占现有栈余量(历史 .bss 事故) | 内存方案 |
| 5 | Loop APP 当前固件实际 bin 大小(验证 60KB 槽余量) | 槽位容量 |
| 6 | 刷写期间 MQTT 保活(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 ①) |