From ef3316f2b3a80904db083539b5e66f0bef538884 Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 12 Aug 2026 13:51:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(vd960DBN):=20BLE=20=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E4=BA=A4=E4=BA=92=E8=BF=87=E7=A8=8B=E6=96=87?= =?UTF-8?q?=E6=A1=A3=20+=20=E5=8D=8F=E8=AE=AE=20V1.01=20=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=88=86=E5=8C=85=20+=20=E5=91=A8=E6=9C=9F=E5=BF=83=E8=B7=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 docs/DLD960_BLE日志查询交互.md: 0x25/0x26/0x27 完整交互示例 (真实日志逐字节解析, 分包规则, 第二分包未发问题记录) - DLD960_BLE协议.md V1.01: 分包上限改为随协商 MTU 动态 (min(MTU-9,94)) - peripheral.c: performPeriodicTask 加 500ms 心跳打印 (确认 TMOS 周期存活 + resp 队列状态, 定位第二分包为何不续传) --- docs/DLD960_BLE协议.md | 14 +- docs/DLD960_BLE日志查询交互.md | 238 ++++++++++++++++++ .../OnlyUpdateApp_Peripheral/APP/peripheral.c | 9 + 3 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 docs/DLD960_BLE日志查询交互.md diff --git a/docs/DLD960_BLE协议.md b/docs/DLD960_BLE协议.md index 42fcc0e..4032ed4 100644 --- a/docs/DLD960_BLE协议.md +++ b/docs/DLD960_BLE协议.md @@ -1,6 +1,6 @@ # DLD960 BLE 通信协议(脱机事件日志) -> 版本: V1.00(2026-08-10,脱机日志部分) +> 版本: V1.01(2026-08-12,分包上限改为随协商 MTU 动态) > 适用: vd960DBN(CH32V208,WCH BLE 协议栈) > 用途: 小程序/APP 经蓝牙读取设备本地 W25Q32 环形事件日志(离线取证:区分设备真复位 vs MQTT 断连重连) @@ -22,9 +22,17 @@ Magic | Header | Data | CheckByte ### 分包(长响应) -响应数据单包上限 **96B**(BLE_BUFF_MAX_LEN=100 − 4)。超过则分包,header 字节 = `(pkg_amount << 4) | pkg_seq`,`pkg_seq` 从 1 递增;收端按 `pkg_amount`/`pkg_seq` 重组,最后一片 `pkg_amount == pkg_seq`。 +响应单包数据上限**随协商 MTU 动态变化**(V1.01,修复"Too large noti 丢包"):`chunk = min(peripheralMTU - 9, 94)`(整包 = 帧头4 + dat + ckb2,须 ≤ MTU-3 且 ≤ 本地缓冲 100B)。分包时 header 字节 = `(pkg_amount << 4) | pkg_seq`,`pkg_seq` 从 1 递增;收端按 `pkg_amount`/`pkg_seq` 重组,最后一片 `pkg_amount == pkg_seq`。 -脱机日志 QUERY 响应最大 130B → 2 包(96 + 34)。 +| 协商 MTU | 单包最大 dat | 整包最大长度 | +|---------|-------------|-------------| +| 23(未协商) | 14 | 20 | +| 96(常见 Android) | 87 | 93 | +| 185(iOS 常见) | 94 | 100 | + +脱机日志 QUERY 响应最大 130B:MTU=96 时 2 包(87+43 dat);MTU≥103 时 2 包(94+36 dat)。 + +> 分包发送由 `performPeriodicTask`(50ms TMOS 周期)主动续传,两包间隔 ≤50ms,不依赖收包事件(2026-08-12 修复)。 --- diff --git a/docs/DLD960_BLE日志查询交互.md b/docs/DLD960_BLE日志查询交互.md new file mode 100644 index 0000000..cd64faa --- /dev/null +++ b/docs/DLD960_BLE日志查询交互.md @@ -0,0 +1,238 @@ +# DLD960 BLE 日志查询交互过程(实测示例) + +> 版本: V1.00(2026-08-12) +> 适用: vd960DBN(CH32V208)+ 微信小程序 +> 目的: 以**真实串口日志**为样例,完整描述"小程序发指令 → 设备处理 → 设备回包"的交互过程,供固件/小程序两端排查对照 + +--- + +## 1 帧格式回顾(与 DLD960_BLE协议.md 一致) + +``` +Magic | Header | Data | CheckByte + | Addr/Sub Len CMD | | Xor Sum +1 Byte | 1B 1B 1B | xx | 1B 1B +``` + +- `Len = len(CMD) + len(Data)`,即 `pkg[2] = 1 + data_len` +- 校验:`Xor = XOR(pkg[1..Len+2])`,`Sum = SUM(pkg[1..Len+2])`(覆盖 header+cmd+data,不含 magic) +- Magic = `0x8F`(MAGIC_BYTE_DBN_DEFAULT) +- **Header 字节分包语义**:`(pkg_amount << 4) | pkg_seq` + - 单包:`0x00`(amount=0, seq=0) + - 分包:高 4 位 = 总包数,低 4 位 = 当前包序号(从 1 递增),最后一片 `amount == seq` +- **单包数据上限(动态,V1.01+)**:`chunk = min(peripheralMTU - 9, 94)`(整包 = 帧头4 + dat + ckb2,须 ≤ MTU-3 且 ≤ 本地缓冲 100B) + +| 协商 MTU | chunk(单包最大 dat) | 整包最大长度 | +|---------|----------------------|-------------| +| 23(未协商) | 14 | 20 | +| 96(本样例手机) | 87 | 93 | +| 185(iOS 常见) | 94 | 100 | + +--- + +## 2 命令码 + +| 命令码 | 名称 | 方向 | 说明 | +|--------|------|------|------| +| `0x25` | `OFFLOG_STAT` | APP→设备 | 查询脱机事件日志统计 | +| `0x26` | `OFFLOG_QUERY` | APP→设备 | 按全局序号分页拉取日志记录 | +| `0x27` | `OFFLOG_CLEAR` | APP→设备 | 清除日志(审计留痕) | + +--- + +## 3 交互示例一:查询日志统计 OFFLOG_STAT (0x25) + +### 3.1 小程序发送(请求帧) + +``` +8F 00 01 25 24 26 +``` + +| 字节 | 值 | 含义 | +|------|-----|------| +| pkg[0] | `8F` | magic | +| pkg[1] | `00` | header:单包(amount=0, seq=0) | +| pkg[2] | `01` | Len = 0 data + 1 cmd = 1 | +| pkg[3] | `25` | cmd = OFFLOG_STAT(查询统计,无 data) | +| pkg[4..5] | `24 26` | ckb:Xor=0x24, Sum=0x26 | + +### 3.2 设备处理日志 + +``` +[Rx][13:45:41.577] profile ChangeCB CHAR1?..:�, len:6 +[Rx][13:45:41.577] 8F 00 01 25 24 26 +[Rx][13:45:41.577] BLE: offlog_stat count=162 seq_first=1 seq_last=162 +[Rx][13:45:41.588] BLE notify OK, len:25, MTU:96 +``` + +### 3.3 设备响应(25B = 帧头4 + dat 19 + ckb 2,单包) + +``` +8F 00 14 25 <19B data> +``` + +响应 data(19B,全小端)与设备日志字段对照: + +| data 偏移 | 长度 | 字段 | 本次值 | 说明 | +|-----------|------|------|--------|------| +| 0 | 1 | `status` | `00` | OK | +| 1 | 2 | `boot_seq` | 待补(需原始 hex) | 启动序号 | +| 3 | 4 | `count` | `A2 00 00 00` = 162 | 有效记录条数 | +| 7 | 4 | `capacity` | `80 1F 00 00` = 8064 | 容量上限 | +| 11 | 4 | `seq_first` | `01 00 00 00` = 1 | 逻辑首条全局序号 | +| 15 | 4 | `seq_last` | `A2 00 00 00` = 162 | 最新全局序号 | + +> 单包响应无分包,`notify OK len:25`(25 = 4+19+2),MTU=96 下 25 ≤ 93,正常。 + +--- + +## 4 交互示例二:读取日志记录 OFFLOG_QUERY (0x26) + +### 4.1 小程序发送(请求帧) + +``` +8F 00 06 26 01 00 00 00 04 25 31 +``` + +| 字节 | 值 | 含义 | +|------|-----|------| +| pkg[0] | `8F` | magic | +| pkg[1] | `00` | header:单包请求 | +| pkg[2] | `06` | Len = 5 data + 1 cmd = 6 | +| pkg[3] | `26` | cmd = OFFLOG_QUERY | +| pkg[4..7] | `01 00 00 00` | start_seq = 1(LE32,含) | +| pkg[8] | `04` | count = 4(≤ OFFLOG_MAX_QUERY_RECORDS=4) | +| pkg[9..10] | `25 31` | ckb | + +### 4.2 设备处理日志 + +``` +[Rx][13:45:47.827] profile ChangeCB CHAR1?..:�, len:11 +[Rx][13:45:47.827] 8F 00 06 26 01 00 00 00 04 25 31 +[Rx][13:45:47.827] BLE: offlog_query start_seq=1 req=4 fetched=4 +[Rx][13:45:47.848] BLE notify OK, len:93, MTU:96 +[Rx][13:45:47.848] 8F 21 58 26 00 04 A5 01 04 00 01 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 18 00 00 00 00 00 00 00 00 00 00 00 A5 01 04 00 02 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 A5 01 04 00 03 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 18 F3 E5 +``` + +### 4.3 响应分包说明 + +响应 dat 总量 = status(1) + count(1) + 4×32B = **130B** > 单包上限 87B(MTU=96)→ **必须分包 2 包**: + +| 包 | header | 整包长 | dat 内容 | +|----|--------|--------|----------| +| 第 1 包 | `0x21`(amount=2, seq=1) | 93B | status+count + 记录1(32B) + 记录2(32B) + 记录3前21B = 87B | +| 第 2 包 | `0x22`(amount=2, seq=2) | **49B(预期)** | 记录3剩余11B + 记录4(32B) = 43B | + +### 4.4 第一包逐字段解析(93B) + +``` +8F 21 58 26 00 04 <记录1 32B> <记录2 32B> <记录3 前21B> F3 E5 +-- -- -- -- -- -- --------------------------------------- ---- +| | | | | | | +-- ckb +| | | | | | +-- dat: status(1)=00, count(1)=04, 然后记录 +| | | | | +-- status=00 OK +| | | | +-- cmd=0x26 +| | | +-- Len=0x58=88 = 87 data + 1 cmd +| | +-- header=0x21: amount=2, seq=1 +| +-- magic=0x8F +``` + +记录结构(OfflogEvt 32B,小端)——本次 3 条记录均为 `type=0x01 04 00`(事件类型,待对照 offlog.h 事件表): + +``` +记录1: A5 01 04 00 | 01 00 00 00 | 00×12 | 01 00 00 00 | 18 00 00 00 +记录2: A5 01 04 00 | 02 00 00 00 | 00×12 | 02 00 00 00 | 08 00 00 00 +记录3: A5 01 04 00 | 03 00 00 00 | 00×12 | 03 00 00 00 | 18(截断于第 21B) +``` + +### 4.5 第二包(预期,未收到——当前问题) + +``` +8F 22 2C 26 <记录3剩余 11B> <记录4 32B> +``` + +- header `0x22`:amount=2, seq=2(最后一片) +- Len = `0x2C` = 44 = 43 data + 1 cmd +- 整包 49B + +### 4.6 当前问题(2026-08-12 排查中) + +**现象**:设备只发出了第 1 包(93B),第 2 包(49B)未发出;小程序按 `amount=2` 等第 2 包永远等不到。 + +**已确认**: +- MTU 分包粒度动态化已生效(93B ≤ 96-3=93,无 `Too large noti`) +- `performPeriodicTask` 已加主动拉包逻辑(不依赖收包事件) +- 已加 `BLE pull FAIL` 诊断打印,但本次日志**未出现** → 说明拉包时 `g_buf_ble_response.flag==0`(队列已被清)或 `_pull==1` 但发送周期未到/被阻塞 + +**下一步排查**: +1. 加 `performPeriodicTask` 周期心跳打印(每 500ms),确认 TMOS 周期事件是否持续执行 +2. 核对 `SBP_PERIODIC_EVT_PERIOD=50` 的 TMOS 时间单位(tick vs ms) +3. 检查 `g_buf_ble_response` 在第 1 包发出后是否被意外 clear(`unpack_packs`/`set_response_buf` 调用路径) + +--- + +## 5 交互示例三:清除日志 OFFLOG_CLEAR (0x27) + +### 5.1 小程序发送 + +``` +8F 00 01 27 XX XX +``` + +| 字节 | 值 | 含义 | +|------|-----|------| +| pkg[0] | `8F` | magic | +| pkg[1] | `00` | header:单包 | +| pkg[2] | `01` | Len = 1 | +| pkg[3] | `27` | cmd = OFFLOG_CLEAR | +| pkg[4..5] | `XX XX` | ckb | + +### 5.2 设备响应 + +``` +8F 00 02 27 00 XX XX +``` + +- dat:`status(1)=00`(OK) +- **注意**:擦除 W25Q32 阻塞约 2.8s,期间 BLE 无响应、网络短时失服务(MQTT keepalive 60s 可吸收) + +--- + +## 6 小程序端分包重组规则(伪代码) + +```js +// 订阅 onBLECharacteristicValueChange, 按 cmd 维护重组缓冲 +let frag = { cmd: null, amount: 0, seq: 0, data: [] }; + +function onNotify(buf) { + const magic = buf[0], header = buf[1], len = buf[2], cmd = buf[3]; + const amount = header >> 4, seq = header & 0x0F; + const data = buf.slice(4, 4 + (len - 1)); // 去掉 cmd 的净数据 + const ckb = buf.slice(-2); // 校验可选 + + if (amount === 0) { // 单包 + dispatch(cmd, data); + } else { // 分包 + if (seq === 1) { frag = { cmd, amount, seq, data: [] }; } + if (frag.cmd !== cmd) return; // 乱序/新响应, 丢弃 + frag.data = frag.data.concat(data); + frag.seq = seq; + if (seq === amount) { // 收齐 + dispatch(cmd, frag.data); + frag = null; + } + } +} +``` + +> 关键点:收到第 1 包(seq=1, amount=2)后**必须继续等待** seq=2 的第二个通知,不要在第 1 包到达时判完成。 + +--- + +## 7 排查时间线 + +| 时间 | 现象 | 结论 | +|------|------|------| +| 08:15 | `Too large noti, len:102, MTU:96` | 分包粒度写死 96B > MTU-3=93,丢包 | +| 11:51 | 修复后第一包 93B 正常发出 | MTU 动态分包生效 | +| 11:51~13:45 | 第二包 49B 未发出,无 pull FAIL 打印 | 分包续传/发送周期链路待查(见 §4.6) | diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral.c index f19479f..22f9ee6 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral.c @@ -684,6 +684,15 @@ static void peripheralStateNotificationCB(gapRole_States_t newState, gapRoleEven */ static void performPeriodicTask(void) { + static uint32_t _dbg_cnt = 0; + if(++_dbg_cnt >= 10) /* 500ms heartbeat: confirm TMOS periodic alive */ + { + _dbg_cnt = 0; + PRINT("BLE tick: ntf_len=%d ntf_flag=%d resp=%d amt=%d seq=%d off=%d\n", + g_notify_buftemp.len, g_notify_buftemp.flag, + g_buf_ble_response.flag, g_buf_ble_response.pkg_amount, + g_buf_ble_response.pkg_seq, g_buf_ble_response.dat_offset); + } if(g_flag_notify_temp){ g_flag_notify_temp = 0; peripheralChar4Notify(g_notify_buftemp.buf, g_notify_buftemp.len);