纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。 Co-authored-by: Cursor <cursoragent@cursor.com>
215 lines
9.1 KiB
Markdown
215 lines
9.1 KiB
Markdown
# 蓝牙连接方案与流程
|
||
|
||
本文档说明车机端(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 的逻辑即可。
|