# 蓝牙连接方案与流程 本文档说明车机端(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 的逻辑即可。