This is HID over GATT Profile
Intro
Reports are HID communication. They could be input report (sent from device to host, such as key presses), output reports (sent from host to device, such as caps lock key on), feature report (read, write data, no clue what theyre for?> battery?).
They support boot vs report protocols. Boot protocols are meant to be simple and usable in BIOS, logins, etc, BEFORE AN OS LOADS. Meant to be super simple but lacks features.
Services
HID Service
UUID: 0x1812
Can be exposed multiple times (keyboard / mouse combo, but oftentimes its just one complex report descriptor)
This profile contains the following characteristics:
| Characteristic | Purpose | Value | Notes |
|---|---|---|---|
| HID Information (0x2A4A) | Basic info about HID (which version, coutnry code) | 4-bytes:bcdHid (2 bytes): HID release numberbCountryCode (1 byte): Hardware country codeflags (1 byte): Bit 0 is set to 1 if device can send wake signal to host, Bit 1 is set to 1 if device will initiate bonding with a new host | Read |
| Report Map Characteristic (0x2A4B) | Contains HID Report descriptor !!! | Raw byte array of report descriptor | Read Contains the HID Report Descriptor defining device capabilities. Optionally uses External Report Reference Descriptor (0x2907). |
| HID Control Point Characteristic (0x2A4C) | Host to send commands to HID | 0x00: Suspend 0x01: exit suspend | WriteWithoutResponse |
| Report Characteristic (0x2A4D) | Used to exchange the input, output, feature reports ^220e7f | Requires CCCD Can have multiple instances. Idk maybe to split up reports across bandwidths? Something about an option ” Report Reference Descriptor” (0x2908) | |
| Protocol Mode Characteristic (0x2A4E) | Used to switch from boot protocol to report protocol | 0x00 - boot (simlified) 0x01 report (idk) | |
| Boot Keyboard Input Report Characteristic (0x2A22) | |||
| Boot Keyboard Output Report Characteristic (UUID: 0x2A32) | |||
| Boot Mouse Input Report Characteristic (UUID: 0x2A33) |
Report Descriptors
Sequence of bytes that describe a device’s input and output capabilities.
Built off items. Items can be short or long. Short items have a 1-byte prefix (4 bits tag, 2 bits type, 2 bits size), and 0-4 data bytes. Long items are a bit more complex, used maybe only 0.01% of the time.
Three types of items: Main (for report fields or groups of report fields), Global (configuration that affects until subsequent changes), Local (affects only the next main item)
Main Items
The type is 00
For input, output, feature, consult section 6.2.2. of https://www.usb.org/sites/default/files/hid1_11.pdf. The type tells the parser how to interpret the data based on usage min / max, etc
| Type | Prefix bits | Description |
|---|---|---|
| Input | 1000 00 xx | Defines data fields sent from the HID device to the host |
| Output | 1001 00 xx | Defines data fields sent from the host to the HID device |
| Feature | 1011 00 xx | Defines data fields for config status |
| Collection | 1010 00 xx | Groups related items into a unit. Essentially a “folder” to group data, and apply scopes (so local items can affect the entire collection and its children) |
| End Collection | 1100 00 xx | Marks the end of a collection |
Global Items
“Scoped” to collections, will be reverted. Sticky
| Type | Prefix bits | Description |
|---|---|---|
| Usage page | 1000 00 xx | Sets the context for Usage items (Keyboard, keypad, generic desktop, etc) |
| Logical Min / Max | 0001 01 xx 0010 01 xx | Defines range of possible values. THe “value” of the data. Tells the host to accept this numerical range to expect |
| Report Size | 1001 01 xx | Defines the size of each field (in bits) |
| Report COunt | 1001 01 xx | Specifies the number of fields described by the current item |
| Report ID | 1000 01 xx | Assigns an ID to a report, allowing multiple reports of the same type (Input, output feature). This separates different report formats (a standard key that uses 8 bytes, a media key that uses only 2?) |
Local Items
| Type | Prefix bits | Description |
|---|---|---|
| Usage | 0000 10 xx | Defines the SPECIFIC function of the next item within the current usage page (keys) |
| Usage Min / Max | 0001 10 xx 0010 10 xx | Defines range of possible values. The “name of the control”. Tells the host what the control is? |
Usage Minimum and Usage Maximum assigns a series of Usage IDs to the data in the array or bitmap. Usage Minimum defines the starting ID and Usage Maximum defines the ending ID.
Example:
For a 3 button, mouse ID’s are assigned to Button1 (0x01), Button2 (0x02), Button3 (0x03)
Usage Minimum and Usage Maximum entries allow a series of similar items to be defined together when the usages are assigned to continuous numbers. Instead of defining 101 Usages separately for each key on the standard keyboard, a single usage can be defined with the maximum and minimum set to the maximum and minimum IDs.
Logical Minimum and Logical Maximum specify the range of values that the report can contain.
Example:
- Mouse that reports position value of -127 to 127 will have a logical maximum of 127 and logical minimum of -127
- Single button state that is either asserted or released will have a logical minimum of 0 and logical maximum of 1
Reports are “concated” together. So if i say i have a Report count(8), and a report count (16), then my final report will be 8 bits + 16 bits. Report IDs seperate that
https://usb.org/sites/default/files/hut1_5.pdf
Examples
Heres my boot keyboard:
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x08, // Report Count (8)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00)
0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
// 67 bytes
Heres my QMK, NKRO HID:
0x06, 0x01, 0x00, // Usage Page (Generic Desktop Ctrls)
0x09, 0x80, // Usage (Sys Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x19, 0x81, // Usage Minimum (Sys Power Down)
0x29, 0x83, // Usage Maximum (Sys Wake Up)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x02, // Report ID (2)
0x19, 0x00, // Usage Minimum (Unassigned)
0x2A, 0xFF, 0x02, // Usage Maximum (0x02FF)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x95, 0x01, // Report Count (1)
0x75, 0x10, // Report Size (16)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x2F, // Usage (0x2F)
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x04, // Report ID (4)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x04, // Usage Minimum (0x04)
0x29, 0x70, // Usage Maximum (0x70)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x78, // Report Count (120)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x85, 0x05, // Report ID (5)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x02, // Usage Maximum (0x02)
0x75, 0x08, // Report Size (8)
0x95, 0x05, // Report Count (5)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x85, 0x06, // Report ID (6)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x02, // Usage Maximum (0x02)
0x75, 0x08, // Report Size (8)
0x96, 0x07, 0x04, // Report Count (1031)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x85, 0x07, // Report ID (7)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x05, // Usage Maximum (0x05)
0x75, 0x01, // Report Size (1)
0x95, 0x05, // Report Count (5)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x03, // Report Count (3)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x16, 0x00, 0x80, // Logical Minimum (-32768)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x75, 0x10, // Report Size (16)
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x09, 0x38, // Usage (Wheel)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x0C, // Usage Page (Consumer)
0x0A, 0x38, 0x02, // Usage (AC Pan)
0x95, 0x01, // Report Count (1)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x85, 0x08, // Report ID (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x09, 0x00, // Usage (0x00)
0x75, 0x08, // Report Size (8)
0x96, 0x7D, 0x01, // Report Count (381)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
// 251 bytes
Resources
https://github.com/esp32beans/BLE_HID_Client
https://github.com/olegos76/nimble_kbdhid_example
https://www.ti.com/lit/an/swra715/swra715.pdf?ts=1765644181805