This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

BLE Integrated HID Keyboard and Mouse Not Working on Android (Works on iOS & PC)

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();
}

Parents
  • Hello,

    Have you tried to clear the BT cache on the phone? Something like this:
    https://help.elitehrv.com/article/135-clear-bluetooth-cache-android

    Tried on a different phone?

    In addition to looking at existing devzone cases, it might also to provide on-air sniffer logs of working and failing:
    https://www.nordicsemi.com/Products/Development-tools/nRF-Sniffer-for-Bluetooth-LE 

    Then we can check if for instance the notifications are enabled and sent or not, and also compare working and failing.

    Best regards,
    Kenneth

  • Hi Kenneth,

    Thanks for your response. I tried clearing the cache and storage of the Android device and trying a different phone, but ran into the same issues.

    I setup the sniffer and found that no data was being sent when trying to send mouse commands, only keyboard commands. So I debugged the firmware and found that when sending mouse commands, I return an error code 8 (NRF_ERROR_INVALID_STATE). Below is my function that I took from the BLE mouse example.

    static void mouse_movement_send(int16_t x_delta, int16_t y_delta)
    {
        ret_code_t err_code;
    
        if (m_in_boot_mode)
        {
            x_delta = MIN(x_delta, 0x00ff);
            y_delta = MIN(y_delta, 0x00ff);
    
            err_code = ble_hids_boot_mouse_inp_rep_send(&m_hids_mouse,
                                                        0x00,
                                                        (int8_t)x_delta,
                                                        (int8_t)y_delta,
                                                        0,
                                                        NULL,
                                                        m_conn_handle);
            NRF_LOG_INFO("Mouse send boot mode, error: %d", err_code);
        }
        else
        {
            uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
    
            APP_ERROR_CHECK_BOOL(INPUT_REP_MOVEMENT_LEN == 3);
    
            x_delta = MIN(x_delta, 0x0fff);
            y_delta = MIN(y_delta, 0x0fff);
    
            buffer[0] = x_delta & 0x00ff;
            buffer[1] = ((y_delta & 0x000f) << 4) | ((x_delta & 0x0f00) >> 8);
            buffer[2] = (y_delta & 0x0ff0) >> 4;
    
            err_code = ble_hids_inp_rep_send(&m_hids_mouse,
                                             INPUT_REP_MOVEMENT_INDEX,
                                             INPUT_REP_MOVEMENT_LEN,
                                             buffer,
                                             m_conn_handle);
            NRF_LOG_INFO("Mouse send, error: %d", err_code);  // <- error code 8
        }
    
        if ((err_code != NRF_SUCCESS) &&
            (err_code != NRF_ERROR_INVALID_STATE) &&
            (err_code != NRF_ERROR_RESOURCES) &&
            (err_code != NRF_ERROR_BUSY) &&
            (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
           )
        {
            APP_ERROR_HANDLER(err_code);
        }
    }

    Any idea what could cause an invalid state only on Android? iOS returns error code 0.

    *Edit: The error code appears to be coming from the function call: sd_ble_gatts_hvx(conn_handle, &hvx_params) on line 1373 of ble_hids.c.

Reply
  • Hi Kenneth,

    Thanks for your response. I tried clearing the cache and storage of the Android device and trying a different phone, but ran into the same issues.

    I setup the sniffer and found that no data was being sent when trying to send mouse commands, only keyboard commands. So I debugged the firmware and found that when sending mouse commands, I return an error code 8 (NRF_ERROR_INVALID_STATE). Below is my function that I took from the BLE mouse example.

    static void mouse_movement_send(int16_t x_delta, int16_t y_delta)
    {
        ret_code_t err_code;
    
        if (m_in_boot_mode)
        {
            x_delta = MIN(x_delta, 0x00ff);
            y_delta = MIN(y_delta, 0x00ff);
    
            err_code = ble_hids_boot_mouse_inp_rep_send(&m_hids_mouse,
                                                        0x00,
                                                        (int8_t)x_delta,
                                                        (int8_t)y_delta,
                                                        0,
                                                        NULL,
                                                        m_conn_handle);
            NRF_LOG_INFO("Mouse send boot mode, error: %d", err_code);
        }
        else
        {
            uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
    
            APP_ERROR_CHECK_BOOL(INPUT_REP_MOVEMENT_LEN == 3);
    
            x_delta = MIN(x_delta, 0x0fff);
            y_delta = MIN(y_delta, 0x0fff);
    
            buffer[0] = x_delta & 0x00ff;
            buffer[1] = ((y_delta & 0x000f) << 4) | ((x_delta & 0x0f00) >> 8);
            buffer[2] = (y_delta & 0x0ff0) >> 4;
    
            err_code = ble_hids_inp_rep_send(&m_hids_mouse,
                                             INPUT_REP_MOVEMENT_INDEX,
                                             INPUT_REP_MOVEMENT_LEN,
                                             buffer,
                                             m_conn_handle);
            NRF_LOG_INFO("Mouse send, error: %d", err_code);  // <- error code 8
        }
    
        if ((err_code != NRF_SUCCESS) &&
            (err_code != NRF_ERROR_INVALID_STATE) &&
            (err_code != NRF_ERROR_RESOURCES) &&
            (err_code != NRF_ERROR_BUSY) &&
            (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
           )
        {
            APP_ERROR_HANDLER(err_code);
        }
    }

    Any idea what could cause an invalid state only on Android? iOS returns error code 0.

    *Edit: The error code appears to be coming from the function call: sd_ble_gatts_hvx(conn_handle, &hvx_params) on line 1373 of ble_hids.c.

Children
  • From documentation of sd_ble_gatts_hvx() on your error code:

    NRF_ERROR_INVALID_STATE One or more of the following is true:
    • Invalid Connection State
    • Notifications and/or indications not enabled in the CCCD
    • An ATT_MTU exchange is ongoing

    Since it only affect mouse or keyboard commands, it is likely because CCCD's are not enabled. The CCCD should be enabled by Android after the report map have been parsed, it's likely caused by an error in the report map that cause Android from writing to the CCCD. You may try the HID descriptor tool to check if there may be any errors: https://www.usb.org/document-library/hid-descriptor-tool 

    Kenneth

  • Ended up getting it to work. I made a few changes which included combining the hid services into one and changing the report map. I'm not exactly able to pinpoint what caused the issue but it could have been very likely a report map problem. For anyone interested here's my working code related to the mouse and keyboard hid service.

    HID Definitions

    #define INPUT_REP_REF_ID                    1                                       /**< Id of reference to Keyboard Input Report. */
    #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 OUTPUT_REP_REF_ID                   1                                       /**< Id of reference to Keyboard Output Report. */
    #define FEATURE_REP_REF_ID                  1                                       /**< 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              4                                           /**< 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         1                                           /**< Index of Mouse Input Report containing button data. */
    #define INPUT_REP_MOVEMENT_INDEX        2                                           /**< Index of Mouse Input Report containing movement data. */
    #define INPUT_REP_MPLAYER_INDEX         3                                           /**< Index of Mouse Input Report containing media player data. */
    #define INPUT_REP_REF_BUTTONS_ID        2                                           /**< Id of reference to Mouse Input Report containing button data. */
    #define INPUT_REP_REF_MOVEMENT_ID       3                                           /**< Id of reference to Mouse Input Report containing movement data. */
    #define INPUT_REP_REF_MPLAYER_ID        4                                           /**< Id of reference to Mouse Input Report containing media player data. */
    
    BLE_HIDS_DEF(m_hids,                                                                /**< HID service instance. */
                 NRF_SDH_BLE_TOTAL_LINK_COUNT,
                 INPUT_REPORT_KEYS_MAX_LEN,
                 OUTPUT_REPORT_MAX_LEN,
                 FEATURE_REPORT_MAX_LEN,
                 INPUT_REP_BUTTONS_LEN,
                 INPUT_REP_MOVEMENT_LEN,
                 INPUT_REP_MEDIA_PLAYER_LEN);

    Report Map

    static uint8_t m_report_map_data[] =
    {
        0x05, 0x01,                 // Usage Page (Generic Desktop)
        0x09, 0x06,                 // Usage (Keyboard)
        0xA1, 0x01,                 // Collection (Application)
        0x85, 0x01,                 //     Report Id 1
        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 Count (2)
        0x95, 0x02,                 //     Report Size (8 bit)
        0xB1, 0x02,                 //     Feature (Data, Variable, Absolute)
    
        0xC0,                        // End Collection (Application)
        
        0x05, 0x01, // Usage Page (Generic Desktop)
        0x09, 0x02, // Usage (Mouse)
    
        0xA1, 0x01, // Collection (Application)
    
        // Report ID 2: Mouse buttons + scroll/pan
        0x85, 0x02,       // Report Id 2
        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 3: Mouse motion
        0x85, 0x03,       // Report Id 3
        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 4: Advanced buttons
        0x05, 0x0C,       // Usage Page (Consumer)
        0x09, 0x01,       // Usage (Consumer Control)
        0xA1, 0x01,       // Collection (Application)
        0x85, 0x04,       // Report Id (4)
        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
    };

    HID Initialization

    /**@brief Function for initializing HID Service.
     */
    static void hids_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[INPUT_REPORT_COUNT];
        static ble_hids_outp_rep_init_t    output_report_array[1];
        static ble_hids_feature_rep_init_t feature_report_array[1];
    
        memset((void *)input_report_array, 0, sizeof(ble_hids_inp_rep_init_t) * INPUT_REPORT_COUNT);
        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 Mouse HID Service.
        p_input_report                      = &input_report_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                      = &input_report_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                      = &input_report_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;
    
        // Initialize KBD 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                       = true;
        hids_init_obj.inp_rep_count                  = INPUT_REPORT_COUNT;
        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(m_report_map_data);
        hids_init_obj.rep_map.p_data                 = m_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.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, &hids_init_obj);
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for initializing services that will be used by the application.
     */
    static void services_init(void)
    {
        qwr_init();
        dis_init();
        bas_init();
        hids_init();
    }

Related