feat: 添加HID主机命令和主题颜色功能

- 添加了新的事件类型包括display_theme_event、hid_host_ack_event、
  hid_host_command_error_event和hid_host_command_event用于处理HID主机命令

- 在CMakeLists.txt中添加了新的源文件,包括显示主题事件和HID主机命令相关模块

- 实现了HID主机命令协议定义,包括主题颜色和时间同步命令

- 在BLE HID模块中添加了对供应商命令报告的支持,增加新的报告ID用于主机命令传输

- 扩展了HID传输路由机制,支持USB和BLE双通道传输

- 实现了显示模块的主题颜色存储功能,支持通过settings持久化保存主题颜色

- 添加了完整的BLE时间同步服务PC主机接入文档

- 修改了电池采样逻辑,增加2秒延迟以等待电池电压稳定
This commit is contained in:
2026-03-30 15:57:38 +08:00
parent 277462a8fe
commit 2c7eae4de1
25 changed files with 1157 additions and 94 deletions

View File

@@ -27,7 +27,11 @@ target_sources(app PRIVATE
src/main.c src/main.c
src/events/battery_status_event.c src/events/battery_status_event.c
src/events/config_event.c src/events/config_event.c
src/events/display_theme_event.c
src/events/hid_boot_event.c src/events/hid_boot_event.c
src/events/hid_host_ack_event.c
src/events/hid_host_command_error_event.c
src/events/hid_host_command_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_done_event.c
@@ -41,9 +45,9 @@ target_sources(app PRIVATE
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_time_sync_module.c
src/modules/ble_slot_ctrl_module.c src/modules/ble_slot_ctrl_module.c
src/modules/display_module.c src/modules/display_module.c
src/modules/hid_host_command_module.c
src/modules/hid_tx_manager_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

View File

@@ -0,0 +1,281 @@
# BLE 时间同步服务 PC 上位机接入文档
## 1. 概述
当前项目实现了一套独立的 BLE 时间同步服务,供 PC 上位机在蓝牙连接后向键盘写入当前 UTC 时间、时区和时间精度。
这套服务的设计目标是:
- 不走 HID 报告通道,避免与键盘输入链路耦合。
- BLE 侧只负责协议适配,实际时间状态统一交给 `time_manager` 管理。
- 主机只负责“下发时间”,设备不通过此服务回读时间,也不通过 notify 返回确认。
结论上,这是一条:
- 自定义 GATT Service
- 单个可写 Characteristic
- 仅支持写入
- 需要加密连接
的单向时间同步通道。
## 2. GATT 定义
### 2.1 Service UUID
`0b7f5000-38d2-4f62-8f6f-36c4fd73a110`
### 2.2 Characteristic UUID
`0b7f5001-38d2-4f62-8f6f-36c4fd73a110`
### 2.3 Characteristic 属性
- `Write`
- `Write Without Response`
### 2.4 Characteristic 权限
- `Write Encrypted`
也就是说,上位机在写入前必须先完成配对/加密。未加密链路下,写入会被 GATT 层拒绝。
## 3. 设备端处理逻辑
### 3.1 依赖条件
设备端只有在以下两个条件同时满足时,才接受时间写入:
- BLE 栈已经 ready
- `time_manager` 已经 ready
如果模块尚未 ready写入会返回 ATT 错误。
### 3.2 写入成功后的行为
设备端收到合法 payload 后会:
1. 校验版本、长度和 flags。
2. 解析 `utc_ms``timezone_min``accuracy_ms`
3. 自动把同步来源标记为 `BLE`
4. 投递 `time_sync_event``time_manager`
5. `time_manager` 更新运行时钟状态,并延迟异步写入 settings。
注意:
- 当前 BLE 时间同步服务没有 `Read``Notify` 能力。
- 主机拿到 GATT 写成功,只能说明设备接受了这次写入。
- 设备不会通过这个服务主动回传“当前时间”。
## 4. 数据包格式
### 4.1 总长度
固定 `16` 字节。
### 4.2 字段布局
| 偏移 | 长度 | 字段名 | 类型 | 字节序 |
| --- | --- | --- | --- | --- |
| 0 | 1 | `version` | `uint8` | - |
| 1 | 1 | `flags` | `uint8` | - |
| 2 | 2 | `timezone_min` | `int16` | little-endian |
| 4 | 8 | `utc_ms` | `uint64` | little-endian |
| 12 | 4 | `accuracy_ms` | `uint32` | little-endian |
### 4.3 当前协议版本
- `version = 1`
### 4.4 flags 定义
当前只定义 1 个 bit
- `BIT(0)` = `TIMEZONE_VALID`
因此当前版本必须满足:
- `flags & 0x01 != 0`
推荐主机固定写:
- `flags = 0x01`
## 5. 字段语义
### 5.1 `timezone_min`
单位:分钟。
含义:本地时区相对 UTC 的偏移。
示例:
- 中国标准时间 UTC+8 -> `480`
- 印度 UTC+5:30 -> `330`
- UTC-5 -> `-300`
设备端当前接受范围:
- `-1440 ~ +1440`
### 5.2 `utc_ms`
单位:毫秒。
含义UTC 时间戳,不带本地时区偏移。
要求:
- 必须大于 `0`
### 5.3 `accuracy_ms`
单位:毫秒。
含义:本次时间来源的估计精度。
建议:
- 若上位机没有可靠精度信息,可直接写 `0`
- 若来自系统时间并已做网络对时,也可以给一个保守值,例如 `50``100`
## 6. 主机写入建议
### 6.1 推荐使用 Write Request
虽然设备同时支持:
- Write
- Write Without Response
但 PC 上位机侧建议优先使用 **Write Request带响应写**,原因是:
- 更容易拿到 ATT 层成功/失败结果
- 更方便在开发阶段定位协议错误
- 更适合作为配置/同步类命令
### 6.2 推荐时序
1. 扫描并连接设备。
2. 完成配对/加密。
3. 发现时间同步 Service 和 Characteristic。
4. 组包为固定 16 字节 payload。
5. 执行一次带响应写入。
6. 若写成功,可视为设备已接受此次同步请求。
## 7. Python 示例
下面示例基于 `bleak`,演示如何向设备写入当前系统时间。
```python
import asyncio
import struct
import time
from bleak import BleakClient
TIME_SYNC_CHAR_UUID = "0b7f5001-38d2-4f62-8f6f-36c4fd73a110"
def build_time_sync_payload(timezone_min: int, accuracy_ms: int = 0) -> bytes:
version = 1
flags = 0x01 # TIMEZONE_VALID
utc_ms = int(time.time() * 1000)
return struct.pack("<BBhQI", version, flags, timezone_min, utc_ms, accuracy_ms)
async def main(address: str):
payload = build_time_sync_payload(timezone_min=480, accuracy_ms=50)
async with BleakClient(address) as client:
await client.write_gatt_char(
TIME_SYNC_CHAR_UUID,
payload,
response=True,
)
print("time sync write done")
if __name__ == "__main__":
asyncio.run(main("XX:XX:XX:XX:XX:XX"))
```
说明:
- `struct.pack("<BBhQI", ...)` 对应协议定义的 little-endian 格式。
- `timezone_min=480` 表示 UTC+8。
- `response=True` 表示使用带响应写入。
## 8. 常见错误与排查
### 8.1 写入被拒绝
常见原因:
- 还没有完成配对/加密
- 写入长度不是 16 字节
- `version != 1`
- `flags` 未设置 `TIMEZONE_VALID`
- 使用了 prepare write / long write
### 8.2 写入成功但设备时间没更新
优先检查:
- 设备日志是否出现 `Accepted BLE time sync ...`
- 设备日志是否出现 `Time synchronized src=1 ...`
- 上位机是否错误地把本地时间直接当成 UTC 写入
### 8.3 时区显示错误
最常见原因:
- 主机把“本地毫秒时间戳”写进了 `utc_ms`
- 同时又传了 `timezone_min`
正确做法是:
- `utc_ms` 始终写 UTC 毫秒
- 本地时区单独放在 `timezone_min`
## 9. ATT 错误码语义
设备端当前会返回的典型 ATT 错误如下:
- `BT_ATT_ERR_UNLIKELY`
- 模块尚未 ready例如 BLE 栈或 `time_manager` 还未初始化完成
- `BT_ATT_ERR_INVALID_OFFSET`
- 非零 offset 写入
- `BT_ATT_ERR_ATTRIBUTE_NOT_LONG`
- 使用 prepare write / 长写流程
- `BT_ATT_ERR_VALUE_NOT_ALLOWED`
- payload 长度、版本、flags 或字段内容不合法
## 10. 当前服务边界
当前版本仅支持:
- 主机 -> 设备 单向校时
当前不支持:
- BLE 读回当前设备时间
- 校时结果通知
- 历史同步记录查询
- DST 单独字段
- 通过 payload 指定同步来源
同步来源在设备端固定记为:
- `TIME_SYNC_SOURCE_BLE`
## 11. 对接建议
对 PC 上位机实现建议如下:
- 首选带响应写入,不要默认用 write without response
- 时间戳统一使用 UTC 毫秒
- 时区单独使用分钟偏移
- 每次建立加密连接后可主动同步一次时间
- 若 PC 有系统授时状态,可把估计精度填入 `accuracy_ms`

View File

@@ -0,0 +1,16 @@
#ifndef HID_HOST_COMMAND_PROTOCOL_H__
#define HID_HOST_COMMAND_PROTOCOL_H__
#include <stdint.h>
#define HID_HOST_CMD_DATA_SIZE 8U
#define HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE (1U + HID_HOST_CMD_DATA_SIZE)
#define HID_HOST_CMD_ACK_PAYLOAD_SIZE 1U
#define HID_HOST_CMD_ID_THEME_COLOR 0x01U
#define HID_HOST_CMD_ID_TIME_SYNC 0x02U
#define HID_HOST_CMD_THEME_PARAM_SIZE 3U
#define HID_HOST_CMD_TIME_SYNC_PARAM_SIZE 8U
#endif /* HID_HOST_COMMAND_PROTOCOL_H__ */

9
inc/hid_host_transport.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef HID_HOST_TRANSPORT_H__
#define HID_HOST_TRANSPORT_H__
enum hid_host_transport {
HID_HOST_TRANSPORT_USB = 0,
HID_HOST_TRANSPORT_BLE,
};
#endif /* HID_HOST_TRANSPORT_H__ */

View File

@@ -3,11 +3,14 @@
#include <zephyr/usb/class/usbd_hid.h> #include <zephyr/usb/class/usbd_hid.h>
#include "hid_host_command_protocol.h"
/* 与 HID Report Map 对齐的 Report ID。 */ /* 与 HID Report Map 对齐的 Report ID。 */
enum { enum {
REPORT_ID_KEYBOARD = 1, REPORT_ID_KEYBOARD = 1,
REPORT_ID_CONSUMER = 3, REPORT_ID_CONSUMER = 3,
REPORT_ID_VENDOR = 4, REPORT_ID_VENDOR = 4,
REPORT_ID_VENDOR_CMD = 5,
}; };
#define HID_KBD_USAGE_MAX 0x00E7U #define HID_KBD_USAGE_MAX 0x00E7U
@@ -18,6 +21,7 @@ enum {
#define HID_BOOT_KBD_PAYLOAD_SIZE 8U #define HID_BOOT_KBD_PAYLOAD_SIZE 8U
#define HID_CONSUMER_PAYLOAD_SIZE 2U #define HID_CONSUMER_PAYLOAD_SIZE 2U
#define HID_VENDOR_PAYLOAD_SIZE HID_KBD_PAYLOAD_SIZE #define HID_VENDOR_PAYLOAD_SIZE HID_KBD_PAYLOAD_SIZE
#define HID_VENDOR_ACK_PAYLOAD_SIZE HID_HOST_CMD_ACK_PAYLOAD_SIZE
#define HID_KBD_LED_PAYLOAD_SIZE 1U #define HID_KBD_LED_PAYLOAD_SIZE 1U
#define HID_FULL_REPORT_SIZE(payload) (1U + (payload)) #define HID_FULL_REPORT_SIZE(payload) (1U + (payload))
@@ -33,9 +37,10 @@ enum {
* 键盘(NKRO) + Consumer + Vendor 的复合 Report 描述符: * 键盘(NKRO) + Consumer + Vendor 的复合 Report 描述符:
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义, * - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
* 避免两边手写常量后长期演进出现不一致。 * 避免两边手写常量后长期演进出现不一致。
* - Vendor Report 复用 NKRO 键盘状态相同的 payload 结构: * - Report ID 0x04 继续复用 NKRO payload承载私有状态/遮罩语义。
* [modifier(1B) | usage_bitmap(29B)]。 * - Report ID 0x05 预留给“主机命令 + 设备 ACK”通道
* 这样主机可以下发“屏蔽遮罩”,设备也可以上报“真实键盘状态”。 * - Output payload 固定 9 字节:[cmd(1) | data(8)]
* - Input payload 固定 1 字节:[cmd]
*/ */
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \ #define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
{ \ { \
@@ -105,6 +110,22 @@ enum {
HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \ HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \
HID_USAGE(0x02U), \ HID_USAGE(0x02U), \
HID_OUTPUT(0x02), \ HID_OUTPUT(0x02), \
HID_END_COLLECTION, \
\
/* Vendor 页(0xFF01):主机命令写入 + 设备 ACK 返回。 */ \
HID_USAGE_PAGE16(0x01, 0xFF), \
HID_USAGE(0x05U), \
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
HID_REPORT_ID(REPORT_ID_VENDOR_CMD), \
HID_LOGICAL_MIN8(0), \
HID_LOGICAL_MAX16(0xFF, 0x00), \
HID_REPORT_SIZE(8), \
HID_REPORT_COUNT(HID_VENDOR_ACK_PAYLOAD_SIZE), \
HID_USAGE(0x05U), \
HID_INPUT(0x02), \
HID_REPORT_COUNT(HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE), \
HID_USAGE(0x05U), \
HID_OUTPUT(0x02), \
HID_END_COLLECTION, \ HID_END_COLLECTION, \
} }

View File

@@ -18,6 +18,7 @@ enum time_sync_source {
TIME_SYNC_SOURCE_BLE, TIME_SYNC_SOURCE_BLE,
TIME_SYNC_SOURCE_USB, TIME_SYNC_SOURCE_USB,
TIME_SYNC_SOURCE_MANUAL, TIME_SYNC_SOURCE_MANUAL,
TIME_SYNC_SOURCE_HID,
}; };
/* /*

View File

@@ -52,8 +52,8 @@ 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=40 CONFIG_BT_HIDS_ATTR_MAX=40
CONFIG_BT_HIDS_INPUT_REP_MAX=3 CONFIG_BT_HIDS_INPUT_REP_MAX=4
CONFIG_BT_HIDS_OUTPUT_REP_MAX=2 CONFIG_BT_HIDS_OUTPUT_REP_MAX=3
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

View File

@@ -0,0 +1,31 @@
#include "display_theme_event.h"
static void log_display_theme_event(const struct app_event_header *aeh)
{
const struct display_theme_event *event = cast_display_theme_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "rgb=(%u,%u,%u)",
event->red, event->green, event->blue);
}
static void profile_display_theme_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct display_theme_event *event = cast_display_theme_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->red);
nrf_profiler_log_encode_uint8(buf, event->green);
nrf_profiler_log_encode_uint8(buf, event->blue);
}
APP_EVENT_INFO_DEFINE(display_theme_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("red", "green", "blue"),
profile_display_theme_event);
APP_EVENT_TYPE_DEFINE(display_theme_event,
log_display_theme_event,
&display_theme_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,30 @@
#ifndef DISPLAY_THEME_EVENT_H__
#define DISPLAY_THEME_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct display_theme_event {
struct app_event_header header;
uint8_t red;
uint8_t green;
uint8_t blue;
};
APP_EVENT_TYPE_DECLARE(display_theme_event);
static inline void display_theme_event_submit(uint8_t red,
uint8_t green,
uint8_t blue)
{
struct display_theme_event *event = new_display_theme_event();
event->red = red;
event->green = green;
event->blue = blue;
APP_EVENT_SUBMIT(event);
}
#endif /* DISPLAY_THEME_EVENT_H__ */

View File

@@ -0,0 +1,29 @@
#include "hid_host_ack_event.h"
static void log_hid_host_ack_event(const struct app_event_header *aeh)
{
const struct hid_host_ack_event *event = cast_hid_host_ack_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport=%u cmd=0x%02x",
event->transport, event->cmd);
}
static void profile_hid_host_ack_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_ack_event *event = cast_hid_host_ack_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
}
APP_EVENT_INFO_DEFINE(hid_host_ack_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd"),
profile_hid_host_ack_event);
APP_EVENT_TYPE_DEFINE(hid_host_ack_event,
log_hid_host_ack_event,
&hid_host_ack_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,29 @@
#ifndef HID_HOST_ACK_EVENT_H__
#define HID_HOST_ACK_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_host_transport.h"
struct hid_host_ack_event {
struct app_event_header header;
enum hid_host_transport transport;
uint8_t cmd;
};
APP_EVENT_TYPE_DECLARE(hid_host_ack_event);
static inline void hid_host_ack_event_submit(enum hid_host_transport transport,
uint8_t cmd)
{
struct hid_host_ack_event *event = new_hid_host_ack_event();
event->transport = transport;
event->cmd = cmd;
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_ACK_EVENT_H__ */

View File

@@ -0,0 +1,36 @@
#include "hid_host_command_error_event.h"
static void log_hid_host_command_error_event(const struct app_event_header *aeh)
{
const struct hid_host_command_error_event *event =
cast_hid_host_command_error_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"transport=%u cmd=0x%02x reason=%u",
event->transport,
event->cmd,
event->reason);
}
static void profile_hid_host_command_error_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_command_error_event *event =
cast_hid_host_command_error_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->reason);
}
APP_EVENT_INFO_DEFINE(hid_host_command_error_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd", "reason"),
profile_hid_host_command_error_event);
APP_EVENT_TYPE_DEFINE(hid_host_command_error_event,
log_hid_host_command_error_event,
&hid_host_command_error_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,41 @@
#ifndef HID_HOST_COMMAND_ERROR_EVENT_H__
#define HID_HOST_COMMAND_ERROR_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_host_transport.h"
enum hid_host_command_error_reason {
HID_HOST_COMMAND_ERROR_UNKNOWN_CMD = 0,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH,
HID_HOST_COMMAND_ERROR_INVALID_PARAM,
HID_HOST_COMMAND_ERROR_NOT_READY,
};
struct hid_host_command_error_event {
struct app_event_header header;
enum hid_host_transport transport;
enum hid_host_command_error_reason reason;
uint8_t cmd;
};
APP_EVENT_TYPE_DECLARE(hid_host_command_error_event);
static inline void hid_host_command_error_event_submit(
enum hid_host_transport transport,
uint8_t cmd,
enum hid_host_command_error_reason reason)
{
struct hid_host_command_error_event *event =
new_hid_host_command_error_event();
event->transport = transport;
event->cmd = cmd;
event->reason = reason;
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_COMMAND_ERROR_EVENT_H__ */

View File

@@ -0,0 +1,47 @@
#include "hid_host_command_event.h"
static const char *transport_name(enum hid_host_transport transport)
{
switch (transport) {
case HID_HOST_TRANSPORT_USB:
return "usb";
case HID_HOST_TRANSPORT_BLE:
return "ble";
default:
return "unknown";
}
}
static void log_hid_host_command_event(const struct app_event_header *aeh)
{
const struct hid_host_command_event *event =
cast_hid_host_command_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport=%s cmd=0x%02x data_len=%u",
transport_name(event->transport),
event->cmd,
event->data_len);
}
static void profile_hid_host_command_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_command_event *event =
cast_hid_host_command_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
nrf_profiler_log_encode_uint8(buf, event->data_len);
}
APP_EVENT_INFO_DEFINE(hid_host_command_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd", "data_len"),
profile_hid_host_command_event);
APP_EVENT_TYPE_DEFINE(hid_host_command_event,
log_hid_host_command_event,
&hid_host_command_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,44 @@
#ifndef HID_HOST_COMMAND_EVENT_H__
#define HID_HOST_COMMAND_EVENT_H__
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include <zephyr/sys/util.h>
#include "hid_host_command_protocol.h"
#include "hid_host_transport.h"
struct hid_host_command_event {
struct app_event_header header;
enum hid_host_transport transport;
uint8_t cmd;
uint8_t data_len;
uint8_t data[HID_HOST_CMD_DATA_SIZE];
};
APP_EVENT_TYPE_DECLARE(hid_host_command_event);
static inline void hid_host_command_event_submit(enum hid_host_transport transport,
uint8_t cmd,
const uint8_t *data,
size_t data_len)
{
struct hid_host_command_event *event = new_hid_host_command_event();
size_t copy_len = MIN(data_len, (size_t)HID_HOST_CMD_DATA_SIZE);
event->transport = transport;
event->cmd = cmd;
event->data_len = (uint8_t)copy_len;
memset(event->data, 0, sizeof(event->data));
if ((copy_len > 0U) && (data != NULL)) {
memcpy(event->data, data, copy_len);
}
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_COMMAND_EVENT_H__ */

View File

@@ -11,8 +11,12 @@ static void log_hid_tx_event(const struct app_event_header *aeh)
payload_len = event->dyndata.size - 1U; payload_len = event->dyndata.size - 1U;
} }
APP_EVENT_MANAGER_LOG(aeh, "kind=%u report_id=0x%02x payload_len=%u", APP_EVENT_MANAGER_LOG(aeh,
event->kind, report_id, payload_len); "kind=%u route=%u report_id=0x%02x payload_len=%u",
event->kind,
event->route,
report_id,
payload_len);
} }
static void profile_hid_tx_event(struct log_event_buf *buf, static void profile_hid_tx_event(struct log_event_buf *buf,
@@ -26,15 +30,17 @@ static void profile_hid_tx_event(struct log_event_buf *buf,
} }
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind); nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->route);
nrf_profiler_log_encode_uint8(buf, report_id); nrf_profiler_log_encode_uint8(buf, report_id);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size); nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
} }
APP_EVENT_INFO_DEFINE(hid_tx_event, APP_EVENT_INFO_DEFINE(hid_tx_event,
ENCODE(NRF_PROFILER_ARG_U8, ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16), NRF_PROFILER_ARG_U16),
ENCODE("kind", "report_id", "len"), ENCODE("kind", "route", "report_id", "len"),
profile_hid_tx_event); profile_hid_tx_event);
APP_EVENT_TYPE_DEFINE(hid_tx_event, APP_EVENT_TYPE_DEFINE(hid_tx_event,

View File

@@ -13,21 +13,30 @@ enum hid_tx_kind {
HID_TX_KIND_REPORT, HID_TX_KIND_REPORT,
}; };
enum hid_tx_route {
HID_TX_ROUTE_AUTO = 0,
HID_TX_ROUTE_USB,
HID_TX_ROUTE_BLE,
};
struct hid_tx_event { struct hid_tx_event {
struct app_event_header header; struct app_event_header header;
enum hid_tx_kind kind; enum hid_tx_kind kind;
enum hid_tx_route route;
struct event_dyndata dyndata; struct event_dyndata dyndata;
}; };
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_event); APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_event);
static inline void hid_tx_event_submit(enum hid_tx_kind kind, static inline void hid_tx_event_submit_routed(enum hid_tx_kind kind,
const uint8_t *data, enum hid_tx_route route,
size_t size) const uint8_t *data,
size_t size)
{ {
struct hid_tx_event *event = new_hid_tx_event(size); struct hid_tx_event *event = new_hid_tx_event(size);
event->kind = kind; event->kind = kind;
event->route = route;
if ((size > 0U) && (data != NULL)) { if ((size > 0U) && (data != NULL)) {
memcpy(event->dyndata.data, data, size); memcpy(event->dyndata.data, data, size);
} }
@@ -35,6 +44,13 @@ static inline void hid_tx_event_submit(enum hid_tx_kind kind,
APP_EVENT_SUBMIT(event); APP_EVENT_SUBMIT(event);
} }
static inline void hid_tx_event_submit(enum hid_tx_kind kind,
const uint8_t *data,
size_t size)
{
hid_tx_event_submit_routed(kind, HID_TX_ROUTE_AUTO, data, size);
}
static inline const uint8_t *hid_tx_event_get_data(const struct hid_tx_event *event) static inline const uint8_t *hid_tx_event_get_data(const struct hid_tx_event *event)
{ {
return event->dyndata.data; return event->dyndata.data;
@@ -45,4 +61,9 @@ static inline size_t hid_tx_event_get_size(const struct hid_tx_event *event)
return event->dyndata.size; return event->dyndata.size;
} }
static inline enum hid_tx_route hid_tx_event_get_route(const struct hid_tx_event *event)
{
return event->route;
}
#endif /* HID_TX_EVENT_H__ */ #endif /* HID_TX_EVENT_H__ */

View File

@@ -12,6 +12,8 @@ static const char *time_sync_source_name(enum time_sync_source source)
return "usb"; return "usb";
case TIME_SYNC_SOURCE_MANUAL: case TIME_SYNC_SOURCE_MANUAL:
return "manual"; return "manual";
case TIME_SYNC_SOURCE_HID:
return "hid";
default: default:
return "unknown"; return "unknown";
} }

View File

@@ -230,7 +230,8 @@ static void battery_sampling_set_enabled(bool enable)
if (enable) if (enable)
{ {
k_work_reschedule(&battery.sample_work, K_NO_WAIT); // 延迟2s开始采样等待电池电压稳定
k_work_reschedule(&battery.sample_work, K_MSEC(2000));
} }
else else
{ {

View File

@@ -8,6 +8,7 @@
#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_host_command_event.h"
#include "hid_report_descriptor.h" #include "hid_report_descriptor.h"
#include "hid_tx_done_event.h" #include "hid_tx_done_event.h"
#include "hid_tx_event.h" #include "hid_tx_event.h"
@@ -18,8 +19,8 @@
#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 3 #define INPUT_REPORT_COUNT 4
#define OUTPUT_REPORT_COUNT 2 #define OUTPUT_REPORT_COUNT 3
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0); BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
@@ -52,6 +53,19 @@ static bool ble_hid_is_connected(void)
return ble_hid.link.conn != NULL; return ble_hid.link.conn != NULL;
} }
static bool ble_hid_should_handle_tx_event(const struct hid_tx_event *event)
{
switch (hid_tx_event_get_route(event)) {
case HID_TX_ROUTE_AUTO:
return ble_hid.policy.ble_mode_selected;
case HID_TX_ROUTE_BLE:
return true;
case HID_TX_ROUTE_USB:
default:
return false;
}
}
static bool ble_hid_is_boot_mode(void) static bool ble_hid_is_boot_mode(void)
{ {
return ble_hid.link.protocol_mode == BT_HIDS_PM_BOOT; return ble_hid.link.protocol_mode == BT_HIDS_PM_BOOT;
@@ -156,6 +170,25 @@ static void vendor_output_report_handler(struct bt_hids_rep *rep,
LOG_INF("Vendor mask updated over BLE len=%u", rep->size); LOG_INF("Vendor mask updated over BLE len=%u", rep->size);
} }
static void vendor_cmd_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_HOST_CMD_OUTPUT_PAYLOAD_SIZE)) {
return;
}
hid_host_command_event_submit(HID_HOST_TRANSPORT_BLE,
rep->data[0],
&rep->data[1],
rep->size - 1U);
LOG_INF("Vendor cmd updated over BLE cmd=0x%02x len=%u",
rep->data[0], 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();
@@ -182,6 +215,10 @@ static int hids_service_init(void)
input_report[2].size = HID_VENDOR_PAYLOAD_SIZE; input_report[2].size = HID_VENDOR_PAYLOAD_SIZE;
input_report[2].handler = report_notify_handler; input_report[2].handler = report_notify_handler;
input_report[3].id = REPORT_ID_VENDOR_CMD;
input_report[3].size = HID_VENDOR_ACK_PAYLOAD_SIZE;
input_report[3].handler = report_notify_handler;
output_report[0].id = REPORT_ID_KEYBOARD; output_report[0].id = REPORT_ID_KEYBOARD;
output_report[0].size = HID_KBD_LED_PAYLOAD_SIZE; 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;
@@ -190,6 +227,10 @@ static int hids_service_init(void)
output_report[1].size = HID_VENDOR_PAYLOAD_SIZE; output_report[1].size = HID_VENDOR_PAYLOAD_SIZE;
output_report[1].handler = vendor_output_report_handler; output_report[1].handler = vendor_output_report_handler;
output_report[2].id = REPORT_ID_VENDOR_CMD;
output_report[2].size = HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE;
output_report[2].handler = vendor_cmd_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;
@@ -227,7 +268,7 @@ static void handle_ble_peer_event(const struct ble_peer_event *event)
static bool handle_hid_tx_event(const struct hid_tx_event *event) static bool handle_hid_tx_event(const struct hid_tx_event *event)
{ {
if (!ble_hid.policy.ble_mode_selected) { if (!ble_hid_should_handle_tx_event(event)) {
return false; return false;
} }
@@ -291,6 +332,8 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event)
rep_index = 1U; rep_index = 1U;
} else if (report_id == REPORT_ID_VENDOR) { } else if (report_id == REPORT_ID_VENDOR) {
rep_index = 2U; rep_index = 2U;
} else if (report_id == REPORT_ID_VENDOR_CMD) {
rep_index = 3U;
} else { } else {
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false); hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
return false; return false;

View File

@@ -1,4 +1,5 @@
#include <errno.h> #include <errno.h>
#include <string.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
@@ -7,6 +8,7 @@
#include <zephyr/drivers/display.h> #include <zephyr/drivers/display.h>
#include <zephyr/drivers/led.h> #include <zephyr/drivers/led.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <app_event_manager.h> #include <app_event_manager.h>
#include <lvgl.h> #include <lvgl.h>
@@ -18,6 +20,7 @@
#include <caf/events/power_event.h> #include <caf/events/power_event.h>
#include "battery_status_event.h" #include "battery_status_event.h"
#include "display_theme_event.h"
#include "keyboard_led_event.h" #include "keyboard_led_event.h"
#include "mode_event.h" #include "mode_event.h"
#include "time_manager.h" #include "time_manager.h"
@@ -28,6 +31,8 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define DISPLAY_UPDATE_PERIOD_MS 1000 #define DISPLAY_UPDATE_PERIOD_MS 1000
#define DISPLAY_IDLE_TIMEOUT_MIN 1 #define DISPLAY_IDLE_TIMEOUT_MIN 1
#define DISPLAY_BACKLIGHT_BRIGHTNESS 100 #define DISPLAY_BACKLIGHT_BRIGHTNESS 100
#define DISPLAY_THEME_SAVE_DELAY K_SECONDS(1)
#define DISPLAY_THEME_STORAGE_KEY "theme"
#define DISPLAY_DEMO_BASE_YEAR 2026 #define DISPLAY_DEMO_BASE_YEAR 2026
#define DISPLAY_DEMO_BASE_MONTH 3 #define DISPLAY_DEMO_BASE_MONTH 3
#define DISPLAY_DEMO_BASE_DAY 27 #define DISPLAY_DEMO_BASE_DAY 27
@@ -78,10 +83,20 @@ struct display_ctx
struct display_capabilities caps; struct display_capabilities caps;
struct k_work_delayable update_work; struct k_work_delayable update_work;
struct k_work_delayable idle_work; struct k_work_delayable idle_work;
struct k_work_delayable theme_save_work;
struct display_ui_state ui; struct display_ui_state ui;
uint32_t tick_count; uint32_t tick_count;
enum display_pm_state pm_state; enum display_pm_state pm_state;
bool initialized; bool initialized;
bool theme_storage_dirty;
bool theme_storage_loaded;
};
struct display_theme_storage {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t valid_marker;
}; };
static struct display_ctx disp = { static struct display_ctx disp = {
@@ -95,6 +110,8 @@ static struct display_ctx disp = {
.pm_state = DISPLAY_PM_STATE_OFF, .pm_state = DISPLAY_PM_STATE_OFF,
}; };
static struct display_theme_storage display_theme_storage;
static const struct led_dt_spec display_backlight = static const struct led_dt_spec display_backlight =
LED_DT_SPEC_GET(DT_NODELABEL(backlight)); LED_DT_SPEC_GET(DT_NODELABEL(backlight));
@@ -107,6 +124,94 @@ static const char *const g_status_texts[DISPLAY_STATUS_COUNT] = {
static void display_refresh_all_locked(void); static void display_refresh_all_locked(void);
static int display_theme_store(const struct display_theme_storage *storage)
{
char key[] = MODULE_NAME "/" DISPLAY_THEME_STORAGE_KEY;
int err = settings_save_one(key, storage, sizeof(*storage));
if (err) {
LOG_ERR("Failed to save display theme err=%d", err);
return err;
}
LOG_INF("Stored display theme rgb=(%u,%u,%u)",
storage->red, storage->green, storage->blue);
return 0;
}
static void display_theme_set_rgb(uint8_t red,
uint8_t green,
uint8_t blue,
bool persist)
{
disp.ui.theme_color = lv_color_make(red, green, blue);
if (persist) {
display_theme_storage.red = red;
display_theme_storage.green = green;
display_theme_storage.blue = blue;
display_theme_storage.valid_marker = 1U;
disp.theme_storage_loaded = true;
disp.theme_storage_dirty = true;
k_work_reschedule(&disp.theme_save_work, DISPLAY_THEME_SAVE_DELAY);
}
}
static void display_theme_apply_loaded_storage(void)
{
if (!disp.theme_storage_loaded ||
(display_theme_storage.valid_marker != 1U)) {
return;
}
display_theme_set_rgb(display_theme_storage.red,
display_theme_storage.green,
display_theme_storage.blue,
false);
}
static void display_theme_save_work_fn(struct k_work *work)
{
struct display_theme_storage storage;
ARG_UNUSED(work);
if (!disp.theme_storage_dirty || !disp.theme_storage_loaded) {
return;
}
disp.theme_storage_dirty = false;
storage = display_theme_storage;
(void)display_theme_store(&storage);
}
static int settings_set(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t rc;
if (strcmp(key, DISPLAY_THEME_STORAGE_KEY) != 0) {
return 0;
}
if (len_rd != sizeof(display_theme_storage)) {
disp.theme_storage_loaded = false;
return 0;
}
rc = read_cb(cb_arg, &display_theme_storage, sizeof(display_theme_storage));
disp.theme_storage_loaded = (rc == sizeof(display_theme_storage));
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(display,
MODULE_NAME,
NULL,
settings_set,
NULL,
NULL);
static void display_schedule_update(k_timeout_t delay) static void display_schedule_update(k_timeout_t delay)
{ {
#ifdef CONFIG_LV_Z_RUN_LVGL_ON_WORKQUEUE #ifdef CONFIG_LV_Z_RUN_LVGL_ON_WORKQUEUE
@@ -166,6 +271,12 @@ static void display_sleep(void)
(void)k_work_cancel_delayable(&disp.update_work); (void)k_work_cancel_delayable(&disp.update_work);
(void)k_work_cancel_delayable(&disp.idle_work); (void)k_work_cancel_delayable(&disp.idle_work);
(void)k_work_cancel_delayable(&disp.theme_save_work);
if (disp.theme_storage_dirty && disp.theme_storage_loaded) {
disp.theme_storage_dirty = false;
(void)display_theme_store(&display_theme_storage);
}
err = display_blanking_on(disp.dev); err = display_blanking_on(disp.dev);
if (err) if (err)
@@ -553,7 +664,9 @@ static int display_init(void)
k_work_init_delayable(&disp.update_work, display_update_work_fn); k_work_init_delayable(&disp.update_work, display_update_work_fn);
k_work_init_delayable(&disp.idle_work, display_idle_timeout_fn); k_work_init_delayable(&disp.idle_work, display_idle_timeout_fn);
k_work_init_delayable(&disp.theme_save_work, display_theme_save_work_fn);
disp.tick_count = 0U; disp.tick_count = 0U;
display_theme_apply_loaded_storage();
err = display_blanking_off(disp.dev); err = display_blanking_off(disp.dev);
if (err) if (err)
@@ -631,6 +744,20 @@ static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
return false; return false;
} }
static bool handle_display_theme_event(const struct display_theme_event *event)
{
display_theme_set_rgb(event->red, event->green, event->blue, true);
if (!disp.initialized || !display_is_active()) {
return false;
}
lvgl_lock();
display_refresh_status_bar_locked();
lvgl_unlock();
return false;
}
/* 任意按钮事件都可点亮屏幕并重置 1 分钟空闲计时。 */ /* 任意按钮事件都可点亮屏幕并重置 1 分钟空闲计时。 */
static bool handle_button_event(const struct button_event *event) static bool handle_button_event(const struct button_event *event)
{ {
@@ -654,6 +781,18 @@ static bool handle_wake_up_event(void)
static bool handle_module_state_event(const struct module_state_event *event) static bool handle_module_state_event(const struct module_state_event *event)
{ {
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY)) {
display_theme_apply_loaded_storage();
if (disp.initialized && display_is_active()) {
lvgl_lock();
display_refresh_status_bar_locked();
lvgl_unlock();
}
return false;
}
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY))
return false; return false;
@@ -683,6 +822,9 @@ static bool app_event_handler(const struct app_event_header *aeh)
if (is_keyboard_led_event(aeh)) if (is_keyboard_led_event(aeh))
return handle_keyboard_led_event(cast_keyboard_led_event(aeh)); return handle_keyboard_led_event(cast_keyboard_led_event(aeh));
if (is_display_theme_event(aeh))
return handle_display_theme_event(cast_display_theme_event(aeh));
if (is_button_event(aeh)) if (is_button_event(aeh))
return handle_button_event(cast_button_event(aeh)); return handle_button_event(cast_button_event(aeh));
@@ -699,6 +841,7 @@ 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, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, battery_status_event); APP_EVENT_SUBSCRIBE(MODULE, battery_status_event);
APP_EVENT_SUBSCRIBE(MODULE, display_theme_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event); APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event); APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);
APP_EVENT_SUBSCRIBE(MODULE, button_event); APP_EVENT_SUBSCRIBE(MODULE, button_event);

View File

@@ -0,0 +1,121 @@
#include <zephyr/sys/byteorder.h>
#include <app_event_manager.h>
#define MODULE hid_host_command
#include <caf/events/module_state_event.h>
#include "display_theme_event.h"
#include "hid_host_ack_event.h"
#include "hid_host_command_error_event.h"
#include "hid_host_command_event.h"
#include "hid_host_command_protocol.h"
#include "time_manager.h"
#include "time_sync_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static bool module_ready;
static bool handle_theme_color_command(const struct hid_host_command_event *event)
{
if (event->data_len < HID_HOST_CMD_THEME_PARAM_SIZE) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH);
return false;
}
display_theme_event_submit(event->data[0], event->data[1], event->data[2]);
hid_host_ack_event_submit(event->transport, event->cmd);
return false;
}
static bool handle_time_sync_command(const struct hid_host_command_event *event)
{
struct time_sync_update update = {
.timezone_min = 0,
.accuracy_ms = 0,
.source = TIME_SYNC_SOURCE_HID,
};
if (event->data_len != HID_HOST_CMD_TIME_SYNC_PARAM_SIZE) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH);
return false;
}
if (!time_manager_is_ready()) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_NOT_READY);
return false;
}
update.utc_ms = sys_get_le64(event->data);
if (update.utc_ms == 0U) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_PARAM);
return false;
}
time_sync_event_submit(&update);
hid_host_ack_event_submit(event->transport, event->cmd);
return false;
}
static bool handle_hid_host_command_event(const struct hid_host_command_event *event)
{
if (!module_ready) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_NOT_READY);
return false;
}
switch (event->cmd) {
case HID_HOST_CMD_ID_THEME_COLOR:
return handle_theme_color_command(event);
case HID_HOST_CMD_ID_TIME_SYNC:
return handle_time_sync_command(event);
default:
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_UNKNOWN_CMD);
return false;
}
}
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)) {
module_ready = true;
module_set_state(MODULE_STATE_READY);
}
return false;
}
if (is_hid_host_command_event(aeh)) {
return handle_hid_host_command_event(cast_hid_host_command_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, hid_host_command_event);

View File

@@ -10,6 +10,7 @@
#include "hid_report_descriptor.h" #include "hid_report_descriptor.h"
#include "hid_boot_event.h" #include "hid_boot_event.h"
#include "hid_host_ack_event.h"
#include "hid_report_event.h" #include "hid_report_event.h"
#include "hid_tx_done_event.h" #include "hid_tx_done_event.h"
#include "hid_tx_event.h" #include "hid_tx_event.h"
@@ -26,14 +27,15 @@ enum hid_tx_flag {
HID_TX_FLAG_IN_FLIGHT, HID_TX_FLAG_IN_FLIGHT,
HID_TX_FLAG_BOOT_VALID, HID_TX_FLAG_BOOT_VALID,
HID_TX_FLAG_BOOT_DIRTY, HID_TX_FLAG_BOOT_DIRTY,
HID_TX_FLAG_NKRO_VALID, HID_TX_FLAG_KEYBOARD_VALID,
HID_TX_FLAG_NKRO_DIRTY, HID_TX_FLAG_KEYBOARD_DIRTY,
HID_TX_FLAG_VENDOR_VALID, HID_TX_FLAG_VENDOR_VALID,
HID_TX_FLAG_VENDOR_DIRTY, HID_TX_FLAG_VENDOR_DIRTY,
}; };
struct hid_tx_item { struct hid_tx_item {
enum hid_tx_kind kind; enum hid_tx_kind kind;
enum hid_tx_route route;
size_t len; size_t len;
uint8_t data[HID_TX_MAX_DATA]; uint8_t data[HID_TX_MAX_DATA];
}; };
@@ -41,7 +43,7 @@ struct hid_tx_item {
struct hid_tx_ctx { struct hid_tx_ctx {
atomic_t flags; atomic_t flags;
struct hid_tx_item boot_state; struct hid_tx_item boot_state;
struct hid_tx_item nkro_state; struct hid_tx_item keyboard_state;
struct hid_tx_item vendor_state; struct hid_tx_item vendor_state;
struct hid_tx_item inflight_item; struct hid_tx_item inflight_item;
mode_type_t active_mode; mode_type_t active_mode;
@@ -51,10 +53,13 @@ static struct hid_tx_ctx tx = {
.active_mode = MODE_TYPE_COUNT, .active_mode = MODE_TYPE_COUNT,
}; };
K_MSGQ_DEFINE(hid_tx_queue_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4); K_MSGQ_DEFINE(hid_tx_consumer_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(hid_tx_ack_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(hid_tx_misc_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
static bool hid_tx_item_store(struct hid_tx_item *item, static bool hid_tx_item_store(struct hid_tx_item *item,
enum hid_tx_kind kind, enum hid_tx_kind kind,
enum hid_tx_route route,
const uint8_t *data, const uint8_t *data,
size_t len) size_t len)
{ {
@@ -64,6 +69,7 @@ static bool hid_tx_item_store(struct hid_tx_item *item,
} }
item->kind = kind; item->kind = kind;
item->route = route;
item->len = len; item->len = len;
if ((len > 0U) && (data != NULL)) { if ((len > 0U) && (data != NULL)) {
memcpy(item->data, data, len); memcpy(item->data, data, len);
@@ -72,15 +78,19 @@ static bool hid_tx_item_store(struct hid_tx_item *item,
return true; return true;
} }
static bool hid_tx_queue_push(enum hid_tx_kind kind, const uint8_t *data, size_t len) static bool hid_tx_queue_push(struct k_msgq *queue,
enum hid_tx_kind kind,
enum hid_tx_route route,
const uint8_t *data,
size_t len)
{ {
struct hid_tx_item item; struct hid_tx_item item;
if (!hid_tx_item_store(&item, kind, data, len)) { if (!hid_tx_item_store(&item, kind, route, data, len)) {
return false; return false;
} }
if (k_msgq_put(&hid_tx_queue_msgq, &item, K_NO_WAIT)) { if (k_msgq_put(queue, &item, K_NO_WAIT)) {
LOG_WRN("Drop HID tx kind=%u len=%u: queue full", kind, len); LOG_WRN("Drop HID tx kind=%u len=%u: queue full", kind, len);
return false; return false;
} }
@@ -88,11 +98,16 @@ static bool hid_tx_queue_push(enum hid_tx_kind kind, const uint8_t *data, size_t
return true; return true;
} }
static bool hid_tx_auto_route_available(void)
{
return (tx.active_mode == MODE_TYPE_USB) || (tx.active_mode == MODE_TYPE_BLE);
}
static bool hid_tx_dispatch_item(const struct hid_tx_item *item) static bool hid_tx_dispatch_item(const struct hid_tx_item *item)
{ {
tx.inflight_item = *item; tx.inflight_item = *item;
atomic_set_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT); atomic_set_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT);
hid_tx_event_submit(item->kind, item->data, item->len); hid_tx_event_submit_routed(item->kind, item->route, item->data, item->len);
return true; return true;
} }
@@ -105,33 +120,44 @@ static void dispatch_next_if_possible(void)
return; return;
} }
if ((tx.active_mode != MODE_TYPE_USB) && (tx.active_mode != MODE_TYPE_BLE)) { if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY) &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY);
(void)hid_tx_dispatch_item(&tx.keyboard_state);
return; return;
} }
if (atomic_test_bit(&tx.flags, HID_TX_FLAG_NKRO_DIRTY) && if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_NKRO_VALID)) { atomic_test_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY) &&
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_test_bit(&tx.flags, HID_TX_FLAG_BOOT_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY); atomic_clear_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY);
(void)hid_tx_dispatch_item(&tx.boot_state); (void)hid_tx_dispatch_item(&tx.boot_state);
return; return;
} }
if (!k_msgq_get(&hid_tx_queue_msgq, &item, K_NO_WAIT)) { if (hid_tx_auto_route_available() &&
!k_msgq_get(&hid_tx_consumer_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item); (void)hid_tx_dispatch_item(&item);
return; return;
} }
if (atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY) && if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY) &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID)) { atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY); atomic_clear_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
(void)hid_tx_dispatch_item(&tx.vendor_state); (void)hid_tx_dispatch_item(&tx.vendor_state);
return;
}
if (!k_msgq_get(&hid_tx_ack_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item);
return;
}
if (hid_tx_auto_route_available() &&
!k_msgq_get(&hid_tx_misc_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item);
} }
} }
@@ -156,10 +182,23 @@ static bool handle_mode_event(const struct mode_event *event)
return false; return false;
} }
static enum hid_tx_route hid_tx_route_from_transport(enum hid_host_transport transport)
{
switch (transport) {
case HID_HOST_TRANSPORT_USB:
return HID_TX_ROUTE_USB;
case HID_HOST_TRANSPORT_BLE:
return HID_TX_ROUTE_BLE;
default:
return HID_TX_ROUTE_AUTO;
}
}
static bool handle_hid_boot_request_event(const struct hid_boot_event *event) static bool handle_hid_boot_request_event(const struct hid_boot_event *event)
{ {
(void)hid_tx_item_store(&tx.boot_state, (void)hid_tx_item_store(&tx.boot_state,
HID_TX_KIND_BOOT, HID_TX_KIND_BOOT,
HID_TX_ROUTE_AUTO,
hid_boot_event_get_data(event), hid_boot_event_get_data(event),
hid_boot_event_get_size(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_VALID);
@@ -174,21 +213,51 @@ static bool handle_hid_report_request_event(const struct hid_report_event *event
size_t len = hid_report_event_get_size(event); size_t len = hid_report_event_get_size(event);
if ((len > 0U) && (data[0] == REPORT_ID_KEYBOARD)) { if ((len > 0U) && (data[0] == REPORT_ID_KEYBOARD)) {
(void)hid_tx_item_store(&tx.nkro_state, HID_TX_KIND_REPORT, data, len); (void)hid_tx_item_store(&tx.keyboard_state,
atomic_set_bit(&tx.flags, HID_TX_FLAG_NKRO_VALID); HID_TX_KIND_REPORT,
atomic_set_bit(&tx.flags, HID_TX_FLAG_NKRO_DIRTY); HID_TX_ROUTE_AUTO,
data, len);
atomic_set_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_VALID);
atomic_set_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY);
} else if ((len > 0U) && (data[0] == REPORT_ID_CONSUMER)) {
(void)hid_tx_queue_push(&hid_tx_consumer_msgq,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
} else if ((len > 0U) && (data[0] == REPORT_ID_VENDOR)) { } else if ((len > 0U) && (data[0] == REPORT_ID_VENDOR)) {
(void)hid_tx_item_store(&tx.vendor_state, HID_TX_KIND_REPORT, data, len); (void)hid_tx_item_store(&tx.vendor_state,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID); atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID);
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY); atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
} else { } else {
(void)hid_tx_queue_push(HID_TX_KIND_REPORT, data, len); (void)hid_tx_queue_push(&hid_tx_misc_msgq,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
} }
dispatch_next_if_possible(); dispatch_next_if_possible();
return true; return true;
} }
static bool handle_hid_host_ack_event(const struct hid_host_ack_event *event)
{
uint8_t report[1U + HID_VENDOR_ACK_PAYLOAD_SIZE] = {
REPORT_ID_VENDOR_CMD,
event->cmd,
};
(void)hid_tx_queue_push(&hid_tx_ack_msgq,
HID_TX_KIND_REPORT,
hid_tx_route_from_transport(event->transport),
report,
sizeof(report));
dispatch_next_if_possible();
return true;
}
static bool handle_hid_tx_done_event(const struct hid_tx_done_event *event) 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)) { if (!atomic_test_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT)) {
@@ -227,6 +296,10 @@ static bool app_event_handler(const struct app_event_header *aeh)
return handle_hid_tx_done_event(cast_hid_tx_done_event(aeh)); return handle_hid_tx_done_event(cast_hid_tx_done_event(aeh));
} }
if (is_hid_host_ack_event(aeh)) {
return handle_hid_host_ack_event(cast_hid_host_ack_event(aeh));
}
__ASSERT_NO_MSG(false); __ASSERT_NO_MSG(false);
return false; return false;
} }
@@ -236,4 +309,5 @@ APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event); APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_host_ack_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_done_event); APP_EVENT_SUBSCRIBE(MODULE, hid_tx_done_event);

View File

@@ -17,7 +17,7 @@
#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 TIME_MANAGER_SAVE_DELAY_MS 1000 #define TIME_MANAGER_SAVE_DELAY K_HOURS(24)
#define TIME_MANAGER_STORAGE_KEY "state" #define TIME_MANAGER_STORAGE_KEY "state"
/* /*
@@ -58,6 +58,7 @@ static bool time_manager_source_is_valid(enum time_sync_source source)
case TIME_SYNC_SOURCE_BLE: case TIME_SYNC_SOURCE_BLE:
case TIME_SYNC_SOURCE_USB: case TIME_SYNC_SOURCE_USB:
case TIME_SYNC_SOURCE_MANUAL: case TIME_SYNC_SOURCE_MANUAL:
case TIME_SYNC_SOURCE_HID:
return true; return true;
default: default:
return false; return false;
@@ -72,8 +73,8 @@ static bool time_manager_timezone_is_valid(int16_t timezone_min)
/* /*
* 保存工作在系统工作队列里执行: * 保存工作在系统工作队列里执行:
* - 这样 GATT 写回调和事件派发路径都只做内存更新; * - 这样同步入口只做内存更新;
* - 真正可能触发 flash 擦写的 settings_save_one 被挪到异步上下文 * - flash 写入被节流到 24 小时窗口,降低频繁校时带来的磨损
*/ */
static int time_manager_store_state(const struct time_manager_storage_data *storage) static int time_manager_store_state(const struct time_manager_storage_data *storage)
{ {
@@ -144,11 +145,13 @@ static void time_manager_apply_update(const struct time_sync_update *update)
k_spin_unlock(&time_ctx.lock, key); k_spin_unlock(&time_ctx.lock, key);
/* /*
* 保存延迟 1 秒: * 时间同步允许立即生效,但 flash 落盘不要求同步完成后立刻发生。
* - 避免未来 BLE/USB 连续校时时反复触发 flash 写入; * 这里将写入节流到 24 小时窗口:只要当前还没有待执行的保存工作,
* - 也避免在同步入口上下文里直接阻塞等待存储完成 * 就挂一个延迟保存;后续新的校时只更新内存快照,不反复重置定时器
*/ */
k_work_reschedule(&time_ctx.save_work, K_MSEC(TIME_MANAGER_SAVE_DELAY_MS)); if (!k_work_delayable_is_pending(&time_ctx.save_work)) {
k_work_schedule(&time_ctx.save_work, TIME_MANAGER_SAVE_DELAY);
}
LOG_INF("Time synchronized src=%u tz=%d utc_ms=%llu acc=%u", LOG_INF("Time synchronized src=%u tz=%d utc_ms=%llu acc=%u",
update->source, update->source,
@@ -269,37 +272,6 @@ static bool handle_time_sync_event(const struct time_sync_event *event)
return false; return false;
} }
/*
* 掉电前尽量把最后一次同步结果落盘:
* - 如果没有脏数据,立即返回;
* - 如果有待保存数据,则同步执行一次保存,降低突然掉电时的丢失概率。
*/
static bool handle_power_down_event(void)
{
struct time_manager_storage_data storage;
bool should_store;
k_spinlock_key_t key;
if (!time_manager_is_ready()) {
return false;
}
(void)k_work_cancel_delayable(&time_ctx.save_work);
key = k_spin_lock(&time_ctx.lock);
should_store = time_ctx.storage_dirty && time_ctx.has_persisted_time;
storage = time_ctx.persisted;
time_ctx.storage_dirty = false;
k_spin_unlock(&time_ctx.lock, key);
if (!should_store) {
return false;
}
(void)time_manager_store_state(&storage);
return false;
}
/* 仅在 settings_loader 完成后宣布 READY保证外部读取到的是稳定状态。 */ /* 仅在 settings_loader 完成后宣布 READY保证外部读取到的是稳定状态。 */
static bool handle_module_state_event(const struct module_state_event *event) static bool handle_module_state_event(const struct module_state_event *event)
{ {
@@ -393,10 +365,6 @@ static bool app_event_handler(const struct app_event_header *aeh)
return handle_time_sync_event(cast_time_sync_event(aeh)); return handle_time_sync_event(cast_time_sync_event(aeh));
} }
if (is_power_down_event(aeh)) {
return handle_power_down_event();
}
__ASSERT_NO_MSG(false); __ASSERT_NO_MSG(false);
return false; return false;
} }
@@ -404,4 +372,3 @@ 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, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, time_sync_event); APP_EVENT_SUBSCRIBE(MODULE, time_sync_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);

View File

@@ -14,6 +14,7 @@
#include "hid_report_descriptor.h" #include "hid_report_descriptor.h"
#include "hid_boot_event.h" #include "hid_boot_event.h"
#include "hid_host_command_event.h"
#include "hid_protocol_event.h" #include "hid_protocol_event.h"
#include "hid_tx_done_event.h" #include "hid_tx_done_event.h"
#include "hid_tx_event.h" #include "hid_tx_event.h"
@@ -106,6 +107,19 @@ static bool usb_hid_should_be_active(void)
return g_usb_hid.policy.usb_mode_selected && !g_usb_hid.policy.pm_suspended; return g_usb_hid.policy.usb_mode_selected && !g_usb_hid.policy.pm_suspended;
} }
static bool usb_hid_should_handle_tx_event(const struct hid_tx_event *event)
{
switch (hid_tx_event_get_route(event)) {
case HID_TX_ROUTE_AUTO:
return g_usb_hid.policy.usb_mode_selected;
case HID_TX_ROUTE_USB:
return true;
case HID_TX_ROUTE_BLE:
default:
return false;
}
}
static struct usb_hid_iface *usb_hid_iface_from_dev(const struct device *dev) static struct usb_hid_iface *usb_hid_iface_from_dev(const struct device *dev)
{ {
if (dev == g_usb_hid.boot.dev) if (dev == g_usb_hid.boot.dev)
@@ -205,6 +219,35 @@ static bool try_extract_vendor_mask(const struct device *dev,
return true; return true;
} }
static bool try_extract_host_command(const struct device *dev,
uint16_t len,
const uint8_t *buf,
uint8_t *cmd,
const uint8_t **data,
size_t *data_len)
{
if ((buf == NULL) || (len < 1U)) {
return false;
}
if (dev != g_usb_hid.nkro.dev) {
return false;
}
if (buf[0] != REPORT_ID_VENDOR_CMD) {
return false;
}
if ((len - 1U) != HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE) {
return false;
}
*cmd = buf[1];
*data = &buf[2];
*data_len = len - 2U;
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)
@@ -226,6 +269,9 @@ static int hid_stub_set_report(const struct device *dev,
const uint8_t *mask_data; const uint8_t *mask_data;
size_t mask_len; size_t mask_len;
uint8_t cmd;
const uint8_t *cmd_data;
size_t cmd_len;
if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len)) if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len))
{ {
@@ -234,6 +280,13 @@ static int hid_stub_set_report(const struct device *dev,
return 0; return 0;
} }
if (try_extract_host_command(dev, len, buf, &cmd, &cmd_data, &cmd_len))
{
LOG_INF("hid_stub_set_report vendor cmd=0x%02x len=%u", cmd, cmd_len);
hid_host_command_event_submit(HID_HOST_TRANSPORT_USB, cmd, cmd_data, cmd_len);
return 0;
}
if (!should_handle_led_input_from_dev(dev)) if (!should_handle_led_input_from_dev(dev))
{ {
return 0; return 0;
@@ -314,17 +367,29 @@ 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)
{ {
const uint8_t *mask_data;
size_t mask_len;
uint8_t cmd;
const uint8_t *cmd_data;
size_t cmd_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;
}
if (try_extract_host_command(dev, len, buf, &cmd, &cmd_data, &cmd_len))
{
LOG_INF("hid_stub_output_report vendor cmd=0x%02x len=%u", cmd, cmd_len);
hid_host_command_event_submit(HID_HOST_TRANSPORT_USB,
cmd, cmd_data, cmd_len);
return;
}
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;
} }
@@ -711,7 +776,7 @@ static bool handle_wake_up_event(void)
static bool handle_hid_tx_event(const struct hid_tx_event *event) static bool handle_hid_tx_event(const struct hid_tx_event *event)
{ {
if (!g_usb_hid.policy.usb_mode_selected) if (!usb_hid_should_handle_tx_event(event))
{ {
return false; return false;
} }
@@ -793,7 +858,8 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event)
if ((report_id != REPORT_ID_KEYBOARD) && if ((report_id != REPORT_ID_KEYBOARD) &&
(report_id != REPORT_ID_CONSUMER) && (report_id != REPORT_ID_CONSUMER) &&
(report_id != REPORT_ID_VENDOR)) (report_id != REPORT_ID_VENDOR) &&
(report_id != REPORT_ID_VENDOR_CMD))
{ {
submit_usb_tx_done(HID_TX_KIND_REPORT, false); submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false; return false;