Compare commits
5 Commits
2a389ef19b
...
7e0f224ec8
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e0f224ec8 | |||
| a46b7ad8b8 | |||
| 3ff9f8c6fa | |||
| 9b29910299 | |||
| 579dc35a36 |
@@ -22,16 +22,22 @@ target_sources(app PRIVATE
|
|||||||
src/events/hid_boot_event.c
|
src/events/hid_boot_event.c
|
||||||
src/events/hid_protocol_event.c
|
src/events/hid_protocol_event.c
|
||||||
src/events/hid_report_event.c
|
src/events/hid_report_event.c
|
||||||
|
src/events/hid_tx_done_event.c
|
||||||
|
src/events/hid_tx_event.c
|
||||||
|
src/events/hid_vendor_mask_event.c
|
||||||
src/events/keyboard_led_event.c
|
src/events/keyboard_led_event.c
|
||||||
src/events/mode_event.c
|
src/events/mode_event.c
|
||||||
|
src/events/qdec_step_event.c
|
||||||
src/modules/battery_module.c
|
src/modules/battery_module.c
|
||||||
src/modules/ble_adv_ctrl_module.c
|
src/modules/ble_adv_ctrl_module.c
|
||||||
src/modules/ble_battery_module.c
|
src/modules/ble_battery_module.c
|
||||||
src/modules/ble_bond_module.c
|
src/modules/ble_bond_module.c
|
||||||
src/modules/ble_slot_ctrl_module.c
|
src/modules/ble_slot_ctrl_module.c
|
||||||
|
src/modules/hid_tx_manager_module.c
|
||||||
src/modules/keyboard_module.c
|
src/modules/keyboard_module.c
|
||||||
src/modules/led_state_module.c
|
src/modules/led_state_module.c
|
||||||
src/modules/mode_switch_module.c
|
src/modules/mode_switch_module.c
|
||||||
|
src/modules/qdec_module.c
|
||||||
src/modules/usb_hid_module.c
|
src/modules/usb_hid_module.c
|
||||||
src/modules/ble_hid_module.c
|
src/modules/ble_hid_module.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
protocol-code = "none";
|
protocol-code = "none";
|
||||||
in-report-size = <31>;
|
in-report-size = <31>;
|
||||||
in-polling-period-us = <1000>;
|
in-polling-period-us = <1000>;
|
||||||
out-report-size = <8>;
|
out-report-size = <31>;
|
||||||
out-polling-period-us = <1000>;
|
out-polling-period-us = <1000>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,3 +74,7 @@
|
|||||||
&usbd {
|
&usbd {
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
qdec: &qdec {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|||||||
411
docs/ble_bond_design_notes.md
Normal file
411
docs/ble_bond_design_notes.md
Normal file
@@ -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 和重连应被视为正常流程,而不是异常
|
||||||
|
|
||||||
@@ -7,8 +7,20 @@
|
|||||||
enum {
|
enum {
|
||||||
REPORT_ID_KEYBOARD = 1,
|
REPORT_ID_KEYBOARD = 1,
|
||||||
REPORT_ID_CONSUMER = 3,
|
REPORT_ID_CONSUMER = 3,
|
||||||
|
REPORT_ID_VENDOR = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define HID_KBD_USAGE_MAX 0x00E7U
|
||||||
|
#define HID_KBD_MOD_COUNT 8U
|
||||||
|
#define HID_KBD_BITMAP_BITS (HID_KBD_USAGE_MAX + 1U)
|
||||||
|
#define HID_KBD_BITMAP_SIZE ((HID_KBD_BITMAP_BITS + 7U) / 8U)
|
||||||
|
#define HID_KBD_PAYLOAD_SIZE (1U + HID_KBD_BITMAP_SIZE)
|
||||||
|
#define HID_BOOT_KBD_PAYLOAD_SIZE 8U
|
||||||
|
#define HID_CONSUMER_PAYLOAD_SIZE 2U
|
||||||
|
#define HID_VENDOR_PAYLOAD_SIZE HID_KBD_PAYLOAD_SIZE
|
||||||
|
#define HID_KBD_LED_PAYLOAD_SIZE 1U
|
||||||
|
#define HID_FULL_REPORT_SIZE(payload) (1U + (payload))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HID_USAGE_PAGE() 只支持 1 字节 Usage Page。
|
* HID_USAGE_PAGE() 只支持 1 字节 Usage Page。
|
||||||
* Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本,
|
* Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本,
|
||||||
@@ -18,9 +30,12 @@ enum {
|
|||||||
HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page_lsb, page_msb
|
HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page_lsb, page_msb
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 键盘(NKRO) + Consumer 的复合 Report 描述符:
|
* 键盘(NKRO) + Consumer + Vendor 的复合 Report 描述符:
|
||||||
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
|
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
|
||||||
* 避免两边手写常量后长期演进出现不一致。
|
* 避免两边手写常量后长期演进出现不一致。
|
||||||
|
* - Vendor Report 复用与 NKRO 键盘状态相同的 payload 结构:
|
||||||
|
* [modifier(1B) | usage_bitmap(29B)]。
|
||||||
|
* 这样主机可以下发“屏蔽遮罩”,设备也可以上报“真实键盘状态”。
|
||||||
*/
|
*/
|
||||||
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
|
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
|
||||||
{ \
|
{ \
|
||||||
@@ -44,7 +59,7 @@ enum {
|
|||||||
HID_LOGICAL_MIN8(0), \
|
HID_LOGICAL_MIN8(0), \
|
||||||
HID_LOGICAL_MAX8(1), \
|
HID_LOGICAL_MAX8(1), \
|
||||||
HID_REPORT_SIZE(1), \
|
HID_REPORT_SIZE(1), \
|
||||||
HID_REPORT_COUNT(0xE7 + 1), \
|
HID_REPORT_COUNT(HID_KBD_BITMAP_BITS), \
|
||||||
HID_INPUT(0x02), \
|
HID_INPUT(0x02), \
|
||||||
\
|
\
|
||||||
/* Report 协议下键盘 LED 输出(NumLock/CapsLock/ScrollLock/Compose/Kana)。 */ \
|
/* Report 协议下键盘 LED 输出(NumLock/CapsLock/ScrollLock/Compose/Kana)。 */ \
|
||||||
@@ -74,6 +89,22 @@ enum {
|
|||||||
HID_REPORT_SIZE(16), \
|
HID_REPORT_SIZE(16), \
|
||||||
HID_REPORT_COUNT(1), \
|
HID_REPORT_COUNT(1), \
|
||||||
HID_INPUT(0x00), \
|
HID_INPUT(0x00), \
|
||||||
|
HID_END_COLLECTION, \
|
||||||
|
\
|
||||||
|
/* Vendor 页:双向传输完整键盘状态/屏蔽遮罩,payload 结构与 NKRO 一致。 */ \
|
||||||
|
HID_USAGE_PAGE16(0x00, 0xFF), \
|
||||||
|
HID_USAGE(0x02U), \
|
||||||
|
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||||
|
HID_REPORT_ID(REPORT_ID_VENDOR), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX16(0xFF, 0x00), \
|
||||||
|
HID_REPORT_SIZE(8), \
|
||||||
|
HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \
|
||||||
|
HID_USAGE(0x02U), \
|
||||||
|
HID_INPUT(0x02), \
|
||||||
|
HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \
|
||||||
|
HID_USAGE(0x02U), \
|
||||||
|
HID_OUTPUT(0x02), \
|
||||||
HID_END_COLLECTION, \
|
HID_END_COLLECTION, \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
prj.conf
7
prj.conf
@@ -45,9 +45,9 @@ CONFIG_BT_CONN_CTX=y
|
|||||||
CONFIG_BT_GATT_POOL=y
|
CONFIG_BT_GATT_POOL=y
|
||||||
CONFIG_BT_GATT_CHRC_POOL_SIZE=16
|
CONFIG_BT_GATT_CHRC_POOL_SIZE=16
|
||||||
CONFIG_BT_GATT_UUID16_POOL_SIZE=24
|
CONFIG_BT_GATT_UUID16_POOL_SIZE=24
|
||||||
CONFIG_BT_HIDS_ATTR_MAX=32
|
CONFIG_BT_HIDS_ATTR_MAX=40
|
||||||
CONFIG_BT_HIDS_INPUT_REP_MAX=2
|
CONFIG_BT_HIDS_INPUT_REP_MAX=3
|
||||||
CONFIG_BT_HIDS_OUTPUT_REP_MAX=1
|
CONFIG_BT_HIDS_OUTPUT_REP_MAX=2
|
||||||
CONFIG_BT_HIDS_FEATURE_REP_MAX=0
|
CONFIG_BT_HIDS_FEATURE_REP_MAX=0
|
||||||
|
|
||||||
CONFIG_USB_DEVICE_STACK_NEXT=y
|
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||||
@@ -77,5 +77,6 @@ CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
|
|||||||
CONFIG_ADC=y
|
CONFIG_ADC=y
|
||||||
CONFIG_I2C=y
|
CONFIG_I2C=y
|
||||||
CONFIG_IP5305=y
|
CONFIG_IP5305=y
|
||||||
|
CONFIG_SENSOR=y
|
||||||
|
|
||||||
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096
|
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096
|
||||||
|
|||||||
29
src/events/hid_tx_done_event.c
Normal file
29
src/events/hid_tx_done_event.c
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "hid_tx_done_event.h"
|
||||||
|
|
||||||
|
static void log_hid_tx_done_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_tx_done_event *event = cast_hid_tx_done_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "kind=%u success=%u",
|
||||||
|
event->kind, event->success);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_hid_tx_done_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_tx_done_event *event = cast_hid_tx_done_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->success ? 1U : 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(hid_tx_done_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("kind", "success"),
|
||||||
|
profile_hid_tx_done_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(hid_tx_done_event,
|
||||||
|
log_hid_tx_done_event,
|
||||||
|
&hid_tx_done_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
28
src/events/hid_tx_done_event.h
Normal file
28
src/events/hid_tx_done_event.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef HID_TX_DONE_EVENT_H__
|
||||||
|
#define HID_TX_DONE_EVENT_H__
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
#include "hid_tx_event.h"
|
||||||
|
|
||||||
|
struct hid_tx_done_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
enum hid_tx_kind kind;
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(hid_tx_done_event);
|
||||||
|
|
||||||
|
static inline void hid_tx_done_event_submit(enum hid_tx_kind kind, bool success)
|
||||||
|
{
|
||||||
|
struct hid_tx_done_event *event = new_hid_tx_done_event();
|
||||||
|
|
||||||
|
event->kind = kind;
|
||||||
|
event->success = success;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HID_TX_DONE_EVENT_H__ */
|
||||||
43
src/events/hid_tx_event.c
Normal file
43
src/events/hid_tx_event.c
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "hid_tx_event.h"
|
||||||
|
|
||||||
|
static void log_hid_tx_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_tx_event *event = cast_hid_tx_event(aeh);
|
||||||
|
uint8_t report_id = 0x00;
|
||||||
|
uint16_t payload_len = event->dyndata.size;
|
||||||
|
|
||||||
|
if ((event->kind == HID_TX_KIND_REPORT) && (event->dyndata.size >= 1U)) {
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
payload_len = event->dyndata.size - 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "kind=%u report_id=0x%02x payload_len=%u",
|
||||||
|
event->kind, report_id, payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_hid_tx_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_tx_event *event = cast_hid_tx_event(aeh);
|
||||||
|
uint8_t report_id = 0x00;
|
||||||
|
|
||||||
|
if ((event->kind == HID_TX_KIND_REPORT) && (event->dyndata.size >= 1U)) {
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, report_id);
|
||||||
|
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(hid_tx_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U16),
|
||||||
|
ENCODE("kind", "report_id", "len"),
|
||||||
|
profile_hid_tx_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(hid_tx_event,
|
||||||
|
log_hid_tx_event,
|
||||||
|
&hid_tx_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
48
src/events/hid_tx_event.h
Normal file
48
src/events/hid_tx_event.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef HID_TX_EVENT_H__
|
||||||
|
#define HID_TX_EVENT_H__
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
enum hid_tx_kind {
|
||||||
|
HID_TX_KIND_BOOT = 0,
|
||||||
|
HID_TX_KIND_REPORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hid_tx_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
enum hid_tx_kind kind;
|
||||||
|
struct event_dyndata dyndata;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_event);
|
||||||
|
|
||||||
|
static inline void hid_tx_event_submit(enum hid_tx_kind kind,
|
||||||
|
const uint8_t *data,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
struct hid_tx_event *event = new_hid_tx_event(size);
|
||||||
|
|
||||||
|
event->kind = kind;
|
||||||
|
if ((size > 0U) && (data != NULL)) {
|
||||||
|
memcpy(event->dyndata.data, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const uint8_t *hid_tx_event_get_data(const struct hid_tx_event *event)
|
||||||
|
{
|
||||||
|
return event->dyndata.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t hid_tx_event_get_size(const struct hid_tx_event *event)
|
||||||
|
{
|
||||||
|
return event->dyndata.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HID_TX_EVENT_H__ */
|
||||||
26
src/events/hid_vendor_mask_event.c
Normal file
26
src/events/hid_vendor_mask_event.c
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "hid_vendor_mask_event.h"
|
||||||
|
|
||||||
|
static void log_hid_vendor_mask_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "len=%u", event->dyndata.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_hid_vendor_mask_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(hid_vendor_mask_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U16),
|
||||||
|
ENCODE("len"),
|
||||||
|
profile_hid_vendor_mask_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(hid_vendor_mask_event,
|
||||||
|
log_hid_vendor_mask_event,
|
||||||
|
&hid_vendor_mask_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
39
src/events/hid_vendor_mask_event.h
Normal file
39
src/events/hid_vendor_mask_event.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef HID_VENDOR_MASK_EVENT_H__
|
||||||
|
#define HID_VENDOR_MASK_EVENT_H__
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
struct hid_vendor_mask_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
struct event_dyndata dyndata;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_vendor_mask_event);
|
||||||
|
|
||||||
|
static inline void hid_vendor_mask_event_submit(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
struct hid_vendor_mask_event *event = new_hid_vendor_mask_event(size);
|
||||||
|
|
||||||
|
if ((size > 0U) && (data != NULL)) {
|
||||||
|
memcpy(event->dyndata.data, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const uint8_t *hid_vendor_mask_event_get_data(const struct hid_vendor_mask_event *event)
|
||||||
|
{
|
||||||
|
return event->dyndata.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t hid_vendor_mask_event_get_size(const struct hid_vendor_mask_event *event)
|
||||||
|
{
|
||||||
|
return event->dyndata.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HID_VENDOR_MASK_EVENT_H__ */
|
||||||
26
src/events/qdec_step_event.c
Normal file
26
src/events/qdec_step_event.c
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "qdec_step_event.h"
|
||||||
|
|
||||||
|
static void log_qdec_step_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct qdec_step_event *event = cast_qdec_step_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "step=%d", event->step);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_qdec_step_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct qdec_step_event *event = cast_qdec_step_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_int8(buf, event->step);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(qdec_step_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_S8),
|
||||||
|
ENCODE("step"),
|
||||||
|
profile_qdec_step_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(qdec_step_event,
|
||||||
|
log_qdec_step_event,
|
||||||
|
&qdec_step_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
36
src/events/qdec_step_event.h
Normal file
36
src/events/qdec_step_event.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef QDEC_STEP_EVENT_H__
|
||||||
|
#define QDEC_STEP_EVENT_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
struct qdec_step_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
int8_t step;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(qdec_step_event);
|
||||||
|
|
||||||
|
static inline void qdec_step_event_submit(int8_t step)
|
||||||
|
{
|
||||||
|
struct qdec_step_event *event;
|
||||||
|
|
||||||
|
__ASSERT((step == 1) || (step == -1), "qdec step event must be +/-1");
|
||||||
|
|
||||||
|
if (step == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = new_qdec_step_event();
|
||||||
|
event->step = step;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int8_t qdec_step_event_get_step(const struct qdec_step_event *event)
|
||||||
|
{
|
||||||
|
return event->step;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* QDEC_STEP_EVENT_H__ */
|
||||||
@@ -8,21 +8,18 @@
|
|||||||
#include <caf/events/ble_common_event.h>
|
#include <caf/events/ble_common_event.h>
|
||||||
|
|
||||||
#include "hid_protocol_event.h"
|
#include "hid_protocol_event.h"
|
||||||
#include "hid_boot_event.h"
|
|
||||||
#include "hid_report_event.h"
|
|
||||||
#include "hid_report_descriptor.h"
|
#include "hid_report_descriptor.h"
|
||||||
|
#include "hid_tx_done_event.h"
|
||||||
|
#include "hid_tx_event.h"
|
||||||
|
#include "hid_vendor_mask_event.h"
|
||||||
#include "keyboard_led_event.h"
|
#include "keyboard_led_event.h"
|
||||||
#include "mode_event.h"
|
#include "mode_event.h"
|
||||||
|
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
#define INPUT_REPORT_COUNT 2
|
#define INPUT_REPORT_COUNT 3
|
||||||
#define OUTPUT_REPORT_COUNT 1
|
#define OUTPUT_REPORT_COUNT 2
|
||||||
#define KEYBOARD_REPORT_LEN 30
|
|
||||||
#define CONSUMER_REPORT_LEN 2
|
|
||||||
#define KEYBOARD_LED_REPORT_LEN 1
|
|
||||||
#define BOOT_KEYBOARD_REPORT_LEN 8
|
|
||||||
|
|
||||||
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
|
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
|
||||||
|
|
||||||
@@ -142,7 +139,7 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep,
|
|||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) {
|
if (!write || !rep || !rep->data || (rep->size < HID_KBD_LED_PAYLOAD_SIZE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +147,20 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep,
|
|||||||
LOG_DBG("Report KB out report 0x%02x", rep->data[0]);
|
LOG_DBG("Report KB out report 0x%02x", rep->data[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void vendor_output_report_handler(struct bt_hids_rep *rep,
|
||||||
|
struct bt_conn *conn,
|
||||||
|
bool write)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
|
if (!write || !rep || !rep->data || (rep->size != HID_VENDOR_PAYLOAD_SIZE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_vendor_mask_event_submit(rep->data, rep->size);
|
||||||
|
LOG_INF("Vendor mask updated over BLE len=%u", rep->size);
|
||||||
|
}
|
||||||
|
|
||||||
static int hids_service_init(void)
|
static int hids_service_init(void)
|
||||||
{
|
{
|
||||||
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
||||||
@@ -165,17 +176,25 @@ static int hids_service_init(void)
|
|||||||
init_param.rep_map.size = sizeof(report_map);
|
init_param.rep_map.size = sizeof(report_map);
|
||||||
|
|
||||||
input_report[0].id = REPORT_ID_KEYBOARD;
|
input_report[0].id = REPORT_ID_KEYBOARD;
|
||||||
input_report[0].size = KEYBOARD_REPORT_LEN;
|
input_report[0].size = HID_KBD_PAYLOAD_SIZE;
|
||||||
input_report[0].handler = report_notify_handler;
|
input_report[0].handler = report_notify_handler;
|
||||||
|
|
||||||
input_report[1].id = REPORT_ID_CONSUMER;
|
input_report[1].id = REPORT_ID_CONSUMER;
|
||||||
input_report[1].size = CONSUMER_REPORT_LEN;
|
input_report[1].size = HID_CONSUMER_PAYLOAD_SIZE;
|
||||||
input_report[1].handler = report_notify_handler;
|
input_report[1].handler = report_notify_handler;
|
||||||
|
|
||||||
|
input_report[2].id = REPORT_ID_VENDOR;
|
||||||
|
input_report[2].size = HID_VENDOR_PAYLOAD_SIZE;
|
||||||
|
input_report[2].handler = report_notify_handler;
|
||||||
|
|
||||||
output_report[0].id = REPORT_ID_KEYBOARD;
|
output_report[0].id = REPORT_ID_KEYBOARD;
|
||||||
output_report[0].size = KEYBOARD_LED_REPORT_LEN;
|
output_report[0].size = HID_KBD_LED_PAYLOAD_SIZE;
|
||||||
output_report[0].handler = keyboard_output_report_handler;
|
output_report[0].handler = keyboard_output_report_handler;
|
||||||
|
|
||||||
|
output_report[1].id = REPORT_ID_VENDOR;
|
||||||
|
output_report[1].size = HID_VENDOR_PAYLOAD_SIZE;
|
||||||
|
output_report[1].handler = vendor_output_report_handler;
|
||||||
|
|
||||||
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
|
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
|
||||||
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
|
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
|
||||||
init_param.pm_evt_handler = pm_evt_handler;
|
init_param.pm_evt_handler = pm_evt_handler;
|
||||||
@@ -211,37 +230,35 @@ static void handle_ble_peer_event(const struct ble_peer_event *event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool handle_hid_boot_event(const struct hid_boot_event *event)
|
static bool handle_hid_tx_event(const struct hid_tx_event *event)
|
||||||
{
|
{
|
||||||
if (!ble_hid_is_active()) {
|
if (!ble_hid_is_active()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->kind == HID_TX_KIND_BOOT) {
|
||||||
|
const uint8_t *payload = hid_tx_event_get_data(event);
|
||||||
|
size_t payload_len = hid_tx_event_get_size(event);
|
||||||
|
int err;
|
||||||
|
|
||||||
if (!ble_hid_is_boot_mode()) {
|
if (!ble_hid_is_boot_mode()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *payload = hid_boot_event_get_data(event);
|
if (payload_len != HID_BOOT_KBD_PAYLOAD_SIZE) {
|
||||||
size_t payload_len = hid_boot_event_get_size(event);
|
|
||||||
|
|
||||||
if (payload_len != BOOT_KEYBOARD_REPORT_LEN) {
|
|
||||||
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
|
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
|
||||||
|
hid_tx_done_event_submit(HID_TX_KIND_BOOT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = bt_hids_boot_kb_inp_rep_send(&hids_obj, ble_hid.link.conn,
|
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, ble_hid.link.conn,
|
||||||
payload, payload_len, NULL);
|
payload, payload_len, NULL);
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_WRN("BLE HID boot send failed err=%d", err);
|
LOG_WRN("BLE HID boot send failed err=%d", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
hid_tx_done_event_submit(HID_TX_KIND_BOOT, (err == 0));
|
||||||
}
|
|
||||||
|
|
||||||
static bool handle_hid_report_event(const struct hid_report_event *event)
|
|
||||||
{
|
|
||||||
if (!ble_hid_is_active()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,13 +266,14 @@ static bool handle_hid_report_event(const struct hid_report_event *event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *data = hid_report_event_get_data(event);
|
const uint8_t *data = hid_tx_event_get_data(event);
|
||||||
size_t data_len = hid_report_event_get_size(event);
|
size_t data_len = hid_tx_event_get_size(event);
|
||||||
uint8_t report_id;
|
uint8_t report_id;
|
||||||
const uint8_t *payload;
|
const uint8_t *payload;
|
||||||
size_t payload_len;
|
size_t payload_len;
|
||||||
|
|
||||||
if (data_len < 1U) {
|
if (data_len < 1U) {
|
||||||
|
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,12 +287,16 @@ static bool handle_hid_report_event(const struct hid_report_event *event)
|
|||||||
rep_index = 0U;
|
rep_index = 0U;
|
||||||
} else if (report_id == REPORT_ID_CONSUMER) {
|
} else if (report_id == REPORT_ID_CONSUMER) {
|
||||||
rep_index = 1U;
|
rep_index = 1U;
|
||||||
|
} else if (report_id == REPORT_ID_VENDOR) {
|
||||||
|
rep_index = 2U;
|
||||||
} else {
|
} else {
|
||||||
|
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload_len > UINT8_MAX) {
|
if (payload_len > UINT8_MAX) {
|
||||||
LOG_WRN("Payload too large=%u", payload_len);
|
LOG_WRN("Payload too large=%u", payload_len);
|
||||||
|
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +307,7 @@ static bool handle_hid_report_event(const struct hid_report_event *event)
|
|||||||
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
|
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hid_tx_done_event_submit(HID_TX_KIND_REPORT, (err == 0));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,12 +344,8 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_hid_report_event(aeh)) {
|
if (is_hid_tx_event(aeh)) {
|
||||||
return handle_hid_report_event(cast_hid_report_event(aeh));
|
return handle_hid_tx_event(cast_hid_tx_event(aeh));
|
||||||
}
|
|
||||||
|
|
||||||
if (is_hid_boot_event(aeh)) {
|
|
||||||
return handle_hid_boot_event(cast_hid_boot_event(aeh));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__ASSERT_NO_MSG(false);
|
__ASSERT_NO_MSG(false);
|
||||||
@@ -337,5 +356,4 @@ APP_EVENT_LISTENER(MODULE, app_event_handler);
|
|||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, hid_boot_event);
|
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, hid_report_event);
|
|
||||||
|
|||||||
240
src/modules/hid_tx_manager_module.c
Normal file
240
src/modules/hid_tx_manager_module.c
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/sys/atomic.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE hid_tx_manager
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include "hid_report_descriptor.h"
|
||||||
|
#include "hid_boot_event.h"
|
||||||
|
#include "hid_report_event.h"
|
||||||
|
#include "hid_tx_done_event.h"
|
||||||
|
#include "hid_tx_event.h"
|
||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define HID_TX_QUEUE_SIZE 16
|
||||||
|
#define HID_TX_MAX_DATA 32
|
||||||
|
|
||||||
|
enum hid_tx_flag {
|
||||||
|
HID_TX_FLAG_INITIALIZED = 0,
|
||||||
|
HID_TX_FLAG_IN_FLIGHT,
|
||||||
|
HID_TX_FLAG_BOOT_VALID,
|
||||||
|
HID_TX_FLAG_BOOT_DIRTY,
|
||||||
|
HID_TX_FLAG_NKRO_VALID,
|
||||||
|
HID_TX_FLAG_NKRO_DIRTY,
|
||||||
|
HID_TX_FLAG_VENDOR_VALID,
|
||||||
|
HID_TX_FLAG_VENDOR_DIRTY,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hid_tx_item {
|
||||||
|
enum hid_tx_kind kind;
|
||||||
|
size_t len;
|
||||||
|
uint8_t data[HID_TX_MAX_DATA];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hid_tx_ctx {
|
||||||
|
atomic_t flags;
|
||||||
|
struct hid_tx_item boot_state;
|
||||||
|
struct hid_tx_item nkro_state;
|
||||||
|
struct hid_tx_item vendor_state;
|
||||||
|
struct hid_tx_item inflight_item;
|
||||||
|
mode_type_t active_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hid_tx_ctx tx = {
|
||||||
|
.active_mode = MODE_TYPE_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
K_MSGQ_DEFINE(hid_tx_queue_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
|
||||||
|
|
||||||
|
static bool hid_tx_item_store(struct hid_tx_item *item,
|
||||||
|
enum hid_tx_kind kind,
|
||||||
|
const uint8_t *data,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
if (len > HID_TX_MAX_DATA) {
|
||||||
|
LOG_WRN("Drop HID tx kind=%u len=%u: too large", kind, len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
item->kind = kind;
|
||||||
|
item->len = len;
|
||||||
|
if ((len > 0U) && (data != NULL)) {
|
||||||
|
memcpy(item->data, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hid_tx_queue_push(enum hid_tx_kind kind, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
struct hid_tx_item item;
|
||||||
|
|
||||||
|
if (!hid_tx_item_store(&item, kind, data, len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k_msgq_put(&hid_tx_queue_msgq, &item, K_NO_WAIT)) {
|
||||||
|
LOG_WRN("Drop HID tx kind=%u len=%u: queue full", kind, len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hid_tx_dispatch_item(const struct hid_tx_item *item)
|
||||||
|
{
|
||||||
|
tx.inflight_item = *item;
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT);
|
||||||
|
hid_tx_event_submit(item->kind, item->data, item->len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dispatch_next_if_possible(void)
|
||||||
|
{
|
||||||
|
struct hid_tx_item item;
|
||||||
|
|
||||||
|
if (!atomic_test_bit(&tx.flags, HID_TX_FLAG_INITIALIZED) ||
|
||||||
|
atomic_test_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tx.active_mode != MODE_TYPE_USB) && (tx.active_mode != MODE_TYPE_BLE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atomic_test_bit(&tx.flags, HID_TX_FLAG_NKRO_DIRTY) &&
|
||||||
|
atomic_test_bit(&tx.flags, HID_TX_FLAG_NKRO_VALID)) {
|
||||||
|
atomic_clear_bit(&tx.flags, HID_TX_FLAG_NKRO_DIRTY);
|
||||||
|
(void)hid_tx_dispatch_item(&tx.nkro_state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atomic_test_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY) &&
|
||||||
|
atomic_test_bit(&tx.flags, HID_TX_FLAG_BOOT_VALID)) {
|
||||||
|
atomic_clear_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY);
|
||||||
|
(void)hid_tx_dispatch_item(&tx.boot_state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!k_msgq_get(&hid_tx_queue_msgq, &item, K_NO_WAIT)) {
|
||||||
|
(void)hid_tx_dispatch_item(&item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tx.active_mode == MODE_TYPE_BLE) &&
|
||||||
|
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY) &&
|
||||||
|
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID)) {
|
||||||
|
atomic_clear_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
|
||||||
|
(void)hid_tx_dispatch_item(&tx.vendor_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_module_state_event(const struct module_state_event *event)
|
||||||
|
{
|
||||||
|
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(!atomic_test_bit(&tx.flags, HID_TX_FLAG_INITIALIZED));
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_INITIALIZED);
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
dispatch_next_if_possible();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_mode_event(const struct mode_event *event)
|
||||||
|
{
|
||||||
|
tx.active_mode = event->mode_type;
|
||||||
|
dispatch_next_if_possible();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_boot_request_event(const struct hid_boot_event *event)
|
||||||
|
{
|
||||||
|
(void)hid_tx_item_store(&tx.boot_state,
|
||||||
|
HID_TX_KIND_BOOT,
|
||||||
|
hid_boot_event_get_data(event),
|
||||||
|
hid_boot_event_get_size(event));
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_BOOT_VALID);
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY);
|
||||||
|
dispatch_next_if_possible();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_report_request_event(const struct hid_report_event *event)
|
||||||
|
{
|
||||||
|
const uint8_t *data = hid_report_event_get_data(event);
|
||||||
|
size_t len = hid_report_event_get_size(event);
|
||||||
|
|
||||||
|
if ((len > 0U) && (data[0] == REPORT_ID_KEYBOARD)) {
|
||||||
|
(void)hid_tx_item_store(&tx.nkro_state, HID_TX_KIND_REPORT, data, len);
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_NKRO_VALID);
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_NKRO_DIRTY);
|
||||||
|
} else if ((len > 0U) && (data[0] == REPORT_ID_VENDOR)) {
|
||||||
|
(void)hid_tx_item_store(&tx.vendor_state, HID_TX_KIND_REPORT, data, len);
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID);
|
||||||
|
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
|
||||||
|
} else {
|
||||||
|
(void)hid_tx_queue_push(HID_TX_KIND_REPORT, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_next_if_possible();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_tx_done_event(const struct hid_tx_done_event *event)
|
||||||
|
{
|
||||||
|
if (!atomic_test_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->kind != tx.inflight_item.kind) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_clear_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT);
|
||||||
|
dispatch_next_if_possible();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
return handle_module_state_event(cast_module_state_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mode_event(aeh)) {
|
||||||
|
return handle_mode_event(cast_mode_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_boot_event(aeh)) {
|
||||||
|
return handle_hid_boot_request_event(cast_hid_boot_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_report_event(aeh)) {
|
||||||
|
return handle_hid_report_request_event(cast_hid_report_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_tx_done_event(aeh)) {
|
||||||
|
return handle_hid_tx_done_event(cast_hid_tx_done_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_done_event);
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
#include "hid_boot_event.h"
|
#include "hid_boot_event.h"
|
||||||
#include "hid_protocol_event.h"
|
#include "hid_protocol_event.h"
|
||||||
#include "hid_report_event.h"
|
#include "hid_report_event.h"
|
||||||
|
#include "hid_vendor_mask_event.h"
|
||||||
|
#include "qdec_step_event.h"
|
||||||
|
|
||||||
#include <zephyr/sys/util.h>
|
#include <zephyr/sys/util.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
@@ -90,19 +92,14 @@ static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */
|
/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */
|
||||||
#define KEYBOARD_USAGE_MAX 0x00E7
|
#define KEYBOARD_USAGE_MAX HID_KBD_USAGE_MAX
|
||||||
#define KEYBOARD_BITMAP_SIZE DIV_ROUND_UP(KEYBOARD_USAGE_MAX + 1, 8)
|
#define KEYBOARD_BITMAP_SIZE HID_KBD_BITMAP_SIZE
|
||||||
#define KEYBOARD_REPORT_PAYLOAD (1 + KEYBOARD_BITMAP_SIZE)
|
|
||||||
|
|
||||||
/* Boot 协议键盘 payload: modifier(1) + reserved(1) + 6 keys。 */
|
|
||||||
#define BOOT_KEYBOARD_PAYLOAD 8
|
|
||||||
|
|
||||||
/* Consumer payload 固定 16-bit usage。 */
|
|
||||||
#define CONSUMER_PAYLOAD 2
|
|
||||||
|
|
||||||
struct keyboard_state {
|
struct keyboard_state {
|
||||||
uint8_t modifier_bm;
|
uint8_t physical_modifier_bm;
|
||||||
uint8_t usage_bm[KEYBOARD_BITMAP_SIZE];
|
uint8_t physical_usage_bm[KEYBOARD_BITMAP_SIZE];
|
||||||
|
uint8_t mask_modifier_bm;
|
||||||
|
uint8_t mask_bm[KEYBOARD_BITMAP_SIZE];
|
||||||
enum hid_protocol_type current_protocol;
|
enum hid_protocol_type current_protocol;
|
||||||
uint16_t consumer_usage;
|
uint16_t consumer_usage;
|
||||||
};
|
};
|
||||||
@@ -118,6 +115,17 @@ static enum hid_protocol_type active_protocol_get(void)
|
|||||||
return ks.current_protocol;
|
return ks.current_protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void submit_hid_report(enum hid_protocol_type protocol,
|
||||||
|
uint8_t report_id,
|
||||||
|
const uint8_t *payload,
|
||||||
|
size_t payload_len);
|
||||||
|
|
||||||
|
static void keyboard_mask_init(void)
|
||||||
|
{
|
||||||
|
ks.mask_modifier_bm = 0xFF;
|
||||||
|
memset(ks.mask_bm, 0xFF, sizeof(ks.mask_bm));
|
||||||
|
}
|
||||||
|
|
||||||
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
|
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
|
||||||
static bool usage_pressed(uint16_t usage)
|
static bool usage_pressed(uint16_t usage)
|
||||||
{
|
{
|
||||||
@@ -125,7 +133,7 @@ static bool usage_pressed(uint16_t usage)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (ks.usage_bm[usage / 8] & BIT(usage % 8)) != 0U;
|
return (ks.physical_usage_bm[usage / 8] & BIT(usage % 8)) != 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -144,13 +152,13 @@ static bool keyboard_usage_update(uint16_t usage_id, bool pressed)
|
|||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
if ((ks.usage_bm[idx] & mask) == 0U) {
|
if ((ks.physical_usage_bm[idx] & mask) == 0U) {
|
||||||
ks.usage_bm[idx] |= mask;
|
ks.physical_usage_bm[idx] |= mask;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((ks.usage_bm[idx] & mask) != 0U) {
|
if ((ks.physical_usage_bm[idx] & mask) != 0U) {
|
||||||
ks.usage_bm[idx] &= (uint8_t)~mask;
|
ks.physical_usage_bm[idx] &= (uint8_t)~mask;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,15 +167,42 @@ static bool keyboard_usage_update(uint16_t usage_id, bool pressed)
|
|||||||
if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) {
|
if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) {
|
||||||
uint8_t mod_mask = BIT(usage_id - 0x00E0);
|
uint8_t mod_mask = BIT(usage_id - 0x00E0);
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
ks.modifier_bm |= mod_mask;
|
ks.physical_modifier_bm |= mod_mask;
|
||||||
} else {
|
} else {
|
||||||
ks.modifier_bm &= (uint8_t)~mod_mask;
|
ks.physical_modifier_bm &= (uint8_t)~mod_mask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t masked_modifier_get(void)
|
||||||
|
{
|
||||||
|
return ks.physical_modifier_bm & ks.mask_modifier_bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_masked_keyboard_payload(uint8_t payload[HID_KBD_PAYLOAD_SIZE])
|
||||||
|
{
|
||||||
|
payload[0] = masked_modifier_get();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < KEYBOARD_BITMAP_SIZE; i++) {
|
||||||
|
payload[1U + i] = ks.physical_usage_bm[i] & ks.mask_bm[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void submit_vendor_report_payload(void)
|
||||||
|
{
|
||||||
|
uint8_t payload[HID_VENDOR_PAYLOAD_SIZE];
|
||||||
|
|
||||||
|
if (active_protocol_get() != HID_PROTO_REPORT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[0] = ks.physical_modifier_bm;
|
||||||
|
memcpy(&payload[1], ks.physical_usage_bm, sizeof(ks.physical_usage_bm));
|
||||||
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_VENDOR, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 提交 HID 报告事件:
|
* 提交 HID 报告事件:
|
||||||
* - Report 协议编码为 [report_id | payload]
|
* - Report 协议编码为 [report_id | payload]
|
||||||
@@ -178,7 +213,7 @@ static void submit_hid_report(enum hid_protocol_type protocol,
|
|||||||
const uint8_t *payload,
|
const uint8_t *payload,
|
||||||
size_t payload_len)
|
size_t payload_len)
|
||||||
{
|
{
|
||||||
uint8_t report_buf[KEYBOARD_REPORT_PAYLOAD + 1U];
|
uint8_t report_buf[HID_FULL_REPORT_SIZE(HID_KBD_PAYLOAD_SIZE)];
|
||||||
|
|
||||||
if (protocol == HID_PROTO_REPORT) {
|
if (protocol == HID_PROTO_REPORT) {
|
||||||
size_t report_len = payload_len + 1U;
|
size_t report_len = payload_len + 1U;
|
||||||
@@ -199,10 +234,9 @@ static void submit_hid_report(enum hid_protocol_type protocol,
|
|||||||
static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
|
static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
|
||||||
{
|
{
|
||||||
if (protocol == HID_PROTO_REPORT) {
|
if (protocol == HID_PROTO_REPORT) {
|
||||||
uint8_t payload[KEYBOARD_REPORT_PAYLOAD];
|
uint8_t payload[HID_KBD_PAYLOAD_SIZE];
|
||||||
|
|
||||||
payload[0] = ks.modifier_bm;
|
build_masked_keyboard_payload(payload);
|
||||||
memcpy(&payload[1], ks.usage_bm, sizeof(ks.usage_bm));
|
|
||||||
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD,
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD,
|
||||||
payload, sizeof(payload));
|
payload, sizeof(payload));
|
||||||
return;
|
return;
|
||||||
@@ -212,10 +246,10 @@ static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
|
|||||||
* Boot 协议只支持 6KRO。
|
* Boot 协议只支持 6KRO。
|
||||||
* 从 usage 位图中按升序提取最多 6 个普通键,modifier 走独立字节。
|
* 从 usage 位图中按升序提取最多 6 个普通键,modifier 走独立字节。
|
||||||
*/
|
*/
|
||||||
uint8_t payload[BOOT_KEYBOARD_PAYLOAD] = { 0 };
|
uint8_t payload[HID_BOOT_KBD_PAYLOAD_SIZE] = { 0 };
|
||||||
size_t key_pos = 2;
|
size_t key_pos = 2;
|
||||||
|
|
||||||
payload[0] = ks.modifier_bm;
|
payload[0] = masked_modifier_get();
|
||||||
|
|
||||||
for (uint16_t usage = 0x04; usage <= 0x65; usage++) {
|
for (uint16_t usage = 0x04; usage <= 0x65; usage++) {
|
||||||
if (!usage_pressed(usage)) {
|
if (!usage_pressed(usage)) {
|
||||||
@@ -234,13 +268,30 @@ static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
|
|||||||
/* 组包并提交 consumer 报告(16-bit usage)。 */
|
/* 组包并提交 consumer 报告(16-bit usage)。 */
|
||||||
static void submit_consumer_report_payload(void)
|
static void submit_consumer_report_payload(void)
|
||||||
{
|
{
|
||||||
uint8_t payload[CONSUMER_PAYLOAD];
|
uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE];
|
||||||
|
|
||||||
payload[0] = ks.consumer_usage & 0xFF;
|
payload[0] = ks.consumer_usage & 0xFF;
|
||||||
payload[1] = (ks.consumer_usage >> 8) & 0xFF;
|
payload[1] = (ks.consumer_usage >> 8) & 0xFF;
|
||||||
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void submit_consumer_click_usage(uint16_t usage_id)
|
||||||
|
{
|
||||||
|
uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE];
|
||||||
|
|
||||||
|
if (active_protocol_get() == HID_PROTO_BOOT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[0] = usage_id & 0xFF;
|
||||||
|
payload[1] = (usage_id >> 8) & 0xFF;
|
||||||
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
|
||||||
|
|
||||||
|
payload[0] = 0U;
|
||||||
|
payload[1] = 0U;
|
||||||
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 处理键盘类 usage:
|
* 处理键盘类 usage:
|
||||||
* - 仅在按键状态实际变化时提交报告,避免无效重复上报。
|
* - 仅在按键状态实际变化时提交报告,避免无效重复上报。
|
||||||
@@ -251,6 +302,7 @@ static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool press
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
submit_keyboard_report_payload(active_protocol_get());
|
submit_keyboard_report_payload(active_protocol_get());
|
||||||
|
submit_vendor_report_payload();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +363,39 @@ static bool handle_hid_protocol_event(const struct hid_protocol_event *event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_vendor_mask_event(const struct hid_vendor_mask_event *event)
|
||||||
|
{
|
||||||
|
const uint8_t *data = hid_vendor_mask_event_get_data(event);
|
||||||
|
size_t size = hid_vendor_mask_event_get_size(event);
|
||||||
|
|
||||||
|
if (size != HID_VENDOR_PAYLOAD_SIZE) {
|
||||||
|
LOG_WRN("Ignore vendor mask len=%u expect=%u",
|
||||||
|
size, HID_VENDOR_PAYLOAD_SIZE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ks.mask_modifier_bm = data[0];
|
||||||
|
memcpy(ks.mask_bm, &data[1], sizeof(ks.mask_bm));
|
||||||
|
submit_keyboard_report_payload(active_protocol_get());
|
||||||
|
submit_vendor_report_payload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_qdec_step_event(const struct qdec_step_event *event)
|
||||||
|
{
|
||||||
|
int8_t step = qdec_step_event_get_step(event);
|
||||||
|
uint16_t usage_id;
|
||||||
|
|
||||||
|
if (step == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usage_id = (step > 0) ? 0x00E9U : 0x00EAU;
|
||||||
|
submit_consumer_click_usage(usage_id);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* 模块总事件分发入口。 */
|
/* 模块总事件分发入口。 */
|
||||||
static bool app_event_handler(const struct app_event_header *aeh)
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
{
|
{
|
||||||
@@ -322,12 +407,21 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
|
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_qdec_step_event(aeh)) {
|
||||||
|
return handle_qdec_step_event(cast_qdec_step_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_vendor_mask_event(aeh)) {
|
||||||
|
return handle_hid_vendor_mask_event(cast_hid_vendor_mask_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
if (is_module_state_event(aeh)) {
|
if (is_module_state_event(aeh)) {
|
||||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
/* 主模块 ready 后做一次 keymap 结构校验。 */
|
/* 主模块 ready 后做一次 keymap 结构校验。 */
|
||||||
hid_keymap_init_local();
|
hid_keymap_init_local();
|
||||||
|
keyboard_mask_init();
|
||||||
module_set_state(MODULE_STATE_READY);
|
module_set_state(MODULE_STATE_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,4 +435,6 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
|
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, hid_vendor_mask_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, qdec_step_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
|||||||
155
src/modules/qdec_module.c
Normal file
155
src/modules/qdec_module.c
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/sensor.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE qdec
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include "qdec_step_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define QDEC_DEG_PER_STEP_EVENT 36
|
||||||
|
#define QDEC_EVENT_INTERVAL_MS 20
|
||||||
|
|
||||||
|
BUILD_ASSERT(QDEC_DEG_PER_STEP_EVENT > 0, "QDEC_DEG_PER_STEP_EVENT must be positive");
|
||||||
|
BUILD_ASSERT(QDEC_EVENT_INTERVAL_MS > 0, "QDEC_EVENT_INTERVAL_MS must be positive");
|
||||||
|
|
||||||
|
struct qdec_ctx {
|
||||||
|
const struct device *dev;
|
||||||
|
struct sensor_trigger trigger;
|
||||||
|
struct k_work_delayable emit_work;
|
||||||
|
int32_t acc_deg;
|
||||||
|
bool emit_scheduled;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct qdec_ctx qdec = {
|
||||||
|
.dev = DEVICE_DT_GET(DT_NODELABEL(qdec)),
|
||||||
|
.trigger = {
|
||||||
|
.type = SENSOR_TRIG_DATA_READY,
|
||||||
|
.chan = SENSOR_CHAN_ROTATION,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void schedule_emit_work(void)
|
||||||
|
{
|
||||||
|
if (qdec.emit_scheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qdec.emit_scheduled = true;
|
||||||
|
(void)k_work_reschedule(&qdec.emit_work, K_MSEC(QDEC_EVENT_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qdec_emit_work_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
int8_t step_delta;
|
||||||
|
|
||||||
|
ARG_UNUSED(work);
|
||||||
|
|
||||||
|
qdec.emit_scheduled = false;
|
||||||
|
|
||||||
|
if ((qdec.acc_deg < QDEC_DEG_PER_STEP_EVENT) &&
|
||||||
|
(qdec.acc_deg > -QDEC_DEG_PER_STEP_EVENT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qdec.acc_deg > 0) {
|
||||||
|
step_delta = 1;
|
||||||
|
qdec.acc_deg -= QDEC_DEG_PER_STEP_EVENT;
|
||||||
|
} else {
|
||||||
|
step_delta = -1;
|
||||||
|
qdec.acc_deg += QDEC_DEG_PER_STEP_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
qdec_step_event_submit(step_delta);
|
||||||
|
|
||||||
|
if ((qdec.acc_deg >= QDEC_DEG_PER_STEP_EVENT) ||
|
||||||
|
(qdec.acc_deg <= -QDEC_DEG_PER_STEP_EVENT)) {
|
||||||
|
schedule_emit_work();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qdec_data_handler(const struct device *dev,
|
||||||
|
const struct sensor_trigger *trigger)
|
||||||
|
{
|
||||||
|
struct sensor_value rotation = {0};
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ARG_UNUSED(trigger);
|
||||||
|
|
||||||
|
err = sensor_sample_fetch_chan(dev, SENSOR_CHAN_ROTATION);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("QDEC sample fetch failed: %d", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sensor_channel_get(dev, SENSOR_CHAN_ROTATION, &rotation);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("QDEC channel get failed: %d", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qdec.acc_deg += rotation.val1;
|
||||||
|
LOG_DBG("QDEC rotation=%d acc_deg=%d", rotation.val1, qdec.acc_deg);
|
||||||
|
|
||||||
|
if ((qdec.acc_deg >= QDEC_DEG_PER_STEP_EVENT) ||
|
||||||
|
(qdec.acc_deg <= -QDEC_DEG_PER_STEP_EVENT)) {
|
||||||
|
schedule_emit_work();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qdec_init(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!device_is_ready(qdec.dev)) {
|
||||||
|
LOG_ERR("QDEC device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
qdec.acc_deg = 0;
|
||||||
|
qdec.emit_scheduled = false;
|
||||||
|
k_work_init_delayable(&qdec.emit_work, qdec_emit_work_handler);
|
||||||
|
|
||||||
|
err = sensor_trigger_set(qdec.dev, &qdec.trigger, qdec_data_handler);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("QDEC trigger set failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INF("QDEC initialized: %d deg/step, <=1 step per %d ms",
|
||||||
|
QDEC_DEG_PER_STEP_EVENT, QDEC_EVENT_INTERVAL_MS);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
int err = qdec_init();
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
} else {
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
#include "hid_report_descriptor.h"
|
#include "hid_report_descriptor.h"
|
||||||
#include "hid_boot_event.h"
|
#include "hid_boot_event.h"
|
||||||
#include "hid_protocol_event.h"
|
#include "hid_protocol_event.h"
|
||||||
#include "hid_report_event.h"
|
#include "hid_tx_done_event.h"
|
||||||
|
#include "hid_tx_event.h"
|
||||||
|
#include "hid_vendor_mask_event.h"
|
||||||
#include "keyboard_led_event.h"
|
#include "keyboard_led_event.h"
|
||||||
#include "mode_event.h"
|
#include "mode_event.h"
|
||||||
|
|
||||||
@@ -67,6 +69,11 @@ static struct usb_hid_ctx g_usb_hid = {
|
|||||||
.current_protocol = HID_PROTO_REPORT,
|
.current_protocol = HID_PROTO_REPORT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void submit_usb_tx_done(enum hid_tx_kind kind, bool success)
|
||||||
|
{
|
||||||
|
hid_tx_done_event_submit(kind, success);
|
||||||
|
}
|
||||||
|
|
||||||
USBD_DEVICE_DEFINE(new_kbd_usbd,
|
USBD_DEVICE_DEFINE(new_kbd_usbd,
|
||||||
DEVICE_DT_GET(DT_NODELABEL(usbd)),
|
DEVICE_DT_GET(DT_NODELABEL(usbd)),
|
||||||
APP_USB_VID, APP_USB_PID);
|
APP_USB_VID, APP_USB_PID);
|
||||||
@@ -158,6 +165,33 @@ static bool try_extract_led_mask(const struct device *dev,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool try_extract_vendor_mask(const struct device *dev,
|
||||||
|
uint16_t len,
|
||||||
|
const uint8_t *buf,
|
||||||
|
const uint8_t **mask_data,
|
||||||
|
size_t *mask_len)
|
||||||
|
{
|
||||||
|
if ((buf == NULL) || (len < 1U)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev != g_usb_hid.nkro.dev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[0] != REPORT_ID_VENDOR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((len - 1U) != HID_VENDOR_PAYLOAD_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*mask_data = &buf[1];
|
||||||
|
*mask_len = len - 1U;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int hid_stub_get_report(const struct device *dev,
|
static int hid_stub_get_report(const struct device *dev,
|
||||||
uint8_t type, uint8_t id,
|
uint8_t type, uint8_t id,
|
||||||
uint16_t len, uint8_t *buf)
|
uint16_t len, uint8_t *buf)
|
||||||
@@ -178,6 +212,14 @@ static int hid_stub_set_report(const struct device *dev,
|
|||||||
ARG_UNUSED(id);
|
ARG_UNUSED(id);
|
||||||
|
|
||||||
if (!should_handle_led_input_from_dev(dev)) {
|
if (!should_handle_led_input_from_dev(dev)) {
|
||||||
|
const uint8_t *mask_data;
|
||||||
|
size_t mask_len;
|
||||||
|
|
||||||
|
if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len)) {
|
||||||
|
LOG_INF("hid_stub_set_report vendor mask len=%u", mask_len);
|
||||||
|
hid_vendor_mask_event_submit(mask_data, mask_len);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +284,9 @@ static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
|
|||||||
|
|
||||||
if (iface) {
|
if (iface) {
|
||||||
iface->in_flight = false;
|
iface->in_flight = false;
|
||||||
|
submit_usb_tx_done((dev == g_usb_hid.boot.dev) ?
|
||||||
|
HID_TX_KIND_BOOT : HID_TX_KIND_REPORT,
|
||||||
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +296,14 @@ static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
|
|||||||
static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf)
|
static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf)
|
||||||
{
|
{
|
||||||
if (!should_handle_led_input_from_dev(dev)) {
|
if (!should_handle_led_input_from_dev(dev)) {
|
||||||
|
const uint8_t *mask_data;
|
||||||
|
size_t mask_len;
|
||||||
|
|
||||||
|
if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len)) {
|
||||||
|
LOG_INF("hid_stub_output_report vendor mask len=%u", mask_len);
|
||||||
|
hid_vendor_mask_event_submit(mask_data, mask_len);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,73 +649,78 @@ static bool handle_wake_up_event(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool handle_hid_boot_event(const struct hid_boot_event *event)
|
static bool handle_hid_tx_event(const struct hid_tx_event *event)
|
||||||
{
|
{
|
||||||
if (!g_usb_hid.policy.usb_mode_selected || !usb_hid_stack_is_active()) {
|
if (!g_usb_hid.policy.usb_mode_selected || !usb_hid_stack_is_active()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->kind == HID_TX_KIND_BOOT) {
|
||||||
|
const uint8_t *payload = hid_tx_event_get_data(event);
|
||||||
|
size_t payload_len = hid_tx_event_get_size(event);
|
||||||
|
int err;
|
||||||
|
|
||||||
if (g_usb_hid.current_protocol != HID_PROTO_BOOT) {
|
if (g_usb_hid.current_protocol != HID_PROTO_BOOT) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *payload = hid_boot_event_get_data(event);
|
|
||||||
size_t payload_len = hid_boot_event_get_size(event);
|
|
||||||
|
|
||||||
if (!g_usb_hid.boot.iface_ready || !g_usb_hid.boot.dev) {
|
if (!g_usb_hid.boot.iface_ready || !g_usb_hid.boot.dev) {
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (g_usb_hid.boot.in_flight) {
|
if (g_usb_hid.boot.in_flight) {
|
||||||
LOG_WRN("Drop boot report: previous report not sent");
|
LOG_WRN("Drop boot tx: previous report not sent");
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = hid_device_submit_report(g_usb_hid.boot.dev,
|
err = hid_device_submit_report(g_usb_hid.boot.dev,
|
||||||
payload_len,
|
payload_len,
|
||||||
payload);
|
payload);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_WRN("USB boot report send failed err=%d", err);
|
LOG_WRN("USB boot report send failed err=%d", err);
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
|
||||||
} else {
|
} else {
|
||||||
g_usb_hid.boot.in_flight = true;
|
g_usb_hid.boot.in_flight = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool handle_hid_report_event(const struct hid_report_event *event)
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* USB 侧仅在 active 条件满足时发送:
|
* USB 侧仅在 active 条件满足时发送:
|
||||||
* - 当前 mode 为 USB;
|
* - 当前 mode 为 USB;
|
||||||
* - USB HID 栈已启用且对应接口 ready。
|
* - USB HID 栈已启用且对应接口 ready。
|
||||||
*/
|
*/
|
||||||
if (!g_usb_hid.policy.usb_mode_selected || !usb_hid_stack_is_active()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_usb_hid.current_protocol != HID_PROTO_REPORT) {
|
if (g_usb_hid.current_protocol != HID_PROTO_REPORT) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *data = hid_report_event_get_data(event);
|
const uint8_t *data = hid_tx_event_get_data(event);
|
||||||
size_t data_len = hid_report_event_get_size(event);
|
size_t data_len = hid_tx_event_get_size(event);
|
||||||
uint8_t report_id;
|
uint8_t report_id;
|
||||||
|
|
||||||
if (data_len < 1U) {
|
if (data_len < 1U) {
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
report_id = data[0];
|
report_id = data[0];
|
||||||
|
|
||||||
if (!g_usb_hid.nkro.iface_ready || !g_usb_hid.nkro.dev) {
|
if (!g_usb_hid.nkro.iface_ready || !g_usb_hid.nkro.dev) {
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((report_id != REPORT_ID_KEYBOARD) && (report_id != REPORT_ID_CONSUMER)) {
|
if ((report_id != REPORT_ID_KEYBOARD) &&
|
||||||
|
(report_id != REPORT_ID_CONSUMER) &&
|
||||||
|
(report_id != REPORT_ID_VENDOR)) {
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (g_usb_hid.nkro.in_flight) {
|
if (g_usb_hid.nkro.in_flight) {
|
||||||
LOG_WRN("Drop report id=0x%02x: previous report not sent", report_id);
|
LOG_WRN("Drop tx report id=0x%02x: previous report not sent", report_id);
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,6 +728,7 @@ static bool handle_hid_report_event(const struct hid_report_event *event)
|
|||||||
int err = hid_device_submit_report(g_usb_hid.nkro.dev, data_len, data);
|
int err = hid_device_submit_report(g_usb_hid.nkro.dev, data_len, data);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
|
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
|
||||||
|
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
|
||||||
} else {
|
} else {
|
||||||
g_usb_hid.nkro.in_flight = true;
|
g_usb_hid.nkro.in_flight = true;
|
||||||
}
|
}
|
||||||
@@ -695,12 +754,8 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
return handle_wake_up_event();
|
return handle_wake_up_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_hid_report_event(aeh)) {
|
if (is_hid_tx_event(aeh)) {
|
||||||
return handle_hid_report_event(cast_hid_report_event(aeh));
|
return handle_hid_tx_event(cast_hid_tx_event(aeh));
|
||||||
}
|
|
||||||
|
|
||||||
if (is_hid_boot_event(aeh)) {
|
|
||||||
return handle_hid_boot_event(cast_hid_boot_event(aeh));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__ASSERT_NO_MSG(false);
|
__ASSERT_NO_MSG(false);
|
||||||
@@ -712,5 +767,4 @@ APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
|||||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_tx_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user