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