Hi!
I'm developing an application that requires both keyboard and mouse control over BLE. I have gone through various guides to get this working, and the only method that ended up working is by initializing two separate HID services, one for keyboard and one for mouse (got the idea from this post: https://devzone.nordicsemi.com/f/nordic-q-a/49980/how-to-combine-hid-keyboard-and-hid-mouse).
This works fine on iOS and PC, but fails on Android. Specifically, only the first service that was initialized will work. For example, if I initialize the mouse then keyboard, only the mouse will work on Android, and if I initialize the keyboard then mouse, then only the keyboard works. However both iOS and Windows have no issue with whatever order I initialize in and both will run completely fine.
If anyone could help me with this that would be great! Below are the relevant sections of my code pertaining to setting up the HID services.
HID Definitions:
#define INPUT_REPORT_KEYS_INDEX 0 /**< Index of Input Report. */ #define INPUT_REPORT_KEYS_MAX_LEN 8 /**< Maximum length of the Input Report characteristic. */ #define OUTPUT_REPORT_INDEX 0 /**< Index of Output Report. */ #define OUTPUT_REPORT_MAX_LEN 1 /**< Maximum length of Output Report. */ #define OUTPUT_REPORT_BIT_MASK_CAPS_LOCK 0x02 /**< CAPS LOCK bit in Output Report (based on 'LED Page (0x08)' of the Universal Serial Bus HID Usage Tables). */ #define INPUT_REP_REF_ID 0 /**< Id of reference to Keyboard Input Report. */ #define OUTPUT_REP_REF_ID 0 /**< Id of reference to Keyboard Output Report. */ #define FEATURE_REP_REF_ID 0 /**< ID of reference to Keyboard Feature Report. */ #define FEATURE_REPORT_MAX_LEN 2 /**< Maximum length of Feature Report. */ #define FEATURE_REPORT_INDEX 0 /**< Index of Feature Report. */ #define MOVEMENT_SPEED 5 /**< Number of pixels by which the cursor is moved each time a button is pushed. */ #define INPUT_REPORT_COUNT 3 /**< Number of input reports in this application. */ #define INPUT_REP_BUTTONS_LEN 3 /**< Length of Mouse Input Report containing button data. */ #define INPUT_REP_MOVEMENT_LEN 3 /**< Length of Mouse Input Report containing movement data. */ #define INPUT_REP_MEDIA_PLAYER_LEN 1 /**< Length of Mouse Input Report containing media player data. */ #define INPUT_REP_BUTTONS_INDEX 0 /**< Index of Mouse Input Report containing button data. */ #define INPUT_REP_MOVEMENT_INDEX 1 /**< Index of Mouse Input Report containing movement data. */ #define INPUT_REP_MPLAYER_INDEX 2 /**< Index of Mouse Input Report containing media player data. */ #define INPUT_REP_REF_BUTTONS_ID 1 /**< Id of reference to Mouse Input Report containing button data. */ #define INPUT_REP_REF_MOVEMENT_ID 2 /**< Id of reference to Mouse Input Report containing movement data. */ #define INPUT_REP_REF_MPLAYER_ID 3 /**< Id of reference to Mouse Input Report containing media player data. */ BLE_HIDS_DEF(m_hids_kbd, /**< HID service instance. */ NRF_SDH_BLE_TOTAL_LINK_COUNT, INPUT_REPORT_KEYS_MAX_LEN, OUTPUT_REPORT_MAX_LEN, FEATURE_REPORT_MAX_LEN); BLE_HIDS_DEF(m_hids_mouse, /**< HID service instance. */ NRF_SDH_BLE_TOTAL_LINK_COUNT, INPUT_REP_BUTTONS_LEN, INPUT_REP_MOVEMENT_LEN, INPUT_REP_MEDIA_PLAYER_LEN);
HID Initialization functions:
/**@brief Function for initializing HID Service. */ static void hids_mouse_init(void) { ret_code_t err_code; ble_hids_init_t hids_init_obj; ble_hids_inp_rep_init_t * p_input_report; uint8_t hid_info_flags; static ble_hids_inp_rep_init_t inp_rep_array[INPUT_REPORT_COUNT]; static uint8_t rep_map_data[] = { 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) // Report ID 1: Mouse buttons + scroll/pan 0x85, 0x01, // Report Id 1 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x95, 0x05, // Report Count (3) 0x75, 0x01, // Report Size (1) 0x05, 0x09, // Usage Page (Buttons) 0x19, 0x01, // Usage Minimum (01) 0x29, 0x05, // Usage Maximum (05) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x03, // Report Size (3) 0x81, 0x01, // Input (Constant) for padding 0x75, 0x08, // Report Size (8) 0x95, 0x01, // Report Count (1) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x38, // Usage (Wheel) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) 0x81, 0x06, // Input (Data, Variable, Relative) 0x05, 0x0C, // Usage Page (Consumer) 0x0A, 0x38, 0x02, // Usage (AC Pan) 0x95, 0x01, // Report Count (1) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0xC0, // End Collection (Physical) // Report ID 2: Mouse motion 0x85, 0x02, // Report Id 2 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x75, 0x0C, // Report Size (12) 0x95, 0x02, // Report Count (2) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x16, 0x01, 0xF8, // Logical maximum (2047) 0x26, 0xFF, 0x07, // Logical minimum (-2047) 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0, // End Collection (Physical) 0xC0, // End Collection (Application) // Report ID 3: Advanced buttons 0x05, 0x0C, // Usage Page (Consumer) 0x09, 0x01, // Usage (Consumer Control) 0xA1, 0x01, // Collection (Application) 0x85, 0x03, // Report Id (3) 0x15, 0x00, // Logical minimum (0) 0x25, 0x01, // Logical maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x01, // Report Count (1) 0x09, 0xCD, // Usage (Play/Pause) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x0A, 0x83, 0x01, // Usage (AL Consumer Control Configuration) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x09, 0xB5, // Usage (Scan Next Track) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x09, 0xB6, // Usage (Scan Previous Track) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x09, 0xEA, // Usage (Volume Down) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x09, 0xE9, // Usage (Volume Up) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x0A, 0x25, 0x02, // Usage (AC Forward) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0x0A, 0x24, 0x02, // Usage (AC Back) 0x81, 0x06, // Input (Data,Value,Relative,Bit Field) 0xC0 // End Collection }; memset(inp_rep_array, 0, sizeof(inp_rep_array)); // Initialize HID Service. p_input_report = &inp_rep_array[INPUT_REP_BUTTONS_INDEX]; p_input_report->max_len = INPUT_REP_BUTTONS_LEN; p_input_report->rep_ref.report_id = INPUT_REP_REF_BUTTONS_ID; p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT; p_input_report->sec.cccd_wr = SEC_JUST_WORKS; p_input_report->sec.wr = SEC_JUST_WORKS; p_input_report->sec.rd = SEC_JUST_WORKS; p_input_report = &inp_rep_array[INPUT_REP_MOVEMENT_INDEX]; p_input_report->max_len = INPUT_REP_MOVEMENT_LEN; p_input_report->rep_ref.report_id = INPUT_REP_REF_MOVEMENT_ID; p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT; p_input_report->sec.cccd_wr = SEC_JUST_WORKS; p_input_report->sec.wr = SEC_JUST_WORKS; p_input_report->sec.rd = SEC_JUST_WORKS; p_input_report = &inp_rep_array[INPUT_REP_MPLAYER_INDEX]; p_input_report->max_len = INPUT_REP_MEDIA_PLAYER_LEN; p_input_report->rep_ref.report_id = INPUT_REP_REF_MPLAYER_ID; p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT; p_input_report->sec.cccd_wr = SEC_JUST_WORKS; p_input_report->sec.wr = SEC_JUST_WORKS; p_input_report->sec.rd = SEC_JUST_WORKS; hid_info_flags = HID_INFO_FLAG_REMOTE_WAKE_MSK | HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK; memset(&hids_init_obj, 0, sizeof(hids_init_obj)); hids_init_obj.evt_handler = on_hids_evt; hids_init_obj.error_handler = service_error_handler; hids_init_obj.is_kb = false; hids_init_obj.is_mouse = true; hids_init_obj.inp_rep_count = INPUT_REPORT_COUNT; hids_init_obj.p_inp_rep_array = inp_rep_array; hids_init_obj.outp_rep_count = 0; hids_init_obj.p_outp_rep_array = NULL; hids_init_obj.feature_rep_count = 0; hids_init_obj.p_feature_rep_array = NULL; hids_init_obj.rep_map.data_len = sizeof(rep_map_data); hids_init_obj.rep_map.p_data = rep_map_data; hids_init_obj.hid_information.bcd_hid = BASE_USB_HID_SPEC_VERSION; hids_init_obj.hid_information.b_country_code = 0; hids_init_obj.hid_information.flags = hid_info_flags; hids_init_obj.included_services_count = 0; hids_init_obj.p_included_services_array = NULL; hids_init_obj.rep_map.rd_sec = SEC_JUST_WORKS; hids_init_obj.hid_information.rd_sec = SEC_JUST_WORKS; hids_init_obj.boot_mouse_inp_rep_sec.cccd_wr = SEC_JUST_WORKS; hids_init_obj.boot_mouse_inp_rep_sec.wr = SEC_JUST_WORKS; hids_init_obj.boot_mouse_inp_rep_sec.rd = SEC_JUST_WORKS; hids_init_obj.protocol_mode_rd_sec = SEC_JUST_WORKS; hids_init_obj.protocol_mode_wr_sec = SEC_JUST_WORKS; hids_init_obj.ctrl_point_wr_sec = SEC_JUST_WORKS; err_code = ble_hids_init(&m_hids_mouse, &hids_init_obj); APP_ERROR_CHECK(err_code); } /**@brief Function for initializing HID Service. */ static void hids_kbd_init(void) { ret_code_t err_code; ble_hids_init_t hids_init_obj; ble_hids_inp_rep_init_t * p_input_report; ble_hids_outp_rep_init_t * p_output_report; ble_hids_feature_rep_init_t * p_feature_report; uint8_t hid_info_flags; static ble_hids_inp_rep_init_t input_report_array[1]; static ble_hids_outp_rep_init_t output_report_array[1]; static ble_hids_feature_rep_init_t feature_report_array[1]; static uint8_t report_map_data[] = { 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x05, 0x07, // Usage Page (Key Codes) 0x19, 0xe0, // Usage Minimum (224) 0x29, 0xe7, // Usage Maximum (231) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x08, // Report Size (8) 0x81, 0x01, // Input (Constant) reserved byte(1) 0x95, 0x05, // Report Count (5) 0x75, 0x01, // Report Size (1) 0x05, 0x08, // Usage Page (Page# for LEDs) 0x19, 0x01, // Usage Minimum (1) 0x29, 0x05, // Usage Maximum (5) 0x91, 0x02, // Output (Data, Variable, Absolute), Led report 0x95, 0x01, // Report Count (1) 0x75, 0x03, // Report Size (3) 0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding 0x95, 0x06, // Report Count (6) 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) 0x05, 0x07, // Usage Page (Key codes) 0x19, 0x00, // Usage Minimum (0) 0x29, 0x65, // Usage Maximum (101) 0x81, 0x00, // Input (Data, Array) Key array(6 bytes) 0x09, 0x05, // Usage (Vendor Defined) 0x15, 0x00, // Logical Minimum (0) 0x26, 0xFF, 0x00, // Logical Maximum (255) 0x75, 0x08, // Report Size (8 bit) 0x95, 0x02, // Report Count (2) 0xB1, 0x02, // Feature (Data, Variable, Absolute) 0xC0 // End Collection (Application) }; memset((void *)input_report_array, 0, sizeof(ble_hids_inp_rep_init_t)); memset((void *)output_report_array, 0, sizeof(ble_hids_outp_rep_init_t)); memset((void *)feature_report_array, 0, sizeof(ble_hids_feature_rep_init_t)); // Initialize HID Service p_input_report = &input_report_array[INPUT_REPORT_KEYS_INDEX]; p_input_report->max_len = INPUT_REPORT_KEYS_MAX_LEN; p_input_report->rep_ref.report_id = INPUT_REP_REF_ID; p_input_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_INPUT; p_input_report->sec.cccd_wr = SEC_JUST_WORKS; p_input_report->sec.wr = SEC_JUST_WORKS; p_input_report->sec.rd = SEC_JUST_WORKS; p_output_report = &output_report_array[OUTPUT_REPORT_INDEX]; p_output_report->max_len = OUTPUT_REPORT_MAX_LEN; p_output_report->rep_ref.report_id = OUTPUT_REP_REF_ID; p_output_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_OUTPUT; p_output_report->sec.wr = SEC_JUST_WORKS; p_output_report->sec.rd = SEC_JUST_WORKS; p_feature_report = &feature_report_array[FEATURE_REPORT_INDEX]; p_feature_report->max_len = FEATURE_REPORT_MAX_LEN; p_feature_report->rep_ref.report_id = FEATURE_REP_REF_ID; p_feature_report->rep_ref.report_type = BLE_HIDS_REP_TYPE_FEATURE; p_feature_report->sec.rd = SEC_JUST_WORKS; p_feature_report->sec.wr = SEC_JUST_WORKS; hid_info_flags = HID_INFO_FLAG_REMOTE_WAKE_MSK | HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK; memset(&hids_init_obj, 0, sizeof(hids_init_obj)); hids_init_obj.evt_handler = on_hids_evt; hids_init_obj.error_handler = service_error_handler; hids_init_obj.is_kb = true; hids_init_obj.is_mouse = false; hids_init_obj.inp_rep_count = 1; hids_init_obj.p_inp_rep_array = input_report_array; hids_init_obj.outp_rep_count = 1; hids_init_obj.p_outp_rep_array = output_report_array; hids_init_obj.feature_rep_count = 1; hids_init_obj.p_feature_rep_array = feature_report_array; hids_init_obj.rep_map.data_len = sizeof(report_map_data); hids_init_obj.rep_map.p_data = report_map_data; hids_init_obj.hid_information.bcd_hid = BASE_USB_HID_SPEC_VERSION; hids_init_obj.hid_information.b_country_code = 0; hids_init_obj.hid_information.flags = hid_info_flags; hids_init_obj.included_services_count = 0; hids_init_obj.p_included_services_array = NULL; hids_init_obj.rep_map.rd_sec = SEC_JUST_WORKS; hids_init_obj.hid_information.rd_sec = SEC_JUST_WORKS; hids_init_obj.boot_kb_inp_rep_sec.cccd_wr = SEC_JUST_WORKS; hids_init_obj.boot_kb_inp_rep_sec.rd = SEC_JUST_WORKS; hids_init_obj.boot_kb_outp_rep_sec.rd = SEC_JUST_WORKS; hids_init_obj.boot_kb_outp_rep_sec.wr = SEC_JUST_WORKS; hids_init_obj.protocol_mode_rd_sec = SEC_JUST_WORKS; hids_init_obj.protocol_mode_wr_sec = SEC_JUST_WORKS; hids_init_obj.ctrl_point_wr_sec = SEC_JUST_WORKS; err_code = ble_hids_init(&m_hids_kbd, &hids_init_obj); APP_ERROR_CHECK(err_code); }
Initializing all BLE services:
/**@brief Function for initializing services that will be used by the application. */ static void services_init(void) { qwr_init(); dis_init(); bas_init(); hids_kbd_init(); hids_mouse_init(); }