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

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

View File

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

View File

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

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

View File

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

View File

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