Compare commits

..

5 Commits

Author SHA1 Message Date
7e0f224ec8 docs(ble_bond): 添加自定义BLE Bond多身份连接设计说明文档
新增详细的自定义BLE Bond设计文档,涵盖多身份连接场景的支持说明。
文档内容包括:CAF自带BLE Bond与自定义BLE Bond的差异对比、架构图解、
多身份实现的关键工作点、实现注意事项以及推荐的分层设计方案。
2026-03-20 15:54:21 +08:00
a46b7ad8b8 feat(usb_hid): 支持HID供应商报告类型并增加输出报告大小
支持HID供应商特定报告类型的处理,在USB HID模块中添加了对REPORT_ID_VENDOR
的支持,并相应地修改了设备覆盖文件中的输出报告大小配置。

功能变更包括:
- 在app.overlay中将out-report-size从8增加到31以支持更大的报告
- 添加hid_vendor_mask_event.h头文件引入
- 实现try_extract_vendor_mask函数用于解析供应商特定掩码数据
- 在hid_stub_set_report和hid_stub_output_report函数中添加供应商掩码处理逻辑
- 更新handle_hid_tx_event函数以允许REPORT_ID_VENDOR类型的报告
2026-03-20 15:50:20 +08:00
3ff9f8c6fa feat(hid): 添加Vendor报告类型支持键盘掩码功能
- 新增REPORT_ID_VENDOR报告ID和相关常量定义
- 在HID报告描述符中添加Vendor页面的输入输出集合定义
- 更新BLE HID服务配置以支持3个输入报告和2个输出报告
- 实现hid_vendor_mask_event事件用于处理Vendor掩码数据
- 修改keyboard模块以支持物理状态和掩码状态分离
- 添加vendor_output_report_handler处理来自主机的掩码更新
- 更新CMakeLists.txt包含新的事件源文件
- 修改hid_tx_manager以支持Vendor报告的发送管理
2026-03-20 15:17:47 +08:00
9b29910299 feat(hid_tx_manager): 重构HID传输管理器以支持报告状态跟踪
- 引入原子标志位替换布尔变量,提高线程安全性
- 使用消息队列替代循环缓冲区实现传输队列
- 添加boot和NKRO报告状态管理功能
- 实现脏标记机制优化报告发送流程
- 改进传输完成事件处理逻辑
2026-03-20 13:47:54 +08:00
579dc35a36 feat: 添加HID传输管理和旋转编码器支持
添加了hid_tx_event和hid_tx_done_event事件类型,用于统一管理HID
数据传输,并在ble_hid_module和usb_hid_module中实现相应的处理逻辑。

新增qdec_module模块来处理旋转编码器输入,将旋转事件转换为步进事件,
并在keyboard_module中集成音量控制功能。

更新CMakeLists.txt以包含新的事件和模块文件,在app.overlay中启
用qdec设备,并在prj.conf中添加SENSOR配置。

BREAKING CHANGE: 将原有的hid_boot_event和hid_report_event替换
为统一的hid_tx_event事件系统。
2026-03-20 11:04:48 +08:00
18 changed files with 1409 additions and 118 deletions

View File

@@ -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
) )

View File

@@ -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";
};

View 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 和重连应被视为正常流程,而不是异常

View File

@@ -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, \
} }

View File

@@ -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

View 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));

View 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
View 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
View 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__ */

View 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));

View 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__ */

View 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));

View 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__ */

View File

@@ -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);

View 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);

View File

@@ -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
View 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);

View File

@@ -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);