14 KiB
BLE JSON 通讯模块开发计划
新增功能,保留原有 BluFi 配网等全部功能不变。
一、功能定位
在现有 BluFi 蓝牙配网基础上,新增一个自定义 BLE GATT Service,使用 JSON 格式进行设备与 App 之间的双向通讯。
- BluFi 配网模块(
bluetooth_provisioning.*):完全保留,不做任何修改 - 新 BLE JSON 通讯模块(
ble_service.*):独立注册 GATTS App,与 BluFi 共存
共存原理
Bluedroid GATTS 栈(已配置 max_profiles=8)
├── App 0: BluFi Service (UUID: 0xFFFF) ← 已有,不动
└── App 1: JSON Service (UUID: 0xAB00) ← 新增
两个 App 独立回调、独立 handle,共享同一个 Bluedroid 栈和 BLE 连接。
二、底层传输参数约束
| 参数 | 值 | 说明 |
|---|---|---|
| 默认 MTU | 23 bytes | BLE 标准默认值 |
| 协商目标 MTU | 512 bytes | esp_ble_gatt_set_local_mtu(512) |
| ATT 协议头开销 | 3 bytes | 固定开销 |
| 单包最大有效载荷 | 509 bytes | 512 - 3 |
| 广播包最大长度 | 31 + 31 bytes | ADV + SCAN RSP |
| BLE 协议版本 | 4.2 | BLE 5.0 已关闭(BluFi 兼容) |
| 最大连接数 | 4 (CONFIG_BT_ACL_CONNECTIONS) | 当前 BluFi 占 1 |
三、GATT Service 设计
Custom JSON Communication Service
│
├── Service UUID: 0xAB00 (Primary Service)
│
├── Characteristic 1: JSON_WRITE (App → 设备)
│ ├── UUID: 0xAB01
│ ├── Properties: WRITE
│ ├── Permissions: ESP_GATT_PERM_WRITE
│ └── Max Value: 512 bytes
│
├── Characteristic 2: JSON_NOTIFY (设备 → App)
│ ├── UUID: 0xAB02
│ ├── Properties: NOTIFY | READ
│ ├── Permissions: ESP_GATT_PERM_READ
│ ├── Max Value: 512 bytes
│ └── Descriptor: CCCD (0x2902, 2 bytes, 用于开启/关闭 NOTIFY)
│
└── Characteristic 3: JSON_STATUS (设备状态被动读取,可选)
├── UUID: 0xAB03
├── Properties: READ
├── Permissions: ESP_GATT_PERM_READ
└── Max Value: 512 bytes
四、JSON 消息格式
4.1 公共格式
请求(App → 设备):
{"cmd":"xxx","id":1,"data":{...}}
响应(设备 → App):
{"cmd":"xxx","id":1,"code":0,"data":{...}}
主动推送(设备 → App,无 id):
{"cmd":"event","data":{"type":"xxx",...}}
4.2 固定开销
| 字段 | 占用 | 说明 |
|---|---|---|
{"cmd":""} |
10 bytes | 命令名空壳 |
,"id":1 |
7 bytes | 消息 ID(1~999) |
,"data":{} |
10 bytes | 数据域空壳 |
,"code":0 |
9 bytes | 响应码(仅响应) |
| 请求固定开销 | ~27 bytes | 留给 data 约 482 bytes |
| 响应固定开销 | ~36 bytes | 留给 data 约 473 bytes |
五、逐条命令参数与大小计算
5.1 set_wifi — WiFi 配置
方向: App → 设备
请求:
{"cmd":"set_wifi","id":1,"data":{"ssid":"MyHomeWiFi_5G","pass":"myP@ssw0rd123"}}
| 字段 | 类型 | 最大长度 | 来源 |
|---|---|---|---|
| ssid | string | 32 bytes | IEEE 802.11 标准 |
| pass | string | 64 bytes | WPA2 标准 |
最大请求大小: 55(框架) + 32(ssid) + 64(pass) = 151 bytes → 单包 ✅
响应:
{"cmd":"set_wifi","id":1,"code":0,"data":{"status":"connecting"}}
大小: ~60 bytes → 单包 ✅
5.2 wifi_list — 获取 WiFi 列表
方向: App → 设备
请求:
{"cmd":"wifi_list","id":2}
大小: 24 bytes → 单包 ✅
响应:
{"cmd":"wifi_list","id":2,"code":0,"data":{"list":[{"s":"MyWiFi","r":-40},{"s":"Office","r":-55}]}}
| 字段 | 类型 | 最大长度 | 说明 |
|---|---|---|---|
| s | string | 32 bytes | SSID 名称 |
| r | number | 4 bytes | RSSI 值 (-100~0) |
单条记录最大: ~46 bytes(SSID 32字符时)
容量计算(可用空间 = 509 - 58 = 451 bytes):
| 场景 | 每条大小 | 单包可容纳 |
|---|---|---|
| 典型(SSID ~10字符) | ~25 bytes | ~17 条 |
| 最坏(SSID 32字符) | ~46 bytes | ~9 条 |
| 设计限制 | — | 最多返回 8 条 |
最大响应大小: 58(框架) + 8 × 46 = 426 bytes → 单包 ✅
5.3 dev_info — 获取设备信息
方向: App → 设备
请求:
{"cmd":"dev_info","id":3}
大小: 23 bytes → 单包 ✅
响应:
{"cmd":"dev_info","id":3,"code":0,"data":{"model":"Kapi_Rtc","fw":"1.0.0","mac":"AA:BB:CC:DD:EE:FF","board":"movecall-moji-esp32s3","uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}
| 字段 | 类型 | 最大长度 | 说明 |
|---|---|---|---|
| model | string | 20 bytes | 产品型号 |
| fw | string | 12 bytes | 固件版本 x.x.x |
| mac | string | 17 bytes | MAC 地址 AA:BB:CC:DD:EE:FF |
| board | string | 30 bytes | 板型名称 |
| uuid | string | 36 bytes | 设备 UUID |
最大响应大小: ~180 bytes → 单包 ✅
5.4 status — 获取设备运行状态
方向: App → 设备
请求:
{"cmd":"status","id":4}
大小: 21 bytes → 单包 ✅
响应:
{"cmd":"status","id":4,"code":0,"data":{"state":"idle","bat":85,"chg":false,"rssi":-45,"vol":70}}
| 字段 | 类型 | 最大长度 | 说明 |
|---|---|---|---|
| state | string | 11 bytes | 设备状态(见状态枚举表) |
| bat | number | 3 bytes | 电池电量 0~100 |
| chg | boolean | 5 bytes | 是否充电中 |
| rssi | number | 4 bytes | WiFi 信号 -100~0 |
| vol | number | 3 bytes | 音量 0~100 |
设备状态枚举对照:
| DeviceState 枚举 | JSON 值 | 字节数 |
|---|---|---|
| kDeviceStateUnknown | "unknown" |
7 |
| kDeviceStateStarting | "starting" |
8 |
| kDeviceStateWifiConfiguring | "wifi_config" |
11 |
| kDeviceStateIdle | "idle" |
4 |
| kDeviceStateConnecting | "connecting" |
10 |
| kDeviceStateListening | "listening" |
9 |
| kDeviceStateSpeaking | "speaking" |
8 |
| kDeviceStateDialog | "dialog" |
6 |
| kDeviceStateUpgrading | "upgrading" |
9 |
| kDeviceStateActivating | "activating" |
10 |
| kDeviceStateFatalError | "error" |
5 |
最大响应大小: ~105 bytes → 单包 ✅
5.5 set_vol — 设置音量
方向: App → 设备
请求:
{"cmd":"set_vol","id":5,"data":{"vol":80}}
| 字段 | 类型 | 范围 | 说明 |
|---|---|---|---|
| vol | number | 0~100 | 音量百分比 |
最大请求大小: 37 bytes → 单包 ✅
响应:
{"cmd":"set_vol","id":5,"code":0}
大小: 30 bytes → 单包 ✅
5.6 iot — 控制 IoT 设备属性
方向: App → 设备
请求:
{"cmd":"iot","id":6,"data":{"thing":"lamp","prop":"brightness","val":50}}
| 字段 | 类型 | 最大长度 | 说明 |
|---|---|---|---|
| thing | string | 20 bytes | IoT 设备名 (speaker/lamp/screen 等) |
| prop | string | 20 bytes | 属性名 |
| val | number/string/bool | 20 bytes | 属性值 |
最大请求大小: ~95 bytes → 单包 ✅
响应:
{"cmd":"iot","id":6,"code":0}
大小: 27 bytes → 单包 ✅
5.7 reboot — 重启设备
方向: App → 设备
请求:
{"cmd":"reboot","id":7}
大小: 22 bytes → 单包 ✅
响应:
{"cmd":"reboot","id":7,"code":0}
大小: 29 bytes → 单包 ✅
5.8 ota — 检查固件更新
方向: App → 设备
请求:
{"cmd":"ota","id":8}
大小: 19 bytes → 单包 ✅
响应:
{"cmd":"ota","id":8,"code":0,"data":{"cur":"1.0.0","new":"1.1.0","has_update":true}}
| 字段 | 类型 | 最大长度 | 说明 |
|---|---|---|---|
| cur | string | 12 bytes | 当前版本 |
| new | string | 12 bytes | 最新版本 |
| has_update | boolean | 5 bytes | 是否有更新 |
最大响应大小: ~78 bytes → 单包 ✅
5.9 ping — 心跳保活
方向: 双向
请求:
{"cmd":"ping","id":9}
大小: 20 bytes → 单包 ✅
响应:
{"cmd":"ping","id":9,"code":0}
大小: 28 bytes → 单包 ✅
5.10 event — 设备主动推送
方向: 设备 → App(通过 NOTIFY,无 id)
WiFi 连接成功:
{"cmd":"event","data":{"type":"wifi_connected","ssid":"Home","ip":"192.168.1.100","rssi":-40}}
最大大小: ~125 bytes → 单包 ✅
WiFi 断开:
{"cmd":"event","data":{"type":"wifi_disconnected","reason":201}}
大小: ~58 bytes → 单包 ✅
电池低电量:
{"cmd":"event","data":{"type":"low_battery","bat":10}}
大小: ~51 bytes → 单包 ✅
设备状态变化:
{"cmd":"event","data":{"type":"state_changed","state":"listening"}}
大小: ~62 bytes → 单包 ✅
六、总览表
| # | 命令 | 方向 | 请求最大 | 响应最大 | 单包? |
|---|---|---|---|---|---|
| 1 | set_wifi |
App→设备 | 151 B | 60 B | ✅ |
| 2 | wifi_list |
App→设备 | 24 B | 426 B (限8条) | ✅ |
| 3 | dev_info |
App→设备 | 23 B | 180 B | ✅ |
| 4 | status |
App→设备 | 21 B | 105 B | ✅ |
| 5 | set_vol |
App→设备 | 37 B | 30 B | ✅ |
| 6 | iot |
App→设备 | 95 B | 27 B | ✅ |
| 7 | reboot |
App→设备 | 22 B | 29 B | ✅ |
| 8 | ota |
App→设备 | 19 B | 78 B | ✅ |
| 9 | ping |
双向 | 20 B | 28 B | ✅ |
| 10 | event |
设备→App | — | ≤125 B | ✅ |
结论:MTU=512 时,所有命令均可单包传输,不需要分包机制。
七、错误码定义
| code | 含义 | 示例场景 |
|---|---|---|
| 0 | 成功 | 所有正常响应 |
| 1 | 参数错误 | JSON 格式错误 / 缺少必要字段 |
| 2 | 命令不支持 | 未知的 cmd |
| 3 | 设备忙 | 正在 OTA / 正在配网 |
| 4 | WiFi 连接失败 | SSID 不存在 / 密码错误 |
| 5 | 超时 | 操作超时 |
| 6 | 内部错误 | 设备内部异常 |
错误响应示例:
{"cmd":"set_wifi","id":1,"code":4,"msg":"wrong password"}
八、通讯时序示例
场景:App 配置 WiFi
App 设备
│ │
│──── BLE Connect ──────────────────────────>│
│<─── MTU Exchange (512) ───────────────────>│
│──── Enable NOTIFY (write CCCD=0x0001) ───>│
│ │
│──── WRITE: {"cmd":"status","id":1} │
│<─── NOTIFY: {"cmd":"status","id":1, │
│ "code":0,"data":{"state":"idle", │
│ "bat":85,"chg":false,"rssi":0, │
│ "vol":70}} │
│ │
│──── WRITE: {"cmd":"wifi_list","id":2} │
│ [设备扫描WiFi]
│<─── NOTIFY: {"cmd":"wifi_list","id":2, │
│ "code":0,"data":{"list":[ │
│ {"s":"Home","r":-40}, │
│ {"s":"Office","r":-55}]}} │
│ │
│──── WRITE: {"cmd":"set_wifi","id":3, │
│ "data":{"ssid":"Home", │
│ "pass":"123456"}} │
│<─── NOTIFY: {"cmd":"set_wifi","id":3, │
│ "code":0,"data":{ │
│ "status":"connecting"}} │
│ [设备连接WiFi]
│<─── NOTIFY: {"cmd":"event","data": │
│ {"type":"wifi_connected", │
│ "ssid":"Home", │
│ "ip":"192.168.1.100","rssi":-40}} │
│ │
九、实现文件清单
| 文件 | 动作 | 说明 |
|---|---|---|
main/ble_service.h |
新增 | BleJsonService 类定义 |
main/ble_service.cc |
新增 | GATT Server 实现 + JSON 收发 |
main/ble_service_config.h |
新增 | UUID / MTU / 超时等配置宏 |
main/application.cc |
修改 | 集成 BleJsonService,注册命令处理回调 |
main/application.h |
修改 | 添加 BleJsonService 成员指针 |
main/CMakeLists.txt |
修改 | 添加 ble_service.cc 到编译列表 |
sdkconfig.defaults |
可能修改 | 若需调整 GATT profile 数量 |
bluetooth_provisioning.* |
不动 | BluFi 配网保持原样 |
bluetooth_provisioning_config.h |
不动 | BluFi 配置保持原样 |
十、备用分包协议(当前不需要实现)
若未来某条消息超过 509 字节,可启用以下分包协议:
分包头 (2 bytes):
Byte 0: [7:4] 总包数(1~15), [3:0] 当前包号(0~14)
Byte 1: 0x00=中间包, 0x01=最后一包
Byte 2~N: JSON 片段
单包判断: 首字节为 '{' (0x7B) → 完整 JSON,无分包头
首字节非 '{' → 分包数据,需重组
当前设计下 WiFi 列表限制 8 条,所有消息均 ≤509 字节,无需实现分包。
十一、依赖与约束
- JSON 库: cJSON(项目已有,无需引入新依赖)
- BLE 栈: Bluedroid(已启用,与 BluFi 共用)
- GATT 资源: max_profiles=8(BluFi 占 1,新模块占 1,富余 6 个)
- 内存: 8MB PSRAM + 320KB DRAM(JSON 解析开销可忽略)
- 输出格式: 使用
cJSON_PrintUnformatted()紧凑输出,无空格无换行 - BLE 回调线程安全: GATTS 回调中不直接解析 JSON,通过 FreeRTOS 队列转发到应用任务处理