diff --git a/CMakeLists.txt b/CMakeLists.txt index 54c1498..59b556d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,18 +10,25 @@ project(new_kbd) zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc) zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events) +zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/configuration/atguigu_mini_keyboard_nrf52840) + +target_compile_definitions(app PRIVATE + APP_HID_KEYMAP_DEF_PATH=\"hid_keymap_def.h\" +) target_sources(app PRIVATE src/main.c src/events/battery_status_event.c src/events/config_event.c + src/events/hid_protocol_event.c + src/events/hid_report_event.c src/events/mode_event.c src/events/usb_hid_event.c src/modules/battery_module.c src/modules/ble_adv_ctrl_module.c src/modules/ble_bond_module.c - src/modules/button_map_module.c + src/modules/keyboard_module.c src/modules/mode_switch_module.c src/modules/usb_hid_module.c - src/modules/hids_module.c + src/modules/ble_hid_module.c ) diff --git a/configuration/atguigu_mini_keyboard_nrf52840/hid_keymap_def.h b/configuration/atguigu_mini_keyboard_nrf52840/hid_keymap_def.h new file mode 100644 index 0000000..a5d4f0b --- /dev/null +++ b/configuration/atguigu_mini_keyboard_nrf52840/hid_keymap_def.h @@ -0,0 +1,43 @@ +/* + * HID keymap for current new_kbd numeric keypad layout. + * 说明: + * - 本文件仿照 nrf_desktop 的 hid_keymap_def.h 组织方式; + * - 仅由 keyboard_module.c 包含一次(通过 APP_HID_KEYMAP_DEF_PATH); + * - 条目必须按 key_id 升序排列(按 KEY_ID(col, row) 计算后的数值顺序)。 + */ + +#include + +/* + * 防止该定义文件被多处 include 导致重复符号。 + * 约定仅由 keyboard_module.c 包含一次。 + */ +const struct {} hid_keymap_def_include_once; + +static const struct hid_keymap hid_keymap[] = { + /* col 0 */ + { KEY_ID(0, 1), 0x0053, REPORT_ID_KEYBOARD }, /* Num Lock */ + { KEY_ID(0, 2), 0x005F, REPORT_ID_KEYBOARD }, /* Keypad 7 */ + { KEY_ID(0, 3), 0x005C, REPORT_ID_KEYBOARD }, /* Keypad 4 */ + { KEY_ID(0, 4), 0x0059, REPORT_ID_KEYBOARD }, /* Keypad 1 */ + { KEY_ID(0, 5), 0x0062, REPORT_ID_KEYBOARD }, /* Keypad 0 */ + + /* col 1 */ + { KEY_ID(1, 1), 0x0054, REPORT_ID_KEYBOARD }, /* Keypad / */ + { KEY_ID(1, 2), 0x0060, REPORT_ID_KEYBOARD }, /* Keypad 8 */ + { KEY_ID(1, 3), 0x005D, REPORT_ID_KEYBOARD }, /* Keypad 5 */ + { KEY_ID(1, 4), 0x005A, REPORT_ID_KEYBOARD }, /* Keypad 2 */ + { KEY_ID(1, 5), 0x0063, REPORT_ID_KEYBOARD }, /* Keypad . */ + + /* col 2 */ + { KEY_ID(2, 1), 0x0055, REPORT_ID_KEYBOARD }, /* Keypad * */ + { KEY_ID(2, 2), 0x0061, REPORT_ID_KEYBOARD }, /* Keypad 9 */ + { KEY_ID(2, 3), 0x005E, REPORT_ID_KEYBOARD }, /* Keypad 6 */ + { KEY_ID(2, 4), 0x005B, REPORT_ID_KEYBOARD }, /* Keypad 3 */ + + /* col 3 */ + { KEY_ID(3, 0), 0x00E2, REPORT_ID_CONSUMER }, /* Mute */ + { KEY_ID(3, 1), 0x0056, REPORT_ID_KEYBOARD }, /* Keypad - */ + { KEY_ID(3, 3), 0x0057, REPORT_ID_KEYBOARD }, /* Keypad + */ + { KEY_ID(3, 5), 0x0058, REPORT_ID_KEYBOARD }, /* Keypad Enter */ +}; diff --git a/inc/hid_report_descriptor.h b/inc/hid_report_descriptor.h index 6e2d1c8..a92920a 100644 --- a/inc/hid_report_descriptor.h +++ b/inc/hid_report_descriptor.h @@ -1,9 +1,14 @@ #ifndef HID_REPORT_DESCRIPTOR_H_ #define HID_REPORT_DESCRIPTOR_H_ -#include "hid_types.h" #include +/* 与 HID Report Map 对齐的 Report ID。 */ +enum { + REPORT_ID_KEYBOARD = 1, + REPORT_ID_CONSUMER = 3, +}; + /* * HID_USAGE_PAGE() 只支持 1 字节 Usage Page。 * Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本, diff --git a/src/events/hid_protocol_event.c b/src/events/hid_protocol_event.c new file mode 100644 index 0000000..767a8b0 --- /dev/null +++ b/src/events/hid_protocol_event.c @@ -0,0 +1,42 @@ +#include "hid_protocol_event.h" + +static const char *const hid_transport_name[] = { + [HID_TRANSPORT_BLE] = "BLE", + [HID_TRANSPORT_USB] = "USB", +}; + +static const char *const hid_protocol_name[] = { + [HID_PROTO_BOOT] = "BOOT", + [HID_PROTO_REPORT] = "REPORT", +}; + +static void log_hid_protocol_event(const struct app_event_header *aeh) +{ + const struct hid_protocol_event *event = cast_hid_protocol_event(aeh); + + __ASSERT_NO_MSG(event->transport < ARRAY_SIZE(hid_transport_name)); + __ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name)); + + APP_EVENT_MANAGER_LOG(aeh, "transport=%s protocol=%s", + hid_transport_name[event->transport], + hid_protocol_name[event->protocol]); +} + +static void profile_hid_protocol_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct hid_protocol_event *event = cast_hid_protocol_event(aeh); + + nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport); + nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol); +} + +APP_EVENT_INFO_DEFINE(hid_protocol_event, + ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8), + ENCODE("transport", "protocol"), + profile_hid_protocol_event); + +APP_EVENT_TYPE_DEFINE(hid_protocol_event, + log_hid_protocol_event, + &hid_protocol_event_info, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/hid_protocol_event.h b/src/events/hid_protocol_event.h new file mode 100644 index 0000000..c190a2a --- /dev/null +++ b/src/events/hid_protocol_event.h @@ -0,0 +1,30 @@ +#ifndef HID_PROTOCOL_EVENT_H__ +#define HID_PROTOCOL_EVENT_H__ + +#include +#include + +enum hid_transport_type { + HID_TRANSPORT_BLE = 0, + HID_TRANSPORT_USB, +}; + +enum hid_protocol_type { + HID_PROTO_BOOT = 0, + HID_PROTO_REPORT, +}; + +/* + * HID 传输层在收到主机 set_protocol 请求后上报该事件, + * keyboard_module 会依据当前 active 传输的协议格式打包 hid_report_event。 + */ +struct hid_protocol_event { + struct app_event_header header; + + enum hid_transport_type transport; + enum hid_protocol_type protocol; +}; + +APP_EVENT_TYPE_DECLARE(hid_protocol_event); + +#endif /* HID_PROTOCOL_EVENT_H__ */ diff --git a/src/events/hid_report_event.c b/src/events/hid_report_event.c new file mode 100644 index 0000000..33b9204 --- /dev/null +++ b/src/events/hid_report_event.c @@ -0,0 +1,53 @@ +#include "hid_report_event.h" + +static const char *const hid_protocol_name[] = { + [HID_PROTO_BOOT] = "BOOT", + [HID_PROTO_REPORT] = "REPORT", +}; + +static void log_hid_report_event(const struct app_event_header *aeh) +{ + const struct hid_report_event *event = cast_hid_report_event(aeh); + uint8_t report_id = 0x00; + uint16_t payload_len = event->dyndata.size; + + __ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name)); + + if (event->protocol == HID_PROTO_REPORT) { + __ASSERT_NO_MSG(event->dyndata.size >= 1U); + report_id = event->dyndata.data[0]; + payload_len = event->dyndata.size - 1U; + } + + APP_EVENT_MANAGER_LOG(aeh, "protocol=%s report_id=0x%02x payload_len=%u", + hid_protocol_name[event->protocol], + report_id, + payload_len); +} + +static void profile_hid_report_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct hid_report_event *event = cast_hid_report_event(aeh); + uint8_t report_id = 0x00; + + if ((event->protocol == HID_PROTO_REPORT) && (event->dyndata.size >= 1U)) { + report_id = event->dyndata.data[0]; + } + + nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol); + nrf_profiler_log_encode_uint8(buf, report_id); + nrf_profiler_log_encode_uint16(buf, event->dyndata.size); +} + +APP_EVENT_INFO_DEFINE(hid_report_event, + ENCODE(NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U8, + NRF_PROFILER_ARG_U16), + ENCODE("protocol", "report_id", "len"), + profile_hid_report_event); + +APP_EVENT_TYPE_DEFINE(hid_report_event, + log_hid_report_event, + &hid_report_event_info, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/hid_report_event.h b/src/events/hid_report_event.h new file mode 100644 index 0000000..3e5582d --- /dev/null +++ b/src/events/hid_report_event.h @@ -0,0 +1,24 @@ +#ifndef HID_REPORT_EVENT_H__ +#define HID_REPORT_EVENT_H__ + +#include +#include + +#include "hid_protocol_event.h" + +/* + * HID 输入报告统一事件: + * - protocol 指示当前 dyndata 编码规则; + * - 当 protocol=HID_PROTO_REPORT:dyndata[0]=report_id,dyndata[1..]=payload; + * - 当 protocol=HID_PROTO_BOOT:dyndata 仅包含 boot payload(不含 report_id)。 + */ +struct hid_report_event { + struct app_event_header header; + + enum hid_protocol_type protocol; + struct event_dyndata dyndata; +}; + +APP_EVENT_TYPE_DYNDATA_DECLARE(hid_report_event); + +#endif /* HID_REPORT_EVENT_H__ */ diff --git a/src/events/usb_hid_event.c b/src/events/usb_hid_event.c index db9330b..fcf5bd2 100644 --- a/src/events/usb_hid_event.c +++ b/src/events/usb_hid_event.c @@ -10,27 +10,17 @@ static const char *const usb_hid_usbd_state_name[] = { [USB_HID_USBD_SUSPENDED] = "SUSPENDED", }; -static const char *const usb_hid_stack_state_name[] = { - [USB_HID_STACK_OFF] = "OFF", - [USB_HID_STACK_READY] = "READY", - [USB_HID_STACK_ACTIVE] = "ACTIVE", - [USB_HID_STACK_SUSPENDED] = "SUSPENDED", - [USB_HID_STACK_ERROR] = "ERROR", -}; - static void log_usb_hid_event(const struct app_event_header *aeh) { const struct usb_hid_event *event = cast_usb_hid_event(aeh); __ASSERT_NO_MSG(event->evt_type < ARRAY_SIZE(usb_hid_evt_type_name)); __ASSERT_NO_MSG(event->usbd_state < ARRAY_SIZE(usb_hid_usbd_state_name)); - __ASSERT_NO_MSG(event->hid_state < ARRAY_SIZE(usb_hid_stack_state_name)); - APP_EVENT_MANAGER_LOG(aeh, "type=%s en=%u usbd=%s hid=%s", + APP_EVENT_MANAGER_LOG(aeh, "type=%s en=%u usbd=%s", usb_hid_evt_type_name[event->evt_type], event->enable, - usb_hid_usbd_state_name[event->usbd_state], - usb_hid_stack_state_name[event->hid_state]); + usb_hid_usbd_state_name[event->usbd_state]); } static void profile_usb_hid_event(struct log_event_buf *buf, @@ -41,15 +31,13 @@ static void profile_usb_hid_event(struct log_event_buf *buf, nrf_profiler_log_encode_uint8(buf, (uint8_t)event->evt_type); nrf_profiler_log_encode_uint8(buf, (uint8_t)event->enable); nrf_profiler_log_encode_uint8(buf, (uint8_t)event->usbd_state); - nrf_profiler_log_encode_uint8(buf, (uint8_t)event->hid_state); } APP_EVENT_INFO_DEFINE(usb_hid_event, ENCODE(NRF_PROFILER_ARG_U8, - NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8), - ENCODE("evt_type", "enable", "usbd", "hid"), + ENCODE("evt_type", "enable", "usbd"), profile_usb_hid_event); APP_EVENT_TYPE_DEFINE(usb_hid_event, diff --git a/src/events/usb_hid_event.h b/src/events/usb_hid_event.h index d0293ef..cb8bfc5 100644 --- a/src/events/usb_hid_event.h +++ b/src/events/usb_hid_event.h @@ -18,22 +18,12 @@ enum usb_hid_usbd_state { USB_HID_USBD_SUSPENDED, }; -/* HID 协议栈状态(偏“服务是否运行”) */ -enum usb_hid_stack_state { - USB_HID_STACK_OFF = 0, - USB_HID_STACK_READY, - USB_HID_STACK_ACTIVE, - USB_HID_STACK_SUSPENDED, - USB_HID_STACK_ERROR, -}; - struct usb_hid_event { struct app_event_header header; enum usb_hid_event_type evt_type; bool enable; enum usb_hid_usbd_state usbd_state; - enum usb_hid_stack_state hid_state; }; APP_EVENT_TYPE_DECLARE(usb_hid_event); diff --git a/src/modules/hids_module.c b/src/modules/ble_hid_module.c similarity index 59% rename from src/modules/hids_module.c rename to src/modules/ble_hid_module.c index 0d4b57a..86aa6e3 100644 --- a/src/modules/hids_module.c +++ b/src/modules/ble_hid_module.c @@ -1,13 +1,16 @@ #include +#include #include -#define MODULE hids +#define MODULE ble_hid #include #include -#include "hid_types.h" +#include "hid_protocol_event.h" +#include "hid_report_event.h" #include "hid_report_descriptor.h" +#include "mode_event.h" #include LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); @@ -17,13 +20,27 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define KEYBOARD_REPORT_LEN 30 #define CONSUMER_REPORT_LEN 2 #define KEYBOARD_LED_REPORT_LEN 1 +#define BOOT_KEYBOARD_REPORT_LEN 8 -/* 注册 HIDS 实例。此版本聚焦最小可用链路(Boot + Report)。 */ BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0); static struct bt_conn *active_conn; static enum bt_hids_pm current_pm = BT_HIDS_PM_REPORT; +static bool ble_mode_selected; +static enum hid_protocol_type pm_to_protocol(enum bt_hids_pm pm) +{ + return (pm == BT_HIDS_PM_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT; +} + +static void publish_hid_protocol_event(enum hid_protocol_type protocol) +{ + struct hid_protocol_event *event = new_hid_protocol_event(); + + event->transport = HID_TRANSPORT_BLE; + event->protocol = protocol; + APP_EVENT_SUBMIT(event); +} static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn) { @@ -33,10 +50,16 @@ static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn) case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED: current_pm = BT_HIDS_PM_BOOT; LOG_INF("HIDS protocol: boot"); + if (active_conn) { + publish_hid_protocol_event(HID_PROTO_BOOT); + } break; case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED: current_pm = BT_HIDS_PM_REPORT; LOG_INF("HIDS protocol: report"); + if (active_conn) { + publish_hid_protocol_event(HID_PROTO_REPORT); + } break; default: break; @@ -59,7 +82,6 @@ static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep, { ARG_UNUSED(conn); - /* Basic boot protocol support: accept host LED writes and keep state locally. */ if (!write || !rep || (rep->size == 0) || !rep->data) { return; } @@ -73,22 +95,11 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep, { ARG_UNUSED(conn); - /* - * 该回调用于 Report 协议的键盘 LED 输出(NumLock 等)。 - * 这里仅做最小解析并暴露注册回调,具体业务(例如驱动指示灯)留给上层实现。 - */ if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) { return; } - uint8_t leds = rep->data[0]; - LOG_DBG("Report KB out report 0x%02x", leds); - - /* - * 预留:后续在这里把 LED 输出转换为 CAF 事件(例如 NumLock 状态事件), - * 由上层模块消费并驱动板级指示灯。 - */ - ARG_UNUSED(leds); + LOG_DBG("Report KB out report 0x%02x", rep->data[0]); } static int hids_service_init(void) @@ -113,10 +124,6 @@ static int hids_service_init(void) input_report[1].size = CONSUMER_REPORT_LEN; input_report[1].handler = report_notify_handler; - /* - * Report 协议键盘输出报告: - * 与 Report Map 中 REPORT_ID_KEYBOARD 下定义的 1 字节 LED Output 对齐。 - */ output_report[0].id = REPORT_ID_KEYBOARD; output_report[0].size = KEYBOARD_LED_REPORT_LEN; output_report[0].handler = keyboard_output_report_handler; @@ -140,6 +147,8 @@ static void handle_ble_peer_event(const struct ble_peer_event *event) if (bt_hids_connected(&hids_obj, active_conn)) { LOG_WRN("bt_hids_connected failed"); } + /* 连接建立后按当前协议主动同步一次,避免 keyboard_module 等待下一次 set_protocol。 */ + publish_hid_protocol_event(pm_to_protocol(current_pm)); break; case PEER_STATE_DISCONNECTED: @@ -156,6 +165,80 @@ static void handle_ble_peer_event(const struct ble_peer_event *event) } } +static bool handle_hid_report_event(const struct hid_report_event *event) +{ + if (!ble_mode_selected || !active_conn) { + return false; + } + + uint8_t report_id; + const uint8_t *payload; + size_t payload_len; + + /* + * keyboard_module 已经按照 protocol 打包好了完整 payload。 + * BLE 模块这里只做协议一致性检查与发送。 + */ + if ((current_pm == BT_HIDS_PM_BOOT) && (event->protocol != HID_PROTO_BOOT)) { + return false; + } + if ((current_pm == BT_HIDS_PM_REPORT) && (event->protocol != HID_PROTO_REPORT)) { + return false; + } + + if (event->protocol == HID_PROTO_BOOT) { + report_id = REPORT_ID_KEYBOARD; + payload = event->dyndata.data; + payload_len = event->dyndata.size; + } else { + if (event->dyndata.size < 1U) { + return false; + } + report_id = event->dyndata.data[0]; + payload = &event->dyndata.data[1]; + payload_len = event->dyndata.size - 1U; + } + + int err = 0; + + if (event->protocol == HID_PROTO_BOOT) { + if (report_id != REPORT_ID_KEYBOARD) { + return false; + } + if (payload_len != BOOT_KEYBOARD_REPORT_LEN) { + LOG_WRN("Invalid boot keyboard payload len=%u", payload_len); + return false; + } + + err = bt_hids_boot_kb_inp_rep_send(&hids_obj, active_conn, + payload, payload_len, NULL); + } else { + uint8_t rep_index; + + if (report_id == REPORT_ID_KEYBOARD) { + rep_index = 0U; + } else if (report_id == REPORT_ID_CONSUMER) { + rep_index = 1U; + } else { + return false; + } + + if (payload_len > UINT8_MAX) { + LOG_WRN("Payload too large=%u", payload_len); + return false; + } + + err = bt_hids_inp_rep_send(&hids_obj, active_conn, rep_index, + payload, (uint8_t)payload_len, NULL); + } + + if (err) { + LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err); + } + + return false; +} + static bool app_event_handler(const struct app_event_header *aeh) { if (is_module_state_event(aeh)) { @@ -183,11 +266,22 @@ static bool app_event_handler(const struct app_event_header *aeh) return false; } + if (is_mode_event(aeh)) { + const struct mode_event *event = cast_mode_event(aeh); + ble_mode_selected = (event->mode_type == MODE_TYPE_BLE); + return false; + } + + if (is_hid_report_event(aeh)) { + return handle_hid_report_event(cast_hid_report_event(aeh)); + } + __ASSERT_NO_MSG(false); return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); -/* Ensure GATT HIDS is registered before BLE is enabled by ble_state. */ APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event); +APP_EVENT_SUBSCRIBE(MODULE, mode_event); +APP_EVENT_SUBSCRIBE(MODULE, hid_report_event); diff --git a/src/modules/button_map_module.c b/src/modules/button_map_module.c deleted file mode 100644 index 853b910..0000000 --- a/src/modules/button_map_module.c +++ /dev/null @@ -1,69 +0,0 @@ -#include - -#define MODULE button_map -#include - -#include -#include -#include -#include - -LOG_MODULE_REGISTER(MODULE); - -/* - * keymap[row][col] 直接对齐板级 DTS 里的 MATRIX_KEY(row, col, key) 定义。 - * 值为 Linux input key code;-1 表示该矩阵位置未映射功能键。 - */ -static const int16_t keymap[6][4] = { - /* row 0 */ { -1, -1, -1, INPUT_KEY_MUTE }, - /* row 1 */ { INPUT_KEY_NUMLOCK, INPUT_KEY_KPSLASH, INPUT_KEY_KPASTERISK, INPUT_KEY_KPMINUS }, - /* row 2 */ { INPUT_KEY_KP7, INPUT_KEY_KP8, INPUT_KEY_KP9, -1 }, - /* row 3 */ { INPUT_KEY_KP4, INPUT_KEY_KP5, INPUT_KEY_KP6, INPUT_KEY_KPPLUS }, - /* row 4 */ { INPUT_KEY_KP1, INPUT_KEY_KP2, INPUT_KEY_KP3, -1 }, - /* row 5 */ { INPUT_KEY_KP0, INPUT_KEY_KPDOT, -1, INPUT_KEY_KPENTER }, -}; - -static bool app_event_handler(const struct app_event_header *aeh) -{ - if (is_button_event(aeh)) { - const struct button_event *event = cast_button_event(aeh); - uint8_t row = KEY_ROW(event->key_id); - uint8_t col = KEY_COL(event->key_id); - - /* - * 防御性检查:若行列越界,说明 buttons_def 与实际扫描结果不一致, - * 记录错误以便尽快发现硬件矩阵定义或固件配置问题。 - */ - if ((row >= ARRAY_SIZE(keymap)) || (col >= ARRAY_SIZE(keymap[0]))) { - LOG_ERR("Unknown key_id=0x%04x (row=%u, col=%u)", event->key_id, row, col); - return false; - } - - int16_t code = keymap[row][col]; - if (code < 0) { - LOG_INF("Button %s row=%u col=%u unmapped", event->pressed ? "down" : "up", row, col); - } else { - LOG_INF("Button %s row=%u col=%u keycode=%d", event->pressed ? "down" : "up", row, col, code); - } - - return false; - } - - 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_set_state(MODULE_STATE_READY); - } - - return false; - } - - /* 未处理但已订阅的事件类型不应进入这里。 */ - __ASSERT_NO_MSG(false); - return false; -} - -APP_EVENT_LISTENER(MODULE, app_event_handler); -APP_EVENT_SUBSCRIBE(MODULE, button_event); -APP_EVENT_SUBSCRIBE(MODULE, module_state_event); diff --git a/src/modules/keyboard_module.c b/src/modules/keyboard_module.c new file mode 100644 index 0000000..b6c8709 --- /dev/null +++ b/src/modules/keyboard_module.c @@ -0,0 +1,368 @@ +#include +#include + +#include + +#define MODULE keyboard +#include + +#include +#include + +#include "hid_report_descriptor.h" +#include "hid_protocol_event.h" +#include "hid_report_event.h" +#include "mode_event.h" + +#include +#include + +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +/* + * 参考 nrf_desktop 的表驱动设计: + * - key_id -> (usage_id, report_id) 映射定义在外部 hid_keymap_def.h; + * - keyboard_module 内部完成映射表校验与查询,不再依赖独立 hid_keymap 模块。 + */ +struct hid_keymap { + uint16_t key_id; + uint16_t usage_id; + uint8_t report_id; +}; + +#include APP_HID_KEYMAP_DEF_PATH + +static bool hid_keymap_initialized; + +/* 比较函数:供 bsearch 按 key_id 升序查找映射项。 */ +static int hid_keymap_compare(const void *a, const void *b) +{ + const struct hid_keymap *pa = a; + const struct hid_keymap *pb = b; + + return ((int)pa->key_id - (int)pb->key_id); +} + +/* + * 初始化并校验 hid_keymap: + * - 仅在 CONFIG_ASSERT 打开时执行校验,避免 release 构建引入额外开销; + * - 校验 key_id 严格升序,确保二分查找行为正确; + * - 校验 report_id 只落在当前模块支持的 Keyboard/Consumer 两类。 + */ +static void hid_keymap_init_local(void) +{ + if (!IS_ENABLED(CONFIG_ASSERT) || hid_keymap_initialized) { + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(hid_keymap); i++) { + if (i > 0U) { + __ASSERT(hid_keymap[i - 1].key_id < hid_keymap[i].key_id, + "hid_keymap must be sorted by key_id"); + } + + __ASSERT((hid_keymap[i].report_id == REPORT_ID_KEYBOARD) || + (hid_keymap[i].report_id == REPORT_ID_CONSUMER), + "hid_keymap uses unsupported report_id"); + } + + hid_keymap_initialized = true; +} + +/* 查询指定 key_id 的 HID 映射,查不到返回 NULL。 */ +static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id) +{ + if (ARRAY_SIZE(hid_keymap) == 0U) { + return NULL; + } + + struct hid_keymap key = { + .key_id = key_id, + .usage_id = 0U, + .report_id = 0U, + }; + + return bsearch(&key, + hid_keymap, + ARRAY_SIZE(hid_keymap), + sizeof(hid_keymap[0]), + hid_keymap_compare); +} + +/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */ +#define KEYBOARD_USAGE_MAX 0x00E7 +#define KEYBOARD_BITMAP_SIZE DIV_ROUND_UP(KEYBOARD_USAGE_MAX + 1, 8) +#define KEYBOARD_REPORT_PAYLOAD (1 + KEYBOARD_BITMAP_SIZE) + +/* Boot 协议键盘 payload: modifier(1) + reserved(1) + 6 keys。 */ +#define BOOT_KEYBOARD_PAYLOAD 8 + +/* Consumer payload 固定 16-bit usage。 */ +#define CONSUMER_PAYLOAD 2 + +struct keyboard_state { + uint8_t modifier_bm; + uint8_t usage_bm[KEYBOARD_BITMAP_SIZE]; + enum hid_protocol_type ble_protocol; + enum hid_protocol_type usb_protocol; + mode_type_t current_mode; + uint16_t consumer_usage; +}; + +static struct keyboard_state ks = { + .ble_protocol = HID_PROTO_REPORT, + .usb_protocol = HID_PROTO_REPORT, + .current_mode = MODE_TYPE_COUNT, + .consumer_usage = 0, +}; + +/* 依据当前 mode 选择生效的 HID 协议来源(BLE 或 USB)。 */ +static enum hid_protocol_type active_protocol_get(void) +{ + return (ks.current_mode == MODE_TYPE_USB) ? ks.usb_protocol : ks.ble_protocol; +} + +/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */ +static bool usage_pressed(uint16_t usage) +{ + if (usage > KEYBOARD_USAGE_MAX) { + return false; + } + + return (ks.usage_bm[usage / 8] & BIT(usage % 8)) != 0U; +} + +/* + * 更新键盘 usage 位图与 modifier 状态。 + * 返回 true 表示状态有变化,需要向传输层同步新报告。 + */ +static bool keyboard_usage_update(uint16_t usage_id, bool pressed) +{ + if (usage_id > KEYBOARD_USAGE_MAX) { + LOG_WRN("Unsupported usage_id=0x%04x", usage_id); + return false; + } + + uint8_t idx = usage_id / 8; + uint8_t mask = BIT(usage_id % 8); + bool changed = false; + + if (pressed) { + if ((ks.usage_bm[idx] & mask) == 0U) { + ks.usage_bm[idx] |= mask; + changed = true; + } + } else { + if ((ks.usage_bm[idx] & mask) != 0U) { + ks.usage_bm[idx] &= (uint8_t)~mask; + changed = true; + } + } + + /* modifier(E0~E7) 额外维护一份 bitmask,便于 Boot/Report 复用。 */ + if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) { + uint8_t mod_mask = BIT(usage_id - 0x00E0); + if (pressed) { + ks.modifier_bm |= mod_mask; + } else { + ks.modifier_bm &= (uint8_t)~mod_mask; + } + } + + return changed; +} + +/* + * 提交 HID 报告事件: + * - Report 协议编码为 [report_id | payload] + * - Boot 协议编码为 [payload](不含 report_id) + */ +static void submit_hid_report(enum hid_protocol_type protocol, + uint8_t report_id, + const uint8_t *payload, + size_t payload_len) +{ + size_t report_len = (protocol == HID_PROTO_REPORT) ? (payload_len + 1U) : payload_len; + struct hid_report_event *event = new_hid_report_event(report_len); + + event->protocol = protocol; + if (protocol == HID_PROTO_REPORT) { + event->dyndata.data[0] = report_id; + memcpy(&event->dyndata.data[1], payload, payload_len); + } else { + memcpy(event->dyndata.data, payload, payload_len); + } + + APP_EVENT_SUBMIT(event); +} + +/* + * 组包并提交键盘报告: + * - Report 协议发送 NKRO payload; + * - Boot 协议降级为 6KRO 固定 8 字节格式。 + */ +static void submit_keyboard_report_payload(enum hid_protocol_type protocol) +{ + if (protocol == HID_PROTO_REPORT) { + uint8_t payload[KEYBOARD_REPORT_PAYLOAD]; + + payload[0] = ks.modifier_bm; + memcpy(&payload[1], ks.usage_bm, sizeof(ks.usage_bm)); + submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD, + payload, sizeof(payload)); + return; + } + + /* + * Boot 协议只支持 6KRO。 + * 从 usage 位图中按升序提取最多 6 个普通键,modifier 走独立字节。 + */ + uint8_t payload[BOOT_KEYBOARD_PAYLOAD] = { 0 }; + size_t key_pos = 2; + + payload[0] = ks.modifier_bm; + + for (uint16_t usage = 0x04; usage <= 0x65; usage++) { + if (!usage_pressed(usage)) { + continue; + } + + payload[key_pos++] = (uint8_t)usage; + if (key_pos >= ARRAY_SIZE(payload)) { + break; + } + } + + submit_hid_report(HID_PROTO_BOOT, REPORT_ID_KEYBOARD, payload, sizeof(payload)); +} + +/* 组包并提交 consumer 报告(16-bit usage)。 */ +static void submit_consumer_report_payload(void) +{ + uint8_t payload[CONSUMER_PAYLOAD]; + + payload[0] = ks.consumer_usage & 0xFF; + payload[1] = (ks.consumer_usage >> 8) & 0xFF; + submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload)); +} + +/* + * 处理键盘类 usage: + * - 仅在按键状态实际变化时提交报告,避免无效重复上报。 + */ +static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool pressed) +{ + if (!keyboard_usage_update(map->usage_id, pressed)) + return false; + + submit_keyboard_report_payload(active_protocol_get()); + return false; +} + +/* + * 处理 consumer 类 usage: + * - Boot 协议不发送 consumer 报告; + * - 按下时上报 usage,抬起时上报 0 清状态。 + */ +static bool handle_consumer_usage_event(const struct hid_keymap *map, bool pressed) +{ + if (active_protocol_get() == HID_PROTO_BOOT) + return false; + + if (pressed) { + if (ks.consumer_usage == map->usage_id) + return false; + + ks.consumer_usage = map->usage_id; + submit_consumer_report_payload(); + return false; + } + + if (ks.consumer_usage != map->usage_id) + return false; + + ks.consumer_usage = 0U; + submit_consumer_report_payload(); + return false; +} + +/* + * 处理 button_event: + * - 先查 key_id 映射; + * - 再按 report_id 分派到键盘/consumer 分支。 + */ +static bool handle_button_event(const struct button_event *event) +{ + const struct hid_keymap *map = hid_keymap_get_local(event->key_id); + if (!map) { + return false; + } + + if (map->report_id == REPORT_ID_KEYBOARD) + return handle_keyboard_usage_event(map, event->pressed); + + if (map->report_id == REPORT_ID_CONSUMER) + return handle_consumer_usage_event(map, event->pressed); + + LOG_WRN("Unsupported report_id=%u key_id=0x%04x", map->report_id, event->key_id); + + return false; +} + +/* 同步 BLE/USB 传输层上报的当前协议。 */ +static bool handle_hid_protocol_event(const struct hid_protocol_event *event) +{ + if (event->transport == HID_TRANSPORT_BLE) { + ks.ble_protocol = event->protocol; + } else if (event->transport == HID_TRANSPORT_USB) { + ks.usb_protocol = event->protocol; + } else { + __ASSERT_NO_MSG(false); + } + + return false; +} + +/* 更新当前激活模式,决定协议来源取 BLE 还是 USB。 */ +static bool handle_mode_event(const struct mode_event *event) +{ + ks.current_mode = event->mode_type; + return false; +} + +/* 模块总事件分发入口。 */ +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_button_event(aeh)) { + return handle_button_event(cast_button_event(aeh)); + } + + if (is_hid_protocol_event(aeh)) { + return handle_hid_protocol_event(cast_hid_protocol_event(aeh)); + } + + if (is_mode_event(aeh)) { + return handle_mode_event(cast_mode_event(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)) { + /* 主模块 ready 后做一次 keymap 结构校验。 */ + hid_keymap_init_local(); + module_set_state(MODULE_STATE_READY); + } + + return false; + } + + __ASSERT_NO_MSG(false); + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, button_event); +APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event); +APP_EVENT_SUBSCRIBE(MODULE, mode_event); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); diff --git a/src/modules/usb_hid_module.c b/src/modules/usb_hid_module.c index d89d969..f1767c7 100644 --- a/src/modules/usb_hid_module.c +++ b/src/modules/usb_hid_module.c @@ -13,6 +13,8 @@ #include #include "hid_report_descriptor.h" +#include "hid_protocol_event.h" +#include "hid_report_event.h" #include "mode_event.h" #include "usb_hid_event.h" @@ -48,14 +50,16 @@ struct usb_hid_ctx { bool boot_iface_ready; bool nkro_iface_ready; bool raw_iface_ready; + bool boot_in_flight; + bool nkro_in_flight; enum usb_hid_usbd_state usbd_state; - enum usb_hid_stack_state hid_state; + enum hid_protocol_type current_protocol; }; static struct usb_hid_ctx g_usb_hid = { .usbd_state = USB_HID_USBD_DISCONNECTED, - .hid_state = USB_HID_STACK_OFF, + .current_protocol = HID_PROTO_REPORT, }; USBD_DEVICE_DEFINE(new_kbd_usbd, @@ -78,37 +82,13 @@ static void publish_usb_hid_state(void) event->evt_type = USB_HID_EVT_STATE_REPORT; event->enable = g_usb_hid.stack_enabled; event->usbd_state = g_usb_hid.usbd_state; - event->hid_state = g_usb_hid.hid_state; APP_EVENT_SUBMIT(event); } static void recompute_hid_state(void) { - enum usb_hid_stack_state new_hid_state; - - if (g_usb_hid.stack_error) { - new_hid_state = USB_HID_STACK_ERROR; - } else if (g_usb_hid.pm_suspended && g_usb_hid.stack_enabled) { - new_hid_state = USB_HID_STACK_SUSPENDED; - } else if (!g_usb_hid.stack_initialized) { - new_hid_state = USB_HID_STACK_OFF; - } else if (g_usb_hid.stack_enabled && - (g_usb_hid.boot_iface_ready || - g_usb_hid.nkro_iface_ready || - g_usb_hid.raw_iface_ready)) { - new_hid_state = USB_HID_STACK_ACTIVE; - } else { - /* - * 栈已初始化但未进入 ACTIVE(例如刚 enable 还未配置、或 mode 切走后 disable)。 - * 用 READY 表示“协议栈可用但当前未承载有效 HID 会话”。 - */ - new_hid_state = USB_HID_STACK_READY; - } - - if (g_usb_hid.hid_state != new_hid_state) { - g_usb_hid.hid_state = new_hid_state; - publish_usb_hid_state(); - } + /* 兼容现有调用点:对外仅发布 enable + usbd 状态。 */ + publish_usb_hid_state(); } static void set_usbd_state(enum usb_hid_usbd_state state) @@ -160,13 +140,49 @@ static uint32_t hid_stub_get_idle(const struct device *dev, uint8_t id) static void hid_stub_set_protocol(const struct device *dev, uint8_t proto) { ARG_UNUSED(dev); - ARG_UNUSED(proto); + + enum hid_protocol_type new_protocol = + (proto == HID_PROTOCOL_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT; + + if (g_usb_hid.current_protocol == new_protocol) { + return; + } + + g_usb_hid.current_protocol = new_protocol; + + /* + * 按需求:USB HID 在连接后收到 set_protocol 时上报 hid_protocol_event。 + * 这里额外检查接口 ready,避免在未枚举完成阶段上报无意义协议切换。 + */ + if (g_usb_hid.boot_iface_ready || g_usb_hid.nkro_iface_ready) { + struct hid_protocol_event *event = new_hid_protocol_event(); + + event->transport = HID_TRANSPORT_USB; + event->protocol = new_protocol; + APP_EVENT_SUBMIT(event); + } } static void hid_stub_input_done(const struct device *dev, const uint8_t *report) { - ARG_UNUSED(dev); ARG_UNUSED(report); + + /* + * 发送完成回调: + * - 仅在这里清除“在途发送”标志,确保“上一包未完成则丢弃新包”的策略可闭环; + * - 若收到未知 dev 的回调,仅记录告警,避免静默状态错乱。 + */ + if (dev == g_usb_hid.boot_dev) { + g_usb_hid.boot_in_flight = false; + return; + } + + if (dev == g_usb_hid.nkro_dev) { + g_usb_hid.nkro_in_flight = false; + return; + } + + LOG_WRN("input_done from unknown HID dev: %p", (void *)dev); } static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf) @@ -180,14 +196,26 @@ static void hid_iface_ready_cb(const struct device *dev, bool ready) { if (dev == g_usb_hid.boot_dev) { g_usb_hid.boot_iface_ready = ready; + if (!ready) { + g_usb_hid.boot_in_flight = false; + } } else if (dev == g_usb_hid.nkro_dev) { g_usb_hid.nkro_iface_ready = ready; + if (!ready) { + g_usb_hid.nkro_in_flight = false; + } } else if (dev == g_usb_hid.raw_dev) { g_usb_hid.raw_iface_ready = ready; } if (ready) { set_usbd_state(USB_HID_USBD_CONNECTED); + /* 连接可用后同步一次当前协议,让 keyboard_module 与传输侧编码一致。 */ + struct hid_protocol_event *event = new_hid_protocol_event(); + + event->transport = HID_TRANSPORT_USB; + event->protocol = g_usb_hid.current_protocol; + APP_EVENT_SUBMIT(event); } recompute_hid_state(); @@ -210,6 +238,7 @@ static const struct hid_device_ops report_hid_ops = { .set_report = hid_stub_set_report, .set_idle = hid_stub_set_idle, .get_idle = hid_stub_get_idle, + .set_protocol = hid_stub_set_protocol, .input_report_done = hid_stub_input_done, .output_report = hid_stub_output_report, }; @@ -435,6 +464,8 @@ static int usb_hid_set_enabled(bool enable) g_usb_hid.boot_iface_ready = false; g_usb_hid.nkro_iface_ready = false; g_usb_hid.raw_iface_ready = false; + g_usb_hid.boot_in_flight = false; + g_usb_hid.nkro_in_flight = false; set_usbd_state(USB_HID_USBD_DISCONNECTED); } @@ -517,6 +548,80 @@ static bool handle_wake_up_event(void) return false; } +static bool handle_hid_report_event(const struct hid_report_event *event) +{ + /* + * USB 侧仅在 active 条件满足时发送: + * - 当前 mode 为 USB; + * - USB HID 栈已启用且对应接口 ready。 + */ + if (!g_usb_hid.usb_mode_selected || !g_usb_hid.stack_enabled) { + return false; + } + + uint8_t report_id; + + if (event->protocol != g_usb_hid.current_protocol) { + return false; + } + + if (event->protocol == HID_PROTO_BOOT) { + const uint8_t *payload = event->dyndata.data; + size_t payload_len = event->dyndata.size; + + report_id = REPORT_ID_KEYBOARD; + + if (!g_usb_hid.boot_iface_ready || !g_usb_hid.boot_dev) { + return false; + } + if (report_id != REPORT_ID_KEYBOARD) { + return false; + } + if (g_usb_hid.boot_in_flight) { + LOG_WRN("Drop boot report: previous report not sent"); + return false; + } + + int err = hid_device_submit_report(g_usb_hid.boot_dev, + payload_len, + payload); + if (err) { + LOG_WRN("USB boot report send failed err=%d", err); + } else { + g_usb_hid.boot_in_flight = true; + } + return false; + } + + if (event->dyndata.size < 1U) { + return false; + } + + report_id = event->dyndata.data[0]; + + if (!g_usb_hid.nkro_iface_ready || !g_usb_hid.nkro_dev) { + return false; + } + + if ((report_id != REPORT_ID_KEYBOARD) && (report_id != REPORT_ID_CONSUMER)) { + return false; + } + if (g_usb_hid.nkro_in_flight) { + LOG_WRN("Drop report id=0x%02x: previous report not sent", report_id); + return false; + } + + /* Report 协议下 dyndata 是 [report_id|payload],可直接透传。 */ + int err = hid_device_submit_report(g_usb_hid.nkro_dev, event->dyndata.size, event->dyndata.data); + if (err) { + LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err); + } else { + g_usb_hid.nkro_in_flight = true; + } + + return false; +} + static bool app_event_handler(const struct app_event_header *aeh) { if (is_module_state_event(aeh)) { @@ -535,6 +640,10 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_wake_up_event(); } + if (is_hid_report_event(aeh)) { + return handle_hid_report_event(cast_hid_report_event(aeh)); + } + __ASSERT_NO_MSG(false); return false; } @@ -544,3 +653,4 @@ APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, mode_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);