29 KiB
蓝牙数据传输协议与 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) |
主动推送数据与状态,不做请求匹配 |
协议特点
- 统一帧格式
AA 55 | VER | CMD | SEQ | LEN | PAYLOAD | CRC,二进制紧凑,适合嵌入式设备。 - 命令与数据分离:命令走
b1/b2一问一答,数据与状态走a1/a2/b3主动推送。 - 结果以
TLV_RESULT_CODE判定,错误码/状态码的文案由客户端本地映射。 - 支持 Notify 分包与客户端重组(当前固定 20 字节一包)。
- 状态推送去重:仅在状态变化时经
b3发送。 - 配网等耗时命令采用"先
PROCESSING、后最终结果"的异步响应模型。
当前实现参数
| 项目 | 值 |
|---|---|
| 协议版本 | 0x01 |
| 帧头 | 0xAA 0x55 |
| 校验 | CRC16-CCITT(覆盖 VERSION ~ PAYLOAD,大端) |
| Notify 分包 | 固定 20 字节 |
| 命令接收缓冲 | 256 字节(超限返回 ERR_PROTO_FRAME_TOO_LARGE) |
| 默认连续推送间隔 | 500 ms(可通过 TLV_INTERVAL_MS 设置为 100~10000 ms) |
一、设计原则
- 客户端必须先按 GATT 特征 UUID 分流,再按
CMD命令码解析。 b1/b2是命令请求/响应通道:客户端写b1,设备通过b2Notify 响应。a1/a2/b3是主动推送通道,不参与请求响应匹配。- Notify 单包可能小于整帧,客户端必须按特征分别维护重组 buffer。
- 命令结果只以
TLV_RESULT_CODE为事实来源,错误文案由客户端按错误码本地映射。 - 运行状态变化通过
b3推送,与命令结果解耦。 - 协议层不再使用
flags、ACK、TLV_STATE、TLV_STEP、TLV_MESSAGE、TLV_ERROR_MESSAGE。 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 基本原则
- 先按特征 UUID 分流,再按
cmd解析。 - 每个 Notify 特征维护独立重组 buffer。
- 只在
b2上按seq匹配请求响应。 a1/a2/b3不做请求响应匹配。- 所有结果判断以
TLV_RESULT_CODE为准,文案本地映射。
12.2 伪代码
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 停止
十三、数据解析注意事项
- 心率/呼吸率:
TLV_HEART_RATE_X10、TLV_BREATH_RATE_X10为 uint16,实际值 = 值 / 10.0。 - 波形:
TLV_HEART_WAVEFORM、TLV_BREATH_WAVEFORM原始为 int8,线上为原始值 + 128,解析时减 128。(当前 a1 未发送波形。) - 坐标:
TLV_POS_X_MM/Y/Z为 int16,单位 mm;米 = 值 / 1000.0。 - 设备序列号:
TLV_DEVICE_SN固定 uint64,无 SN 时不发送,客户端不应依赖。 - 密码回包:
CMD_GET_SAVED_WIFI的TLV_WIFI_ITEM含TLV_PASSWORD,仅用于本地配网管理。 - 时间戳:
TLV_TIMESTAMP为设备millis(),非绝对时间。
十四、固件实现约束
- 所有业务 Notify 必须通过
sendFrameToBLE()发送,不允许直接setValue(整帧) + notify()。 a1/a2/b2/b3均为 NOTIFY;b1为 WRITE。- 命令响应
seq必须与请求一致;主动推送a1自增、a2/b3为0。 b1单次写入超过 256 字节时返回ERR_PROTO_FRAME_TOO_LARGE;命令队列(10 深度)满时返回ERR_DEV_QUEUE_FULL。- 仅当客户端订阅了对应特征的 Notify 时才发送,避免无效通知。
- 所有多字节字段与 CRC 均为大端。
- 修改协议时需同步更新本文档(新增命令/字段须保持向后兼容)。
十五、版本历史
| 版本 | 日期 | 说明 |
|---|---|---|
| 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 语义 |