Notes on Human Interface Device (HID) implementation

April 18, 2026 · View on GitHub

This project implements a HID device (sorry for the redundancy) which may be used both through the USB and Bluetooth GATT protocol stacks. Most relevant implementation details can be found at file HID_definitions.h

This document does not intent to explain HID devices. A good source is Jan Axelson's Lakeview Research.

USB

Any HID device requires a vendor identifier ("VID" from now on) in order to work. Those identifiers are assigned to individual vendors through a license managed by "The USB Implementers Forum", aka "USB-IF". That license is not open-source-friendly and the $6000 fee discourages any home-made project attempt.

More information at Open source Hardware Association.

This project features an USB implementation using the existing PID (product identifier) and VID from your DevKit manufacturer, which you should not change.

As a result, you are not allowed to use this project outside of your personal sphere. You are not allowed to exhibit the USB logo nor any other licensed material from the USB-IF.

Bluetooth

When using the Bluetooth stack, HID devices may use VIDs from different "sources". As seen at the Device Information Service Specification:

Vendor ID sourceDescription
0x01Bluetooth SIG-assigned Device ID Vendor ID value from the Assigned Numbers document
0x02USB Implementer’s Forum assigned Vendor ID value
otherReserved for future use

I don't know what is the legal situation when a reserved VID source is used. At least, there is a way to avoid the USB-IF's licenses.

HID reports

This project make use of a number of HID reports:

Report IDTypePurpose
1InputButtons, clutch, POV and the like
2FeatureWheel capabilities
3FeatureWheel configuration
4FeatureUser-defined buttons map
5FeatureCustom hardware ID
20OutputTelemetry data / Powertrain
21OutputTelemetry data / ECU
22OutputTelemetry data / Race control
23OutputTelemetry data / Gauges
24OutputTelemetry data / Wheels
30OutputPixel control

Note that feature reports are both read and write.

Data format of report ID 1 (input)

FieldBits sizeByte index
Buttons state1280
Rz axis816
Ry axis817
Rx axis818
POV (D-PAD)419
Feature notification419
  • Buttons state: one bit per button (1=pressed, 0=non-pressed). The least significant bit is the first button.
  • Axes: an unsigned byte in the range 0 to 254.
    • Rz: F1-Style clutch (combined input from left and right clutch paddles)
    • Ry: Left clutch paddle
    • Rx: Right clutch paddle
  • POV (D-PAD): 4 least significant bits of byte index 19. Range: 0 to 8.
  • Feature notification: 4 most significant bits of byte index 19. Valid values: 0 (nothing to notify) or 3 (wheel configuration has changed).

Data format of report ID 2 (wheel capabilities)

Write attempts will be ignored, so this report is read-only.

Byte indexSize (Bytes)Purpose (field)NoteSince data version
02Magic numberAlways set to BF51 (hexadecimal)1.0
22Major VersionVersion of this specification1.0
42Minor VersionVersion of this specification1.0
62FlagsDevice capabilities1.0
88IDChip identifier1.1
161Max FPSMaximum frames per second1.3
171Pixel countIn the "telemetry leds" group1.4
181Pixel countIn the "Buttons lighting" group1.4
191Pixel countIn the "Individual LEDs" group1.4

Report ID 1 (input) is not affected by versioning.

Magic number

The purpose of such a field is to enable device detection and recognition, without the need to rely on a particular VID or product ID.

Major and minor version

Two different major versions means incompatible data formats. A greater minor version means a backwards-compatible data format. Host-side software should check for version compatibility. Some examples:

Device versionSupported version at hostResult
1.52.0Incompatible
2.01.1Incompatible
1.61.1Incompatible
2.32.7Compatible

However, host-side software may support several data versions at the same time.

Current data version is 1.6.

Flags

The "flags" field is a set of 1-bit flags. Flags are indexed starting from the least significant bit. Non indexed bits are reserved for future use. Current flags are enumerated in DeviceCapability at file SimWheelTypes.hpp This is a summary:

  • Digital clutch paddles
  • Analog clutch paddles
  • "ALT" buttons
  • DPAD
  • Battery
  • Calibration data for the battery
  • Display for powertrain telemetry
  • Display for ECU telemetry
  • Display for race control telemetry
  • Display for gauges telemetry
  • Rotary encoders

ID

This is the internal chip identifier as reported by esp_efuse_mac_get_default(). Useful to distinguish one device from another.

Max FPS

This is the maximum display rate (in frames per second) supported by the device's user interface. Zero will be read if there is no user interface. When sending telemetry data (output reports), the host computer should adapt its data rate to this display rate.

Pixel count fields

The number of RGB pixels available for pixel control (see report ID 30). There are three groups of pixels. Zero means that pixel control is not available in that group.

Data format of report ID 3 (wheel configuration)

While writing, any value outside of the valid range will be ignored, so they me be used to mask which fields to modify or not.

Byte indexSize (bytes)Purpose (field)Since data version
01Working mode of clutch paddles1.0
11Working mode of ALT buttons1.0
21Current bite point1.0
31Simple command / Current battery level1.0
41Working mode of DPAD inputs1.1
51Security lock1.2
61Pulse width multiplier for rotary encoders1.5

Working mode of clutch paddles

Read/write (unless locked). Valid values are enumerated in ClutchWorkingMode at file SimWheelTypes.hpp. Write FF (hexadecimal) to ignore this field.

Working mode of ALT buttons

Read/write (unless locked). Non zero means "ALT mode". Zero means "regular buttons". Write FF (hexadecimal) to ignore this field.

Current bite point

Read/write (unless locked). Write FF (hexadecimal) to ignore this field.

Simple command / Current battery level

At read:

  • Retrieve current battery level. Non meaningful if there is no battery. Check capabilities.

At write (unless locked):

  • Send a simple command. Valid commands are enumerated in SimpleCommand at file SimWheelTypes.hpp. This is a summary:

    Simple command (decimal)Description
    1Recalibrate analog clutch paddles (if any).
    2Restart battery auto-calibration. Ignored if the battery was "factory-calibrated".
    3Reset user-defined button mapping to factory defaults.
    4Save all user settings at once (including button mapping).
    5Reverse left clutch paddle polarity (analog only if any).
    6Reverse right clutch paddle polarity (analog only if any).
    7Show all pixels in all groups at once
    8Turn off all pixels in all groups
  • Write FF (hexadecimal) to ignore this field.

Working mode of DPAD inputs

Read/write (unless locked). Non zero means "navigation controls". Zero means "regular buttons". Write FF (hexadecimal) to ignore this field.

Security lock

Read-only.

When zero, write is allowed. Otherwise, write is forbidden.

For security concerns, the user can lock or unlock all writing attempts to HID reports by using just hardware inputs. This is a security precaution to stop unauthorized configuration modifications caused by rogue programs.

Pulse width multiplier for rotary encoders

Read/write (unless locked). Write FF (hexadecimal) to ignore this field.

Valid values are in the range from 1 to 6 (inclusive). Invalid values are ignored. Valid values are saved to flash memory without delay.

Data format of report ID 4 (user-defined buttons map)

Byte indexSize (bytes)Purpose (field)Since data version
01Selected firmware-defined button number1.1
11User-defined button number when ALT mode is disengaged1.1
21User-defined button number when ALT mode is engaged1.1

Important note: any change in the user-defined map is not automatically saved to flash memory. In order to save, you must issue the corresponding simple command using report ID 3.

Firmware-defined button number

At read:

  • A selected button number in the range from 0 to 63, which identifies a firmware-defined button.
  • Any value outside of the previous range may be read, which means no button number is selected.

In order to select another button number, write to this field first.

At write (unless locked):

  • Select a firmware-defined button number. Any value outside the valid range will be ignored.
  • You should read after a write in order to known the user-defined map for the selected button number. Note that another application may also write this field, so don't assume the button you select is the button you will read next.

User-defined map (bytes at index 1 and 2)

At read:

  • The current user-defined button number for the selected firmware-defined button number.
  • A value of FF (hexadecimal) means the selected button is not available.

At write (unless locked):

  • Any value in the range from 0 to 127: set a user-defined button number for the selected firmware-defined button number.
  • Any value outside of the previous range: just select another firmware-defined button number, but do not overwrite current map.

Examples (pseudo-code):

  • To list all available inputs and current map:

    for (uint8_t i = 0; i < 64; i++) {
       report4.write(i,0xFF,0xFF);
       report4.read(j,map,mapAlt);
       if (i != j)
          another_app_is_interfering();
       else if ((map > 127) || (mapAlt > 127))
          input_number_not_available(i);
       else
          show_map(i,map,mapAlt);
    }
    
  • To map button number 13 to button numbers 16 (ALT disengaged) and 112 (ALT engaged):

    report4.write(13,16,112);
    
  • To map button number 17 as it was in factory defaults:

    report4.write(17,17,17+64);
    
  • To save current map to flash memory:

    report3.write(0xFF,0xFF,0xFF,4);
    
  • To revert user-defined map to factory defaults (and save):

    report3.write(0xFF,0xFF,0xFF,3);
    report3.write(0xFF,0xFF,0xFF,4);
    

Data format of report ID 5 (custom hardware ID)

Byte indexSize (bytes)Purpose (field)Since data version
02Custom vendor ID1.2
22Custom product ID1.2
42Control code1.2

Custom vendor ID (VID) and product ID (PID)

Those fields enable the user to set a custom VID and PID for BLE devices only. Changes will be available after a reset or power-off.

At read:

The VID and PID configured for the subsequent reboot will be retrieved. Note that the custom VID will be ignored in boards having USB-OTG, unless BLE-only connectivity is hard coded.

At write (unless locked):

  • If both VID and PID are set to 0000, the hardware ID will return to factory defaults after the next reboot.
  • Otherwise, the device will save the given VID and PID in flash memory and use them after the next reboot.

Control code

This field prevents accidental changes.

At read:

  • No meaning

At write (unless locked):

  • Ignored in USB devices.

  • If either VID or PID (or both) is set to 0000, this field must match AA96 (hexadecimal).

  • Otherwise, this field must match (VIDPID)mod65536(VID*PID)\mod{65536}.

    For example, if VID=7504VID=7504 and PID=303PID=303, then this control code must match (7504303)mod65536=2273712mod65536=45488(7504*303)\mod{65536}=2273712\mod{65536}=45488

No changes are made if there is no match.

Telemetry (output) reports

The host computer should look at device capabilities first (report 2 - "flags"), then it should avoid writing to non-used output reports, thus reducing bandwidth usage.

The nature of telemetry data is near real-time. So the device may be unable to keep the pace of the host computer. For this reason, the host computer should match the output rate to the value in the "Max FPS" field in report 2.

The display hardware is not synced with the reception of telemetry data. Some data may go unnoticed.

Data format of report ID 20 (Telemetry / Powertrain)

Byte indexSizeFieldData typeData versionRelated SimHub property (DataCorePlugin.GameData)
01Gearchar1.3Gear
12RPMuint161.3Rpms
31RPM percentuint81.3CarSettings_CurrentDisplayedRPMPercent
41Shift light 1uint81.3CarSettings_RPMShiftLight1
51Shift light 2uint81.3CarSettings_RPMShiftLight2
61Rev limiterboolean1.3CarSettings_RedLineRPM
71Engine startedboolean1.3EngineStarted
82Speeduint161.3SpeedLocal
  • Gear: a single ASCII character, typically "R", "N", "1", "2", etc.
  • RPM: absolute revolutions per minute.
  • RPM percent: relative revolutions per minute as a percentage for display.
  • Shift light 1: Non-zero when the engine has reached maximum torque.
  • Shift light 2: Non-zero when the engine has reached maximum power.
  • Rev limiter: true when the engine has reached maximum RPM.
  • Engine started: true if the engine is running.
  • Speed: car speed in user-selected units. Do not assume Kph nor Mph.

Data format of report ID 21 (Telemetry / ECU)

Byte indexSizeFieldData typeData versionRelated SimHub property (DataCorePlugin.GameData)
01ABS engagedboolean1.3ABSActive
11TC engagedboolean1.3TCActive
21DRS engagedboolean1.3DRSEnabled
31Pit limiterboolean1.3PitLimiterOn
41Low fuel alertboolean1.3CarSettings_FuelAlertActive
51ABS leveluint81.3ABSLevel
61TC Leveluint81.3TCLevel
71TC Cutuint81.3
81Brake bias percentuint81.3BrakeBias
  • TC Level: sometimes called "TC1".
  • TC Cut: sometimes called "TC2".
  • Brake bias: a percentage of braking force towards the front wheels, in the range 0-100.

Data format of report ID 22 (Telemetry / Race control)

Byte indexSizeFieldData typeData versionRelated SimHub property (DataCorePlugin.GameData)
01Black flagboolean1.3Flag_Black
11Blue flagboolean1.3Flag_Blue
21Checkered flagboolean1.3Flag_Checkered
31Green flagboolean1.3Flag_Green
41Orange flagboolean1.3Flag_Orange
51White flagboolean1.3Flag_White
61Yellow flagboolean1.3Flag_Yellow
72Remaining session lapsuint161.3RemainingLaps
92Remaining session time (minutes)uint161.3SessionTimeLeft
  • Remaining session laps should be zero if the session is time-based.
  • Remaining session time should be zero if the session is laps-based.

Data format of report ID 23 (Telemetry / Gauges )

Byte indexSizeFieldData typeData versionRelated SimHub property (DataCorePlugin.GameData)
01Relative turbo pressureuint81.3TurboPercent
12Absolute turbo pressureuint16 fixed point1.3Turbo
32Water temperatureuint161.3WaterTemperature
52Oil pressureuint16 fixed point1.3OilPressure
72Oil temperatureuint161.3OilTemperature
91Relative remaining fueluint81.3FuelPercent
102Absolute remaining fueluint161.3Fuel

All absolute values are expressed in user-selected units.

The following fields should be in the range from 0 to 100:

  • Relative turbo pressure.
  • Relative remaining fuel.

The following fields will be interpreted as fixed-point numbers with two decimals:

  • Absolute turbo pressure.
  • Oil pressure.

For example, the value 113 in oil pressure means 1.13 pressure units.

Data format of report ID 24 (Telemetry / Wheels )

Byte indexSizeFieldData typeData versionRelated SimHub property (DataCorePlugin.GameData)
02Front-left tire temp.uint161.7TyreTemperatureFrontLeft
22Front-right tire temp.uint161.7TyreTemperatureFrontRight
42Rear-left tire temp.uint161.7TyreTemperatureRearLeft
62Rear-right tire temp.uint161.7TyreTemperatureRearRight
82Front-left tire pressureuint16 fixed point1.7TyrePressureFrontLeft
102Front-right tire pressureuint16 fixed point1.7TyrePressureFrontRight
122Rear-left tire pressureuint16 fixed point1.7TyrePressureRearLeft
142Rear-right tire pressureuint16 fixed point1.7TyrePressureRearRight
162Front-left brake temp.uint161.7BrakesTemperatureFrontLeft
182Front-right brake temp.uint161.7BrakesTemperatureFrontRight
202Rear-left brake temp.uint161.7BrakesTemperatureRearLeft
222Rear-right brake temp.uint161.7BrakesTemperatureRearRight
241Front-left wear percentageuint81.7TyreWearFrontLeft
251Front-right wear percentageuint81.7TyreWearFrontRight
261Rear-left wear percentageuint81.7TyreWearRearLeft
271Rear-right wear percentageuint81.7TyreWearRearRight

All absolute values are expressed in user-selected units. All percentages should be in the range [0,100].

Tire pressures will be interpreted as fixed-point numbers with two decimals.

Note

This data is not time-critical. The host computer should send this data at a very low rate (less than 1 FPS) ignoring the "MaxFPS" parameter. For example, once each five seconds.

Data format of report ID 30 (Pixel control)

Byte indexSizeFieldData version
01Pixel group / pixel command1.4
11Pixel index1.4
21Blue channel1.4
31Green channel1.4
41Red channel1.4
51Reserved1.4

This report will set the color of a single pixel, but does not display it immediately. You will need to issue the appropriate simple command (see report ID 3) or a pixel command (see below) in order to display all the pixels at once.

Note: Invalid values will be ignored with no effect.

  • Pixel group: the group in which the pixel is to be set or a pixel command (new in data version 1.6). One of the constants defined in the PixelGroup enumeration or one pixel command:

    • 0xFF: show all pixels at once
    • 0xFE: turn off all pixels in all groups

    If a pixel command is found, other fields are ignored.

  • Pixel index: the index of the pixel to be set in the given group, starting with zero. The number of available pixels can be obtained from report ID 2.

  • Blue, Red and Green channels: define the color of the given pixel.

  • Reserved: this field is reserved for future use and is ignored for now. Any value is valid.

Tip

"Pixel commands" overlap with "simple commands" for a reason. MS Window's HidD_SetFeature() API call, used to send feature reports such as ID 3, has a big impact on performance. Output reports do not have this problem.