Files
vd_960/docs/DLD960_BLE协议.md
T
wangfq 2e73155388 docs(BLE): DLD960_BLE协议 V1.02→V1.03 — 命令总表补齐 + 未实现如实标注
对照 dbn_ble_srv.c(GBK+CRLF) 逐命令梳理,补齐既有配置命令:
- 0x09/0x10/0x11/0x12: 序列码/设备信息/SSC网络读写
- 0x13/0x14/0x15/0x16: IoT网络/Topic读写(字符串数组 0x00 分隔)
- 0x1C/0x1D: 密码验证/修改(0x1D 在 unpack_packs,不在 switch)
- 0x1E/0x92/0x1F/0x22: 出厂/复位/子功能码
- 0x31: UART波特率读写(⚠ 写响应多 1B 重复低字节)
- 0xC5: 传感上报使能(⚠ report_sens_acs 无定义,0xC0 上报未实现)
- 透传通道 0x7F/0x9F
未实现如实标注: 0x17/0x18/0x20/0x21/0x87/0x88/0x89 无 case;
0x23/0x24/0x8A 空壳/空响应

同步: README 文档索引 V1.03 + devlog 置顶条目(2026-08-31)
2026-08-31 13:58:59 +08:00

487 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DLD960 BLE 通信协议
> 版本: V1.032026-08-31,命令总表补齐既有配置命令 0x09~0xC5 + 透传通道;未实现命令如实标注)
> 适用: vd960DBNCH32V208WCH BLE 协议栈)
> 用途: 小程序/APP 经蓝牙对 vd960DBN 进行**配置管理、参数查询、脱机日志/传感快照读取**(离线取证:事件流区分真复位 vs MQTT 断连重连;快照流回放 0xC0 传感波形)
---
## 1 帧格式
```
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
- 校验实现:`check_pkg()` / `compute_ckb()`dbn_ble_srv.c
### 1.1 魔数(Magic
| 魔数 | 名称 | 用途 |
|------|------|------|
| `0x8F` | MAGIC_BYTE_DBN_DEFAULT | 设备配置/查询/日志/快照命令族(本协议主体) |
| `0x7F` | MAGIC_BYTE_DBN_TRANSPARENT | 透传:显示屏协议、地感协议、探头协议等 → 原样转发 UART2(Loop MCU 口) |
| `0x9F` | MAGIC_BYTE_DBN_SUB_OTA | 透传子设备 OTA 升级(g_flag_counter_ota.flag 时进入透传模式) |
### 1.2 分包(请求与响应)
**请求分包**unpack_packs0x13/0x15/0x1C/0x1D/0x22 等长请求):header 字节高 4 位 = 总包数,低 4 位 = 序号(从 1 递增);首包初始化重组缓冲,中间包校验 `amount/cmd/seq 连续`,末包 `amount == seq` 触发处理。校验失败丢弃整包。
**响应分包**set_response_to_notify):单包 data 上限**随协商 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`
| 协商 MTU | 单包最大 dat | 整包最大长度 |
|---------|-------------|-------------|
| 23(未协商) | 14 | 20 |
| 96(常见 Android | 87 | 93 |
| 185iOS 常见) | 94 | 100 |
脱机日志 QUERY 响应最大 130BMTU=96 时 2 包(87+43 dat);MTU≥103 时 2 包(94+36 dat)。
> 分包发送由 `performPeriodicTask`50ms TMOS 周期)主动续传,两包间隔 ≤50ms,不依赖收包事件(2026-08-12 修复)。
---
## 2 命令总表
| 命令码 | 名称 | 方向 | 实现状态 |
|--------|------|------|---------|
| `0x09` | UPDATE_DEV_SERIAL | APP→设备 | ✅ §3.1 |
| `0x10` | GET_DEV_INFO | APP→设备 | ✅ §3.2 |
| `0x11` | SET_LSSC_NET | APP→设备 | ✅ §3.3 |
| `0x12` | GET_LSSC_NET | APP→设备 | ✅ §3.4 |
| `0x13` | SET_IOT_NET | APP→设备 | ✅ §3.5 |
| `0x14` | GET_IOT_NET | APP→设备 | ✅ §3.6 |
| `0x15` | SET_IOT_TOPIC | APP→设备 | ✅ §3.7 |
| `0x16` | GET_IOT_TOPIC | APP→设备 | ✅ §3.8 |
| `0x17` | SET_SUB_TRAFFIC_PARAM | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x18` | GET_SUB_TRAFFIC_PARAM | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x1C` | CHECK_PASS | APP→设备 | ✅ §4.1 |
| `0x1D` | MODIFY_PASS | APP→设备 | ✅ §4.2 |
| `0x1E` | SET_FACTORY | APP→设备 | ✅ §4.3 |
| `0x1F` | RESET_DEV | APP→设备 | ✅ §4.4 |
| `0x20` | SET_NOTIFY | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x21` | SET_NOTIFY_LOOP | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x22` | SET_SUB_CODE | APP→设备 | ✅ §4.5 |
| `0x23` | SET_CJQ_PARAM | APP→设备 | ⚠ 空壳(仅回 [0],参数未处理)§4.6 |
| `0x24` | GET_CJQ_PARAM | APP→设备 | ⚠ 空响应(无数据)§4.7 |
| `0x25` | OFFLOG_STAT | APP→设备 | ✅ §6 |
| `0x26` | OFFLOG_QUERY | APP→设备 | ✅ §7 |
| `0x27` | OFFLOG_CLEAR | APP→设备 | ✅ §8 |
| `0x28` | SNAP_STAT | APP→设备 | ✅ §9 |
| `0x29` | SNAP_QUERY | APP→设备 | ✅ §9 |
| `0x2A` | SNAP_CLEAR | APP→设备 | ✅ §9 |
| `0x31` | RW_UART_BAUD | APP→设备 | ✅ §4.8 |
| `0x87` | LOOP_SAMPLE_PARAM | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x88` | LOOP_BALANCE_PARAM | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x89` | LOOP_RELEASE_PLANB | APP→设备 | ⚠ 未实现(头文件定义,无 case) |
| `0x8A` | LOOP_SENS_LIST | APP→设备 | ⚠ 空响应(无数据)§4.9 |
| `0x92` | SET_CJQ_FACTORY | APP→设备 | ✅ §4.10 |
| `0xC0` | SUB_SENS_REPORT | 设备→APP | ⚠ 未实现(report_sens_acs 仅声明无定义;0xC5 使能后无上报动作) |
| `0xC5` | SENS_ACS_ENABLE | APP→设备 | ✅(设置生效,无响应)§4.11 |
> 与 MQTT / TCP JSON 的 `log_stat` / `log_query` / `log_clear` 语义一致,通道不同(V1.02 起)。
---
## 3 设备配置命令
### 3.1 更改设备序列码 `UPDATE_DEV_SERIAL` (0x09)
**请求 data6B):** 新序列码(6 字节,g_dev_number 更新源为 `alter_dev_serila()`)。
**响应 data1B):** `status``0x01`=成功。
### 3.2 查询设备信息 `GET_DEV_INFO` (0x10)
**请求 data** 无(`Len=1`)。
**响应 data19 + N 字节):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 6 | `mac` | 设备 MAC 地址(gMacAddr |
| 6 | 1 | `hw_ver_main` | 硬件版本主 |
| 7 | 1 | `hw_ver_sub` | 硬件版本次 |
| 8 | 1 | `fw_ver_main` | 固件版本主 |
| 9 | 1 | `fw_ver_sub` | 固件版本次 |
| 10 | 1 | `model_len` | 型号字符串长度 N |
| 11 | N | `model` | 产品型号(PRODUCT_MODELASCII |
| 11+N | 6 | `dev_number` | 设备编号(6B |
| 17+N | 1 | `sub_code_lo` | 子功能码低字节(当前恒 0) |
| 18+N | 1 | `sub_code_hi` | 子功能码高字节(当前恒 0) |
| 19+N | 1 | `bus1_amount` | 总线1 设备数(当前恒 0) |
| 20+N | 1 | `bus2_amount` | 总线2 设备数(当前恒 0) |
> `sub_code` / `bus_amount` 字段代码中为占位(注释掉的旧逻辑),恒 0。
### 3.3 设置 SSC 网络配置 `SET_LSSC_NET` (0x11)
**请求 data30B):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 4 | `lip` | 本机 IP |
| 4 | 4 | `subnet_mask` | 子网掩码 |
| 8 | 4 | `route_ip` | 网关 |
| 12 | 4 | `lssc_ip` | 中心 IP |
| 16 | 4 | `dns` | DNS(代码未读入,保留位) |
| 20 | 2 | `port_ssc_tcp` | SSC TCP 端口(小端) |
| 22 | 2 | `port_ssc_udp` | SSC UDP 端口(小端) |
| 24 | 2 | `port_ssc_udp_message` | SSC UDP 消息端口(小端) |
| 26 | 2 | `port_dev_tcp` | 设备 TCP 端口(小端) |
| 28 | 2 | `port_dev_udp` | 设备 UDP 端口(小端) |
**响应 data1B):** `status``0x00`=成功(配置写入 Flash)。
### 3.4 查询 SSC 网络配置 `GET_LSSC_NET` (0x12)
**请求 data** 无。
**响应 data30B):** 与请求布局一致:`lip(4)+sub(4)+gw(4)+lssc_ip(4)+dns(4)+` 5×端口(2B 小端)。注意响应含 `dns`(读 local_net_cfg.dns)。
### 3.5 设置 IoT 网络配置 `SET_IOT_NET` (0x13)
**请求 data** 字符串数组,`0x00` 分隔(支持分包,见 §1.2):
```
<host>\0<port(ascii)>\0<client_id>\0<username>\0<password>\0
```
- `host`MQTT 服务器地址
- `port`:MQTT 端口(ASCII 十进制,代码逐字符换算)
- `client_id`ClientID**单字节 `0x20`(空格)表示使用本地序列号**
- `username` / `password`MQTT 鉴权
**响应 data1B):** `status``0x00`=成功(写入 Flash)。
### 3.6 查询 IoT 网络配置 `GET_IOT_NET` (0x14)
**请求 data** 无。
**响应 data** 字符串数组,`0x00` 分隔(同 §3.5 布局):`<remote_addr>\0<port(ascii)>\0<client_id>\0<username>\0<password>\0`
### 3.7 设置设备 Topic `SET_IOT_TOPIC` (0x15)
**请求 data** 字符串数组,`0x00` 分隔(支持分包):
```
<clientid_enable+0x30>\0<topic_pub>\0<topic_sub>\0
```
- `clientid_enable`ASCII 数值,解析时减 `0x30` 还原
**响应 data1B):** `status``0x00`=成功(写入 Flash)。
### 3.8 查询设备 Topic `GET_IOT_TOPIC` (0x16)
**请求 data** 无。
**响应 data**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `clientid_enable` | `g_iot_topic.clientid_enable + 0x30` |
| 1 | 1 | `rsvd` | 保留(0 |
| 2 | N | `topic_pub` | 发布主题(`\0` 结尾) |
| 2+N+1 | M | `topic_sub` | 订阅主题(`\0` 结尾) |
---
## 4 安全与控制命令
### 4.1 验证密码 `CHECK_PASS` (0x1C)
**请求 data6B):** 密码明文(ASCII)。
**响应 data1B):** `status``0x00`=通过;`0x01`=失败(或长度≠6)。
### 4.2 修改密码 `MODIFY_PASS` (0x1D)
**请求 data12B):** 旧密码(6B+ 新密码(6B)。
**响应 data1B):** `status``0x00`=成功(旧密码校验通过并写入);`0x01`=失败(长度≠12 或旧密码错误)。
> 实现位置:`unpack_packs()`switch 无 case,属分包收尾处理),V1.03 文档补录。
### 4.3 设备出厂初始化 `SET_FACTORY` (0x1E)
**请求 data** 无。
**响应 data1B):** `status``0x00`(先回包后执行 `factory_dev_info()` 写默认参数)。⚠ 未整机复位(代码 TODO:仅复位蓝牙以外部分)。
### 4.4 设备复位 `RESET_DEV` (0x1F)
**请求 data** 无。**无响应**(直接 `NVIC_SystemReset()`)。
### 4.5 设置子功能码 `SET_SUB_CODE` (0x22)
**请求 data2B):** `code_set`(小端,bit 定义如下)。**无响应**(生效并持久化 `update_sub_code_enable()`)。
| bit | 子功能 | bit | 子功能 |
|-----|--------|-----|--------|
| 0 | net_enableSSC 网络) | 6 | radar_enable(雷达) |
| 1 | iot_enableIoT MQTT | 9 | laser_enable(激光) |
| 2 | custom_enable(自定义) | 15 | lora_enableLoRa |
| 3 | loop_enable(地感) | | |
| 4 | dgdus_enable(地感采集器) | | |
| 5 | wbdus_enable(称重采集器) | | |
### 4.6 设置车检器参数 `SET_CJQ_PARAM` (0x23)
**⚠ 空壳:** 代码中参数解析被注释(`unpack_pkg_set_cjq_param` 未调用),仅返回 `[0x00]` 伪成功。**参数实际未写入。**
### 4.7 读取车检器参数 `GET_CJQ_PARAM` (0x24)
**⚠ 空响应:** 仅构造响应帧,data 长度 = 0。**无有效数据返回。**
### 4.8 读写 UART 波特率 `RW_UART_BAUD` (0x31)
**请求 data** `rw(1B)` + 数据(`rw≠0` 时):`uart_num(1B) + baud(3B 小端)`
- `rw=0`:读。
**读响应 data10B):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `rw` | 0(读) |
| 1 | 1 | `status` | 0x01 |
| 2 | 1 | `idle_timeout` | BLE 空闲超时(g_max_counter_bt_min |
| 3 | 3 | `baud_1` | UART1 波特率(3B 小端) |
| 6 | 1 | `idx_2` | 0x02(第二组标识) |
| 7 | 3 | `baud_2` | 第二组波特率(3B 小端) |
- `rw≠0`:写。`alter_dev_baud(&pkg[5])` + 更新 `g_storage_uart_num/baud`
**写响应 data9B 语义):** `rw(1B) + status(1B) + baud(3B 小端) + reserved(4B)`
> ⚠ **固件实际多发 1B**(baud 低字节重复写入,共 10B):接收端以前 3B(offset 2..4)为准。
### 4.9 读取 Loop 灵敏度列表 `LOOP_SENS_LIST` (0x8A)
**⚠ 空响应:** case 仅 `memset` 无组包。**无有效数据返回。**
### 4.10 车检器出厂初始化 `SET_CJQ_FACTORY` (0x92)
与 §4.3 `SET_FACTORY`0x1E)合并处理:响应 `[0x00]` + 执行 `factory_dev_info()`
### 4.11 传感主动上报使能 `SENS_ACS_ENABLE` (0xC5)
**请求 data3B):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `enable` | 0=关闭;非0=开启 |
| 1 | 1 | `interval` | 上报间隔系数(实际 = 值 × 0x7F,单位 tick 待确认) |
| 2 | 1 | `timeout_min` | 超时分钟数(timeout_counter = 值 × 60 × 100 tick |
**无响应。** ⚠ 即使使能,`report_sens_acs()` 无定义无调用,**当前无 0xC0 BLE 主动上报**(上报走 MQTT/TCP 通道)。
---
## 5 透传通道
### 5.1 通用透传 (0x7F)
Magic=`0x7F` 的整帧(含校验)→ `manage_dbn_ble_transparent()`**原样转发 UART2**(Loop MCU 口)。用于显示屏协议、地感协议、探头协议等。响应分包时 header 的 Addr/Sub 字节 = pkg_seq(见 set_response_to_notify 单包分支)。
### 5.2 子设备 OTA 透传 (0x9F)
`g_flag_counter_ota.flag != 0` 时所有 BLE 接收直接走透传 → UART2。用于 Loop MCU OTA0x9F A5/A6/A7 帧透传,协议见《DLD960Loop 串口通信协议》OTA 章节)。
---
## 6 查询日志统计 `OFFLOG_STAT` (0x25)
**请求 data** 无(`Len=1`,仅 cmd 字节)。
**响应 data19B,全小端):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `status` | `0x00`=OK`0x01`=日志未启用(Flash 初始化失败) |
| 1 | 2 | `boot_seq` | 当前启动序号(每次上电 +1,区分复位段) |
| 3 | 4 | `count` | 有效记录条数(0~capacity,环形覆盖后 < capacity |
| 7 | 4 | `capacity` | 容量上限(**随存储芯片动态**W25Q32=16256 / W25Q64=32640 / W25Q128=65408 / W25Q256=130944 |
| 11 | 4 | `seq_first` | 最早一条全局序号(count=0 时=0) |
| 15 | 4 | `seq_last` | 最新一条全局序号 |
示例(boot_seq=2, count=1234, capacity=16256, seq_first=100, seq_last=1333):
```
8F 00 14 25 00 02 00 D2 04 00 00 00 80 3F 00 00 00 64 00 00 00 35 05 00 00 00 XX XX
-- -- -- -- -------------------- -------------------- -------------------- -----
| | | | status count=1234 capacity=16256 seq_first=100
| | | | cmd=0x25 boot_seq=2 seq_last=1333
```
---
## 7 分页拉取日志 `OFFLOG_QUERY` (0x26)
**请求 data5B):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 4 | `start_seq` | 起始全局序号(小端,含);越界(< seq_first 或 > seq_last)返回 0 条 |
| 4 | 1 | `count` | 拉取条数,**上限 4**,超限按 4;0 按 4 处理 |
**响应 data**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `status` | `0x00`=OK`0x01`=日志未启用;`0x02`=请求帧过短 |
| 1 | 1 | `count` | 本次实际返回记录条数(0~4) |
| 2 | N×32 | 记录 | `count` 条 OfflogEvt 原始结构(小端,见 §10) |
记录顺序 = 逻辑序号升序(与 `start_seq` 一致)。越界/空日志:`count=0`
请求示例(start_seq=1330, count=4):
```
8F 00 06 26 32 05 00 00 04 XX XX
-- -- -- -- -- -- -- -- ---
| | | | | | | | count=4
| | | | +--+--+--+ start_seq=1330 (LE)
| | | cmd=0x26
| | Len=5+1=6
```
---
## 8 清除日志 `OFFLOG_CLEAR` (0x27)
**请求 data** 无。
**响应 data1B):** `status``0x00`=OK。
> ⚠ **高风险操作**:清除动作本身写入事件流(`log_clear` 审计——留痕不可清除)。设备侧应做权限控制(与 MQTT `log_clear` 同语义)。
>
> ⚠ **阻塞 ~2.8s**63 个数据扇区 SPI 擦除(~45ms/扇区),期间主循环阻塞。若设备 IoT MQTT 在线,此期间 WCHNET 无法轮询,可能导致断连重连(60s keepalive 内可恢复)。请勿高频调用。
成功后 `OFFLOG_STAT``count` 归 1(仅剩审计记录),`seq_last` 继续递增(序号不复位)。
---
## 9 传感快照日志(0x28/0x29/0x2AV1.02 新增)
**用途**:0xC0 传感帧(4 线圈波形)按上报节奏落盘,与事件日志**分区独立**(快照区 = 总容量 − 固定区 576KB − 事件区)。断网期间波形照常记录,可离线回放。
**线程语义(设备侧)**:采集路径(USART2 中断)只做打包 + RAM 暂存(8 深,满丢新),主循环每轮 `snap_flush()` 落盘——QUERY 读到的是已落盘记录。
### 9.1 查询快照统计 `SNAP_STAT` (0x28)
**请求 data** 无(`Len=1`)。
**响应 data19B,全小端):** 与 §6 `OFFLOG_STAT` 布局相同:
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `status` | `0x00`=OK`0x01`=快照未启用 |
| 1 | 2 | `boot_seq` | 当前启动序号 |
| 3 | 4 | `count` | 有效记录条数(0~capacity |
| 7 | 4 | `capacity` | 容量上限(**随芯片动态**W25Q32=48064 / W25Q64=105408 / W25Q128=220096 / W25Q256=449472 |
| 11 | 4 | `seq_first` | 最早一条全局序号(count=0 时=0) |
| 15 | 4 | `seq_last` | 最新一条全局序号 |
### 9.2 分页拉取快照 `SNAP_QUERY` (0x29)
**请求 data5B):**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 4 | `start_seq` | 起始全局序号(小端,含);越界返回 0 条 |
| 4 | 1 | `count` | 拉取条数,**上限 2**64B×2+2=130B ≤ 单包缓冲),超限按 2;0 按 2 |
**响应 data**
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `status` | `0x00`=OK`0x01`=快照未启用;`0x02`=请求帧过短 |
| 1 | 1 | `count` | 本次实际返回记录条数(0~2) |
| 2 | N×64 | 记录 | `count` 条 SnapRec 原始结构(小端,见 §9.4) |
记录顺序 = 逻辑序号升序。QUERY 响应最大 130BMTU=96 时 2 包(87+43 dat);MTU≥103 时 2 包(94+36 dat)。
### 9.3 清除快照 `SNAP_CLEAR` (0x2A)
**请求 data** 无。**响应 data1B):** `status``0x00`=OK。
> ⚠ **高风险操作**:清除动作本身写入**事件流** `log_clear` 审计(payload[0]=2=快照流,不可清除)。
> 阻塞 ~45ms:逻辑清除(count=0 旧数据立即不可读)+ 只擦写指针起点扇区,其余扇区由环形写覆盖时自动擦(原擦全部 751 扇区 ~34s,会触发 IWDG 4s 复位——2026-08-12 修复)。
成功后 `SNAP_STAT``count` 归 0`seq_last` 继续递增(序号不复位)。
### 9.4 快照记录格式(SnapRec64B 定长,小端)
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `magic` | `0xA6`(区别于事件 `0xA5` |
| 1 | 1 | `len` | 线圈数据有效长度 = coil_count×120~48 |
| 2 | 1 | `flags` | bit0=分包帧(预留) |
| 3 | 1 | `rsvd` | 保留(0 |
| 4 | 4 | `seq` | 全局序号(跨 boot 递增) |
| 8 | 4 | `ts_ms` | boot 内相对时间(ms,采集时刻) |
| 12 | 2 | `boot_seq` | 所属启动段 |
| 14 | 2 | `rsvd2` | 保留(0 |
| 16 | 48 | `coils` | 4×12B 线圈数据,与 0xC0 线上格式一致 |
**线圈 12B 单元**(与 0xC0 帧内每路线圈完全一致):
| 偏移(单元内) | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `cfg` | freq_level(2) + direction(1) + freq_type(1) + sensitivity(4) |
| 1 | 1 | `cond` | condition(4) + loop_state(1) + car_state(1) + misc_type(2) |
| 2 | 3 | `freq` | 频率,3B 小端无符号 |
| 5 | 3 | `variation` | 变化量,3B 小端有符号补码(= Origin CAPVD |
| 8 | 4 | `misc` | 杂项 4B 小端(misc_type=0 时=通过时间,50ms 单位) |
> 绝对时间回算:用事件流 `time_anchor`boot_seq ↔ unix_ts 映射)+ 本记录 `boot_seq`/`ts_ms`。未同步段仅相对时间。
---
## 10 记录格式(OfflogEvt32B 定长,小端)
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 1 | `magic` | `0xA5` |
| 1 | 1 | `type` | 事件类型(下表) |
| 2 | 1 | `len` | payload 有效字节数(0~12 |
| 3 | 1 | `flags` | bit0=1 → `unix_ts` 有效 |
| 4 | 4 | `seq` | 全局序号(跨 boot 递增) |
| 8 | 4 | `ts_ms` | boot 内相对时间(ms,断电归零) |
| 12 | 4 | `unix_ts` | 已同步 Unix 秒;`flags.bit0=0` → 0/无效(未同步段仅相对时间) |
| 16 | 2 | `boot_seq` | 所属启动段 |
| 18 | 2 | `rsvd` | 保留(0 |
| 20 | 12 | `payload` | 事件参数(下表) |
### 事件类型与 payload
| type | 事件 | payload | 说明 |
|------|------|---------|------|
| `0x01` | boot | `[4]` 复位原因寄存器原始值(大端),位解析:bit31=LPWR bit30=WWDG bit29=IWDG(看门狗) bit28=软件复位 bit27=POR(真断电) bit26=NRST引脚 | 上电/复位 |
| `0x10` | iot_connect | — | MQTT TCP 连接成功 |
| `0x11` | iot_ready | — | MQTT 订阅完成 → 发 initialize(重复上线直接证据) |
| `0x12` | iot_disconnect | `[1]` reason1=断开 2=超时 3=CONNACK拒绝 4=连接超时 | MQTT 断连 |
| `0x13` | iot_reconn | `[4]` 重连退避 ms(大端) | 重连退避 |
| `0x30` | evt_retry | `[5]` msg_id(大端4) + retry(1) | event_report ACK 超时重发 |
| `0x31` | evt_giveup | `[4]` msg_id(大端) | event_report 重试耗尽挂起 |
| `0x40` | coil | `[6]` sub(1) + ch(1) + value(大端4, 50ms 单位) | 线圈事件;sub1=car_enter 2=car_leave 3=loop_cut 4=loop_restore |
| `0x50` | time_anchor | —(`unix_ts` 即平台下发值,严格一致) | 时钟同步锚点 |
| `0x70` | log_clear | — | 日志清除(审计,不可清除) |
> payload 内多字节为**大端**(与 offlog 写入侧一致),OfflogEvt 其余字段为**小端**CPU 原生序,直接 memcpy)。
---
## 11 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| V1.00 | 2026-08-10 | 脱机事件日志 3 命令:OFFLOG_STAT / OFFLOG_QUERY / OFFLOG_CLEAR |
| V1.01 | 2026-08-12 | 分包上限改为随协商 MTU 动态:`chunk = min(MTU-9, 94)`;修复 Too large noti 丢包 |
| V1.02 | 2026-08-12 | 新增传感快照 3 命令:SNAP_STAT / SNAP_QUERY / SNAP_CLEAR0x28/0x29/0x2A |
| V1.03 | 2026-08-31 | 命令总表补齐既有配置命令(0x09~0x24、0x31、0x8A、0x92、0xC5+ 透传通道(0x7F/0x9F);对照 dbn_ble_srv.c 如实标注未实现命令(0x17/0x18/0x20/0x21/0x87/0x88/0x89 无 case0x23/0x24/0x8A 空壳;0xC0 上报未实现) |