#include #include #include #include #include #define MODULE ble_hid_module #include #include #include #include #include "hid_led_event.h" #include "hid_channel_state_event.h" #include "hid_report_sent_event.h" #include "hid_tx_report_event.h" #include "keyboard_core.h" #include "module_lifecycle.h" #include "set_protocol_event.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define BLE_HID_KEYS_REPORT_ID 0x01 #define BLE_HID_CONSUMER_REPORT_ID 0x02 #define BLE_HID_KEYS_REPORT_IDX 0 #define BLE_HID_CONSUMER_REPORT_IDX 1 #define BLE_HID_KEYS_LED_REPORT_SIZE 1U #define BASE_USB_HID_SPEC_VERSION 0x0101 struct in_flight_report { bool active; enum keyboard_report_type report_type; uint16_t sequence; }; BT_HIDS_DEF(hids_obj, KEYBOARD_NKRO_REPORT_SIZE, KEYBOARD_CONSUMER_REPORT_SIZE, BLE_HID_KEYS_LED_REPORT_SIZE); struct ble_hid_module_ctx { struct module_lifecycle_ctx lc; struct bt_conn *active_conn; struct in_flight_report in_flight; enum keyboard_protocol_mode protocol_mode; bool secured; bool keyboard_report_notify_enabled; bool consumer_report_notify_enabled; bool boot_keyboard_notify_enabled; }; static int do_init(void); static int do_start(void); static int do_stop(void); static const struct module_lifecycle_cfg lifecycle_cfg = { .mode = ML_MODE_NONE, .stopped_state = MODULE_STATE_OFF, }; static const struct module_lifecycle_ops lifecycle_ops = { .do_init = do_init, .do_start = do_start, .do_stop = do_stop, }; static struct ble_hid_module_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT, }; static const uint8_t hid_report_desc[] = { 0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x06, /* Usage (Keyboard) */ 0xA1, 0x01, /* Collection (Application) */ 0x85, BLE_HID_KEYS_REPORT_ID, 0x05, 0x07, /* Usage Page (Keyboard/Keypad) */ 0x19, 0xE0, /* Usage Minimum (0xE0) */ 0x29, 0xE7, /* Usage Maximum (0xE7) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x95, 0x08, /* Report Count (8) */ 0x81, 0x02, /* Input (Data,Var,Abs) */ 0x05, 0x07, /* Usage Page (Keyboard/Keypad) */ 0x19, 0x00, /* Usage Minimum (0x00) */ 0x2A, 0xDF, 0x00, /* Usage Maximum (0x00DF) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x96, 0xE0, 0x00, /* Report Count (224) */ 0x81, 0x02, /* Input (Data,Var,Abs) */ 0x85, BLE_HID_KEYS_REPORT_ID, 0x05, 0x08, /* Usage Page (LEDs) */ 0x19, 0x01, /* Usage Minimum (1) */ 0x29, 0x05, /* Usage Maximum (5) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x95, 0x05, /* Report Count (5) */ 0x91, 0x02, /* Output (Data,Var,Abs) */ 0x75, 0x03, /* Report Size (3) */ 0x95, 0x01, /* Report Count (1) */ 0x91, 0x01, /* Output (Const,Array,Abs) */ 0xC0, /* End Collection */ 0x05, 0x0C, /* Usage Page (Consumer) */ 0x09, 0x01, /* Usage (Consumer Control) */ 0xA1, 0x01, /* Collection (Application) */ 0x85, BLE_HID_CONSUMER_REPORT_ID, 0x15, 0x00, /* Logical Minimum (0) */ 0x26, 0xFF, 0x03, /* Logical Maximum (1023) */ 0x19, 0x00, /* Usage Minimum (0) */ 0x2A, 0xFF, 0x03, /* Usage Maximum (1023) */ 0x75, 0x10, /* Report Size (16) */ 0x95, 0x01, /* Report Count (1) */ 0x81, 0x00, /* Input (Data,Array,Abs) */ 0xC0 /* End Collection */ }; static void submit_ble_transport_state_event(void) { bool ready = module_lifecycle_is_running(&ctx.lc) && ctx.secured && (ctx.active_conn != NULL); uint8_t report_ready_bm = 0U; if (ready && ((ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ? ctx.boot_keyboard_notify_enabled : ctx.keyboard_report_notify_enabled)) { report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_KEYS); } if (ready && (ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) && ctx.consumer_report_notify_enabled) { report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_CONSUMER); } submit_hid_channel_state_event( HID_SEND_CH_BLE_SHARED, report_ready_bm, ctx.protocol_mode); } static void input_report_notify_handler(uint8_t report_id, enum bt_hids_notify_evt evt) { bool enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED); if (report_id == BLE_HID_KEYS_REPORT_ID) { ctx.keyboard_report_notify_enabled = enabled; } else if (report_id == BLE_HID_CONSUMER_REPORT_ID) { ctx.consumer_report_notify_enabled = enabled; } submit_ble_transport_state_event(); } static void boot_keyboard_notify_handler(enum bt_hids_notify_evt evt) { ctx.boot_keyboard_notify_enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED); submit_ble_transport_state_event(); } static void hid_report_complete_cb(struct bt_conn *conn, void *user_data) { ARG_UNUSED(conn); ARG_UNUSED(user_data); if (!ctx.in_flight.active) { return; } submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED, ctx.in_flight.report_type, ctx.in_flight.sequence, false); ctx.in_flight.active = false; } static void keyboard_led_report_common(struct bt_hids_rep *rep, bool write) { if (!write || (rep->data == NULL) || (rep->size < BLE_HID_KEYS_LED_REPORT_SIZE)) { return; } submit_hid_led_event(HID_TRANSPORT_BLE, rep->data[0]); } static void keyboard_led_report_handler(struct bt_hids_rep *rep, struct bt_conn *conn, bool write) { ARG_UNUSED(conn); keyboard_led_report_common(rep, write); } static void boot_keyboard_led_report_handler(struct bt_hids_rep *rep, struct bt_conn *conn, bool write) { ARG_UNUSED(conn); keyboard_led_report_common(rep, write); } static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn) { ARG_UNUSED(conn); switch (evt) { case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED: ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_BOOT; break; case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED: ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; break; default: return; } submit_set_protocol_event(HID_TRANSPORT_BLE, ctx.protocol_mode); submit_ble_transport_state_event(); } static int do_init(void) { struct bt_hids_init_param hids_init_param = { 0 }; struct bt_hids_inp_rep *input_report; struct bt_hids_outp_feat_rep *output_report; hids_init_param.info.bcd_hid = BASE_USB_HID_SPEC_VERSION; hids_init_param.info.b_country_code = 0x00; hids_init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE; hids_init_param.rep_map.data = hid_report_desc; hids_init_param.rep_map.size = sizeof(hid_report_desc); hids_init_param.pm_evt_handler = pm_evt_handler; hids_init_param.is_kb = true; hids_init_param.boot_kb_notif_handler = boot_keyboard_notify_handler; hids_init_param.boot_kb_outp_rep_handler = boot_keyboard_led_report_handler; input_report = &hids_init_param.inp_rep_group_init.reports[BLE_HID_KEYS_REPORT_IDX]; input_report->id = BLE_HID_KEYS_REPORT_ID; input_report->size = KEYBOARD_NKRO_REPORT_SIZE; input_report->handler_ext = input_report_notify_handler; hids_init_param.inp_rep_group_init.cnt++; input_report = &hids_init_param.inp_rep_group_init.reports[BLE_HID_CONSUMER_REPORT_IDX]; input_report->id = BLE_HID_CONSUMER_REPORT_ID; input_report->size = KEYBOARD_CONSUMER_REPORT_SIZE; input_report->handler_ext = input_report_notify_handler; hids_init_param.inp_rep_group_init.cnt++; output_report = &hids_init_param.outp_rep_group_init.reports[0]; output_report->id = BLE_HID_KEYS_REPORT_ID; output_report->size = BLE_HID_KEYS_LED_REPORT_SIZE; output_report->handler = keyboard_led_report_handler; hids_init_param.outp_rep_group_init.cnt = 1U; return bt_hids_init(&hids_obj, &hids_init_param); } static int do_start(void) { if (module_lifecycle_is_running(&ctx.lc)) { return 0; } submit_ble_transport_state_event(); return 0; } static int do_stop(void) { if (!module_lifecycle_is_running(&ctx.lc)) { return 0; } ctx.in_flight.active = false; submit_ble_transport_state_event(); return 0; } static void reset_connection_state(void) { ctx.active_conn = NULL; ctx.secured = false; ctx.keyboard_report_notify_enabled = false; ctx.consumer_report_notify_enabled = false; ctx.boot_keyboard_notify_enabled = false; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; ctx.in_flight.active = false; } static bool handle_ble_peer_event(const struct ble_peer_event *event) { int err; switch (event->state) { case PEER_STATE_CONNECTED: if (ctx.active_conn != NULL) { return false; } ctx.active_conn = event->id; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; submit_set_protocol_event(HID_TRANSPORT_BLE, ctx.protocol_mode); err = bt_hids_connected(&hids_obj, event->id); if (err) { LOG_ERR("bt_hids_connected failed (%d)", err); } submit_ble_transport_state_event(); return false; case PEER_STATE_SECURED: if (ctx.active_conn != event->id) { return false; } ctx.secured = true; submit_ble_transport_state_event(); return false; case PEER_STATE_DISCONNECTED: if (ctx.active_conn != event->id) { return false; } err = bt_hids_disconnected(&hids_obj, event->id); if (err) { LOG_WRN("bt_hids_disconnected failed (%d)", err); } reset_connection_state(); submit_ble_transport_state_event(); return false; default: return false; } } static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event) { int err; if (!module_lifecycle_is_running(&ctx.lc) || (event->channel != HID_SEND_CH_BLE_SHARED) || ctx.in_flight.active) { return false; } if ((ctx.active_conn == NULL) || !ctx.secured) { return false; } if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) { if (event->protocol_mode != ctx.protocol_mode) { LOG_WRN("Drop BLE keys report due to protocol mismatch"); return false; } ctx.in_flight.active = true; ctx.in_flight.report_type = event->report_type; ctx.in_flight.sequence = event->sequence; if (ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) { err = bt_hids_boot_kb_inp_rep_send(&hids_obj, ctx.active_conn, event->dyndata.data, (uint8_t)event->dyndata.size, hid_report_complete_cb); } else { err = bt_hids_inp_rep_send(&hids_obj, ctx.active_conn, BLE_HID_KEYS_REPORT_IDX, event->dyndata.data, (uint8_t)event->dyndata.size, hid_report_complete_cb); } if (err) { ctx.in_flight.active = false; LOG_WRN("BLE keyboard report submit failed (%d)", err); submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED, KEYBOARD_REPORT_TYPE_KEYS, event->sequence, true); } return false; } if (event->report_type == KEYBOARD_REPORT_TYPE_CONSUMER) { if (ctx.protocol_mode != KEYBOARD_PROTOCOL_MODE_REPORT) { LOG_WRN("Drop BLE consumer report in boot mode"); return false; } ctx.in_flight.active = true; ctx.in_flight.report_type = event->report_type; ctx.in_flight.sequence = event->sequence; err = bt_hids_inp_rep_send(&hids_obj, ctx.active_conn, BLE_HID_CONSUMER_REPORT_IDX, event->dyndata.data, (uint8_t)event->dyndata.size, hid_report_complete_cb); if (err) { ctx.in_flight.active = false; LOG_WRN("BLE consumer report submit failed (%d)", err); submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED, KEYBOARD_REPORT_TYPE_CONSUMER, event->sequence, true); } } return false; } static bool app_event_handler(const struct app_event_header *aeh) { if (is_hid_tx_report_event(aeh)) { return handle_hid_tx_report_event(cast_hid_tx_report_event(aeh)); } if (is_ble_peer_event(aeh)) { return handle_ble_peer_event(cast_ble_peer_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)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);