12 KiB
自定义 BLE Bond 以支持多身份连接的设计说明
1. 背景
CAF 自带的 ble_bond 适合简单外设场景。它默认假设应用只使用 BT_ID_DEFAULT 一个本地身份,职责主要是:
- 等待
settings_loader完成初始化 - 根据按键触发删除默认身份上的 bond
- 通过
CONFIG_CAF_BLE_BOND_SUPPORTED告知 CAF 应用实现了 BLE bond
如果应用希望像多设备键盘一样支持多个 Bluetooth identity 对应多个 slot,则必须提供自定义 ble_bond。因为此时已经不只是“是否有 bond”,而是“哪一个 peer 属于哪一个 slot、当前应使用哪一个 identity、切换 slot 时如何与 ble_adv/ble_state 协同”。
2. CAF 自带 BLE Bond 与自定义 BLE Bond 的差异
2.1 CAF 自带 BLE Bond
特点:
- 只管理默认 identity
- 不维护 slot 到 identity 的映射
- 不发布
ble_peer_operation_event - 不处理多主机切换
- 不处理 peer 所属 slot 自动归位
适用场景:
- 单主机 BLE 外设
- 仅需擦除 bond
- 不需要多身份、多 slot、多 peer 路由
2.2 自定义 BLE Bond
特点:
- 维护应用 slot 与 Bluetooth stack identity 的映射
- 保存当前选中的 slot
- 在 slot 切换时发布
ble_peer_operation_event - 与
ble_adv协同切换广告 identity - 与
ble_state协同确认连接落在正确 identity 上 - 处理同一 peer 属于哪个 slot
- 可扩展实现 peer 迁移、自动回 slot、自动重配等策略
适用场景:
- 多设备键盘
- 每个 slot 对应独立 bond
- 需要在多个 identity 间切换广告和连接
- 需要对 host 行为做应用级策略控制
3. 两版 BLE Bond 架构图
3.1 CAF 自带 BLE Bond 架构
flowchart LR
A[settings_loader] --> B[CAF ble_bond]
C[click_event] --> B
B -->|bt_unpair BT_ID_DEFAULT| D[Bluetooth Stack]
B -->|MODULE_STATE_READY| E[CAF Modules]
D --> F[单一默认 Identity]
说明:
CAF ble_bond不参与 slot 选择- 也不参与广告 identity 路由
- 仅在用户触发擦除时对默认 identity 执行
bt_unpair()
3.2 自定义多身份 BLE Bond 架构
flowchart TD
A[settings_loader] --> B[custom ble_bond]
C[config_event / slot control] --> B
D[ble_peer_event CONNECTED/SECURED/DISCONNECTED] --> B
B --> E[slot <-> bt_stack_id LUT]
B --> F[current selected slot]
B --> G[ble_peer_operation_event]
G --> H[CAF ble_adv]
G --> I[slot name / GAP name provider]
H --> J[Advertising on selected Identity]
J --> K[Windows / Host]
K --> D
B -->|validate connection identity| L[bt_conn_disconnect]
B -->|find peer owner slot| M[auto switch / reconnect]
说明:
- 自定义版本已经成为 BLE peer 管理的核心策略层
- 它既要维护持久化状态,也要处理运行时连接路由
ble_adv只负责按事件切换 identity 广播,不负责理解 slot 语义
4. 相比 CAF 自带 BLE Bond,额外必须完成的工作
4.1 身份资源规划
必须先定义:
- 一共有多少个应用 slot
CONFIG_BT_ID_MAX至少要覆盖这些 slot- 是否保留
BT_ID_DEFAULT - 每个 slot 对应哪个 stack identity
当前实现中:
slot_count = 3APP_PEER_COUNT = CONFIG_BT_ID_MAX - 1BT_ID_DEFAULT不直接用于可切换 slot- slot 0/1/2 分别映射到 identity 1/2/3
4.2 持久化保存应用层状态
CAF 自带实现不保存 slot 语义。自定义版本必须自行保存:
- 当前选中的 slot
- slot 到 stack identity 的 LUT
否则重启后:
ble_adv可能在错误 identity 上广播- 连接会落到旧 identity
- 导致加密后被应用判定为错误 slot
4.3 向 CAF 明确声明“应用实现了 BLE bond”
如果应用没有通过 Kconfig select CAF_BLE_BOND_SUPPORTED:
ble_adv不会等待自定义ble_bondready- 开机可能在错误 identity 上提前广播
- 最终出现连接到 old id、加密后又断开的异常
因此自定义 BLE bond 不能只写 C 文件,还必须补齐 Kconfig 接线。
4.4 设计并发布 peer operation 事件
CAF 自带 ble_bond 不发布 ble_peer_operation_event,但多身份实现必须发布,例如:
PEER_OPERATION_SELECTEDPEER_OPERATION_ERASE_ADVPEER_OPERATION_ERASED
这些事件会驱动:
ble_adv切换广告 identity- 名称提供器更新 GAP/广告名
- LED/状态模块同步 UI
4.5 处理 slot 切换时的现有连接
当用户切换 slot 时,应用必须决定:
- 现有 LE 连接是否断开
- 断开后是否立刻以新 identity 重启广告
如果不主动断开:
- 连接仍停留在旧 identity
- 逻辑上已经切到新 slot,但链路仍属于旧 slot
- 后续加密、重连、广告状态都会错乱
4.6 在连接建立后校验“连接是否属于当前 slot”
多身份实现必须在 PEER_STATE_SECURED 甚至更早的 CONNECTED 时校验:
- 当前连接的
info.id - 当前选中 slot 对应的
bt_stack_id
如果不一致:
- 这是旧 identity、旧连接或错误路由
- 必须主动断开,避免错误 bond 被继续使用
4.7 处理 peer 与 slot 的归属关系
CAF 自带实现没有“peer 属于哪个 slot”这个概念。多身份实现必须处理:
- 同一个 peer 之前绑定在哪个 slot
- 当前连接是不是连错 slot
- 是否自动切回该 peer 所属 slot
- 是否允许 peer 从一个 slot 迁移到另一个 slot
当前 C1 实现已经支持:
- 当 peer 连到错误 slot 时
- 自动识别该 peer 真实所属 slot
- 自动切回正确 slot
- 主动断开一次,等待 host 重连
4.8 处理 bond 擦除和 identity reset
多身份实现通常不能只 bt_unpair(BT_ID_DEFAULT, NULL),还要区分:
- 删除某个 slot 的 peer
- 删除全部 slot
- 是否要
bt_id_reset() - 擦除后是否需要重启该 identity 的广告会话
这部分已经不再是单纯 bond 删除,而是“identity 生命周期管理”。
5. 自定义 BLE Bond 实现中的重点注意事项
5.1 先解决 Kconfig 接线,再调连接逻辑
这是最容易遗漏的点。
如果没有正确让应用 select CAF_BLE_BOND_SUPPORTED:
ble_adv启动顺序会不对- 自定义
ble_bond即使逻辑正确,也会在启动阶段表现异常
这属于架构接线问题,不是连接状态机问题。
5.2 广播名和 GAP 名必须统一策略
多 slot 场景下,名称策略必须明确:
- 是每个 slot 不同名字
- 还是所有 slot 同名
如果广告名和 GAP Device Name 不一致:
- Windows 可能扫描到一个名字
- 配对后读到另一个名字
- 容易造成驱动实例重建、名字重命名、缓存混乱
当前项目已经验证:
- 统一 GAP 名与广告名后
- Windows 行为更稳定
5.3 不要假设 Windows 删除设备就等于双方 bond 都删了
这在多 slot 里尤其关键。
Windows 端删设备后:
- 主机端密钥可能删了
- 设备端旧 slot 的本地 bond 仍然存在
这会导致:
- 新 slot 上重新配对被 SMP 拒绝
- 日志出现
Refusing new pairing. The old bond must be unpaired first.
因此自定义 BLE bond 必须明确“本地 bond”和“主机侧配对记录”不是同一件事。
5.4 host 删除密钥无法直接读取,只能通过行为推断
设备无法直接读取 Windows 是否删除了配对密钥。
只能根据以下现象推断:
- 已绑定 slot 上能否顺利加密
- 是否重新发起 pairing
- 是否出现安全失败或 SMP 冲突
因此设计 C2 这类“自动清旧 bond 并重配”的逻辑时,必须以“推断”而不是“查询”来实现。
5.5 连接事件与安全事件分工要清楚
推荐分层:
CONNECTED阶段处理 peer 归属和 slot 自动路由SECURED阶段确认连接确实落在当前选中 identityDISCONNECTED阶段清理自动切换或迁移的临时状态
如果所有逻辑都堆到 SECURED:
- 处理时机偏晚
- 经常已经进入 SMP/安全过程
- 更容易触发 host 侧异常
5.6 自动切 slot 时要接受“一次断开再重连”
这是应用策略设计里的现实约束。
当 peer 连错 slot 时:
- 应用切换到正确 slot
- 需要让旧连接断开
- 然后等待 host 按正确 identity 重新连入
不要追求“同一条连接无感切换 identity”,这在 BLE identity 语义上不现实,也不稳定。
5.7 bt_foreach_bond() 和地址匹配要谨慎
如果实现 peer 所属 slot 判断,通常会用:
bt_conn_get_dst(conn)bt_foreach_bond(local_id, ...)bt_addr_le_cmp()
需要注意:
- public address 与 random/RPA 地址行为不同
- 已绑定设备可能因为隐私地址导致匹配行为更复杂
- 实际测试必须覆盖 Windows、Android、iOS 等 host
当前项目日志里 host 使用 public address,因此匹配较直接。
5.8 identity reset 的语义要和 UI/配置动作对齐
在自定义多 slot 实现中,erase_peer() 往往同时包含:
- 删除 bond
- reset identity
- 触发广告重启
因此必须保证 UI 动作和内部语义一致。例如:
- “清当前 slot 配对” 是否意味着彻底 reset 当前 identity
- “清全部配对” 是否会影响所有 slot 广播身份
如果定义不清,后续调试会非常混乱。
5.9 必须大量依赖日志建立可观测性
自定义 BLE bond 不像 CAF 默认版那样简单。建议至少保留以下日志:
- 当前 slot 与 stack identity
- settings 恢复结果
- slot 切换请求
PEER_OPERATION_SELECTED- 连接建立的
info.id SECURED时的 identity 匹配结果- peer 所属 slot 自动识别结果
- 自动切 slot 开始与结束
没有这些日志,多身份调试成本会非常高。
6. 推荐的实现分层
为了避免自定义 BLE bond 过度膨胀,建议分层如下:
ble_bond负责 slot/identity 映射、持久化状态、peer 归属、配对策略ble_adv负责在指定 identity 上广播slot_name / GAP name provider负责名称策略slot_ctrl负责把按键或配置命令转换成 slot 选择请求ble_state负责连接、安全、断开事件广播
这个分层里最重要的原则是:
ble_adv不理解 slot 语义ble_state不理解 slot 策略- slot 语义统一由自定义
ble_bond决策
7. 当前项目自定义 BLE Bond 已实现的内容
当前 new_kbd 自定义 ble_bond 已经具备:
- 3 个应用 slot
- slot 到 identity 的持久化映射
- 当前 slot 的持久化保存
settings_loader后初始化 identityPEER_OPERATION_SELECTED广播给ble_adv- slot 切换时主动断开现有 LE 连接
SECURED时校验当前连接是否落在期望 identity- C1: peer 连错 slot 时自动识别所属 slot 并切回
尚未自动化的部分:
- C2: 主机端删密钥后,本地自动删除旧 bond 并允许重配
- peer 迁移到新 slot 的完整自动化策略
- 更复杂 host 场景下的异常恢复
8. 结论
CAF 自带 ble_bond 是“单 identity 的 bond 擦除模块”。
一旦要支持多 slot、多 identity、多 host 路由,自定义 ble_bond 就不再只是替代品,而是整套 BLE peer 管理策略的核心。
相对 CAF 自带实现,额外工作主要集中在:
- identity 规划
- 状态持久化
- 与
ble_adv/ble_state的事件协同 - slot 选择与连接路由
- peer 归属识别
- 多 host / host 缓存 / bond 生命周期处理
实现时最需要注意的是:
- Kconfig 接线必须正确
- 广播 identity 与当前 slot 必须严格一致
- 广告名/GAP 名策略必须统一
- host 删除配对记录不等于本地 bond 已删除
- 自动切 slot 和重连应被视为正常流程,而不是异常