HID 101: The Protocol Primer
Welcome to the world of Human Interface Devices.
If you have ever wondered how your computer knows that this USB cable is a Mouse and that one is a Joystick, without installing any driver... you are in the right place.
The "Self-Describing" Magic
In older protocols (like Serial/RS232), the computer and the device had to agree on a format beforehand.
"I will send you 8 bytes. Byte 1 is X, Byte 2 is Y..."
If the device changed, the driver broke.
HID flips the script. When you plug in a HID device, the first thing it says is not "Here is data", but "Here is who I am".
It sends a Report Descriptor (a map) that explains how to read its future messages.
sequenceDiagram
participant D as Device (Mouse)
participant H as Host (PC/OS)
Note over D, H: Enumeration Phase
H->>D: Who are you?
D->>H: I am a HID Device!
H->>D: Give me your Map (Descriptor).
D->>H: Here is a binary blob describing my buttons and axes.
Note over H: Host compiles the Map...<br/>"Okay, 3 bits for buttons, 8 bits for X..."
Note over D, H: Runtime Phase
D->>H: 01 00 7F (Raw Data)
Note over H: Host uses the Map to decode:<br/>Button 1 = Clicked<br/>X = 127
The Three Pillars of HID
To understand HID, you need to capture three concepts.
1. The Report Descriptor (The Map)
This is a static block of binary data. It describes the structure of the data packets. It defines:
- What the data represents (e.g., "X Axis", "Fire Button").
- How the data is formatted (e.g., "8-bit integer", "1-bit boolean").
- Where it is located in the packet (implicit offset).
2. The Usage Tables (The Vocabulary)
HID uses standard codes to identify functions. These are called Usages. Instead of sending the string "Volume Up", the device sends a standardized code.
- Generic Desktop Page (0x01): Includes Mouse, Keyboard, Joystick, X, Y, Z.
- Button Page (0x09): Button 1, Button 2, etc.
- Consumer Page (0x0C): Media keys (Play, Pause, Volume).
Fun Fact: There are Usages for almost everything, including "Magic Carpet Simulation", "Submarine", and "Gun Turret".
3. The Report (The Payload)
This is the actual data packet sent during operation. It is extremely compact. It contains no metadata, only raw values packed together as tightly as possible.
Anatomy of a Descriptor
A Descriptor is a program written in a simple language. It consists of Items.
Global Items (The Context)
They set the state for everything that follows.
Usage Page: "I am now talking about Buttons".Logical Min/Max: "The following values are between -127 and 127".Report Size: "The following fields are 8 bits wide".
Local Items (The Labels)
They apply only to the next main item.
Usage: "This field represents the X Axis".
Main Items (The Action)
They generate the fields in the report based on the current State and Labels.
Input: "Create a field here (Device -> Host)".Output: "Create a field here (Host -> Device, e.g., LED)".
Example Visualization
Imagine a descriptor as a recipe:
| Instruction | Meaning | Effect |
|---|---|---|
| GLOBAL | Set Size to 1 bit | Size = 1 |
| GLOBAL | Set Count to 3 | Count = 3 |
| LOCAL | Tag next as "Button" | Current_Usage = Button |
| MAIN | Create Input | Generates 3 fields of 1 bit (Buttons) |
| GLOBAL | Set Size to 5 bits | Size = 5 |
| MAIN | Create Input (Const) | Generates 5 bits of Padding |
Total: 3 bits + 5 bits = 8 bits (1 Byte).
Why is hid-declarative needed?
Writing a descriptor manually is painful because:
- It is Stateful: You have to remember that 10 lines ago, you set the
Report Sizeto 4. - It is packed: If you miss 2 bits of padding, your byte alignment shifts, and your "X Axis" reads garbage.
- It is obscure:
0x05 0x01 0x09 0x06...is not readable.
hid-declarative handles the state machine, calculates the padding, and lets you use human-readable names.
Instead of writing assembly-like instructions, you define the Goal (Schema), and we generate the Instructions (Spec).