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

14 KiB
Raw Blame History

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 槽位

那么推荐配置应为:

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不参与槽位
  • 1Slot 1
  • 2Slot 2
  • 3Slot 3
  • 4Dongle 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,共享接口放到事件头文件

如果你认可这个版本,下一步就可以按这份方案进入实现阶段。