# 自定义 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 架构 ```mermaid 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 架构 ```mermaid 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 = 3` - `APP_PEER_COUNT = CONFIG_BT_ID_MAX - 1` - `BT_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_bond` ready - 开机可能在错误 identity 上提前广播 - 最终出现连接到 old id、加密后又断开的异常 因此自定义 BLE bond 不能只写 C 文件,还必须补齐 Kconfig 接线。 ### 4.4 设计并发布 peer operation 事件 CAF 自带 `ble_bond` 不发布 `ble_peer_operation_event`,但多身份实现必须发布,例如: - `PEER_OPERATION_SELECTED` - `PEER_OPERATION_ERASE_ADV` - `PEER_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` 阶段确认连接确实落在当前选中 identity - `DISCONNECTED` 阶段清理自动切换或迁移的临时状态 如果所有逻辑都堆到 `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` 后初始化 identity - `PEER_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 和重连应被视为正常流程,而不是异常