docs: 新增蓝牙通信协议文档,训练任务书蓝牙协议改为引用
This commit is contained in:
+3
-251
@@ -33,17 +33,6 @@
|
||||
- [A.4 主控命令帧](#a4-主控命令帧)
|
||||
- [A.5 常用命令示例](#a5-常用命令示例)
|
||||
- [附录 B:蓝牙通信协议](#附录-b蓝牙通信协议)
|
||||
- [B.1 总体设计](#b1-总体设计)
|
||||
- [B.2 GATT 服务与特征](#b2-gatt-服务与特征)
|
||||
- [B.3 帧格式](#b3-帧格式)
|
||||
- [B.4 Notify 分包与重组](#b4-notify-分包与重组)
|
||||
- [B.5 命令码](#b5-命令码)
|
||||
- [B.6 TLV 编码](#b6-tlv-编码)
|
||||
- [B.7 TLV 类型](#b7-tlv-类型)
|
||||
- [B.8 结果码](#b8-结果码)
|
||||
- [B.9 设备状态码](#b9-设备状态码)
|
||||
- [B.10 主要命令交互](#b10-主要命令交互)
|
||||
- [B.11 异步长命令模型](#b11-异步长命令模型)
|
||||
|
||||
---
|
||||
|
||||
@@ -323,245 +312,8 @@ CHECKSUM = (sum(frame[0] .. frame[帧长-4])) & 0xFF // 位于 frame[帧长
|
||||
|
||||
## 附录 B:蓝牙通信协议
|
||||
|
||||
### B.1 总体设计
|
||||
蓝牙通信协议(GATT 服务与特征、帧格式、TLV 编码、命令码、结果码、状态码、主动推送、客户端接入要求等)统一见同目录文档:
|
||||
|
||||
1. 所有业务数据统一封装为 TLV 二进制帧,通过 GATT Notify 分包发送。
|
||||
2. 客户端必须先按特征 UUID 分流,再按命令字 `CMD` 解析。
|
||||
3. `b1/b2` 为一问一答通道:客户端写 `b1`,设备从 `b2` 通知响应。
|
||||
4. `a1/a2/b3` 为主动推送通道,不参与请求响应匹配。
|
||||
5. 命令结果只以 `TLV_RESULT_CODE` 为准。
|
||||
6. 状态变化通过 `b3` 推送,与命令结果解耦。
|
||||
### [`蓝牙通信协议.md`](蓝牙通信协议.md)
|
||||
|
||||
### B.2 GATT 服务与特征
|
||||
|
||||
**Radar Data Service**:`a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a`
|
||||
|
||||
| 名称 | UUID | 属性 | 方向 | 职责 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| a1 | `beb5483e-36e1-4688-b7f5-ea07361b26a1` | NOTIFY | 设备→客户端 | 连续雷达数据推送 |
|
||||
| a2 | `beb5483e-36e1-4688-b7f5-ea07361b26a2` | NOTIFY | 设备→客户端 | 雷达状态推送 |
|
||||
|
||||
**Device Config Service**:`a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1b`
|
||||
|
||||
| 名称 | UUID | 属性 | 方向 | 职责 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| b1 | `beb5483e-36e1-4688-b7f5-ea07361b26b1` | WRITE | 客户端→设备 | 命令写入 |
|
||||
| b2 | `beb5483e-36e1-4688-b7f5-ea07361b26b2` | NOTIFY | 设备→客户端 | 命令响应 |
|
||||
| b3 | `beb5483e-36e1-4688-b7f5-ea07361b26b3` | NOTIFY | 设备→客户端 | 设备信息 / 状态推送 |
|
||||
|
||||
### B.3 帧格式
|
||||
|
||||
```
|
||||
SOF1 SOF2 VERSION CMD SEQ LEN_H LEN_L PAYLOAD CRC_H CRC_L
|
||||
AA 55 01 xx xx xx xx ... xx xx
|
||||
```
|
||||
|
||||
| 字段 | 长度 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| SOF1 / SOF2 | 2 | 固定 `0xAA 0x55` |
|
||||
| VERSION | 1 | 协议版本,当前 `0x01` |
|
||||
| CMD | 1 | 命令码 |
|
||||
| SEQ | 1 | 命令请求序列号;主动推送由设备侧决定(`b3`、`a2` 固定为 `0`,`a1` 连续推送为设备侧自增),客户端对主动推送不按 `seq` 匹配 |
|
||||
| LEN | 2 | PAYLOAD 长度,大端 |
|
||||
| PAYLOAD | N | TLV 数据区 |
|
||||
| CRC | 2 | CRC16-CCITT,大端 |
|
||||
|
||||
- **CRC 计算范围**:从 VERSION 到 PAYLOAD 末尾,不含 SOF1/SOF2 与 CRC 本身。
|
||||
- **CRC 参数**:多项式 `0x1021`,初值 `0xFFFF`,输入/输出不反转,无最终异或(CRC16-CCITT-FALSE)。
|
||||
- **最小帧长**:9 字节(空载荷)。
|
||||
|
||||
### B.4 Notify 分包与重组
|
||||
|
||||
Notify 单包可能小于整帧,设备按固定 **20 字节**分片发送。客户端必须为 `a1 / a2 / b2 / b3` **各维护一个重组缓冲区**,按帧头 + LEN + CRC 提取完整帧。
|
||||
|
||||
### B.5 命令码
|
||||
|
||||
| 命令 | 值 | 通道 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| CMD_PING | `0x01` | b1/b2 | Ping |
|
||||
| CMD_QUERY_STATUS | `0x10` | b1/b2 | 查询设备状态 |
|
||||
| CMD_QUERY_RADAR | `0x12` | b1/b2 | 查询雷达快照 |
|
||||
| CMD_START_CONTINUOUS | `0x14` | b1/b2 | 启动连续推送 |
|
||||
| CMD_STOP_CONTINUOUS | `0x16` | b1/b2 | 停止连续推送 |
|
||||
| CMD_RADAR_SLEEP_QUERY | `0x17` | b1/b2 | 雷达睡眠查询开关 |
|
||||
| CMD_CONTINUOUS_PUSH | `0x18` | a1 | 连续数据主动推送 |
|
||||
| CMD_DEVICE_INFO_PUSH | `0x19` | b3 | 设备信息 / 状态推送 |
|
||||
| CMD_RADAR_STATUS_PUSH | `0x1A` | a2 | 雷达状态推送 |
|
||||
| CMD_WIFI_SCAN | `0x20` | b1/b2 | WiFi 扫描 |
|
||||
| CMD_WIFI_CONFIG | `0x22` | b1/b2 | WiFi 配网 |
|
||||
| CMD_GET_SAVED_WIFI | `0x24` | b1/b2 | 查询已保存 WiFi |
|
||||
| CMD_DELETE_SAVED_WIFI | `0x26` | b1/b2 | 删除已保存 WiFi |
|
||||
| CMD_ERROR_RESP | `0x7E` | b2 | 协议层错误响应 |
|
||||
|
||||
### B.6 TLV 编码
|
||||
|
||||
```
|
||||
TYPE(1) LEN_H(1) LEN_L(1) VALUE(N)
|
||||
```
|
||||
|
||||
### B.7 TLV 类型
|
||||
|
||||
**设备信息**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_RESULT_CODE | `0x02` | uint8 | 结果码 |
|
||||
| TLV_TIMESTAMP | `0x04` | uint32 | 时间戳(ms) |
|
||||
| TLV_PROTOCOL_VERSION | `0x05` | string | 协议版本 |
|
||||
| TLV_DEVICE_SN | `0x06` | uint64 | 设备序列号 |
|
||||
| TLV_FIRMWARE_VERSION | `0x07` | string | 固件版本 |
|
||||
| TLV_DEVICE_TYPE | `0x08` | string | 设备类型 |
|
||||
| TLV_MAC_ADDRESS | `0x09` | string | MAC 地址 |
|
||||
|
||||
**雷达数据**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_HEART_RATE_X10 | `0x10` | uint16 | 心率 ×10 |
|
||||
| TLV_BREATH_RATE_X10 | `0x11` | uint16 | 呼吸率 ×10 |
|
||||
| TLV_PRESENCE | `0x12` | uint8 | 人体存在 |
|
||||
| TLV_MOTION | `0x13` | uint8 | 运动状态 |
|
||||
| TLV_SLEEP_STATE | `0x14` | uint8 | 睡眠状态 |
|
||||
| TLV_DISTANCE_CM | `0x15` | uint16 | 距离(cm) |
|
||||
| TLV_POS_X_MM | `0x16` | int16 | X 坐标(mm) |
|
||||
| TLV_POS_Y_MM | `0x17` | int16 | Y 坐标(mm) |
|
||||
| TLV_POS_Z_MM | `0x18` | int16 | Z 坐标(mm) |
|
||||
| TLV_BODY_MOVEMENT | `0x19` | uint8 | 体动 |
|
||||
|
||||
**WiFi**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_SSID | `0x20` | string | WiFi 名称 |
|
||||
| TLV_PASSWORD | `0x21` | string | WiFi 密码 |
|
||||
| TLV_WIFI_COUNT | `0x22` | uint16 | WiFi 数量 |
|
||||
| TLV_WIFI_ITEM | `0x23` | block | WiFi 列表项 |
|
||||
| TLV_RSSI | `0x24` | int8 | 信号强度 |
|
||||
| TLV_SECURITY | `0x25` | uint8 | 加密类型 |
|
||||
|
||||
**控制与状态**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_INTERVAL_MS | `0x31` | uint16 | 推送间隔(ms) |
|
||||
| TLV_RADAR_SLEEP_ENABLED | `0x32` | uint8 | 雷达睡眠查询开关 |
|
||||
| TLV_DEVICE_STATUS | `0x33` | uint8 | b3 状态推送 |
|
||||
| TLV_WIFI_STATUS | `0x34` | uint8 | WiFi 状态 |
|
||||
| TLV_MQTT_STATUS | `0x35` | uint8 | MQTT 状态 |
|
||||
| TLV_RADAR_SLEEP_STATUS | `0x36` | uint8 | 雷达睡眠查询状态 |
|
||||
| TLV_LED_ENABLED | `0x37` | uint8 | 指示灯开关 |
|
||||
|
||||
**通用**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_IP_ADDRESS | `0x41` | string | IP 地址 |
|
||||
| TLV_WIFI_CONFIGURED | `0x42` | uint8 | 是否已保存 WiFi |
|
||||
| TLV_WIFI_CONNECTED | `0x43` | uint8 | WiFi 是否连接 |
|
||||
| TLV_ECHO_CONTENT | `0x44` | string | 回显内容 |
|
||||
|
||||
**波形**
|
||||
|
||||
| TLV | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| TLV_HEART_WAVEFORM | `0x60` | uint8 | 心跳波形(原值 + 128) |
|
||||
| TLV_BREATH_WAVEFORM | `0x61` | uint8 | 呼吸波形(原值 + 128) |
|
||||
|
||||
### B.8 结果码
|
||||
|
||||
| 结果码 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| SUCCESS | `0x00` | 成功 |
|
||||
| PROCESSING | `0x01` | 已接收,处理中 |
|
||||
| ERR_PROTO_CMD_UNKNOWN | `0x13` | 未知命令 |
|
||||
| ERR_PROTO_PARAM_MISSING | `0x14` | 参数缺失 |
|
||||
| ERR_PROTO_PARAM_INVALID | `0x15` | 参数非法 |
|
||||
| ERR_PROTO_BUSY | `0x16` | 设备忙 |
|
||||
| ERR_PROTO_FRAME_TOO_LARGE | `0x18` | 帧过大 |
|
||||
| ERR_WIFI_SCAN_TIMEOUT | `0x20` | 扫描超时 |
|
||||
| ERR_WIFI_SSID_NOT_FOUND | `0x21` | 未找到 SSID |
|
||||
| ERR_WIFI_WRONG_PASSWORD | `0x22` | 密码错误 |
|
||||
| ERR_WIFI_SIGNAL_WEAK | `0x25` | 信号弱 |
|
||||
| ERR_WIFI_BUSY | `0x26` | WiFi 忙 |
|
||||
| ERR_DEV_STATE_INVALID | `0x40` | 状态不允许 |
|
||||
| ERR_DEV_STORAGE_FAIL | `0x41` | 存储失败 |
|
||||
| ERR_DEV_QUEUE_FULL | `0x42` | 队列已满 |
|
||||
|
||||
### B.9 设备状态码
|
||||
|
||||
用于 `TLV_DEVICE_STATUS` / `TLV_WIFI_STATUS` / `TLV_MQTT_STATUS` / `TLV_RADAR_SLEEP_STATUS`:
|
||||
|
||||
| 状态 | 值 |
|
||||
| --- | --- |
|
||||
| WIFI_DISCONNECTED | `0x10` |
|
||||
| WIFI_CONNECTING | `0x11` |
|
||||
| WIFI_CONNECTED | `0x12` |
|
||||
| WIFI_FAILED | `0x13` |
|
||||
| DEV_MQTT_DISCONNECTED | `0x20` |
|
||||
| DEV_MQTT_CONNECTING | `0x21` |
|
||||
| DEV_MQTT_CONNECTED | `0x22` |
|
||||
| DEV_MQTT_FAILED | `0x23` |
|
||||
| RADAR_SLEEP_QUERY_DISABLED | `0x30` |
|
||||
| RADAR_SLEEP_QUERY_ENABLED | `0x31` |
|
||||
| LED_DISABLED | `0x32` |
|
||||
| LED_ENABLED | `0x33` |
|
||||
|
||||
### B.10 主要命令交互
|
||||
|
||||
**CMD_QUERY_STATUS(0x10)**
|
||||
|
||||
- 请求:无载荷。
|
||||
- 响应(b2):`TLV_RESULT_CODE` + 设备信息 TLV(`TLV_PROTOCOL_VERSION`、`TLV_FIRMWARE_VERSION`、`TLV_DEVICE_TYPE`、`TLV_MAC_ADDRESS`,存在 SN 时附 `TLV_DEVICE_SN`)+ `TLV_WIFI_CONFIGURED`、`TLV_WIFI_CONNECTED`、`TLV_IP_ADDRESS`(已连接时)、`TLV_SSID`(已连接时)、`TLV_WIFI_STATUS`、`TLV_MQTT_STATUS`、`TLV_RADAR_SLEEP_STATUS`、`TLV_LED_ENABLED`。
|
||||
|
||||
**CMD_QUERY_RADAR(0x12)**
|
||||
|
||||
- 请求:无载荷。
|
||||
- 响应(b2):`TLV_RESULT_CODE` + `TLV_PRESENCE`、`TLV_HEART_RATE_X10`、`TLV_BREATH_RATE_X10`、`TLV_MOTION`、`TLV_DISTANCE_CM`、`TLV_POS_X_MM`、`TLV_POS_Y_MM`、`TLV_POS_Z_MM`、`TLV_BODY_MOVEMENT`;`seq` 与请求一致。
|
||||
|
||||
**CMD_START_CONTINUOUS(0x14)**
|
||||
|
||||
- 请求:`TLV_INTERVAL_MS`(有效范围 100~10000 ms)。
|
||||
- 响应:`TLV_RESULT_CODE`,成功时附 `TLV_INTERVAL_MS`;参数缺失/非法返回对应错误码。
|
||||
- 启动后设备按间隔通过 `a1` 推送 `CMD_CONTINUOUS_PUSH(0x18)` 帧。
|
||||
|
||||
**CMD_STOP_CONTINUOUS(0x16)**
|
||||
|
||||
- 请求无载荷;停止推送,幂等操作,始终返回 `SUCCESS`。
|
||||
|
||||
**CMD_CONTINUOUS_PUSH(0x18,a1 推送)**
|
||||
|
||||
- 载荷:`TLV_TIMESTAMP`、`TLV_PRESENCE`、`TLV_HEART_RATE_X10`、`TLV_BREATH_RATE_X10`、`TLV_MOTION`、`TLV_DISTANCE_CM` 等。
|
||||
|
||||
**CMD_WIFI_SCAN(0x20)**
|
||||
|
||||
- 请求无载荷。先回 `PROCESSING`;完成后回 `TLV_RESULT_CODE` + `TLV_WIFI_COUNT` + 多个 `TLV_WIFI_ITEM`(每项含 `TLV_SSID`、`TLV_RSSI`、`TLV_SECURITY`)。
|
||||
|
||||
**CMD_WIFI_CONFIG(0x22)**
|
||||
|
||||
- 请求:`TLV_SSID`、`TLV_PASSWORD`。
|
||||
- 先回 `PROCESSING`(可含 `TLV_SSID`);完成后回 `SUCCESS` + `TLV_SSID` + `TLV_IP_ADDRESS`,失败回对应错误码。
|
||||
|
||||
**CMD_GET_SAVED_WIFI(0x24)**
|
||||
|
||||
- 响应:`TLV_RESULT_CODE` + `TLV_WIFI_COUNT` + 多个 `TLV_WIFI_ITEM`。
|
||||
|
||||
**CMD_DELETE_SAVED_WIFI(0x26)**
|
||||
|
||||
- 请求:`TLV_SSID`;响应:`TLV_RESULT_CODE`。
|
||||
|
||||
**CMD_DEVICE_INFO_PUSH(0x19,b3 推送)**
|
||||
|
||||
- `seq = 0`;载荷为 `TLV_DEVICE_STATUS`,或设备信息 TLV(`TLV_RESULT_CODE`、协议版本、固件版本、设备类型、MAC、SN 等)。仅在状态变化时推送。
|
||||
|
||||
### B.11 异步长命令模型
|
||||
|
||||
适用于 `CMD_WIFI_SCAN`、`CMD_WIFI_CONFIG`:
|
||||
|
||||
```
|
||||
客户端 --b1--> 请求
|
||||
设备 --b2--> 原命令码, 原 seq, RESULT_CODE = PROCESSING(0x01)
|
||||
... 后台处理 ...
|
||||
设备 --b2--> 原命令码, 原 seq, RESULT_CODE = SUCCESS 或 ERR_XXX + 业务 TLV
|
||||
```
|
||||
|
||||
运行状态(WiFi 等)通过 `b3` 的 `CMD_DEVICE_INFO_PUSH(0x19)` + `TLV_DEVICE_STATUS` 推送。
|
||||
本文不再重复协议细节,实现时以上述文档为准。
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
# 蓝牙数据传输协议与 API 通信文档
|
||||
|
||||
本文档是固件与蓝牙客户端(微信小程序)之间 BLE 通信的唯一协议说明,包含 GATT 定义、二进制帧格式、命令清单、TLV 定义、结果码/状态码、主动推送、响应模型与客户端接入要求。
|
||||
|
||||
> 本文档**以当前工程代码为准**,对应源码:
|
||||
> `include/ble_tlv_protocol.h`、`src/ble_tlv_protocol.cpp`、`src/radar_manager.cpp`、`src/tasks_manager.cpp`、`src/wifi_manager.cpp`。
|
||||
> 若代码与本文档不一致,以代码为准并同步更新本文档。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本协议用于雷达设备与蓝牙客户端(微信小程序)之间的双向通信。设备作为 BLE GATT Server(外设),客户端作为 GATT Client(中心);所有业务数据统一封装为 **TLV 二进制帧**,通过 GATT 特征的写入与 Notify 收发。
|
||||
|
||||
```
|
||||
┌──────────────┐ b1 写入(命令) ┌────────────────┐
|
||||
│ │ ──────────────────────▶ │ │
|
||||
│ BLE 客户端 │ b2 通知(响应) │ 雷达设备 │
|
||||
│ (小程序) │ ◀────────────────────── │ (GATT Server)│
|
||||
│ │ a1/a2/b3 通知(推送) │ │
|
||||
│ │ ◀────────────────────── │ │
|
||||
└──────────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
**通信模型**
|
||||
|
||||
| 方向 | 特征 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 客户端 → 设备 | `b1`(Write) | 下发命令 |
|
||||
| 设备 → 客户端 | `b2`(Notify) | 命令响应,按 `seq` 与请求匹配 |
|
||||
| 设备 → 客户端 | `a1` / `a2` / `b3`(Notify) | 主动推送数据与状态,不做请求匹配 |
|
||||
|
||||
**协议特点**
|
||||
|
||||
1. 统一帧格式 `AA 55 | VER | CMD | SEQ | LEN | PAYLOAD | CRC`,二进制紧凑,适合嵌入式设备。
|
||||
2. 命令与数据分离:命令走 `b1/b2` 一问一答,数据与状态走 `a1/a2/b3` 主动推送。
|
||||
3. 结果以 `TLV_RESULT_CODE` 判定,错误码/状态码的文案由客户端本地映射。
|
||||
4. 支持 Notify 分包与客户端重组(当前固定 20 字节一包)。
|
||||
5. 状态推送去重:仅在状态变化时经 `b3` 发送。
|
||||
6. 配网等耗时命令采用"先 `PROCESSING`、后最终结果"的异步响应模型。
|
||||
|
||||
**当前实现参数**
|
||||
|
||||
| 项目 | 值 |
|
||||
| --- | --- |
|
||||
| 协议版本 | `0x01` |
|
||||
| 帧头 | `0xAA 0x55` |
|
||||
| 校验 | CRC16-CCITT(覆盖 `VERSION` ~ `PAYLOAD`,大端) |
|
||||
| Notify 分包 | 固定 20 字节 |
|
||||
| 命令接收缓冲 | 256 字节(超限返回 `ERR_PROTO_FRAME_TOO_LARGE`) |
|
||||
| 默认连续推送间隔 | 500 ms(可通过 `TLV_INTERVAL_MS` 设置为 100~10000 ms) |
|
||||
|
||||
---
|
||||
|
||||
## 一、设计原则
|
||||
|
||||
1. 客户端必须先按 GATT 特征 UUID 分流,再按 `CMD` 命令码解析。
|
||||
2. `b1/b2` 是命令请求/响应通道:客户端写 `b1`,设备通过 `b2` Notify 响应。
|
||||
3. `a1/a2/b3` 是主动推送通道,不参与请求响应匹配。
|
||||
4. Notify 单包可能小于整帧,客户端必须按特征分别维护重组 buffer。
|
||||
5. 命令结果只以 `TLV_RESULT_CODE` 为事实来源,错误文案由客户端按错误码本地映射。
|
||||
6. 运行状态变化通过 `b3` 推送,与命令结果解耦。
|
||||
7. 协议层不再使用 `flags`、`ACK`、`TLV_STATE`、`TLV_STEP`、`TLV_MESSAGE`、`TLV_ERROR_MESSAGE`。
|
||||
8. `READ` 不作为主协议能力,客户端不依赖读特征获取业务数据(只有 `a1/a2/b2/b3` 为 Notify,`b1` 为 Write)。
|
||||
|
||||
---
|
||||
|
||||
## 二、GATT 服务与特征
|
||||
|
||||
### 2.1 Radar Data Service(雷达数据服务)
|
||||
|
||||
Service UUID:`a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1a`
|
||||
|
||||
| 别名 | UUID | 属性 | 方向 | 职责 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `a1` | `beb5483e-36e1-4688-b7f5-ea07361b26a1` | NOTIFY | 设备→客户端 | 连续雷达数据流推送 |
|
||||
| `a2` | `beb5483e-36e1-4688-b7f5-ea07361b26a2` | NOTIFY | 设备→客户端 | 雷达状态主动推送 |
|
||||
|
||||
### 2.2 Device Config Service(设备配置服务)
|
||||
|
||||
Service UUID:`a8c1e5c0-3d5d-4a9d-8d5e-7c8b6a4e2f1b`
|
||||
|
||||
| 别名 | UUID | 属性 | 方向 | 职责 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `b1` | `beb5483e-36e1-4688-b7f5-ea07361b26b1` | WRITE | 客户端→设备 | 命令写入通道 |
|
||||
| `b2` | `beb5483e-36e1-4688-b7f5-ea07361b26b2` | NOTIFY | 设备→客户端 | 命令响应通道 |
|
||||
| `b3` | `beb5483e-36e1-4688-b7f5-ea07361b26b3` | NOTIFY | 设备→客户端 | 设备信息与状态主动推送 |
|
||||
|
||||
### 2.3 通道职责
|
||||
|
||||
| 通道 | 属性 | 匹配模式 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `b1` | Write | 请求→响应 | 客户端发送命令 |
|
||||
| `b2` | Notify | 请求→响应 | 设备返回命令响应,按 `seq` 匹配 |
|
||||
| `a1` | Notify | 仅推送 | 连续雷达数据流,不做请求匹配 |
|
||||
| `a2` | Notify | 仅推送 | 雷达状态推送,不做请求匹配 |
|
||||
| `b3` | Notify | 仅推送 | 设备信息/状态推送,不做请求匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 三、二进制帧格式
|
||||
|
||||
### 3.1 帧结构
|
||||
|
||||
```
|
||||
┌──────┬──────┬───────┬─────┬─────┬───────┬───────┬─────────┬───────┬───────┐
|
||||
│ SOF1 │ SOF2 │ VER │ CMD │ SEQ │ LEN_H │ LEN_L │ PAYLOAD │ CRC_H │ CRC_L │
|
||||
│ 1B │ 1B │ 1B │ 1B │ 1B │ 1B │ 1B │ N B │ 1B │ 1B │
|
||||
└──────┴──────┴───────┴─────┴─────┴───────┴───────┴─────────┴───────┴───────┘
|
||||
```
|
||||
|
||||
| 字段 | 偏移 | 长度 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `SOF1` | 0 | 1 | 帧头,固定 `0xAA` |
|
||||
| `SOF2` | 1 | 1 | 帧头,固定 `0x55` |
|
||||
| `VERSION` | 2 | 1 | 协议版本,当前 `0x01` |
|
||||
| `CMD` | 3 | 1 | 命令码 |
|
||||
| `SEQ` | 4 | 1 | 命令请求/响应序列号;主动推送见下表 |
|
||||
| `LEN` | 5-6 | 2 | PAYLOAD 长度,大端 |
|
||||
| `PAYLOAD` | 7 | N | TLV 编码数据区 |
|
||||
| `CRC` | 7+N | 2 | CRC16-CCITT,大端 |
|
||||
|
||||
- 最小帧长:**9 字节**(空 PAYLOAD)。
|
||||
- 所有多字节整数均为**大端**。
|
||||
|
||||
**SEQ 取值规则(与代码一致)**
|
||||
|
||||
| 帧类型 | SEQ |
|
||||
| --- | --- |
|
||||
| 命令请求(b1)与响应(b2) | 请求的 `seq`,原样返回 |
|
||||
| `a1` 连续推送 | 设备侧自增(`bleSequenceCounter++`) |
|
||||
| `a2` 雷达状态推送 | 固定 `0x00` |
|
||||
| `b3` 设备信息/状态推送 | 固定 `0x00` |
|
||||
|
||||
> 客户端只在 `b2` 上按 `seq` 匹配请求响应;`a1/a2/b3` 不参与匹配,因此 `a1` 的自增 `seq` 仅作参考。
|
||||
|
||||
### 3.2 CRC16-CCITT
|
||||
|
||||
- 算法:CRC16-CCITT(与代码 `crc16Ccitt()` 一致)
|
||||
- 多项式:`0x1021`
|
||||
- 初值:`0xFFFF`
|
||||
- 输入/输出反转:否
|
||||
- 最终异或:无
|
||||
- 计算范围:从 `VERSION` 到 `PAYLOAD` 末尾(**不含** `SOF1/SOF2` 与 `CRC` 本身)
|
||||
- 字节序:大端
|
||||
|
||||
### 3.3 TLV 编码
|
||||
|
||||
```
|
||||
┌────────┬───────┬───────┬─────────┐
|
||||
│ TYPE │ LEN_H │ LEN_L │ VALUE │
|
||||
│ 1B │ 1B │ 1B │ N B │
|
||||
└────────┴───────┴───────┴─────────┘
|
||||
```
|
||||
|
||||
`LEN` 为大端,`VALUE` 长度由 `LEN` 指定。
|
||||
|
||||
### 3.4 Notify 分包与重组
|
||||
|
||||
固件统一通过 `sendFrameToBLE()` 发送 Notify:
|
||||
|
||||
- 当前为**固定 20 字节分片**模式(`BLE_FIXED_20_BYTE_MODE = 1`),单包最大 20 字节。
|
||||
- 发送带互斥锁与流控(约 500 B/s,包间隔 ≥5ms)。
|
||||
- 仅当对应特征已被客户端订阅(CCCD `0x2902`)且已连接时才发送。
|
||||
|
||||
客户端必须为每个 Notify 特征分别维护重组 buffer:
|
||||
|
||||
| 通道 | Buffer | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `a1` | `a1Buffer` | 连续数据流重组 |
|
||||
| `a2` | `a2Buffer` | 雷达状态重组 |
|
||||
| `b2` | `b2Buffer` | 命令响应重组 |
|
||||
| `b3` | `b3Buffer` | 设备信息/状态重组 |
|
||||
|
||||
---
|
||||
|
||||
## 四、命令码总览
|
||||
|
||||
| 命令 | 值 | 通道 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `CMD_PING` | `0x01` | b1/b2 | Ping 请求/响应(可携带回显内容) |
|
||||
| `CMD_QUERY_STATUS` | `0x10` | b1/b2 | 查询设备概览 |
|
||||
| `CMD_QUERY_RADAR` | `0x12` | b1/b2 | 查询雷达数据快照 |
|
||||
| `CMD_START_CONTINUOUS` | `0x14` | b1/b2 | 启动连续推送 |
|
||||
| `CMD_STOP_CONTINUOUS` | `0x16` | b1/b2 | 停止连续推送 |
|
||||
| `CMD_RADAR_SLEEP_QUERY` | `0x17` | b1/b2 | 雷达睡眠/综合状态查询开关 |
|
||||
| `CMD_CONTINUOUS_PUSH` | `0x18` | a1 | 连续雷达数据主动推送 |
|
||||
| `CMD_DEVICE_INFO_PUSH` | `0x19` | b3 | 设备信息/状态主动推送 |
|
||||
| `CMD_RADAR_STATUS_PUSH` | `0x1A` | a2 | 雷达状态主动推送 |
|
||||
| `CMD_WIFI_SCAN` | `0x20` | b1/b2 | WiFi 扫描 |
|
||||
| `CMD_WIFI_CONFIG` | `0x22` | b1/b2 | WiFi 配网 |
|
||||
| `CMD_GET_SAVED_WIFI` | `0x24` | b1/b2 | 查询已保存 WiFi |
|
||||
| `CMD_DELETE_SAVED_WIFI` | `0x26` | b1/b2 | 删除已保存 WiFi |
|
||||
| `CMD_LED_CONTROL` | `0x30` | b1/b2 | 系统指示灯开关 |
|
||||
| `CMD_ERROR_RESP` | `0x7E` | b2 | 协议层/入口级错误响应 |
|
||||
|
||||
---
|
||||
|
||||
## 五、命令详解(b1 写入 / b2 响应)
|
||||
|
||||
### 5.1 CMD_PING(0x01)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_ECHO_CONTENT` | string | 否 | 回显内容,可携带任意字符串 |
|
||||
|
||||
**响应**(b2):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | `SUCCESS` |
|
||||
| `TLV_ECHO_CONTENT` | string | 仅当请求携带内容时原样回显 |
|
||||
|
||||
---
|
||||
|
||||
### 5.2 CMD_QUERY_STATUS(0x10)
|
||||
|
||||
**请求**:无 PAYLOAD。
|
||||
|
||||
**响应**(b2,按代码 `buildStatusPayload()` 的字段与顺序):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 是 | 结果码 |
|
||||
| `TLV_DEVICE_SN` | uint64 | 否 | 设备序列号,仅 `device_sn > 0` 时发送 |
|
||||
| `TLV_PROTOCOL_VERSION` | string | 是 | 协议版本(当前 `"1.0.0"`) |
|
||||
| `TLV_FIRMWARE_VERSION` | string | 是 | 固件版本 |
|
||||
| `TLV_DEVICE_TYPE` | string | 是 | 设备类型(当前 `"Radar"`) |
|
||||
| `TLV_MAC_ADDRESS` | string | 是 | MAC 地址 |
|
||||
| `TLV_WIFI_CONFIGURED` | uint8 | 是 | 是否保存过 WiFi(0/1) |
|
||||
| `TLV_WIFI_CONNECTED` | uint8 | 是 | WiFi 是否已连接(0/1) |
|
||||
| `TLV_IP_ADDRESS` | string | 否 | IP 地址,仅 WiFi 已连接时发送 |
|
||||
| `TLV_SSID` | string | 否 | 当前 WiFi 名称,仅 WiFi 已连接时发送 |
|
||||
| `TLV_WIFI_STATUS` | uint8 | 否 | WiFi 状态,当前状态非 0 时发送 |
|
||||
| `TLV_MQTT_STATUS` | uint8 | 否 | MQTT 状态,当前状态非 0 时发送 |
|
||||
| `TLV_RADAR_SLEEP_STATUS` | uint8 | 否 | 雷达睡眠查询状态,当前状态非 0 时发送 |
|
||||
| `TLV_LED_ENABLED` | uint8 | 是 | 系统指示灯开关(0/1),始终发送 |
|
||||
|
||||
---
|
||||
|
||||
### 5.3 CMD_QUERY_RADAR(0x12)
|
||||
|
||||
**请求**:无 PAYLOAD。
|
||||
|
||||
**响应**(b2,按代码 `processQueryRadarData()`):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_TIMESTAMP` | uint32 | 设备运行时间戳(`millis()`,单位 ms) |
|
||||
| `TLV_PRESENCE` | uint8 | 是否有人(0=无人,1=有人) |
|
||||
| `TLV_HEART_RATE_X10` | uint16 | 心率×10 |
|
||||
| `TLV_BREATH_RATE_X10` | uint16 | 呼吸率×10 |
|
||||
| `TLV_MOTION` | uint8 | 运动状态 |
|
||||
| `TLV_DISTANCE_CM` | uint16 | 距离(cm) |
|
||||
| `TLV_POS_X_MM` | int16 | X 坐标(mm) |
|
||||
| `TLV_POS_Y_MM` | int16 | Y 坐标(mm) |
|
||||
| `TLV_POS_Z_MM` | int16 | Z 坐标(mm) |
|
||||
| `TLV_BODY_MOVEMENT` | uint8 | 体动 |
|
||||
|
||||
> 快照即使无人也返回,字段为 0 属合法,客户端自行判断有效性。
|
||||
|
||||
---
|
||||
|
||||
### 5.4 CMD_START_CONTINUOUS(0x14)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_INTERVAL_MS` | uint16 | 是 | 推送间隔,有效范围 **100~10000 ms** |
|
||||
|
||||
**响应**(b2):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_INTERVAL_MS` | uint16 | 成功时回传实际生效的间隔 |
|
||||
|
||||
**错误**:缺少参数 → `ERR_PROTO_PARAM_MISSING`;超出范围 → `ERR_PROTO_PARAM_INVALID`。
|
||||
|
||||
**效果**:启动后设备通过 `a1` 推送 `CMD_CONTINUOUS_PUSH`,并通过 `a2` 每 200ms 推送 `CMD_RADAR_STATUS_PUSH`。
|
||||
|
||||
---
|
||||
|
||||
### 5.5 CMD_STOP_CONTINUOUS(0x16)
|
||||
|
||||
**请求**:无 PAYLOAD。
|
||||
|
||||
**响应**(b2):`TLV_RESULT_CODE = SUCCESS`。
|
||||
|
||||
**说明**:幂等操作,无论当前是否在推送都返回成功;BLE 断开时设备自动停止推送,重连后需重新下发 `CMD_START_CONTINUOUS`。
|
||||
|
||||
---
|
||||
|
||||
### 5.6 CMD_RADAR_SLEEP_QUERY(0x17)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RADAR_SLEEP_ENABLED` | uint8 | 是 | `0`=关闭,`1`=开启 |
|
||||
|
||||
**响应**(b2):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_RADAR_SLEEP_ENABLED` | uint8 | 当前开关状态 |
|
||||
|
||||
**错误**:缺失 → `ERR_PROTO_PARAM_MISSING`;非 0/1 → `ERR_PROTO_PARAM_INVALID`。
|
||||
|
||||
**推送**:开关状态变化时经 `b3` 推送 `RADAR_SLEEP_QUERY_ENABLED (0x31)` / `RADAR_SLEEP_QUERY_DISABLED (0x30)`。
|
||||
|
||||
---
|
||||
|
||||
### 5.7 CMD_LED_CONTROL(0x30)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_LED_ENABLED` | uint8 | 是 | `0`=关闭,`1`=开启 |
|
||||
|
||||
**响应**(b2):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_LED_ENABLED` | uint8 | 当前开关状态 |
|
||||
|
||||
**错误**:缺失 → `ERR_PROTO_PARAM_MISSING`;非 0/1 → `ERR_PROTO_PARAM_INVALID`。
|
||||
|
||||
**持久化与推送**:开关状态保存到 Flash(Preferences 命名空间 `radar_data`,键 `ledEnabled`),断电保持;状态变化时经 `b3` 推送 `LED_ENABLED (0x33)` / `LED_DISABLED (0x32)`。
|
||||
|
||||
---
|
||||
|
||||
### 5.8 CMD_WIFI_SCAN(0x20)
|
||||
|
||||
**请求**:无 PAYLOAD。
|
||||
|
||||
**异步响应**(b2):
|
||||
|
||||
第一阶段(立即):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | `PROCESSING (0x01)` |
|
||||
|
||||
第二阶段(扫描完成):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 是 | 结果码 |
|
||||
| `TLV_WIFI_COUNT` | uint16 | 否 | WiFi 数量,成功且非空时发送 |
|
||||
| `TLV_WIFI_ITEM` | block | 否 | WiFi 条目,可重复多个 |
|
||||
|
||||
每个 `TLV_WIFI_ITEM` 内为独立 TLV:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_SSID` | string | WiFi 名称 |
|
||||
| `TLV_RSSI` | int8 | 信号强度(dBm) |
|
||||
| `TLV_SECURITY` | uint8 | 加密类型,见第十节 |
|
||||
|
||||
**错误**:启动扫描失败 → `ERR_WIFI_BUSY`;超时 → `ERR_WIFI_SCAN_TIMEOUT`。
|
||||
|
||||
---
|
||||
|
||||
### 5.9 CMD_WIFI_CONFIG(0x22)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_SSID` | string | 是 | WiFi 名称 |
|
||||
| `TLV_PASSWORD` | string | 是 | WiFi 密码 |
|
||||
|
||||
**异步响应**(b2):
|
||||
|
||||
第一阶段(立即):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | `PROCESSING (0x01)` |
|
||||
| `TLV_SSID` | string | 正在配置的 SSID(回显) |
|
||||
|
||||
第二阶段(完成):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 是 | 结果码 |
|
||||
| `TLV_SSID` | string | 否 | 成功时发送 |
|
||||
| `TLV_IP_ADDRESS` | string | 否 | 成功时发送 |
|
||||
|
||||
**错误**:SSID 为空 → `ERR_PROTO_PARAM_MISSING`;其他常见错误 `ERR_WIFI_BUSY (0x26)`、`ERR_WIFI_SSID_NOT_FOUND (0x21)`、`ERR_WIFI_SIGNAL_WEAK (0x25)`、`ERR_WIFI_WRONG_PASSWORD (0x22)`、`ERR_WIFI_SCAN_TIMEOUT (0x20)`、`ERR_DEV_STORAGE_FAIL (0x41)`。
|
||||
|
||||
---
|
||||
|
||||
### 5.10 CMD_GET_SAVED_WIFI(0x24)
|
||||
|
||||
**请求**:无 PAYLOAD。
|
||||
|
||||
**响应**(b2,即时命令):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 是 | `SUCCESS` 或 `ERR_DEV_STORAGE_FAIL` |
|
||||
| `TLV_WIFI_COUNT` | uint16 | 否 | 成功时发送 |
|
||||
| `TLV_WIFI_ITEM` | block | 否 | 成功时发送,可重复多个 |
|
||||
|
||||
每个 `TLV_WIFI_ITEM` 内为独立 TLV:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_SSID` | string | WiFi 名称 |
|
||||
| `TLV_PASSWORD` | string | WiFi 密码 |
|
||||
|
||||
> 注意:已保存网络回包中包含密码,仅用于本地配网管理。
|
||||
|
||||
---
|
||||
|
||||
### 5.11 CMD_DELETE_SAVED_WIFI(0x26)
|
||||
|
||||
**请求**(b1):
|
||||
|
||||
| TLV | 类型 | 必选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_SSID` | string | 是 | 要删除的 WiFi 名称 |
|
||||
|
||||
**响应**(b2,成功时):
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_SSID` | string | 被删除的 SSID |
|
||||
| `TLV_WIFI_COUNT` | uint16 | 删除后剩余数量 |
|
||||
|
||||
**错误**:SSID 为空 → `ERR_PROTO_PARAM_MISSING`;网络不存在 → `ERR_WIFI_SSID_NOT_FOUND`;删除失败 → `ERR_DEV_STORAGE_FAIL`。
|
||||
|
||||
---
|
||||
|
||||
### 5.12 CMD_ERROR_RESP(0x7E)
|
||||
|
||||
用于协议层/入口级、无法归属到具体业务命令的错误(未知命令、帧过大、队列满、队列未初始化等),设备通过 `b2` 返回:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 错误码 |
|
||||
|
||||
---
|
||||
|
||||
## 六、主动推送(a1 / a2 / b3)
|
||||
|
||||
### 6.1 a1 — CMD_CONTINUOUS_PUSH(0x18)连续雷达数据
|
||||
|
||||
- **触发**:`CMD_START_CONTINUOUS` 成功后,按 `TLV_INTERVAL_MS` 间隔推送(默认 500ms),且仅在数据发生变化时发送。
|
||||
- **帧**:`cmd = 0x18`,`seq = 设备侧自增`。
|
||||
- **TLV**:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_PRESENCE` | uint8 | 是否有人(0/1) |
|
||||
| `TLV_HEART_RATE_X10` | uint16 | 心率×10 |
|
||||
| `TLV_BREATH_RATE_X10` | uint16 | 呼吸率×10 |
|
||||
| `TLV_MOTION` | uint8 | 运动状态 |
|
||||
|
||||
> 无人时上述字段均发 0。`TLV_SLEEP_STATE`、`TLV_HEART_WAVEFORM`、`TLV_BREATH_WAVEFORM` 当前代码未发送(已注释),客户端不应依赖。
|
||||
|
||||
### 6.2 a2 — CMD_RADAR_STATUS_PUSH(0x1A)雷达状态
|
||||
|
||||
- **触发**:`CMD_START_CONTINUOUS` 成功后,每 200ms 推送。
|
||||
- **帧**:`cmd = 0x1A`,`seq = 0x00`。
|
||||
- **TLV**:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_DISTANCE_CM` | uint16 | 距离(cm) |
|
||||
| `TLV_POS_X_MM` | int16 | X 坐标(mm) |
|
||||
| `TLV_POS_Y_MM` | int16 | Y 坐标(mm) |
|
||||
| `TLV_POS_Z_MM` | int16 | Z 坐标(mm) |
|
||||
| `TLV_BODY_MOVEMENT` | uint8 | 体动 |
|
||||
|
||||
### 6.3 b3 — CMD_DEVICE_INFO_PUSH(0x19)设备信息与状态
|
||||
|
||||
- **触发**:BLE 连接建立时同步当前状态;WiFi/MQTT/雷达睡眠查询/指示灯状态变化时推送(去重,仅变化时发送)。
|
||||
- **帧**:`cmd = 0x19`,`seq = 0x00`。
|
||||
|
||||
**状态推送 TLV**:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_DEVICE_STATUS` | uint8 | 设备状态码,见第九节 |
|
||||
|
||||
**设备信息推送 TLV**:
|
||||
|
||||
| TLV | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | uint8 | 结果码 |
|
||||
| `TLV_PROTOCOL_VERSION` | string | 协议版本 |
|
||||
| `TLV_FIRMWARE_VERSION` | string | 固件版本 |
|
||||
| `TLV_DEVICE_TYPE` | string | 设备类型 |
|
||||
| `TLV_MAC_ADDRESS` | string | MAC 地址 |
|
||||
| `TLV_DEVICE_SN` | uint64 | 设备序列号,仅存在时发送 |
|
||||
|
||||
---
|
||||
|
||||
## 七、TLV 类型码
|
||||
|
||||
### 7.1 设备信息(0x01-0x0F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_RESULT_CODE` | `0x02` | uint8 | 结果码 |
|
||||
| `TLV_TIMESTAMP` | `0x04` | uint32 | 时间戳 |
|
||||
| `TLV_PROTOCOL_VERSION` | `0x05` | string | 协议版本 |
|
||||
| `TLV_DEVICE_SN` | `0x06` | uint64 | 设备序列号,仅存在时发送 |
|
||||
| `TLV_FIRMWARE_VERSION` | `0x07` | string | 固件版本 |
|
||||
| `TLV_DEVICE_TYPE` | `0x08` | string | 设备类型 |
|
||||
| `TLV_MAC_ADDRESS` | `0x09` | string | MAC 地址 |
|
||||
|
||||
### 7.2 雷达数据(0x10-0x1F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_HEART_RATE_X10` | `0x10` | uint16 | 心率×10(720 = 72.0 bpm) |
|
||||
| `TLV_BREATH_RATE_X10` | `0x11` | uint16 | 呼吸率×10(180 = 18.0 次/分) |
|
||||
| `TLV_PRESENCE` | `0x12` | uint8 | 存在检测(0=无人,1=有人) |
|
||||
| `TLV_MOTION` | `0x13` | uint8 | 运动状态 |
|
||||
| `TLV_SLEEP_STATE` | `0x14` | uint8 | 睡眠状态 |
|
||||
| `TLV_DISTANCE_CM` | `0x15` | uint16 | 距离(cm) |
|
||||
| `TLV_POS_X_MM` | `0x16` | int16 | X 坐标(mm) |
|
||||
| `TLV_POS_Y_MM` | `0x17` | int16 | Y 坐标(mm) |
|
||||
| `TLV_POS_Z_MM` | `0x18` | int16 | Z 坐标(mm) |
|
||||
| `TLV_BODY_MOVEMENT` | `0x19` | uint8 | 体动 |
|
||||
|
||||
### 7.3 WiFi(0x20-0x2F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_SSID` | `0x20` | string | WiFi 名称 |
|
||||
| `TLV_PASSWORD` | `0x21` | string | WiFi 密码 |
|
||||
| `TLV_WIFI_COUNT` | `0x22` | uint16 | WiFi 数量 |
|
||||
| `TLV_WIFI_ITEM` | `0x23` | block | WiFi 条目(嵌套 TLV) |
|
||||
| `TLV_RSSI` | `0x24` | int8 | 信号强度(dBm) |
|
||||
| `TLV_SECURITY` | `0x25` | uint8 | 加密类型,见第十节 |
|
||||
|
||||
### 7.4 控制与状态(0x30-0x3F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_INTERVAL_MS` | `0x31` | uint16 | 推送间隔(ms) |
|
||||
| `TLV_RADAR_SLEEP_ENABLED` | `0x32` | uint8 | 雷达睡眠查询开关(0=关,1=开) |
|
||||
| `TLV_DEVICE_STATUS` | `0x33` | uint8 | 设备状态码(b3 推送) |
|
||||
| `TLV_WIFI_STATUS` | `0x34` | uint8 | WiFi 状态 |
|
||||
| `TLV_MQTT_STATUS` | `0x35` | uint8 | MQTT 状态 |
|
||||
| `TLV_RADAR_SLEEP_STATUS` | `0x36` | uint8 | 雷达睡眠查询状态 |
|
||||
| `TLV_LED_ENABLED` | `0x37` | uint8 | 系统指示灯开关(0=关,1=开) |
|
||||
|
||||
### 7.5 通用消息(0x40-0x4F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_IP_ADDRESS` | `0x41` | string | IP 地址 |
|
||||
| `TLV_WIFI_CONFIGURED` | `0x42` | uint8 | 是否保存过 WiFi(0/1) |
|
||||
| `TLV_WIFI_CONNECTED` | `0x43` | uint8 | WiFi 是否已连接(0/1) |
|
||||
| `TLV_ECHO_CONTENT` | `0x44` | string | 回显内容 |
|
||||
|
||||
### 7.6 波形(0x60-0x6F)
|
||||
|
||||
| 常量 | 值 | 类型 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `TLV_HEART_WAVEFORM` | `0x60` | uint8 | 心跳波形(原始 int8 + 128) |
|
||||
| `TLV_BREATH_WAVEFORM` | `0x61` | uint8 | 呼吸波形(原始 int8 + 128) |
|
||||
|
||||
---
|
||||
|
||||
## 八、结果码
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `SUCCESS` | `0x00` | 成功 |
|
||||
| `PROCESSING` | `0x01` | 命令已接收,处理中 |
|
||||
| `ERR_PROTO_CMD_UNKNOWN` | `0x13` | 未知命令 |
|
||||
| `ERR_PROTO_PARAM_MISSING` | `0x14` | 参数缺失 |
|
||||
| `ERR_PROTO_PARAM_INVALID` | `0x15` | 参数非法 |
|
||||
| `ERR_PROTO_BUSY` | `0x16` | 设备忙 |
|
||||
| `ERR_PROTO_FRAME_TOO_LARGE` | `0x18` | 请求帧过大(超过 256 字节) |
|
||||
| `ERR_WIFI_SCAN_TIMEOUT` | `0x20` | WiFi 扫描超时 |
|
||||
| `ERR_WIFI_SSID_NOT_FOUND` | `0x21` | 未找到 SSID |
|
||||
| `ERR_WIFI_WRONG_PASSWORD` | `0x22` | WiFi 密码错误 |
|
||||
| `ERR_WIFI_SIGNAL_WEAK` | `0x25` | WiFi 信号弱 |
|
||||
| `ERR_WIFI_BUSY` | `0x26` | WiFi 忙 |
|
||||
| `ERR_DEV_STATE_INVALID` | `0x40` | 当前设备状态不允许 |
|
||||
| `ERR_DEV_STORAGE_FAIL` | `0x41` | 存储失败 |
|
||||
| `ERR_DEV_QUEUE_FULL` | `0x42` | 命令队列已满 |
|
||||
|
||||
---
|
||||
|
||||
## 九、设备状态码
|
||||
|
||||
`TLV_DEVICE_STATUS` / `TLV_WIFI_STATUS` / `TLV_MQTT_STATUS` / `TLV_RADAR_SLEEP_STATUS` 共用同一组状态码。
|
||||
|
||||
### 9.1 WiFi 状态
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `WIFI_DISCONNECTED` | `0x10` | WiFi 断开 |
|
||||
| `WIFI_CONNECTING` | `0x11` | WiFi 连接中 |
|
||||
| `WIFI_CONNECTED` | `0x12` | WiFi 已连接 |
|
||||
| `WIFI_FAILED` | `0x13` | WiFi 连接失败 |
|
||||
|
||||
### 9.2 MQTT 状态
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `DEV_MQTT_DISCONNECTED` | `0x20` | MQTT 断开 |
|
||||
| `DEV_MQTT_CONNECTING` | `0x21` | MQTT 连接中 |
|
||||
| `DEV_MQTT_CONNECTED` | `0x22` | MQTT 已连接 |
|
||||
| `DEV_MQTT_FAILED` | `0x23` | MQTT 连接失败 |
|
||||
|
||||
### 9.3 雷达睡眠查询状态
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `RADAR_SLEEP_QUERY_DISABLED` | `0x30` | 雷达睡眠/综合状态查询已关闭 |
|
||||
| `RADAR_SLEEP_QUERY_ENABLED` | `0x31` | 雷达睡眠/综合状态查询已开启 |
|
||||
|
||||
### 9.4 系统指示灯状态
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `LED_DISABLED` | `0x32` | 系统指示灯已关闭 |
|
||||
| `LED_ENABLED` | `0x33` | 系统指示灯已开启 |
|
||||
|
||||
---
|
||||
|
||||
## 十、WiFi 安全类型
|
||||
|
||||
| 常量 | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `WIFI_SEC_OPEN` | `0` | 开放网络 |
|
||||
| `WIFI_SEC_WEP` | `1` | WEP 加密 |
|
||||
| `WIFI_SEC_WPA` | `2` | WPA 加密 |
|
||||
| `WIFI_SEC_WPA2` | `3` | WPA2 加密 |
|
||||
| `WIFI_SEC_WPA3` | `4` | WPA3 加密 |
|
||||
| `WIFI_SEC_UNKNOWN` | `255` | 未知类型 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、响应模型
|
||||
|
||||
### 11.1 同步命令
|
||||
|
||||
```
|
||||
客户端写 b1 → 设备从 b2 通知响应
|
||||
```
|
||||
|
||||
响应帧:
|
||||
|
||||
```
|
||||
cmd = 原命令码
|
||||
seq = 请求 seq
|
||||
payload = TLV_RESULT_CODE + 业务数据 TLV
|
||||
```
|
||||
|
||||
失败时同样使用原命令码响应,`TLV_RESULT_CODE` 为对应错误码;无法归属具体业务命令的入口级错误使用 `CMD_ERROR_RESP (0x7E)`。
|
||||
|
||||
### 11.2 异步长命令
|
||||
|
||||
适用:`CMD_WIFI_SCAN`、`CMD_WIFI_CONFIG`。
|
||||
|
||||
```
|
||||
阶段一(立即): cmd=原命令码, seq=原seq, TLV_RESULT_CODE = PROCESSING (0x01)
|
||||
阶段二(完成): cmd=原命令码, seq=原seq, TLV_RESULT_CODE = SUCCESS / ERR_XXX + 业务 TLV
|
||||
```
|
||||
|
||||
客户端收到 `PROCESSING` 后**不要清除** pending 请求,继续等待最终结果。
|
||||
|
||||
---
|
||||
|
||||
## 十二、客户端接入要求
|
||||
|
||||
### 12.1 基本原则
|
||||
|
||||
1. 先按特征 UUID 分流,再按 `cmd` 解析。
|
||||
2. 每个 Notify 特征维护独立重组 buffer。
|
||||
3. 只在 `b2` 上按 `seq` 匹配请求响应。
|
||||
4. `a1/a2/b3` 不做请求响应匹配。
|
||||
5. 所有结果判断以 `TLV_RESULT_CODE` 为准,文案本地映射。
|
||||
|
||||
### 12.2 伪代码
|
||||
|
||||
```javascript
|
||||
const buffers = { a1: new Uint8Array(0), a2: new Uint8Array(0), b2: new Uint8Array(0), b3: new Uint8Array(0) }
|
||||
|
||||
function onNotify(characteristicId, chunk) {
|
||||
const channel = mapCharacteristicToChannel(characteristicId)
|
||||
buffers[channel] = concatBytes(buffers[channel], chunk)
|
||||
while (true) {
|
||||
const frame = tryExtractFrame(buffers[channel])
|
||||
if (!frame) break
|
||||
buffers[channel] = frame.remaining
|
||||
if (!verifyCrc(frame.bytes)) continue
|
||||
dispatchByChannel(channel, frame.decoded)
|
||||
}
|
||||
}
|
||||
|
||||
const pending = new Map()
|
||||
|
||||
function sendCommand(cmd, payload) {
|
||||
const seq = nextSeq()
|
||||
pending.set(seq, { cmd })
|
||||
return writeB1(encodeFrame({ cmd, seq, payload }))
|
||||
}
|
||||
|
||||
function handleCommandResponse(frame) {
|
||||
const req = pending.get(frame.seq)
|
||||
if (!req) return
|
||||
const resultCode = getTlvU8(frame.payload, TLV_RESULT_CODE)
|
||||
if (resultCode !== PROCESSING) pending.delete(frame.seq)
|
||||
routeCommandResult(req.cmd, resultCode, frame.payload)
|
||||
}
|
||||
|
||||
function tryExtractFrame(buffer) {
|
||||
const sofIndex = findSOF(buffer)
|
||||
if (sofIndex < 0) return null
|
||||
const headerLen = 7
|
||||
if (buffer.length < sofIndex + headerLen) return null
|
||||
const payloadLen = (buffer[sofIndex + 5] << 8) | buffer[sofIndex + 6]
|
||||
const totalLen = headerLen + payloadLen + 2
|
||||
if (buffer.length < sofIndex + totalLen) return null
|
||||
return {
|
||||
bytes: buffer.slice(sofIndex, sofIndex + totalLen),
|
||||
remaining: buffer.slice(sofIndex + totalLen),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12.3 典型交互流程
|
||||
|
||||
**连接初始化**
|
||||
|
||||
```
|
||||
1. 连接 BLE 设备
|
||||
2. 订阅 b2、b3、a1、a2 的 Notify(未订阅时设备不会发送)
|
||||
3. 等待 b3 推送设备信息 / 发送 CMD_QUERY_STATUS 获取完整状态
|
||||
4. 按需发送 CMD_START_CONTINUOUS
|
||||
```
|
||||
|
||||
**WiFi 配网**
|
||||
|
||||
```
|
||||
1. 发送 CMD_WIFI_SCAN → 收到 PROCESSING → 收到最终结果,展示列表
|
||||
2. 用户选择 SSID 并输入密码
|
||||
3. 发送 CMD_WIFI_CONFIG → 收到 PROCESSING → 收到 SUCCESS(含 IP)
|
||||
4. b3 推送 WIFI_CONNECTED
|
||||
```
|
||||
|
||||
**雷达数据监控**
|
||||
|
||||
```
|
||||
1. 发送 CMD_START_CONTINUOUS(interval=500ms)
|
||||
2. a1 持续推送 CMD_CONTINUOUS_PUSH
|
||||
3. a2 每 200ms 推送 CMD_RADAR_STATUS_PUSH
|
||||
4. 发送 CMD_STOP_CONTINUOUS 停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十三、数据解析注意事项
|
||||
|
||||
1. **心率/呼吸率**:`TLV_HEART_RATE_X10`、`TLV_BREATH_RATE_X10` 为 uint16,实际值 = 值 / 10.0。
|
||||
2. **波形**:`TLV_HEART_WAVEFORM`、`TLV_BREATH_WAVEFORM` 原始为 int8,线上为 `原始值 + 128`,解析时减 128。(当前 a1 未发送波形。)
|
||||
3. **坐标**:`TLV_POS_X_MM`/`Y`/`Z` 为 int16,单位 mm;米 = 值 / 1000.0。
|
||||
4. **设备序列号**:`TLV_DEVICE_SN` 固定 uint64,无 SN 时不发送,客户端不应依赖。
|
||||
5. **密码回包**:`CMD_GET_SAVED_WIFI` 的 `TLV_WIFI_ITEM` 含 `TLV_PASSWORD`,仅用于本地配网管理。
|
||||
6. **时间戳**:`TLV_TIMESTAMP` 为设备 `millis()`,非绝对时间。
|
||||
|
||||
---
|
||||
|
||||
## 十四、固件实现约束
|
||||
|
||||
1. 所有业务 Notify 必须通过 `sendFrameToBLE()` 发送,不允许直接 `setValue(整帧) + notify()`。
|
||||
2. `a1/a2/b2/b3` 均为 NOTIFY;`b1` 为 WRITE。
|
||||
3. 命令响应 `seq` 必须与请求一致;主动推送 `a1` 自增、`a2/b3` 为 `0`。
|
||||
4. `b1` 单次写入超过 256 字节时返回 `ERR_PROTO_FRAME_TOO_LARGE`;命令队列(10 深度)满时返回 `ERR_DEV_QUEUE_FULL`。
|
||||
5. 仅当客户端订阅了对应特征的 Notify 时才发送,避免无效通知。
|
||||
6. 所有多字节字段与 CRC 均为大端。
|
||||
7. 修改协议时需同步更新本文档(新增命令/字段须保持向后兼容)。
|
||||
|
||||
---
|
||||
|
||||
## 十五、版本历史
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 1.0 | 2026-06 | 初始版本,纯 TLV 协议,移除 legacy JSON 兼容 |
|
||||
| 2.0 | 2026-09 | 合并 `ble_protocol.md` 与 `ble_api_communication.md`;以代码为准补齐 `CMD_LED_CONTROL (0x30)`、`TLV_LED_ENABLED (0x37)`、LED 状态码、`CMD_QUERY_RADAR` 的 `TLV_TIMESTAMP`、`CMD_QUERY_STATUS` 的 `TLV_SSID/TLV_LED_ENABLED`、已保存 WiFi 含密码、删除 WiFi 回包字段,并修正 SEQ 语义 |
|
||||
Reference in New Issue
Block a user