Files
blinky/docs/ble_multi_slot_design.md
skiinder 54c5f76c84 feat: 添加蓝牙多槽位绑定支持模块
- 新增 ble_bond_multi_module.c 实现多槽位蓝牙绑定管理功能
- 添加 ble_bond_multi_event 事件系统支持槽位状态广播
- 在 CMakeLists.txt 中注册新模块和事件源文件
- 更新 Kconfig 配置添加 BLINKY_BLE_BOND_MULTI 选项
- 修改 prj.conf 配置支持 4 个配对设备和 5 个身份标识
- 关闭默认 CAF ble_bond 模块使用自定义实现
- 更新 ui_settings_controller.h 接口支持槽位元数据设置
- 在 display_module.c 中添加事件订阅刷新UI显示
- 编写详细的设计文档 ble_multi_slot_design.md
2026-04-25 15:40:49 +08:00

583 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Blinky 多槽蓝牙设计方案
## 1. 目标与约束
`C:\projects\blinky` 增加多槽蓝牙能力,当前阶段只实现 BLE多 dongle 方案先预留结构,不实现完整业务流程。
本方案已经按当前讨论结论收敛为以下硬约束:
- 支持 3 个 BLE 槽位 `Slot 1~3`
- 每个槽位固定对应一个 Bluetooth local identity
- 槽位切换本质上是切换 `bt_identity_id`
- 不引入 `app_slot_id`
- 不使用临时 identity
- 不做 erase advertising
- 不做擦除确认
- 不做擦除回滚
- 擦除当前槽位时直接删除该 identity 上的 bond
- settings 只持久化 `current_slot` 和各槽位 `meta`
- `slot_meta` 中必须保留 `display_name`,用于 UI 显示
- `24G / dongle` 采用方案 A但本阶段只预留专用槽位
## 2. 本地参考基线
本方案主要参考本地 NCS 3.2.3 中以下实现和文档:
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\ble_bond.rst`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\ble_bond.c`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\Kconfig.ble_bond`
- `c:\ncs\v3.2.3\nrf\include\caf\events\ble_common_event.h`
- `c:\ncs\v3.2.3\nrf\subsys\caf\modules\ble_adv.c`
- `c:\ncs\v3.2.3\nrf\subsys\caf\modules\Kconfig.ble_state`
- `c:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state.rst`
参考方式如下:
- 继承 `nrf_desktop` 的总体方向:应用自定义 bond 管理模块,而不是 CAF 默认 `ble_bond`
- 继续通过 `ble_peer_operation_event` 驱动 CAF `ble_adv`
- 保留 dongle 专用 identity 的规划方式
明确不采用 `nrf_desktop` 的部分:
- 不使用 `app_slot_id <-> bt_identity_id` 的二级映射
- 不使用临时 identity
- 不采用 `ERASE_ADV`
- 不采用“新配对成功后再替换旧槽位”的回滚保护机制
## 3. 对现有项目的判断
`blinky` 当前已经具备多槽蓝牙需要的大部分基础能力:
- 已启用 `CONFIG_CAF_BLE_STATE`
- 已启用 `CONFIG_CAF_BLE_ADV`
- 已启用 `CONFIG_BT_SETTINGS`
- HID 和 NUS 都已基于 `ble_peer_event` 跟踪当前连接
- UI 已经预留 3 个蓝牙槽位和擦除当前槽位入口
- 模式策略层已经区分:
- `MODE_SWITCH_BLE -> BLE_PROFILE_POLICY_GENERAL`
- `MODE_SWITCH_24G -> BLE_PROFILE_POLICY_DONGLE`
相关文件:
- `C:\projects\blinky\src\ble_hid_module.c`
- `C:\projects\blinky\src\ble_nus_module.c`
- `C:\projects\blinky\src\ui\ui_settings_ble.c`
- `C:\projects\blinky\src\ui\ui_settings_controller.c`
- `C:\projects\blinky\src\mode_policy_module.c`
- `C:\projects\blinky\prj.conf`
当前限制也很明确:
- `CONFIG_BT_MAX_PAIRED=1`,全设备只能保存 1 个 bond
- 当前是单槽逻辑
- 还在使用 CAF 默认 `CONFIG_CAF_BLE_BOND=y`
- UI 的槽位状态仍是内存假数据
## 4. 配置语义结论
基于本地 Kconfig 和 CAF 文档,关键配置的含义如下:
- `CONFIG_BT_MAX_PAIRED`
说明:整个设备允许保存的 bond 总数
- `CONFIG_CAF_BLE_STATE_MAX_LOCAL_ID_BONDS`
说明:每个 Bluetooth local identity 允许的 bond 数上限
- `CONFIG_BT_ID_MAX`
说明:设备允许使用的 local identity 总数
如果目标是:
- 3 个 BLE 槽位
- 每槽 1 个 bond
- 预留 1 个 dongle 槽位
那么推荐配置应为:
```conf
CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=4
CONFIG_CAF_BLE_STATE_MAX_LOCAL_ID_BONDS=1
CONFIG_BT_ID_MAX=5
```
语义如下:
- `BT_MAX_CONN=1`
说明:同时只保持 1 条 BLE 连接
- `BT_MAX_PAIRED=4`
说明:全设备最多保存 4 个 bond
- `CAF_BLE_STATE_MAX_LOCAL_ID_BONDS=1`
说明:每个 identity 只允许绑定 1 个主机
- `BT_ID_MAX=5`
说明:可用 identity 为 `0..4`
配合本方案的 identity 规划:
- `0`:默认 identity不参与槽位
- `1`Slot 1
- `2`Slot 2
- `3`Slot 3
- `4`Dongle Slot 预留
## 5. 总体架构
## 5.1 设计结论
建议采用:
- 保留 `CAF_BLE_STATE`
- 保留 `CAF_BLE_ADV`
- 关闭 CAF 默认 `BLE_BOND`
- 新增应用自定义模块 `ble_bond_multi_module`
即:
- `CONFIG_CAF_BLE_BOND=n`
- 新增 `CONFIG_BLINKY_BLE_BOND_MULTI`
- 自定义模块 `select CAF_BLE_BOND_SUPPORTED`
- 由自定义模块向 CAF 提交 `ble_peer_operation_event`
原因:
- CAF 默认 `ble_bond` 不适合固定多 identity 槽位管理
- 你的需求已经明确不需要 `nrf_desktop` 那种复杂映射和回滚流程
- 自定义模块更适合和现有 UI、模式策略、settings 同步
## 5.2 核心模型
本方案直接把“槽位”和“identity”绑定
- `Slot 1` = `bt_identity_id 1`
- `Slot 2` = `bt_identity_id 2`
- `Slot 3` = `bt_identity_id 3`
- `Dongle Slot` = `bt_identity_id 4`
所以:
- `current_slot` 实际上就是当前使用的 `bt_identity_id`
- UI 显示“Slot 1/2/3”时后端直接映射到固定 identity
- 不再存在可变的 `id_lut`
这个设计的优点:
- 逻辑简单
- settings 简单
- 调试简单
- 后续接入 dongle 不需要迁移已有 BLE 槽位
## 6. identity 规划
建议固定如下:
| identity | 角色 | 当前阶段 |
| --- | --- | --- |
| `0` | 默认 identity | 不使用 |
| `1` | BLE Slot 1 | 使用 |
| `2` | BLE Slot 2 | 使用 |
| `3` | BLE Slot 3 | 使用 |
| `4` | Dongle Slot | 预留 |
设计原则:
- 不使用 `BT_ID_DEFAULT`
原因:默认 identity 的 reset/unpair 行为不适合作为固定槽位基线
- 3 个 BLE 槽位 identity 固定不变
- `identity 4` 只给 `BLE_PROFILE_POLICY_DONGLE`
## 7. 模块设计
## 7.1 新增模块和文件
建议新增:
- `src/ble_bond_multi_module.c`
- `src/events/ble_bond_multi_event.c`
- `inc/events/ble_bond_multi_event.h`
不建议新增:
- `inc/ble_bond_multi.h`
原因:
- 当前设计是事件驱动模块
- 没有必要向外暴露一组直接调用 API
- 共享内容更适合放在事件头文件里
只有在后续出现明确的跨模块直接调用需求时,才需要再补 `inc/ble_bond_multi.h`
## 7.2 模块职责边界
### `ble_bond_multi_module`
负责:
- 管理当前活动 `bt_identity_id`
- 启动时恢复 `current_slot`
- 维护各 identity 槽位的 `slot_meta`
- 处理“切换当前槽位”
- 处理“直接擦除当前槽位”
- 监听连接、加密、断链事件,更新 `slot_meta`
- 向 CAF 提交 `ble_peer_operation_event`
- 向 UI / 显示层广播槽位状态
不负责:
- 发 HID/NUS 数据
- 改 GATT 服务
- 绘制 UI
### `ble_hid_module` / `ble_nus_module`
保持现有思路,不理解“多槽逻辑”,只跟随当前活动连接:
- 连接上谁,就服务谁
- 切槽或擦除造成断链时,内部复位连接状态
### `mode_policy_module`
负责决定当前是:
- `BLE_PROFILE_POLICY_GENERAL`
- `BLE_PROFILE_POLICY_DONGLE`
但不负责槽位 bond 细节。
## 8. 状态机设计
第一阶段建议极简化,只保留:
- `DISABLED`
- `IDLE`
- `SWITCHING`
- `ERASING`
明确不引入:
- `SELECT_SLOT`
- `ERASE_PENDING`
- `ERASE_ADV`
- `TEMP_IDENTITY`
- `ROLLBACK`
## 8.1 切槽流程
1. 用户在 UI 选择 `Slot N`
2. 模块判断:
- 如果 `N == current_slot`,直接返回
- 否则进入 `SWITCHING`
3. 提交 `PEER_OPERATION_SELECTED`
4. CAF `ble_adv` 切换当前 advertising identity
5. 当前连接断开
6. 设备以新 identity 重新广播
7. 目标主机重新连接
8. 保存新的 `current_slot`
9. 返回 `IDLE`
这里采用“直接切换”,不做预选确认。
## 8.2 擦除当前槽位流程
1. 用户选择“Erase Bond”
2. 模块进入 `ERASING`
3. 对当前 `bt_identity_id` 执行:
- `bt_unpair(identity, BT_ADDR_LE_ANY)`
4. 清除该槽位 `slot_meta`
5. 重新以当前 identity 进入可配对广播
6. 返回 `IDLE`
这里采用产品级简化语义:
- 不确认
- 不保护旧配对
- 不回滚
- 擦除后旧主机立即失效
## 9. Settings 设计
## 9.1 持久化范围
只持久化:
- `current_slot`
- 各槽位 `slot_meta`
不持久化:
- `id_lut`
- 临时 identity 状态
- 擦除中间态
## 9.2 settings namespace
建议采用:
- `ble_multi/current_slot`
- `ble_multi/meta/1`
- `ble_multi/meta/2`
- `ble_multi/meta/3`
- `ble_multi/meta/4`
其中:
- `1/2/3` 是 BLE 槽位
- `4` 是 dongle 预留槽位
## 9.3 slot_meta 结构
`slot_meta` 建议固定包含以下字段:
- `occupied`
- `last_peer_addr`
- `display_name`
字段说明:
- `occupied`
说明:该槽位当前是否有有效 bond
- `last_peer_addr`
说明:最近一次绑定主机的 BLE 地址,用于识别和调试
- `display_name`
说明UI 显示名,不做简化,作为正式字段保留
这里 `display_name` 是明确保留项,不是可选优化项。
设计目的就是让 UI 能直接显示:
- `MacBook Pro`
- `iPad Mini`
- `Windows-PC`
而不是只能显示 `Bonded` / `Empty`
## 9.4 display_name 来源
需要注意一点:
- Zephyr bond 本身不会自动提供“友好主机名”
因此 `display_name` 的来源需要在实现阶段明确,一般有两个方向:
1. 配对后由应用层通过协议或自定义方式写入
2. 若当前阶段无法拿到真实主机名,则先允许 UI/上位机为该槽位写入名称
也就是说:
- `display_name` 必须作为正式持久化字段存在
- 但其填充机制可以分阶段实现
第一阶段如果没有自动名称来源,建议默认值如下:
- 有 bond 但无名称:`"Bonded Device"`
- 无 bond空串或 `"Empty"`
## 10. UI 设计
现有 UI 结构已经适合复用,不建议重做。
现有文件:
- `C:\projects\blinky\src\ui\ui_settings_ble.c`
- `C:\projects\blinky\src\ui\ui_settings_controller.c`
建议交互规则如下:
- 进入 BLE 设置页后显示 `Slot 1~3`
- 每个槽位 value 直接显示 `display_name`
- 若该槽位无 bond则显示 `Empty`
- 当前槽位显示为 `Slot N`
- 擦除项显示为 `Erase Slot N`
操作方式:
- 选中某个槽位后直接切换
- 选中擦除项后直接擦除
- 不加确认页
UI 需要的数据来源:
- `current_slot`
- `slot_meta[1]`
- `slot_meta[2]`
- `slot_meta[3]`
所以建议 UI 不再维护本地假数据,而改为订阅 `ble_bond_multi_event` 或读取统一状态缓存。
## 11. 与模式切换的兼容设计
## 11.1 BLE 档
`MODE_SWITCH_BLE` 下:
- 只允许使用 `identity 1/2/3`
- UI 允许切槽和擦除
- `current_slot` 必须属于 `1..3`
## 11.2 24G / dongle 档
采用方案 A
- `MODE_SWITCH_24G` 绑定专用 `identity 4`
- `identity 4` 不与普通 BLE 槽位混用
- 第一阶段只预留,不实现完整 dongle bond 生命周期
这样做的好处:
- 保持 `mode_policy_module.c` 现有设计语义
- 普通 BLE 槽位和 dongle 配对状态不会互相污染
- 后续接入 dongle 时无需改动 BLE 三槽数据模型
## 12. 事件设计
建议新增应用事件:
- `ble_bond_multi_event`
放在:
- `inc/events/ble_bond_multi_event.h`
- `src/events/ble_bond_multi_event.c`
字段建议:
- `current_slot`
- `active_identity_id`
- `slot_occupied_bitmap`
- `slot_meta_summary`
- `op`
至少要能支持:
- UI 刷新槽位名称
- 显示层提示当前槽位
- 设置层同步当前 active slot
继续复用 CAF 事件:
- `ble_peer_event`
- `ble_peer_operation_event`
第一阶段不需要依赖:
- `ble_peer_search_event`
## 13. 对现有代码的影响评估
## 13.1 必改
- `prj.conf`
- `CMakeLists.txt`
- 新增 `src/ble_bond_multi_module.c`
- 新增 `inc/events/ble_bond_multi_event.h`
- 新增 `src/events/ble_bond_multi_event.c`
- `ui_settings_controller.c`
## 13.2 小改
- `mode_policy_module.c`
说明:把 `BLE_PROFILE_POLICY_GENERAL` 约束到 `identity 1/2/3`,把 `BLE_PROFILE_POLICY_DONGLE` 预留到 `identity 4`
- `display_module.c`
说明:如果要显示当前槽位或名称,需要订阅新事件
## 13.3 原则上不应大改
- `ble_hid_module.c`
- `ble_nus_module.c`
- `keyboard_core_module.c`
这些模块只应该感知当前连接状态,不承担槽位管理职责。
## 14. 实施分阶段建议
## 阶段 1固定 identity 多槽骨架
- 替换 CAF 默认 `ble_bond`
- 新增 `ble_bond_multi_module`
- 固定 `identity 1/2/3` 为 BLE 槽位
- 预留 `identity 4` 为 dongle 槽位
- 恢复和保存 `current_slot`
- 打通直接切槽
交付标准:
- `Slot 1~3` 可以切换
- 每个槽位 bond 独立
- 重启后当前槽位可恢复
## 阶段 2slot_meta 与 UI 打通
- 维护 `occupied`
- 维护 `last_peer_addr`
- 正式持久化 `display_name`
- UI 改为显示真实槽位数据
交付标准:
- 屏幕上显示真实槽位名
- `display_name` 可稳定持久化
## 阶段 3直接擦除与 dongle 预留对齐
- 实现当前槽位直接 `unpair`
- 擦除后刷新 `slot_meta`
- 擦除后重新进入可配对状态
- 明确 `identity 4` 的模式入口
交付标准:
- 擦除后旧主机不能再连接
- 当前槽位可重新配对
- `24G` 路径已有干净预留点
## 15. 风险与注意事项
## 15.1 直接擦除的用户体验
本方案放弃回滚能力。
一旦擦除:
- 旧 bond 立即失效
- 用户必须重新配对
这是当前明确接受的产品行为。
## 15.2 display_name 获取机制
`display_name` 虽然是正式字段,但自动来源未必现成。
实现阶段必须明确:
- 名称是自动采集
- 还是通过 UI / 上位机写入
这不是字段是否保留的问题,而是字段填充路径的问题。
## 15.3 `BT_ID_MAX=5` 可用性
必须在目标板上验证:
- identity 创建正常
- 切换 advertising identity 正常
- settings 恢复正常
## 15.4 默认 identity 不参与槽位
本方案建议 `BT_ID_DEFAULT` 不参与槽位管理,避免后续行为不一致。
## 16. 当前版本结论
按目前所有已确认信息,最终设计结论如下:
1. 只做 BLE 三槽dongle 只预留专用槽位
2. 三个 BLE 槽位直接固定到 `bt_identity_id 1/2/3`
3. `bt_identity_id 4` 预留给 dongle
4. 不使用 `app_slot_id`
5. 不使用临时 identity
6. 不做 erase advertising
7. 不做擦除确认和回滚
8. `slot_meta` 固定包含:
- `occupied`
- `last_peer_addr`
- `display_name`
9. `display_name` 作为正式持久化字段保留,用于 UI 显示
10. 不新增 `inc/ble_bond_multi.h`,共享接口放到事件头文件
如果你认可这个版本,下一步就可以按这份方案进入实现阶段。