Files

215 lines
9.1 KiB
Markdown
Raw Permalink Normal View History

2026-10-13 21:54:50 +08:00
# 蓝牙连接方案与流程
本文档说明车机端(ESP32)蓝牙是如何建立连接、以及连接后的处理流程。基于 NimBLE 协议栈,作为 GATT 从机(Server),等待手机 APP 连接。
---
## 一、整体架构
- 协议栈:NimBLE(ESP-IDF 组件)
- 角色:BLE 从机(Peripheral / Server)
- 设备名:ESP32(宏 DEVICE_NAME)
- 主服务 UUID:0xFFFF
- 连接方式:可连接广播,手机端发起连接
---
## 二、系统启动与 BLE 初始化顺序
```
app_main()
├── 事件组创建 (system_event_group)
├── LCD / LVGL / SPIFFS / 动画任务 等初始化
└── ble_main() // 蓝牙入口
├── NVS 初始化、表情包存储加载
├── nimble_port_init() // NimBLE 端口初始化
├── ble_gatts_count_cfg(gatt_svr_svcs) // 统计 GATT 服务
├── ble_gatts_add_svcs(gatt_svr_svcs) // 注册 GATT 服务与特征
├── nimble_host_config() // 配置主机(reset_cb, sync_cb)
├── nimble_port_freertos_init(nimble_host_task) // 启动 NimBLE 主机任务
├── xTaskCreate(notification_task) // 通知发送任务
└── xTaskCreate(sentinel_task) // 哨兵模式任务
```
说明:GATT 服务在 `ble_main()` 里完成注册,NimBLE 主机在独立任务中运行;主机同步完成后会触发 `sync_cb`,再开始广播。
---
## 三、GATT 服务与特征定义
在 `ble.c` 中通过 `gatt_svr_svcs[]` 定义:
| 类型 | UUID | 说明 | 权限 / 属性 |
|----------|--------|--------------------------|-------------|
| 主服务 | 0xFFFF | 自定义主服务 | - |
| 特征 TX | 0xFF01 | 可读可写,APP 写、车机收 | READ, WRITE |
| 特征 RX | 0xFF02 | 只读,APP 读 | READ |
| 特征 NOTIFY | 0xFF03 | 通知,车机主动推给 APP | READ, NOTIFY, WRITE + CCCD(0x2902) |
- APP 下发指令:写 **TX (0xFF01)**,车机在 `gatt_svr_chr_access` 里收到后调用 `ble_data_ansy()` 解析协议。
- 车机上报数据:通过 **NOTIFY (0xFF03)** 发送协议帧;只有 APP 对 NOTIFY 特征的 CCCD 写入 0x0001 后,车机才能成功发通知。
---
## 四、广播与连接建立流程
### 4.1 主机同步完成后(ble_app_on_sync)
1. 推断本机地址类型:`ble_hs_id_infer_auto()`
2. 设置设备名:`ble_svc_gap_device_name_set("ESP32")`
3. 开始广播:`ble_app_advertise()`
### 4.2 广播内容(ble_app_advertise)
- 广播数据(Advertising Data):
- 设备名:ESP32(完整名)
- 标志:可发现、不支持 BR/EDR
- 16bit UUID:0xFFFF(完整列表)
- 扫描响应(Scan Response):发射功率(自动)
- 广播参数:
- 可连接、可发现(通用)
- 使用快速广播间隔(BLE_GAP_ADV_FAST_INTERVAL1)
- 信道:0x07(3 个广播信道)
- 超时:永久(BLE_HS_FOREVER)
- 连接与广播事件通过回调:`ble_gap_event()` 处理
### 4.3 连接建立(BLE_GAP_EVENT_CONNECT)
当手机 APP 发起连接且 status == 0 时:
1. 置 `is_connected = true`
2. 保存 `conn_handle = event->connect.conn_handle`
3. 置系统事件:`set_system_event(BLE_CONNECTED_BIT)`
4. 可选:`ble_gap_conn_find()` 打印连接间隔/延迟/超时
若 status != 0(连接失败),则重新调用 `ble_app_advertise()` 继续广播。
---
## 五、连接后的业务处理流程
### 5.1 事件传递链
```
BLE 连接成功 (ble_gap_event)
→ set_system_event(BLE_CONNECTED_BIT)
main_task (main.c) 中 wait_system_event(..., BLE_CONNECTED_BIT, ...)
→ 收到 BLE_CONNECTED_BIT
→ xTaskNotify(notification_task_handle, BLE_SEND_VERSION, eSetBits)
→ xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_USING, eSetBits)
```
即:连接成功只置位事件位;由主任务统一检测后,通过任务通知让“通知任务”发送“首次连接”的数据。
### 5.2 通知任务(notification_task)对 BLE_SEND_VERSION 的处理
当收到 BLE_SEND_VERSION 时,依次发送(协议帧,见《蓝牙指令测试文档》):
1. **get_system_ok 帧(2.1A)**:系统就绪
2. **brightness 帧(2.2A)**:当前亮度(默认 50,0–100)
3. **version 帧(2.3A)**:固件版本号
发送前会置 `notify_enabled = 1`,以便通过 `send_protocol_frame()` 走 NOTIFY 特征下发。若 APP 尚未订阅 NOTIFY,底层可能发送失败或 APP 收不到,通常建议 APP 在连接后尽快订阅 NOTIFY 再请求或等待上述三条。
### 5.3 APP 主动请求系统信息(get_sys_info 0x0010)
- APP 通过 **TX (0xFF01)** 下发 get_sys_info 指令。
- 车机在 `ble_data_ansy.c` 中解析到 `get_sys_info` 后,同样通过:
- `xTaskNotify(notification_task_handle, BLE_SEND_VERSION, eSetBits)`
- 通知任务再次发送上述 2.1A / 2.2A / 2.3A 三帧,实现“按需上报”版本与亮度。
### 5.4 订阅通知(BLE_GAP_EVENT_SUBSCRIBE)
- APP 对 NOTIFY 特征的 CCCD(0x2902) 写入 0x0001 时,会触发 BLE_GAP_EVENT_SUBSCRIBE。
- 代码中根据 `attr_handle == notify_char_handle` 更新:`notify_enabled = (cur_notify || cur_indicate)`。
- 此后所有通过 `send_protocol_frame()` 的下发(版本、亮度、OTA 进度、WiFi 状态等)才会真正通过 NOTIFY 推送到 APP。
---
## 六、连接断开与重连
### 6.1 断开(BLE_GAP_EVENT_DISCONNECT)
1. 置 `is_connected = false`、`notify_enabled = false`、`conn_handle = 0`
2. 根据 `event->disconnect.reason` 打日志(如认证失败、超时、用户断开等)
3. 再次调用 `ble_app_advertise()` 开始广播,等待下次连接
### 6.2 广播结束(BLE_GAP_EVENT_ADV_COMPLETE)
若当前未连接(`!is_connected`),会再次调用 `ble_app_advertise()`,保证持续可被发现、可连接。
---
## 七、流程简图(连接与首次上报)
```
[ 车机 ] [ 手机 APP ]
ble_main()
→ 注册 GATT (0xFFFF, TX/RX/NOTIFY)
→ 启动 NimBLE 主机任务
→ 创建 notification_task / sentinel_task
ble_app_on_sync()
→ 设置设备名 "ESP32"
→ ble_app_advertise() // 开始广播
------------ 手机扫描并发起连接 ------------
BLE_GAP_EVENT_CONNECT (status=0)
→ is_connected = true, conn_handle 保存
→ set_system_event(BLE_CONNECTED_BIT)
------------ 主任务事件循环 ------------
main_task: wait_system_event() 得到 BLE_CONNECTED_BIT
→ xTaskNotify(BLE_SEND_VERSION)
→ xTaskNotify(BLE_SEND_EMOJI_USING)
notification_task: 收到 BLE_SEND_VERSION
→ notify_enabled = 1
→ send get_system_ok 帧
→ send brightness 帧
→ send version 帧
(通过 NOTIFY 特征 0xFF03 发送,需 APP 已订阅 CCCD)
------------ 之后 ------------
APP 写 TX(0xFF01) 下发指令
→ gatt_svr_chr_access(W RITE) → ble_data_ansy()
→ 解析协议并执行(表情/亮度/日期/OTA/哨兵等)
车机需要上报时(OTA 进度、WiFi 状态等)
→ notification_task 收到对应 Notify 位
→ send_xxx_frame() → send_protocol_frame() → NOTIFY(0xFF03)
```
---
## 八、关键代码位置(便于对照)
| 流程环节 | 文件 | 函数/位置说明 |
|--------------------|-------------|----------------|
| BLE 入口 | main.c | app_main() 中调用 ble_main() |
| BLE 初始化与 GATT 注册 | ble.c | ble_main() |
| 主机同步与开始广播 | ble.c | ble_app_on_sync() → ble_app_advertise() |
| 广播参数与启动 | ble.c | ble_app_advertise(),ble_gap_adv_start(..., ble_gap_event, ...) |
| 连接/断开/订阅事件 | ble.c | ble_gap_event(),case BLE_GAP_EVENT_* |
| 连接成功置位事件 | ble.c | BLE_GAP_EVENT_CONNECT 内 set_system_event(BLE_CONNECTED_BIT) |
| 主任务处理连接事件 | main.c | main_task() 中 event_bits & BLE_CONNECTED_BIT → xTaskNotify(BLE_SEND_VERSION / BLE_SEND_EMOJI_USING) |
| 首次版本/亮度上报 | ble.c | notification_task() 中 bits & BLE_SEND_VERSION |
| APP 写 TX 收数据 | ble.c | gatt_svr_chr_access(),BLE_CHAR_UUID_TX 的 WRITE → ble_data_ansy() |
| get_sys_info 触发上报 | ble_data_ansy.c | get_sys_info 分支中 xTaskNotify(..., BLE_SEND_VERSION) |
| CCCD 订阅更新 | ble.c | gatt_svr_chr_access() 写 CCCD(0x2902),以及 BLE_GAP_EVENT_SUBSCRIBE 中更新 notify_enabled |
---
## 九、小结
- 车机作为 BLE 从机,上电后完成 NimBLE 与 GATT 注册,在主机同步完成后开始可连接广播。
- 手机连接后,通过事件位 BLE_CONNECTED_BIT 驱动主任务通知 notification_task,发送版本、亮度、get_system_ok;APP 也可通过 get_sys_info 指令再次请求同一套上报。
- 所有车机→APP 的协议帧均通过 NOTIFY 特征(0xFF03) 发送,依赖 APP 订阅 CCCD;APP→车机指令通过写 TX 特征(0xFF01) 下发,由 ble_data_ansy 解析并执行。
如需调整“连接后何时发版本/亮度”或“是否在订阅后再发”,只需修改 main_task 对 BLE_CONNECTED_BIT 的处理,或增加在 BLE_GAP_EVENT_SUBSCRIBE 中触发 BLE_SEND_VERSION 的逻辑即可。