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:
@@ -27,7 +27,11 @@ target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/events/battery_status_event.c
|
||||
src/events/config_event.c
|
||||
src/events/display_theme_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_report_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_battery_module.c
|
||||
src/modules/ble_bond_module.c
|
||||
src/modules/ble_time_sync_module.c
|
||||
src/modules/ble_slot_ctrl_module.c
|
||||
src/modules/display_module.c
|
||||
src/modules/hid_host_command_module.c
|
||||
src/modules/hid_tx_manager_module.c
|
||||
src/modules/keyboard_module.c
|
||||
src/modules/led_state_module.c
|
||||
|
||||
281
docs/ble_time_sync_pc_host.md
Normal file
281
docs/ble_time_sync_pc_host.md
Normal 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`
|
||||
|
||||
16
inc/hid_host_command_protocol.h
Normal file
16
inc/hid_host_command_protocol.h
Normal 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
9
inc/hid_host_transport.h
Normal 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__ */
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
#include <zephyr/usb/class/usbd_hid.h>
|
||||
|
||||
#include "hid_host_command_protocol.h"
|
||||
|
||||
/* 与 HID Report Map 对齐的 Report ID。 */
|
||||
enum {
|
||||
REPORT_ID_KEYBOARD = 1,
|
||||
REPORT_ID_CONSUMER = 3,
|
||||
REPORT_ID_VENDOR = 4,
|
||||
REPORT_ID_VENDOR_CMD = 5,
|
||||
};
|
||||
|
||||
#define HID_KBD_USAGE_MAX 0x00E7U
|
||||
@@ -18,6 +21,7 @@ enum {
|
||||
#define HID_BOOT_KBD_PAYLOAD_SIZE 8U
|
||||
#define HID_CONSUMER_PAYLOAD_SIZE 2U
|
||||
#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_FULL_REPORT_SIZE(payload) (1U + (payload))
|
||||
|
||||
@@ -33,9 +37,10 @@ enum {
|
||||
* 键盘(NKRO) + Consumer + Vendor 的复合 Report 描述符:
|
||||
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
|
||||
* 避免两边手写常量后长期演进出现不一致。
|
||||
* - Vendor Report 复用与 NKRO 键盘状态相同的 payload 结构:
|
||||
* [modifier(1B) | usage_bitmap(29B)]。
|
||||
* 这样主机可以下发“屏蔽遮罩”,设备也可以上报“真实键盘状态”。
|
||||
* - Report ID 0x04 继续复用 NKRO payload,承载私有状态/遮罩语义。
|
||||
* - Report ID 0x05 预留给“主机命令 + 设备 ACK”通道:
|
||||
* - Output payload 固定 9 字节:[cmd(1) | data(8)]
|
||||
* - Input payload 固定 1 字节:[cmd]
|
||||
*/
|
||||
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
|
||||
{ \
|
||||
@@ -105,6 +110,22 @@ enum {
|
||||
HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \
|
||||
HID_USAGE(0x02U), \
|
||||
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, \
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ enum time_sync_source {
|
||||
TIME_SYNC_SOURCE_BLE,
|
||||
TIME_SYNC_SOURCE_USB,
|
||||
TIME_SYNC_SOURCE_MANUAL,
|
||||
TIME_SYNC_SOURCE_HID,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
4
prj.conf
4
prj.conf
@@ -52,8 +52,8 @@ CONFIG_BT_GATT_POOL=y
|
||||
CONFIG_BT_GATT_CHRC_POOL_SIZE=16
|
||||
CONFIG_BT_GATT_UUID16_POOL_SIZE=24
|
||||
CONFIG_BT_HIDS_ATTR_MAX=40
|
||||
CONFIG_BT_HIDS_INPUT_REP_MAX=3
|
||||
CONFIG_BT_HIDS_OUTPUT_REP_MAX=2
|
||||
CONFIG_BT_HIDS_INPUT_REP_MAX=4
|
||||
CONFIG_BT_HIDS_OUTPUT_REP_MAX=3
|
||||
CONFIG_BT_HIDS_FEATURE_REP_MAX=0
|
||||
|
||||
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||
|
||||
31
src/events/display_theme_event.c
Normal file
31
src/events/display_theme_event.c
Normal 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));
|
||||
30
src/events/display_theme_event.h
Normal file
30
src/events/display_theme_event.h
Normal 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__ */
|
||||
29
src/events/hid_host_ack_event.c
Normal file
29
src/events/hid_host_ack_event.c
Normal 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));
|
||||
29
src/events/hid_host_ack_event.h
Normal file
29
src/events/hid_host_ack_event.h
Normal 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__ */
|
||||
36
src/events/hid_host_command_error_event.c
Normal file
36
src/events/hid_host_command_error_event.c
Normal 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));
|
||||
41
src/events/hid_host_command_error_event.h
Normal file
41
src/events/hid_host_command_error_event.h
Normal 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__ */
|
||||
47
src/events/hid_host_command_event.c
Normal file
47
src/events/hid_host_command_event.c
Normal 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));
|
||||
44
src/events/hid_host_command_event.h
Normal file
44
src/events/hid_host_command_event.h
Normal 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__ */
|
||||
@@ -11,8 +11,12 @@ static void log_hid_tx_event(const struct app_event_header *aeh)
|
||||
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);
|
||||
APP_EVENT_MANAGER_LOG(aeh,
|
||||
"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,
|
||||
@@ -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->route);
|
||||
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_U8,
|
||||
NRF_PROFILER_ARG_U16),
|
||||
ENCODE("kind", "report_id", "len"),
|
||||
ENCODE("kind", "route", "report_id", "len"),
|
||||
profile_hid_tx_event);
|
||||
|
||||
APP_EVENT_TYPE_DEFINE(hid_tx_event,
|
||||
|
||||
@@ -13,21 +13,30 @@ enum hid_tx_kind {
|
||||
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 app_event_header header;
|
||||
enum hid_tx_kind kind;
|
||||
enum hid_tx_route route;
|
||||
struct event_dyndata dyndata;
|
||||
};
|
||||
|
||||
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,
|
||||
enum hid_tx_route route,
|
||||
const uint8_t *data,
|
||||
size_t size)
|
||||
{
|
||||
struct hid_tx_event *event = new_hid_tx_event(size);
|
||||
|
||||
event->kind = kind;
|
||||
event->route = route;
|
||||
if ((size > 0U) && (data != NULL)) {
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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__ */
|
||||
|
||||
@@ -12,6 +12,8 @@ static const char *time_sync_source_name(enum time_sync_source source)
|
||||
return "usb";
|
||||
case TIME_SYNC_SOURCE_MANUAL:
|
||||
return "manual";
|
||||
case TIME_SYNC_SOURCE_HID:
|
||||
return "hid";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -230,7 +230,8 @@ static void battery_sampling_set_enabled(bool enable)
|
||||
|
||||
if (enable)
|
||||
{
|
||||
k_work_reschedule(&battery.sample_work, K_NO_WAIT);
|
||||
// 延迟2s开始采样等待电池电压稳定
|
||||
k_work_reschedule(&battery.sample_work, K_MSEC(2000));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <caf/events/ble_common_event.h>
|
||||
|
||||
#include "hid_protocol_event.h"
|
||||
#include "hid_host_command_event.h"
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "hid_tx_done_event.h"
|
||||
#include "hid_tx_event.h"
|
||||
@@ -18,8 +19,8 @@
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define INPUT_REPORT_COUNT 3
|
||||
#define OUTPUT_REPORT_COUNT 2
|
||||
#define INPUT_REPORT_COUNT 4
|
||||
#define OUTPUT_REPORT_COUNT 3
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 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].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].size = HID_KBD_LED_PAYLOAD_SIZE;
|
||||
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].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.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
|
||||
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)
|
||||
{
|
||||
if (!ble_hid.policy.ble_mode_selected) {
|
||||
if (!ble_hid_should_handle_tx_event(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -291,6 +332,8 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event)
|
||||
rep_index = 1U;
|
||||
} else if (report_id == REPORT_ID_VENDOR) {
|
||||
rep_index = 2U;
|
||||
} else if (report_id == REPORT_ID_VENDOR_CMD) {
|
||||
rep_index = 3U;
|
||||
} else {
|
||||
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <zephyr/drivers/display.h>
|
||||
#include <zephyr/drivers/led.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
#include <lvgl.h>
|
||||
@@ -18,6 +20,7 @@
|
||||
#include <caf/events/power_event.h>
|
||||
|
||||
#include "battery_status_event.h"
|
||||
#include "display_theme_event.h"
|
||||
#include "keyboard_led_event.h"
|
||||
#include "mode_event.h"
|
||||
#include "time_manager.h"
|
||||
@@ -28,6 +31,8 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
#define DISPLAY_UPDATE_PERIOD_MS 1000
|
||||
#define DISPLAY_IDLE_TIMEOUT_MIN 1
|
||||
#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_MONTH 3
|
||||
#define DISPLAY_DEMO_BASE_DAY 27
|
||||
@@ -78,10 +83,20 @@ struct display_ctx
|
||||
struct display_capabilities caps;
|
||||
struct k_work_delayable update_work;
|
||||
struct k_work_delayable idle_work;
|
||||
struct k_work_delayable theme_save_work;
|
||||
struct display_ui_state ui;
|
||||
uint32_t tick_count;
|
||||
enum display_pm_state pm_state;
|
||||
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 = {
|
||||
@@ -95,6 +110,8 @@ static struct display_ctx disp = {
|
||||
.pm_state = DISPLAY_PM_STATE_OFF,
|
||||
};
|
||||
|
||||
static struct display_theme_storage display_theme_storage;
|
||||
|
||||
static const struct led_dt_spec display_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 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)
|
||||
{
|
||||
#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.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);
|
||||
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.idle_work, display_idle_timeout_fn);
|
||||
k_work_init_delayable(&disp.theme_save_work, display_theme_save_work_fn);
|
||||
disp.tick_count = 0U;
|
||||
display_theme_apply_loaded_storage();
|
||||
|
||||
err = display_blanking_off(disp.dev);
|
||||
if (err)
|
||||
@@ -631,6 +744,20 @@ static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
|
||||
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 分钟空闲计时。 */
|
||||
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)
|
||||
{
|
||||
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))
|
||||
return false;
|
||||
|
||||
@@ -683,6 +822,9 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
if (is_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))
|
||||
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_SUBSCRIBE(MODULE, module_state_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, keyboard_led_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
||||
|
||||
121
src/modules/hid_host_command_module.c
Normal file
121
src/modules/hid_host_command_module.c
Normal 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);
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "hid_boot_event.h"
|
||||
#include "hid_host_ack_event.h"
|
||||
#include "hid_report_event.h"
|
||||
#include "hid_tx_done_event.h"
|
||||
#include "hid_tx_event.h"
|
||||
@@ -26,14 +27,15 @@ enum hid_tx_flag {
|
||||
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_KEYBOARD_VALID,
|
||||
HID_TX_FLAG_KEYBOARD_DIRTY,
|
||||
HID_TX_FLAG_VENDOR_VALID,
|
||||
HID_TX_FLAG_VENDOR_DIRTY,
|
||||
};
|
||||
|
||||
struct hid_tx_item {
|
||||
enum hid_tx_kind kind;
|
||||
enum hid_tx_route route;
|
||||
size_t len;
|
||||
uint8_t data[HID_TX_MAX_DATA];
|
||||
};
|
||||
@@ -41,7 +43,7 @@ struct hid_tx_item {
|
||||
struct hid_tx_ctx {
|
||||
atomic_t flags;
|
||||
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 inflight_item;
|
||||
mode_type_t active_mode;
|
||||
@@ -51,10 +53,13 @@ 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);
|
||||
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,
|
||||
enum hid_tx_kind kind,
|
||||
enum hid_tx_route route,
|
||||
const uint8_t *data,
|
||||
size_t len)
|
||||
{
|
||||
@@ -64,6 +69,7 @@ static bool hid_tx_item_store(struct hid_tx_item *item,
|
||||
}
|
||||
|
||||
item->kind = kind;
|
||||
item->route = route;
|
||||
item->len = len;
|
||||
if ((len > 0U) && (data != NULL)) {
|
||||
memcpy(item->data, data, len);
|
||||
@@ -72,15 +78,19 @@ static bool hid_tx_item_store(struct hid_tx_item *item,
|
||||
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;
|
||||
|
||||
if (!hid_tx_item_store(&item, kind, data, len)) {
|
||||
if (!hid_tx_item_store(&item, kind, route, data, len)) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
tx.inflight_item = *item;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -105,33 +120,44 @@ static void dispatch_next_if_possible(void)
|
||||
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;
|
||||
}
|
||||
|
||||
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) &&
|
||||
if (hid_tx_auto_route_available() &&
|
||||
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)) {
|
||||
if (hid_tx_auto_route_available() &&
|
||||
!k_msgq_get(&hid_tx_consumer_msgq, &item, K_NO_WAIT)) {
|
||||
(void)hid_tx_dispatch_item(&item);
|
||||
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_clear_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
|
||||
(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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
(void)hid_tx_item_store(&tx.boot_state,
|
||||
HID_TX_KIND_BOOT,
|
||||
HID_TX_ROUTE_AUTO,
|
||||
hid_boot_event_get_data(event),
|
||||
hid_boot_event_get_size(event));
|
||||
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);
|
||||
|
||||
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);
|
||||
(void)hid_tx_item_store(&tx.keyboard_state,
|
||||
HID_TX_KIND_REPORT,
|
||||
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)) {
|
||||
(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_DIRTY);
|
||||
} 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();
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
if (is_hid_host_ack_event(aeh)) {
|
||||
return handle_hid_host_ack_event(cast_hid_host_ack_event(aeh));
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(false);
|
||||
return false;
|
||||
}
|
||||
@@ -236,4 +309,5 @@ 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_host_ack_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_done_event);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <zephyr/logging/log.h>
|
||||
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"
|
||||
|
||||
/*
|
||||
@@ -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_USB:
|
||||
case TIME_SYNC_SOURCE_MANUAL:
|
||||
case TIME_SYNC_SOURCE_HID:
|
||||
return true;
|
||||
default:
|
||||
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)
|
||||
{
|
||||
@@ -144,11 +145,13 @@ static void time_manager_apply_update(const struct time_sync_update *update)
|
||||
k_spin_unlock(&time_ctx.lock, key);
|
||||
|
||||
/*
|
||||
* 保存延迟 1 秒:
|
||||
* - 避免未来 BLE/USB 连续校时时反复触发 flash 写入;
|
||||
* - 也避免在同步入口上下文里直接阻塞等待存储完成。
|
||||
* 时间同步允许立即生效,但 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",
|
||||
update->source,
|
||||
@@ -269,37 +272,6 @@ static bool handle_time_sync_event(const struct time_sync_event *event)
|
||||
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,保证外部读取到的是稳定状态。 */
|
||||
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));
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
return handle_power_down_event();
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(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_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, time_sync_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "hid_boot_event.h"
|
||||
#include "hid_host_command_event.h"
|
||||
#include "hid_protocol_event.h"
|
||||
#include "hid_tx_done_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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (dev == g_usb_hid.boot.dev)
|
||||
@@ -205,6 +219,35 @@ static bool try_extract_vendor_mask(const struct device *dev,
|
||||
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,
|
||||
uint8_t type, uint8_t id,
|
||||
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;
|
||||
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))
|
||||
{
|
||||
@@ -234,6 +280,13 @@ static int hid_stub_set_report(const struct device *dev,
|
||||
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))
|
||||
{
|
||||
return 0;
|
||||
@@ -313,18 +366,30 @@ 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)
|
||||
{
|
||||
if (!should_handle_led_input_from_dev(dev))
|
||||
{
|
||||
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))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -711,7 +776,7 @@ static bool handle_wake_up_event(void)
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -793,7 +858,8 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event)
|
||||
|
||||
if ((report_id != REPORT_ID_KEYBOARD) &&
|
||||
(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);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user