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:
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user