490 lines
14 KiB
Markdown
490 lines
14 KiB
Markdown
# 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 → 设备):**
|
||
```json
|
||
{"cmd":"xxx","id":1,"data":{...}}
|
||
```
|
||
|
||
**响应(设备 → App):**
|
||
```json
|
||
{"cmd":"xxx","id":1,"code":0,"data":{...}}
|
||
```
|
||
|
||
**主动推送(设备 → App,无 id):**
|
||
```json
|
||
{"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 → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"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** → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"cmd":"set_wifi","id":1,"code":0,"data":{"status":"connecting"}}
|
||
```
|
||
**大小:** ~60 bytes → 单包 ✅
|
||
|
||
---
|
||
|
||
### 5.2 wifi_list — 获取 WiFi 列表
|
||
|
||
**方向:** App → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"wifi_list","id":2}
|
||
```
|
||
**大小:** 24 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"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 → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"dev_info","id":3}
|
||
```
|
||
**大小:** 23 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"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 → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"status","id":4}
|
||
```
|
||
**大小:** 21 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"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 → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"set_vol","id":5,"data":{"vol":80}}
|
||
```
|
||
|
||
| 字段 | 类型 | 范围 | 说明 |
|
||
|------|------|------|------|
|
||
| vol | number | 0~100 | 音量百分比 |
|
||
|
||
**最大请求大小:** 37 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"cmd":"set_vol","id":5,"code":0}
|
||
```
|
||
**大小:** 30 bytes → 单包 ✅
|
||
|
||
---
|
||
|
||
### 5.6 iot — 控制 IoT 设备属性
|
||
|
||
**方向:** App → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"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 → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"cmd":"iot","id":6,"code":0}
|
||
```
|
||
**大小:** 27 bytes → 单包 ✅
|
||
|
||
---
|
||
|
||
### 5.7 reboot — 重启设备
|
||
|
||
**方向:** App → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"reboot","id":7}
|
||
```
|
||
**大小:** 22 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"cmd":"reboot","id":7,"code":0}
|
||
```
|
||
**大小:** 29 bytes → 单包 ✅
|
||
|
||
---
|
||
|
||
### 5.8 ota — 检查固件更新
|
||
|
||
**方向:** App → 设备
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"ota","id":8}
|
||
```
|
||
**大小:** 19 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"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 — 心跳保活
|
||
|
||
**方向:** 双向
|
||
|
||
**请求:**
|
||
```json
|
||
{"cmd":"ping","id":9}
|
||
```
|
||
**大小:** 20 bytes → 单包 ✅
|
||
|
||
**响应:**
|
||
```json
|
||
{"cmd":"ping","id":9,"code":0}
|
||
```
|
||
**大小:** 28 bytes → 单包 ✅
|
||
|
||
---
|
||
|
||
### 5.10 event — 设备主动推送
|
||
|
||
**方向:** 设备 → App(通过 NOTIFY,无 id)
|
||
|
||
**WiFi 连接成功:**
|
||
```json
|
||
{"cmd":"event","data":{"type":"wifi_connected","ssid":"Home","ip":"192.168.1.100","rssi":-40}}
|
||
```
|
||
**最大大小:** ~125 bytes → 单包 ✅
|
||
|
||
**WiFi 断开:**
|
||
```json
|
||
{"cmd":"event","data":{"type":"wifi_disconnected","reason":201}}
|
||
```
|
||
**大小:** ~58 bytes → 单包 ✅
|
||
|
||
**电池低电量:**
|
||
```json
|
||
{"cmd":"event","data":{"type":"low_battery","bat":10}}
|
||
```
|
||
**大小:** ~51 bytes → 单包 ✅
|
||
|
||
**设备状态变化:**
|
||
```json
|
||
{"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 | 内部错误 | 设备内部异常 |
|
||
|
||
**错误响应示例:**
|
||
```json
|
||
{"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 队列转发到应用任务处理
|