diff --git a/CMakeLists.txt b/CMakeLists.txt index 24f9170..6f0818a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ target_sources(app PRIVATE src/events/hid_report_event.c src/events/hid_tx_done_event.c src/events/hid_tx_event.c + src/events/hid_vendor_mask_event.c src/events/keyboard_led_event.c src/events/mode_event.c src/events/qdec_step_event.c diff --git a/inc/hid_report_descriptor.h b/inc/hid_report_descriptor.h index a92920a..3ffe8e1 100644 --- a/inc/hid_report_descriptor.h +++ b/inc/hid_report_descriptor.h @@ -7,8 +7,20 @@ enum { REPORT_ID_KEYBOARD = 1, REPORT_ID_CONSUMER = 3, + REPORT_ID_VENDOR = 4, }; +#define HID_KBD_USAGE_MAX 0x00E7U +#define HID_KBD_MOD_COUNT 8U +#define HID_KBD_BITMAP_BITS (HID_KBD_USAGE_MAX + 1U) +#define HID_KBD_BITMAP_SIZE ((HID_KBD_BITMAP_BITS + 7U) / 8U) +#define HID_KBD_PAYLOAD_SIZE (1U + HID_KBD_BITMAP_SIZE) +#define HID_BOOT_KBD_PAYLOAD_SIZE 8U +#define HID_CONSUMER_PAYLOAD_SIZE 2U +#define HID_VENDOR_PAYLOAD_SIZE HID_KBD_PAYLOAD_SIZE +#define HID_KBD_LED_PAYLOAD_SIZE 1U +#define HID_FULL_REPORT_SIZE(payload) (1U + (payload)) + /* * HID_USAGE_PAGE() 只支持 1 字节 Usage Page。 * Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本, @@ -18,9 +30,12 @@ enum { HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page_lsb, page_msb /* - * 键盘(NKRO) + Consumer 的复合 Report 描述符: + * 键盘(NKRO) + Consumer + Vendor 的复合 Report 描述符: * - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义, * 避免两边手写常量后长期演进出现不一致。 + * - Vendor Report 复用与 NKRO 键盘状态相同的 payload 结构: + * [modifier(1B) | usage_bitmap(29B)]。 + * 这样主机可以下发“屏蔽遮罩”,设备也可以上报“真实键盘状态”。 */ #define HID_DESC_KEYBOARD_NKRO_CONSUMER() \ { \ @@ -44,7 +59,7 @@ enum { HID_LOGICAL_MIN8(0), \ HID_LOGICAL_MAX8(1), \ HID_REPORT_SIZE(1), \ - HID_REPORT_COUNT(0xE7 + 1), \ + HID_REPORT_COUNT(HID_KBD_BITMAP_BITS), \ HID_INPUT(0x02), \ \ /* Report 协议下键盘 LED 输出(NumLock/CapsLock/ScrollLock/Compose/Kana)。 */ \ @@ -74,6 +89,22 @@ enum { HID_REPORT_SIZE(16), \ HID_REPORT_COUNT(1), \ HID_INPUT(0x00), \ + HID_END_COLLECTION, \ + \ + /* Vendor 页:双向传输完整键盘状态/屏蔽遮罩,payload 结构与 NKRO 一致。 */ \ + HID_USAGE_PAGE16(0x00, 0xFF), \ + HID_USAGE(0x02U), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_ID(REPORT_ID_VENDOR), \ + HID_LOGICAL_MIN8(0), \ + HID_LOGICAL_MAX16(0xFF, 0x00), \ + HID_REPORT_SIZE(8), \ + HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \ + HID_USAGE(0x02U), \ + HID_INPUT(0x02), \ + HID_REPORT_COUNT(HID_VENDOR_PAYLOAD_SIZE), \ + HID_USAGE(0x02U), \ + HID_OUTPUT(0x02), \ HID_END_COLLECTION, \ } diff --git a/prj.conf b/prj.conf index a89b07b..ab337ce 100644 --- a/prj.conf +++ b/prj.conf @@ -45,9 +45,9 @@ CONFIG_BT_CONN_CTX=y CONFIG_BT_GATT_POOL=y CONFIG_BT_GATT_CHRC_POOL_SIZE=16 CONFIG_BT_GATT_UUID16_POOL_SIZE=24 -CONFIG_BT_HIDS_ATTR_MAX=32 -CONFIG_BT_HIDS_INPUT_REP_MAX=2 -CONFIG_BT_HIDS_OUTPUT_REP_MAX=1 +CONFIG_BT_HIDS_ATTR_MAX=40 +CONFIG_BT_HIDS_INPUT_REP_MAX=3 +CONFIG_BT_HIDS_OUTPUT_REP_MAX=2 CONFIG_BT_HIDS_FEATURE_REP_MAX=0 CONFIG_USB_DEVICE_STACK_NEXT=y diff --git a/src/events/hid_vendor_mask_event.c b/src/events/hid_vendor_mask_event.c new file mode 100644 index 0000000..8009953 --- /dev/null +++ b/src/events/hid_vendor_mask_event.c @@ -0,0 +1,26 @@ +#include "hid_vendor_mask_event.h" + +static void log_hid_vendor_mask_event(const struct app_event_header *aeh) +{ + const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "len=%u", event->dyndata.size); +} + +static void profile_hid_vendor_mask_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh); + + nrf_profiler_log_encode_uint16(buf, event->dyndata.size); +} + +APP_EVENT_INFO_DEFINE(hid_vendor_mask_event, + ENCODE(NRF_PROFILER_ARG_U16), + ENCODE("len"), + profile_hid_vendor_mask_event); + +APP_EVENT_TYPE_DEFINE(hid_vendor_mask_event, + log_hid_vendor_mask_event, + &hid_vendor_mask_event_info, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/events/hid_vendor_mask_event.h b/src/events/hid_vendor_mask_event.h new file mode 100644 index 0000000..66f4b3f --- /dev/null +++ b/src/events/hid_vendor_mask_event.h @@ -0,0 +1,39 @@ +#ifndef HID_VENDOR_MASK_EVENT_H__ +#define HID_VENDOR_MASK_EVENT_H__ + +#include +#include +#include + +#include +#include + +struct hid_vendor_mask_event { + struct app_event_header header; + struct event_dyndata dyndata; +}; + +APP_EVENT_TYPE_DYNDATA_DECLARE(hid_vendor_mask_event); + +static inline void hid_vendor_mask_event_submit(const uint8_t *data, size_t size) +{ + struct hid_vendor_mask_event *event = new_hid_vendor_mask_event(size); + + if ((size > 0U) && (data != NULL)) { + memcpy(event->dyndata.data, data, size); + } + + APP_EVENT_SUBMIT(event); +} + +static inline const uint8_t *hid_vendor_mask_event_get_data(const struct hid_vendor_mask_event *event) +{ + return event->dyndata.data; +} + +static inline size_t hid_vendor_mask_event_get_size(const struct hid_vendor_mask_event *event) +{ + return event->dyndata.size; +} + +#endif /* HID_VENDOR_MASK_EVENT_H__ */ diff --git a/src/modules/ble_hid_module.c b/src/modules/ble_hid_module.c index 70b1325..2deb6ec 100644 --- a/src/modules/ble_hid_module.c +++ b/src/modules/ble_hid_module.c @@ -11,18 +11,15 @@ #include "hid_report_descriptor.h" #include "hid_tx_done_event.h" #include "hid_tx_event.h" +#include "hid_vendor_mask_event.h" #include "keyboard_led_event.h" #include "mode_event.h" #include LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); -#define INPUT_REPORT_COUNT 2 -#define OUTPUT_REPORT_COUNT 1 -#define KEYBOARD_REPORT_LEN 30 -#define CONSUMER_REPORT_LEN 2 -#define KEYBOARD_LED_REPORT_LEN 1 -#define BOOT_KEYBOARD_REPORT_LEN 8 +#define INPUT_REPORT_COUNT 3 +#define OUTPUT_REPORT_COUNT 2 BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0); @@ -142,7 +139,7 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep, { ARG_UNUSED(conn); - if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) { + if (!write || !rep || !rep->data || (rep->size < HID_KBD_LED_PAYLOAD_SIZE)) { return; } @@ -150,6 +147,20 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep, LOG_DBG("Report KB out report 0x%02x", rep->data[0]); } +static void vendor_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_VENDOR_PAYLOAD_SIZE)) { + return; + } + + hid_vendor_mask_event_submit(rep->data, rep->size); + LOG_INF("Vendor mask updated over BLE len=%u", rep->size); +} + static int hids_service_init(void) { static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER(); @@ -165,17 +176,25 @@ static int hids_service_init(void) init_param.rep_map.size = sizeof(report_map); input_report[0].id = REPORT_ID_KEYBOARD; - input_report[0].size = KEYBOARD_REPORT_LEN; + input_report[0].size = HID_KBD_PAYLOAD_SIZE; input_report[0].handler = report_notify_handler; input_report[1].id = REPORT_ID_CONSUMER; - input_report[1].size = CONSUMER_REPORT_LEN; + input_report[1].size = HID_CONSUMER_PAYLOAD_SIZE; input_report[1].handler = report_notify_handler; + input_report[2].id = REPORT_ID_VENDOR; + input_report[2].size = HID_VENDOR_PAYLOAD_SIZE; + input_report[2].handler = report_notify_handler; + output_report[0].id = REPORT_ID_KEYBOARD; - output_report[0].size = KEYBOARD_LED_REPORT_LEN; + output_report[0].size = HID_KBD_LED_PAYLOAD_SIZE; output_report[0].handler = keyboard_output_report_handler; + output_report[1].id = REPORT_ID_VENDOR; + output_report[1].size = HID_VENDOR_PAYLOAD_SIZE; + output_report[1].handler = vendor_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; @@ -226,7 +245,7 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event) return false; } - if (payload_len != BOOT_KEYBOARD_REPORT_LEN) { + if (payload_len != HID_BOOT_KBD_PAYLOAD_SIZE) { LOG_WRN("Invalid boot keyboard payload len=%u", payload_len); hid_tx_done_event_submit(HID_TX_KIND_BOOT, false); return false; @@ -268,6 +287,8 @@ static bool handle_hid_tx_event(const struct hid_tx_event *event) rep_index = 0U; } else if (report_id == REPORT_ID_CONSUMER) { rep_index = 1U; + } else if (report_id == REPORT_ID_VENDOR) { + rep_index = 2U; } else { hid_tx_done_event_submit(HID_TX_KIND_REPORT, false); return false; diff --git a/src/modules/hid_tx_manager_module.c b/src/modules/hid_tx_manager_module.c index e8ca058..842de03 100644 --- a/src/modules/hid_tx_manager_module.c +++ b/src/modules/hid_tx_manager_module.c @@ -28,6 +28,8 @@ enum hid_tx_flag { HID_TX_FLAG_BOOT_DIRTY, HID_TX_FLAG_NKRO_VALID, HID_TX_FLAG_NKRO_DIRTY, + HID_TX_FLAG_VENDOR_VALID, + HID_TX_FLAG_VENDOR_DIRTY, }; struct hid_tx_item { @@ -40,6 +42,7 @@ struct hid_tx_ctx { atomic_t flags; struct hid_tx_item boot_state; struct hid_tx_item nkro_state; + struct hid_tx_item vendor_state; struct hid_tx_item inflight_item; mode_type_t active_mode; }; @@ -122,6 +125,14 @@ static void dispatch_next_if_possible(void) if (!k_msgq_get(&hid_tx_queue_msgq, &item, K_NO_WAIT)) { (void)hid_tx_dispatch_item(&item); + return; + } + + if ((tx.active_mode == MODE_TYPE_BLE) && + 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); } } @@ -167,6 +178,10 @@ static bool handle_hid_report_request_event(const struct hid_report_event *event (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); + } else if ((len > 0U) && (data[0] == REPORT_ID_VENDOR)) { + (void)hid_tx_item_store(&tx.vendor_state, HID_TX_KIND_REPORT, 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); } diff --git a/src/modules/keyboard_module.c b/src/modules/keyboard_module.c index 8a8f46b..0fef737 100644 --- a/src/modules/keyboard_module.c +++ b/src/modules/keyboard_module.c @@ -13,6 +13,7 @@ #include "hid_boot_event.h" #include "hid_protocol_event.h" #include "hid_report_event.h" +#include "hid_vendor_mask_event.h" #include "qdec_step_event.h" #include @@ -91,19 +92,14 @@ static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id) } /* 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 +#define KEYBOARD_USAGE_MAX HID_KBD_USAGE_MAX +#define KEYBOARD_BITMAP_SIZE HID_KBD_BITMAP_SIZE struct keyboard_state { - uint8_t modifier_bm; - uint8_t usage_bm[KEYBOARD_BITMAP_SIZE]; + uint8_t physical_modifier_bm; + uint8_t physical_usage_bm[KEYBOARD_BITMAP_SIZE]; + uint8_t mask_modifier_bm; + uint8_t mask_bm[KEYBOARD_BITMAP_SIZE]; enum hid_protocol_type current_protocol; uint16_t consumer_usage; }; @@ -119,6 +115,17 @@ static enum hid_protocol_type active_protocol_get(void) return ks.current_protocol; } +static void submit_hid_report(enum hid_protocol_type protocol, + uint8_t report_id, + const uint8_t *payload, + size_t payload_len); + +static void keyboard_mask_init(void) +{ + ks.mask_modifier_bm = 0xFF; + memset(ks.mask_bm, 0xFF, sizeof(ks.mask_bm)); +} + /* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */ static bool usage_pressed(uint16_t usage) { @@ -126,7 +133,7 @@ static bool usage_pressed(uint16_t usage) return false; } - return (ks.usage_bm[usage / 8] & BIT(usage % 8)) != 0U; + return (ks.physical_usage_bm[usage / 8] & BIT(usage % 8)) != 0U; } /* @@ -145,13 +152,13 @@ static bool keyboard_usage_update(uint16_t usage_id, bool pressed) bool changed = false; if (pressed) { - if ((ks.usage_bm[idx] & mask) == 0U) { - ks.usage_bm[idx] |= mask; + if ((ks.physical_usage_bm[idx] & mask) == 0U) { + ks.physical_usage_bm[idx] |= mask; changed = true; } } else { - if ((ks.usage_bm[idx] & mask) != 0U) { - ks.usage_bm[idx] &= (uint8_t)~mask; + if ((ks.physical_usage_bm[idx] & mask) != 0U) { + ks.physical_usage_bm[idx] &= (uint8_t)~mask; changed = true; } } @@ -160,15 +167,42 @@ static bool keyboard_usage_update(uint16_t usage_id, bool pressed) if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) { uint8_t mod_mask = BIT(usage_id - 0x00E0); if (pressed) { - ks.modifier_bm |= mod_mask; + ks.physical_modifier_bm |= mod_mask; } else { - ks.modifier_bm &= (uint8_t)~mod_mask; + ks.physical_modifier_bm &= (uint8_t)~mod_mask; } } return changed; } +static uint8_t masked_modifier_get(void) +{ + return ks.physical_modifier_bm & ks.mask_modifier_bm; +} + +static void build_masked_keyboard_payload(uint8_t payload[HID_KBD_PAYLOAD_SIZE]) +{ + payload[0] = masked_modifier_get(); + + for (size_t i = 0; i < KEYBOARD_BITMAP_SIZE; i++) { + payload[1U + i] = ks.physical_usage_bm[i] & ks.mask_bm[i]; + } +} + +static void submit_vendor_report_payload(void) +{ + uint8_t payload[HID_VENDOR_PAYLOAD_SIZE]; + + if (active_protocol_get() != HID_PROTO_REPORT) { + return; + } + + payload[0] = ks.physical_modifier_bm; + memcpy(&payload[1], ks.physical_usage_bm, sizeof(ks.physical_usage_bm)); + submit_hid_report(HID_PROTO_REPORT, REPORT_ID_VENDOR, payload, sizeof(payload)); +} + /* * 提交 HID 报告事件: * - Report 协议编码为 [report_id | payload] @@ -179,7 +213,7 @@ static void submit_hid_report(enum hid_protocol_type protocol, const uint8_t *payload, size_t payload_len) { - uint8_t report_buf[KEYBOARD_REPORT_PAYLOAD + 1U]; + uint8_t report_buf[HID_FULL_REPORT_SIZE(HID_KBD_PAYLOAD_SIZE)]; if (protocol == HID_PROTO_REPORT) { size_t report_len = payload_len + 1U; @@ -200,10 +234,9 @@ static void submit_hid_report(enum hid_protocol_type protocol, static void submit_keyboard_report_payload(enum hid_protocol_type protocol) { if (protocol == HID_PROTO_REPORT) { - uint8_t payload[KEYBOARD_REPORT_PAYLOAD]; + uint8_t payload[HID_KBD_PAYLOAD_SIZE]; - payload[0] = ks.modifier_bm; - memcpy(&payload[1], ks.usage_bm, sizeof(ks.usage_bm)); + build_masked_keyboard_payload(payload); submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD, payload, sizeof(payload)); return; @@ -213,10 +246,10 @@ static void submit_keyboard_report_payload(enum hid_protocol_type protocol) * Boot 协议只支持 6KRO。 * 从 usage 位图中按升序提取最多 6 个普通键,modifier 走独立字节。 */ - uint8_t payload[BOOT_KEYBOARD_PAYLOAD] = { 0 }; + uint8_t payload[HID_BOOT_KBD_PAYLOAD_SIZE] = { 0 }; size_t key_pos = 2; - payload[0] = ks.modifier_bm; + payload[0] = masked_modifier_get(); for (uint16_t usage = 0x04; usage <= 0x65; usage++) { if (!usage_pressed(usage)) { @@ -235,7 +268,7 @@ static void submit_keyboard_report_payload(enum hid_protocol_type protocol) /* 组包并提交 consumer 报告(16-bit usage)。 */ static void submit_consumer_report_payload(void) { - uint8_t payload[CONSUMER_PAYLOAD]; + uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE]; payload[0] = ks.consumer_usage & 0xFF; payload[1] = (ks.consumer_usage >> 8) & 0xFF; @@ -244,7 +277,7 @@ static void submit_consumer_report_payload(void) static void submit_consumer_click_usage(uint16_t usage_id) { - uint8_t payload[CONSUMER_PAYLOAD]; + uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE]; if (active_protocol_get() == HID_PROTO_BOOT) { return; @@ -269,6 +302,7 @@ static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool press return false; submit_keyboard_report_payload(active_protocol_get()); + submit_vendor_report_payload(); return false; } @@ -329,6 +363,24 @@ static bool handle_hid_protocol_event(const struct hid_protocol_event *event) return false; } +static bool handle_hid_vendor_mask_event(const struct hid_vendor_mask_event *event) +{ + const uint8_t *data = hid_vendor_mask_event_get_data(event); + size_t size = hid_vendor_mask_event_get_size(event); + + if (size != HID_VENDOR_PAYLOAD_SIZE) { + LOG_WRN("Ignore vendor mask len=%u expect=%u", + size, HID_VENDOR_PAYLOAD_SIZE); + return false; + } + + ks.mask_modifier_bm = data[0]; + memcpy(ks.mask_bm, &data[1], sizeof(ks.mask_bm)); + submit_keyboard_report_payload(active_protocol_get()); + submit_vendor_report_payload(); + return false; +} + static bool handle_qdec_step_event(const struct qdec_step_event *event) { int8_t step = qdec_step_event_get_step(event); @@ -359,12 +411,17 @@ static bool app_event_handler(const struct app_event_header *aeh) return handle_qdec_step_event(cast_qdec_step_event(aeh)); } + if (is_hid_vendor_mask_event(aeh)) { + return handle_hid_vendor_mask_event(cast_hid_vendor_mask_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(); + keyboard_mask_init(); module_set_state(MODULE_STATE_READY); } @@ -378,5 +435,6 @@ static bool app_event_handler(const struct app_event_header *aeh) APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, button_event); APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event); +APP_EVENT_SUBSCRIBE(MODULE, hid_vendor_mask_event); APP_EVENT_SUBSCRIBE(MODULE, qdec_step_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event);