# 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 队列转发到应用任务处理