0

I have a hid_discriptor which looks like this:

// from USB HID Specification 1.1, Appendix B.1
const uint8_t hid_descriptor_keyboard_boot_mode[] = {
    /*
       Keyboard
     */
    0x05, 0x01,                    // Usage Page (Generic Desktop)
    0x09, 0x06,                    // Usage (Keyboard)
    0xa1, 0x01,                    // Collection (Application)

    0x85,  0x01,                   // Report ID 1

    // Modifier byte

    0x75, 0x01,                    //   Report Size (1)
    0x95, 0x08,                    //   Report Count (8)
    0x05, 0x07,                    //   Usage Page (Key codes)
    0x19, 0xe0,                    //   Usage Minimum (Keyboard LeftControl)
    0x29, 0xe7,                    //   Usage Maxium (Keyboard Right GUI)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x25, 0x01,                    //   Logical Maximum (1)
    0x81, 0x02,                    //   Input (Data, Variable, Absolute)

    // Reserved byte

    0x75, 0x01,                    //   Report Size (1)
    0x95, 0x08,                    //   Report Count (8)
    0x81, 0x03,                    //   Input (Constant, Variable, Absolute)

    // LED report + padding

    0x95, 0x05,                    //   Report Count (5)
    0x75, 0x01,                    //   Report Size (1)
    0x05, 0x08,                    //   Usage Page (LEDs)
    0x19, 0x01,                    //   Usage Minimum (Num Lock)
    0x29, 0x05,                    //   Usage Maxium (Kana)
    0x91, 0x02,                    //   Output (Data, Variable, Absolute)

    0x95, 0x01,                    //   Report Count (1)
    0x75, 0x03,                    //   Report Size (3)
    0x91, 0x03,                    //   Output (Constant, Variable, Absolute)

    // Keycodes

    0x95, 0x06,                    //   Report Count (6)
    0x75, 0x08,                    //   Report Size (8)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x25, 0xff,                    //   Logical Maximum (1)
    0x05, 0x07,                    //   Usage Page (Key codes)
    0x19, 0x00,                    //   Usage Minimum (Reserved (no event indicated))
    0x29, 0xff,                    //   Usage Maxium (Reserved)
    0x81, 0x00,                    //   Input (Data, Array)
    0xc0,                          // End collection
};

Which works for keyboard codes;

However, i also would like to add consumer control to my sample, so i can also send volume up/down etc.

    /*
       Consumer Control
     */
    0x05, 0x0C,                     // Usage Page (Consumer Devices)
    0x09, 0x01,                     // Usage (Consumer Control)
    0xA1, 0x01,                     // Collection (Application)
    0x85, 0x02,                     //      Report ID
    0x75, 0x09, 0x01,               //      Report Size
    0x95, 0x09, 0x01,               //     Report Count
    0x15, 0x00,                     //      Logical Minimum (0)
    0x26, 0xFF, 0x07,               //      Logical Maximum (2047)
    0x19, 0x00,                     //      Usage Minimum (0)
    0x2A, 0xFF, 0x07,               //      Usage Maximum (2047)
    0x81, 0x00,                     //      Input (Data, Ary, Abs)
    0xC0,

The question is, that in the provided demo code; the code to send a report is this:

static void send_report(int modifier, int keycode){
    uint8_t report[] = { /* 0xa1, */ modifier, 0, 0, keycode, 0, 0, 0, 0, 0};
    hids_device_send_input_report(con_handle, report, sizeof(report));
}

This works in terms of sending keystrokes; when i trace that call in the source code, it goes to this function;

void hids_device_send_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len){
    hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
    if (!instance){
        log_error("no instance for handle 0x%02x", con_handle);
        return;
    }
    att_server_notify(con_handle, instance->hid_report_input_value_handle, report, report_len);
}

Which actually uses the instance->hid_report_input_value_handle instead of the hid_descriptor_keyboard_boot_mode; i also know why (in case people are wondering); The spec mentions;

The HID Subclass 1 defines two descriptors for Boot Devices. Devices may append additional data to these boot reports, but the first 8 bytes of keyboard reports and the first 3 bytes of mouse reports must conform to the format defined by the Boot Report descriptor in order for the data to be correctly interpreted by the BIOS.

...snip...

When the HID class driver is loaded, it will issue a Change Protocol, changing from the boot protocol to the report protocol after reading the boot interface’s Report descriptor.

So after the change protocol is called; the report is loaded into the hid_report_input_value_handle. So until now, everything is clear.

Then the main question comes; HOW do i send consumer control to my paired device? Is it just a matter of adding the reportId? e.g.

For keyboard keys as keydown event:

uint8_t report[] = { 0x01, modifier, 0, 0, keycode, 0, 0, 0, 0, 0};
hids_device_send_input_report(con_handle, report, sizeof(report));

And for consumer control (play/pause) as keydown event:

uint8_t report[] = { 0x02, 0xCD, 0x00};
hids_device_send_input_report(con_handle, report, sizeof(report));

Is that about correct? Any guidance would be welcome, BLE is new to me hence the question.

Gilles
  • 351
  • 1
  • 4
  • 7

1 Answers1

0

For the keyboard HID descriptor, given that it represents the following C structure:

//--------------------------------------------------------------------------------
// Keyboard/Keypad Page inputReport 01 (Device --> Host)
//--------------------------------------------------------------------------------

typedef struct
{
  uint8_t  reportId;                                 // Report ID = 0x01 (1)
                                                     // Collection: CA:Keyboard
  uint8_t  KB_KeyboardKeyboardLeftControl : 1;       // Usage 0x000700E0: Keyboard Left Control, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftShift : 1;         // Usage 0x000700E1: Keyboard Left Shift, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftAlt : 1;           // Usage 0x000700E2: Keyboard Left Alt, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardLeftGui : 1;           // Usage 0x000700E3: Keyboard Left GUI, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightControl : 1;      // Usage 0x000700E4: Keyboard Right Control, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightShift : 1;        // Usage 0x000700E5: Keyboard Right Shift, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightAlt : 1;          // Usage 0x000700E6: Keyboard Right Alt, Value = 0 to 1
  uint8_t  KB_KeyboardKeyboardRightGui : 1;          // Usage 0x000700E7: Keyboard Right GUI, Value = 0 to 1
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  : 1;                                      // Pad
  uint8_t  Keyboard[6];                              // Value = 0 to 255
} inputReport01_t;

...then I would expect the send function to look like:

uint8_t report[] = { 0x01, modifier, 0, keycode, 0, 0, 0, 0, 0}; // <-- i.e. one less byte
hids_device_send_input_report(con_handle, report, sizeof(report));

As for the Consumer Device report, the HID report descriptor seems quite wrong. It should be something like (assuming it is concatenated to your keyboard report descriptor):

/*
   Consumer Control
 */
0x05, 0x0C,                     // Usage Page (Consumer Devices)
0x09, 0x01,                     // Usage (Consumer Control)
0xA1, 0x01,                     // Collection (Application)
0x85, 0x02,                     //      Report ID
0x75, 0x10,                     //      Report Size (16)
0x95, 0x01,                     //     Report Count (1)
0x26, 0xFF, 0x07,               //      Logical Maximum (2047)
0x19, 0x00,                     //      Usage Minimum (0)
0x2A, 0xFF, 0x07,               //      Usage Maximum (2047)
0x81, 0x00,                     //      Input (Data, Ary, Abs)
0xC0,

...which represents the following C structure:

//--------------------------------------------------------------------------------
// Decoded Application Collection
//--------------------------------------------------------------------------------

/*
05 0C        (GLOBAL) USAGE_PAGE         0x000C Consumer Device Page 
09 01        (LOCAL)  USAGE              0x000C0001 Consumer Control (Application Collection)  
A1 01        (MAIN)   COLLECTION         0x01 Application (Usage=0x000C0001: Page=Consumer Device Page, Usage=Consumer Control, Type=Application Collection)
85 02          (GLOBAL) REPORT_ID          0x02 (2)  
75 10          (GLOBAL) REPORT_SIZE        0x10 (16) Number of bits per field  
95 01          (GLOBAL) REPORT_COUNT       0x01 (1) Number of fields  
26 FF07        (GLOBAL) LOGICAL_MAXIMUM    0x07FF (2047)  
19 00          (LOCAL)  USAGE_MINIMUM      0x000C0000 Unassigned  <-- Info: Consider replacing 19 00 with 18
2A FF07        (LOCAL)  USAGE_MAXIMUM      0x000C07FF 
81 00          (MAIN)   INPUT              0x00000000 (1 field x 16 bits) 0=Data 0=Array 0=Absolute 
C0           (MAIN)   END_COLLECTION     Application 
*/
//--------------------------------------------------------------------------------
// Consumer Device Page inputReport 02 (Device --> Host)
//--------------------------------------------------------------------------------

typedef struct
{
  uint8_t  reportId;                                 // Report ID = 0x02 (2)
                                                     // Collection: CA:ConsumerControl
  uint16_t ConsumerControl;                          // Value = 0 to 2047
} inputReport02_t;

...in which case, your function to send a Consumer Device request should work without modification:

uint8_t report[] = { 0x02, 0xCD, 0x00};
hids_device_send_input_report(con_handle, report, sizeof(report));

...to send a Play/Pause request.

Don't forget to signal that "no keys are pressed" by following each send with (in the case of the keyboard):

uint8_t report[] = { 0x01, modifier, 0, 0, 0, 0, 0, 0, 0}; // no keys pressed
hids_device_send_input_report(con_handle, report, sizeof(report));

This is the equivalent of a traditional "key up" notification.

aja
  • 1,525
  • 17
  • 20
  • If I get your intention correctly, you suggest to send a keyboard report to release consumer input, which is not the proper way to do it. In order to release consumer codes, you should send a consumer report with "Null" value. I.e. a value outside logical range (HID 5.10). – Nipo Mar 18 '19 at 22:46