docs(vd960DBN): BLE 日志查询交互过程文档 + 协议 V1.01 动态分包 + 周期心跳

- 新增 docs/DLD960_BLE日志查询交互.md: 0x25/0x26/0x27 完整交互示例
  (真实日志逐字节解析, 分包规则, 第二分包未发问题记录)
- DLD960_BLE协议.md V1.01: 分包上限改为随协商 MTU 动态 (min(MTU-9,94))
- peripheral.c: performPeriodicTask 加 500ms 心跳打印 (确认 TMOS 周期存活
  + resp 队列状态, 定位第二分包为何不续传)
This commit is contained in:
wangfq
2026-08-12 13:51:32 +08:00
parent 070db9f383
commit ef3316f2b3
3 changed files with 258 additions and 3 deletions
+238
View File
@@ -0,0 +1,238 @@
# DLD960 BLE 日志查询交互过程(实测示例)
> 版本: V1.002026-08-12
> 适用: vd960DBNCH32V208+ 微信小程序
> 目的: 以**真实串口日志**为样例,完整描述"小程序发指令 → 设备处理 → 设备回包"的交互过程,供固件/小程序两端排查对照
---
## 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 |
| 185iOS 常见) | 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` | ckbXor=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> <ckb2>
```
响应 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 = 1LE32,含) |
| 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** > 单包上限 87BMTU=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> <ckb2>
```
- 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) |