diff --git a/docs/ble_bond_design_notes.md b/docs/ble_bond_design_notes.md new file mode 100644 index 0000000..0ae467f --- /dev/null +++ b/docs/ble_bond_design_notes.md @@ -0,0 +1,411 @@ +# 自定义 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 和重连应被视为正常流程,而不是异常 +