Skip to content

Latest commit

 

History

History
1401 lines (998 loc) · 73.1 KB

File metadata and controls

1401 lines (998 loc) · 73.1 KB

Fanatec HID Protocol

Fanatec wheelbases communicate with the host PC over USB HID (Human Interface Device). All commands and data flow through HID reports organized into separate collections, each with its own endpoint, report size, and purpose.

Status — how to read this. This reference describes the Fanatec HID protocol as pieced together from observation and experimentation — not an official specification. Some details are confirmed against hardware; others are inferred and may be incomplete or wrong.

Table of Contents


USB Identifiers

Field Value
Vendor ID 0x0EB7 (Endor AG / Fanatec)
Product ID Varies by wheelbase (e.g., 0x0020 = ClubSport DD+)

HID Collections

Fanatec devices expose three HID collections:

Collection Report Size Direction Purpose
col01 8 bytes Bidirectional Legacy LED control, 7-segment display, configuration commands
col02 8 bytes Device → Host Input reports (buttons, encoders, axes)
col03 64 bytes Bidirectional Modern LED control, ITM display, tuning menu

col01 — Legacy Control (8 bytes)

The original control channel, used by older wheels and for commands that predate the 64-byte protocol. See col01 Reference for all subcmds.

col02 — Input (8 bytes)

Device-to-host input reports carrying button states, encoder positions, and axis values. Not covered in detail here — this channel is read by the OS HID driver and exposed as a standard game controller.

col03 — Modern Control (64 bytes)

The extended control channel, used by newer devices for features requiring larger payloads. Not all devices support col03. Older wheelbases and rims operate exclusively through col01. See col03 Reference for all command classes.

Report Framing

col01 Reports

All col01 output reports are 8 bytes. The first byte is the report ID, followed by a command header:

Byte:  [0]     [1]   [2]   [3]      [4]      [5]   [6]   [7]
       ReportID 0xF8  0x09  subcmd   data...

The report ID byte (0x00 or 0x01) is device-specific and assigned during initialization: use 0x01 on col03-capable devices and 0x00 on col01-only devices. The 0xF8 byte marks a Fanatec control command, and the bytes after it address it in tiers:

  • byte[2] — command class. 0x09 for most LED/display/config commands; a few dedicated classes such as 0x13/0x14 (base rev LEDs).
  • byte[3] — subcommand (within class 0x09). Most subcommands are complete here — e.g. 0x08 = rev LED data.
  • byte[4] — operation. One subcommand, 0x01, is a group: rather than acting on its own it selects a further set of operations in byte[4].
[ReportID, 0xF8, 0x09, 0x01, operation, data...]    ← group 0x01

col03 Reports

All col03 output reports are 64 bytes, zero-padded. The first byte is always 0xFF:

Byte:  [0]   [1]        [2]      [3..63]
       0xFF  cmd_class  subcmd   payload (zero-padded)

The command class (byte[1]) determines the protocol domain:

Command Class Protocol
0x01 LED control
0x02 ITM enable
0x03 Tuning menu
0x05 ITM display
0x08 System report (identity)

Collection Routing

col01 and col03 are separate HID interfaces — the host opens and writes to each one independently. The two are distinguished by their framing:

Collection First Byte Report Size
col03 0xFF 64 bytes
col01 Device report ID 8 bytes

Note: The Fanatec SDK provides a single high-level send path that uses the first byte of the application buffer as a routing hint (0xFF → col03, anything else → col01 with the first byte replaced by the device report ID). This is an SDK convenience, not a wire-level mechanism.

col03 Endpoint Discovery

The col03 collection is a separate HID interface from col01. On Windows, col03 device paths typically contain "col03" in the path string and can also be identified by a 64-byte maximum output report length.

Whether col03 availability is determined by the wheelbase (always present on modern bases) or the wheel (only present when a col03-capable rim is connected) has not been verified. In practice, col03 should be discovered dynamically at connection time rather than assumed.


col01 Reference

Most col01 commands use the [ReportID, 0xF8, 0x09, ...] framing described in Report Framing. The sections below are grouped by command class (byte[2]): the general 0x09 class, then the base rev-LED classes 0x13/0x14.

0x09 — General Control

The catch-all control class — rev and flag LEDs, displays, and configuration. The subcommand sits in byte[3]; most subcommands are complete there (listed below). The exception is subcommand 0x01, a group whose operations are selected by byte[4].

0x02 — Rim White LED

Turns a single white LED on the rim on or off. It has no effect on rev LED output; the rev LEDs are driven entirely by 0x08 and need no enable command.

[ReportID, 0xF8, 0x09, 0x02, enable, 0x00, 0x00, 0x00]
Byte Field Values
4 enable 0x01 = on, 0x00 = off

Thought to correspond to the small status LED next to the XBox button on some wheels, but this has not yet been verified.

0x06 — RevStripe Enable/Disable (Setting)

Enables or disables the RevStripe LED strip. Found on three rims: CSLESWP1X, CSLESWP1PS4, CSLESWWRC.

Inverted semantics0x00 means ON:

Enable:  [ReportID, 0xF8, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00]
Disable: [ReportID, 0xF8, 0x09, 0x06, 0x01, 0x00, 0x00, 0x00]
Byte Field Values
4 enable 0x00 = on, 0x01 = off (inverted)

Typical RevStripe sequence, as captured from official software driving the strip:

1. Base rev LEDs off: [RID, F8, 14, FF, 00, 00, 00, 00]
2. Set color (red):   [RID, F8, 09, 08, 00, 38, 00, 00]     (repeated per color)
3. Base rev LEDs on:  [RID, F8, 14, 00, 00, 00, 00, 00]

0x07 — Rev LED Mode (Color / Bitmask)

Selects how the rim interprets subsequent 0x08 writes.

[ReportID, 0xF8, 0x09, 0x07, mode, 0x00, 0x00, 0x00]
Byte Field Values
4 mode 0x00 = 0x08 payloads are colors, 0x01 = payloads are on/off patterns

The driver stack sends this command on your behalf. On CSL Elite P1 (Xbox), CSL Elite P1 (PS4) and CSL Elite WRC, Fanatec's driver watches 0x08 writes and injects a mode change in response to specific payloads:

Payload written Driver injects Resulting mode
01 00 0x07 with 0x01 pattern
00 07 (blue) 0x07 with 0x00 color
00 38 (red) 0x07 with 0x00 color

This is edge-triggered, so it fires only on transitions. 0x08 is ambiguous — 01 00 is both a valid color and the "LED 0 only" pattern — so the driver infers intent from the payload. RGB333 r=0, g=4, b=0 encodes to 01 00, which puts the rim into pattern mode. Later color writes are then read as bitmasks: on a RevStripe rim the strip lights in its fixed pattern color whenever data_lo bit 0 is set (green, cyan, yellow, white) and stays dark otherwise. Pure red or pure blue returns it to color mode.

0x08 — Rev LED Data (Bitmask / Color)

Sends LED data — interpreted as either a bitmask or a color depending on the connected rim.

[ReportID, 0xF8, 0x09, 0x08, data_lo, data_hi, 0x00, 0x00]

Non-RGB rims (e.g., CSSWBMWV2): 9-bit bitmask. LED 0 sits alone in data_lo bit 0; LEDs 1-8 pack into data_hi most-significant-first, so LED 1 is bit 7 and LED 8 is bit 0. Note this differs from the 0x13 base ordering.

Example: LEDs 0, 1, 2 on, rest off
  data_lo = 0x01, data_hi = 0xC0   (LED0; LED1 = bit7, LED2 = bit6)

Example: All 9 LEDs on
  data_lo = 0x01, data_hi = 0xFF

RevStripe rims (CSLESWP1X, CSLESWP1PS4, CSLESWWRC): RGB333 color value controlling the entire strip as one unit:

Example: Red     → data_lo = 0x00, data_hi = 0x38
Example: Green   → data_lo = 0x01, data_hi = 0xC0

0x0A — Legacy RGB Rev LEDs (per-LED 3-bit color)

Per-LED color for RGB rims without col03, one bit per channel — seven colors plus off.

[ReportID, 0xF8, 0x09, 0x0A, data0, data1, data2, data3]

27 bits packed LSB-first across the four data bytes, three bits per LED for nine LEDs, in the order [LED0.R, LED0.G, LED0.B, LED1.R, ...]. So LED n's red bit is 3n, green 3n+1, blue 3n+2.

Example: LED 0 red        → data0 = 0x01
Example: LED 0 blue       → data0 = 0x04
Example: all nine green   → 92 24 49 02

Note the channel order here is R, G, B — the only conventional ordering in this protocol, and different from both 0x08 (G, R, B) and the col03 color paths (B, G, R).

0x0B — Legacy Flag LEDs

Per-LED 3-bit color for flag LEDs on col01, packed the same way as 0x0A but for six flag LEDs (18 bits). Only a subset of wheels have flag LEDs — see the devices reference for the support matrix.

[ReportID, 0xF8, 0x09, 0x0B, data0, data1, data2, data3]

0x0C — LED Brightness (Legacy RGB)

Sets overall brightness for the legacy RGB rev LEDs driven by 0x0A.

[ReportID, 0xF8, 0x09, 0x0C, brightness, dirty_flag, 0x00, 0x00]

The brightness value goes through the same conversion the col03 intensity path applies.

0x01 — Extended Operations (Group)

Subcommand 0x01 doesn't act on its own — byte[4] selects the operation:

[ReportID, 0xF8, 0x09, 0x01, operation, data...]
0x02 — 7-Segment Display Data

Controls the 3-digit display found on many Fanatec wheels, hubs, and button modules. Typically used to show gear, speed, or short text strings.

The same command is used regardless of the underlying display hardware — physical LED 7-segment displays and small OLED displays are both addressed identically. On devices with a larger ITM-capable OLED (e.g., PBME), this drives the legacy mode (the last ITM page), which renders 7-segment-style content. See Display Capabilities for per-device display types.

[ReportID, 0xF8, 0x09, 0x01, 0x02, <seg1>, <seg2>, <seg3>]
Byte Field Description
5 seg1 Left digit — segment bitmask
6 seg2 Center digit — segment bitmask
7 seg3 Right digit — segment bitmask

Each segment byte is a bitmask controlling 7 segments plus a decimal point. See 7-Segment Encoding Tables for the full digit, letter, and symbol lookup tables.

   seg 0
  ───────
│       │
5       1
│       │
  ───────  seg 6
│       │
4       2
│       │
  ───────  • seg 7 (dot/decimal point)
   seg 3
Bit Segment Position
0 Top Horizontal top bar
1 Upper-right Vertical right upper
2 Lower-right Vertical right lower
3 Bottom Horizontal bottom bar
4 Lower-left Vertical left lower
5 Upper-left Vertical left upper
6 Middle Horizontal middle bar
7 Dot Decimal point

The decimal point (bit 7, 0x80) can be combined with any character via bitwise OR:

Digit 3 with dot: 0x4F | 0x80 = 0xCF
Letter A with dot: 0x77 | 0x80 = 0xF7

Examples:

Display Gear "5":    [RID, F8, 09, 01, 02, 00, 6D, 00]   (center = 0x6D)
Display Speed "142": [RID, F8, 09, 01, 02, 06, 66, 5B]   (1, 4, 2)
Display Text "Hi":   [RID, F8, 09, 01, 02, 76, 06, 00]   (H, I, blank)
Clear Display:       [RID, F8, 09, 01, 02, 00, 00, 00]
0x06 — Report Trigger / ACK

A general, SubId-parameterized report triggernot a button-module command. A single firmware command requests that the device emit a class of report selected by the SubId byte; button-module refresh is only one of its uses (distinct, non-module purposes such as clutch-bite-point and legacy identity ride the identical F8 09 01 06 frame). Sent as an ON/OFF pair:

Trigger ON:  [ReportID, 0xF8, 0x09, 0x01, 0x06, 0xFF, <SubId>, 0x00]
Trigger OFF: [ReportID, 0xF8, 0x09, 0x01, 0x06, 0x00, 0x00,    0x00]
Byte ON value OFF value Description
4 0x06 0x06 Report trigger subcmd
5 0xFF 0x00 Enable flag (ON/OFF)
6 SubId 0x00 Identifies the trigger purpose
7 0x00 0x00 Padding

SubId values:

SubId Purpose Used By
0 Plain "emit USB input report" / the OFF (deassert) half of every pair input-report trigger; trailing OFF of every ON/OFF pulse
1 Button module detection refresh BME refresh — re-scan the attached button module
2 CBP data request / notification Set CBP / Get CBP
3 Hardware-identity report trigger legacy stack only; superseded by the FF 08 system report on modern col03 bases

SubId=2 tells the firmware either "I just set CBP" (after Set) or "please report the current CBP" (before Get). The firmware responds with a HID input report that the input handler processes.

SubId selects which report the device returns; the same 0x06 frame carries all of them, so the mechanism is general rather than module-specific. From what we've observed, the SubId is understood to select: 1 = re-detect an attached button module, 2 = clutch bite point (paired with Set CBP), 3 = a legacy hardware-identity report (superseded by the System Report on modern col03 bases). A SubId = 4 is also emitted during a modern base's startup sequence; its effect is currently unknown.

The correct sequence is one ON/OFF pair followed by a 100ms sleep. The sleep gives the firmware time to process the trigger and send its response.

0x17 — Set Clutch Bite Point

Sets the clutch bite point (CBP) engagement threshold for analog clutch paddles.

[ReportID, 0xF8, 0x09, 0x01, 0x17, 0x01, <CBP>, 0x00]
Byte Field Description
5 0x01 Enable flag (always 0x01)
6 CBP CBP value (0–100, clamped)

CBP uses a completely separate command path from the tuning menu — it is not part of the tuning payload structure.

Prerequisites:

  • The connected steering wheel must have clutch paddles.
  • CBP operations should not be performed during active display updates to avoid flickering.

Complete Set CBP sequence:

1. Send: [RID, F8, 09, 01, 17, 01, <CBP>, 00]   ← Set CBP value
2. Send: [RID, F8, 09, 01, 06, FF, 02,    00]   ← Trigger ON (SubId=2)
3. Send: [RID, F8, 09, 01, 06, 00, 00,    00]   ← Trigger OFF
4. Wait: 100ms                                    ← Firmware processing time

Complete Get CBP sequence:

1. Send: [RID, F8, 09, 01, 06, FF, 02, 00]   ← Trigger ON (SubId=2)
2. Send: [RID, F8, 09, 01, 06, 00, 00, 00]   ← Trigger OFF
3. Wait: 100ms                                  ← Wait for firmware response
4. Read: CBP value from device input report

The 100ms delay is necessary because the firmware sends a response via the input handler. The official software retrieves the resulting value from a Windows registry key where the filter driver stores it.

Display interaction: The SubId=2 trigger can cause the firmware to momentarily display the CBP value on the 7-segment display. This is by design — the firmware uses the trigger as a signal to show CBP feedback to the user. The 100ms delay helps, but the firmware may hold the CBP display for longer. During this period, sending 7-segment display commands will conflict with the firmware's CBP display, causing visible flickering. For OLED-equipped devices (e.g., PBME), use display ownership to yield to the firmware during CBP operations. For non-OLED devices, display suspension must be managed by the host software.

0x18 — Display Ownership

On certain OLED-equipped devices (currently only the PBME), the host can explicitly take or release control of the display. The official software checks whether the device has an OLED before sending this command.

Host takes control:   [RID, F8, 09, 01, 18, 02, 00, 00]
Release to firmware:  [RID, F8, 09, 01, 18, 01, 00, 00]
Byte[5] Meaning
0x02 Host control — firmware stops updating the display
0x01 Firmware control — firmware resumes display ownership

This is important during operations where the firmware needs to show its own content (e.g., CBP adjustment, tuning menu navigation). The host should release control, wait for the operation to complete, then reclaim control.

For non-OLED devices (including LED 7-segment displays and the PBMR's small OLED), this command is a no-op. Display conflict management on these devices must be handled by the host software.

0x13 / 0x14 — Base Rev LEDs

Some older wheelbases (e.g., CSL Elite Wheel Base) have a resident 9-LED rev strip on the wheelbase itself — separate from any rev indicator on the connected wheel. This can be driven by its own command class at byte[2] = 0x13.

The base and wheel share the rev-LED channel, then split. At power-on both the base strip and the wheel's rev LEDs follow the legacy 0x08 writes. The first 0x13 write moves the base onto its own channel; afterward 0x13 drives the base and 0x08 drives only the wheel — independently, until power-cycle. That's how an RPM display can run on the base strip and a RevStripe wheel at once: drive the wheel via 0x08 and the base via 0x13.

0x13 — Base Rev LED Data

Sets which of the base strip's LEDs are lit:

[ReportID, 0xF8, 0x13, bits_0_7, bit_8, 0x00, 0x00, 0x00]

9-LED bitmask in natural bit order — byte[3] = LEDs 0–7 (bit N → LED N), byte[4] bit 0 = LED 8.

Different packing from 0x08. 0x13 is natural order (bit 0 = LED 0); the wheel's 0x08 bitmask is reversed (data_hi bits 7..0 = LEDs 1..8). Encode each target with its own layout.

0x14 — Base Rev LED Enable

Seen in captures alongside 0x13; appears to enable/disable the base strip:

Enable:  [ReportID, 0xF8, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00]
Disable: [ReportID, 0xF8, 0x14, 0xFF, 0x00, 0x00, 0x00, 0x00]

Captures of official software driving a RevStripe wheel bracket the color writes with 0x140xFF first, 0x00 afterwards.


col03 Reference

All col03 commands use the [0xFF, cmd_class, subcmd, ...] framing described in Report Framing.

0x01 — LED Control

Modern LED protocol using per-LED RGB565 color values. Used by newer rims with col03 support (PBME, CSSWFORMV3, GTSWX, etc.).

Byte:  [0]   [1]   [2]      [3..4]    [5..6]    ...
       0xFF  0x01  subcmd   LED0_RGB  LED1_RGB  ...

Each LED color is a 16-bit BGR565 value stored in big-endian byte order. A color value of 0x0000 means the LED is off.

0x00 — Rev LEDs

RPM/shift indicator colors. Up to 30 LEDs, one RGB565 value per LED.

FF 01 00 [R0hi R0lo] [R1hi R1lo] ... [R8hi R8lo] 00...

0x01 — Flag LEDs

Status/warning indicator colors. Up to 30 LEDs, one RGB565 value per LED.

FF 01 01 [F0hi F0lo] [F1hi F1lo] ... [F5hi F5lo] 00...

0x02 — Button Colors (Staged)

Button backlight RGB colors. Up to 12 RGB565 values. Only applies to devices with RGB-capable button LEDs (currently PSWBMW, GTSWX, and the PBMR module).

Note — PBMR color inconsistency: The PBMR is the only known device that interprets these 16-bit values as BGR555 (5 bits per channel, green MSB ignored). The 6th green bit — bit 10 — is silently discarded. For example, 0x0400 (G=32, a dim green) renders as black on the PBMR because bit 10 is the only bit set and it's the ignored one. Construct colors for the PBMR by quantizing green to 5 bits and shifting it left by 5, which leaves bit 10 clear. See PBMR for details.

Byte:  [0]   [1]   [2]   [3..4]    ... [25..26]  [27]
       0xFF  0x01  0x02  LED0_RGB  ... LED11_RGB  commit
Field Bytes Description
LED colors 3–26 Up to 12 RGB565 values (big-endian)
Commit 27 0x01 = apply, 0x00 = stage only

0x03 — Button Intensities (Staged)

Per-button intensity values. 3-bit values (range 0–7).

Byte:  [0]   [1]   [2]   [3]     [4]     ... [17]    [18]
       0xFF  0x01  0x03  int_0   int_1   ... int_14  commit
Field Bytes Description
Intensities 3–17 15 intensity bytes (3-bit, range 0–7)
Commit 18 0x01 = apply, 0x00 = stage only

The intensity payload has 15 slots while the color payload has 12 — the extra slots exist because intensity values are 1 byte each vs 2 bytes for RGB565 colors. On some devices, the higher-indexed intensity slots control additional lighting elements (e.g., encoder backlighting) that are intensity-only and have no corresponding color slot.

Staging behavior: When both color and intensity need to change:

  1. Send the color report with commit = 0x00 (stage)
  2. Send the intensity report with commit = 0x01 (commit both)

When only one has changed, send that report alone with commit = 0x01.

0x02 — ITM Enable

Conventionally the ITM session enable (command class 0x02, not 0x05). It is separate from the persistent ITM Mode gate (FF 05 02); Fanatec's bring-up sequences include both, though only the gate has been observed to have a measured effect to date — see Control Model.

FF 02 02 00 [00 x60]
Byte Value Description
0 0xFF Report ID
1 0x02 Command class
2 0x02 Sub-command
3 0x00/0x01 Observed as 0x00 (before page changes) and 0x01 (at software startup) in official-software captures. Not a page selector — paging is PageSet. Semantics unresolved.

Important:

  • In hardware testing to date, "Enable" produced no observable effect in any state tried — mid-session, after gate-off, after a wheel hot-swap, after a base power cycle — and cold bring-up succeeds without it (gate + PageSet alone). Until this command is better understood, it is recommended to retain it in bring-up sequences purely to match official-software behavior.
  • On its own, Enable shows nothing. What the display shows is governed by the ITM Mode gate and PageSet — see Control Model for the bring-up.

0x03 — Tuning Menu

Controls all wheelbase settings (SEN, FF, damper, spring, etc.) via command class 0x03.

Byte:  [0]   [1]   [2]      [3]      [4..63]
       0xFF  0x03  subcmd   devId    payload (zero-padded to 64 bytes)

0x00 — WRITE

Sets tuning parameters.

[FF 03 00 devId data[0] data[1] ... data[59]]     (64 bytes)

The tuning data starts at byte[4] in WRITE commands. See Tuning Payload Structure for field offsets and WRITE Report Byte Map for the complete byte-level layout.

The device rejects WRITE commands that don't reflect the current state. Always use the Read-Modify-Write Pattern. The WRITE itself produces no response — once sent, no trigger or acknowledgment is needed.

Subcmd 0x06 disambiguation: Two unrelated commands share the number 0x06. Neither is part of the WRITE flow:

  • TOGGLE (FF 03 06) — col03 tuning subcmd that switches between standard and simplified tuning mode.
  • Report Trigger (RID F8 09 01 06) — col01 group 0x01 subcmd used for CBP operations.

0x01 — SELECT SETUP

Switches the active setup index (0–4).

[FF 03 01 devId setupIndex 00 ... 00]

0x02 — READ

Requests the current tuning state. Triggers a response on the col03 IN endpoint.

Request:

[FF 03 02 00 00 00 ... 00]     (64 bytes)

Response:

[FF 03 devId data[0] data[1] ... data[60]]     (64 bytes)

In the READ response, tuning data starts at byte[3] — shifted by 1 byte relative to the WRITE command. See READ vs WRITE Report Layout for details.

0x03 — SAVE

Persists current tuning state to device flash.

[FF 03 03 devId 00 ... 00]

0x04 — RESET

Restores factory defaults for tuning parameters.

[FF 03 04 devId 00 ... 00]

0x06 — TOGGLE

Toggles between standard and simplified tuning mode.

[FF 03 06 devId 00 ... 00]

Tuning Payload Structure

The tuning payload is embedded in both READ responses and WRITE commands at different byte offsets (see READ vs WRITE Report Layout). The offsets below are relative to the start of the payload, not the HID report:

Offset Field Type Range Description
0 UserSetupIndex byte 0–4 Active setup slot
1 SEN byte 0–255 Steering Sensitivity
2 FF byte 0–255 Force Feedback strength
3 SHO byte 0–255 Shock / vibration intensity
4 BLI byte 0–255 Brake Linearity
5 LIN byte 0–255 Linearity (aliased as FFS in some variants)
6 DEA byte 0–255 Dead Zone
7 DRI sbyte -128–127 Drift Mode (signed — the only signed field)
8 FOR byte 0–255 Force
9 SPR byte 0–255 Spring
10 DPR byte 0–255 Damper
11 NDP byte 0–255 Natural Damper
12 NFR byte 0–255 Natural Friction
13 BRF byte 0–255 Brake Force
14 BRG byte 0–255 Brake Gain
15 FEI byte 0–255 Force Effect Intensity
16 MPS byte 0–255 Max Power Supply / Motor Protection
17 APM byte 0–255 Advanced Paddle Mode (rotary wheels only)
18 INT byte 0–255 Interactivity
19 NIN byte 0–255 Natural Inertia
20 FUL byte 0–255 Full Lock (steering angle)
21 BIL byte 0–255 Bilateral / Balance
22 ROT byte 0–255 Rotation

Notes:

  • DRI (offset 7) is the only signed byte in the structure.
  • LIN vs FFS: Two variants of this data structure exist in the official software — one names offset 5 LIN, the other calls it FFS. Same byte position, same semantics.
  • APM (offset 17): Only populated on APM-capable wheels. Zero on all other wheels.
  • Payload offsets 23–59 are reserved and always zero in WRITE commands (60 payload bytes at HID bytes 4–63). READ responses carry one additional trailing byte (offset 60 at HID byte 63). The SDK's internal managed struct is 65 bytes (offsets 0–64), but only offsets 0–59 appear on the wire in a WRITE.

READ vs WRITE Report Layout

Critical: READ responses and WRITE commands place the tuning data at different byte offsets within the 64-byte HID report.

Direction Header Data Start Reason
READ response [FF 03 devId ...] byte[3] No subcmd byte in response
WRITE command [FF 03 00 devId ...] byte[4] Subcmd 0x00 occupies byte[2]

Conversion from READ → WRITE buffer:

writeBuf[0]     = 0xFF
writeBuf[1]     = 0x03
writeBuf[2]     = 0x00              // subcmd = WRITE
writeBuf[3]     = readBuf[2]        // device ID
writeBuf[4..63] = readBuf[3..62]    // tuning data (shifted by 1)

Read-Modify-Write Pattern

Step 1: Send READ
  → [FF 03 02 00 ... 00]

Step 2: Receive response
  ← [FF 03 02 XX YY ZZ ...]    (devId=0x02, data follows)

Step 3: Build WRITE buffer
  writeBuf = [FF 03 00 02 XX YY ZZ ...]
  (copy readBuf[2..62] → writeBuf[3..63])

Step 4: Modify desired fields
  e.g., writeBuf[4 + field_offset] = newValue

Step 5: Send WRITE
  → [FF 03 00 02 XX YY' ZZ ...]

Example — Changing Force Feedback Strength:

FF is at struct offset 2. In the WRITE buffer: writeBuf[4 + 2] = writeBuf[6]:

writeBuf[6] = 80;    // Set FF to 80

Example — Switching Setup Slot:

UserSetupIndex is at struct offset 0. In the WRITE buffer: writeBuf[4]:

writeBuf[4] = 2;     // Switch to setup slot 2

The dedicated SELECT SETUP subcmd can also be used for this.

WRITE Report Byte Map

Quick reference mapping HID byte positions to tuning fields:

HID Byte Content Struct Offset Field
0 0xFF (report ID)
1 0x03 (cmd class)
2 0x00 (WRITE subcmd)
3 device ID
4 UserSetupIndex 0 Setup slot
5 SEN 1 Sensitivity
6 FF 2 Force Feedback
7 SHO 3 Shock
8 BLI 4 Brake Linearity
9 LIN/FFS 5 Linearity
10 DEA 6 Dead Zone
11 DRI (signed) 7 Drift
12 FOR 8 Force
13 SPR 9 Spring
14 DPR 10 Damper
15 NDP 11 Natural Damper
16 NFR 12 Natural Friction
17 BRF 13 Brake Force
18 BRG 14 Brake Gain
19 FEI 15 Force Effect Int.
20 MPS 16 Max Power Supply
21 APM 17 Adv Paddle Mode
22 INT 18 Interactivity
23 NIN 19 Natural Inertia
24 FUL 20 Full Lock
25 BIL 21 Bilateral
26 ROT 22 Rotation
27–63 0x00 23–59 Reserved

Live Change Notifications

The firmware supports event-driven tuning change notification — no polling required:

Event Description
TuningMenuDataChanged Tuning parameters changed (user adjusted via wheel controls)
ITMPageChanged ITM page subscription changed
AnalysisPageChanged Analysis page updated
DeviceSettingsChanged CBP / TorqueMode / MaxTorque / FFS changed

CBP in Tuning Context

CBP is not part of the tuning data structure. It uses a completely separate command path via col01. See Set Clutch Bite Point.

When setting multiple tuning parameters at once, CBP should be sent after the col03 tuning data write. This matches the ordering observed in the official software — tuning data first, then CBP:

1. Send tuning data via col03  (FF 03 00 ...)    ← WRITE command
2. Send CBP via col01          (RID F8 09 01 17 ...)
3. Send trigger pair           (SubId=2)
4. Wait 100ms

0x05 — ITM Display

Controls OLED and LCD telemetry displays. Provides multi-page telemetry dashboards with parameters like speed, gear, lap times, tyre temperatures, and more. See ITM Supported Devices for the device compatibility matrix.

All ITM display commands share the frame:

Byte:  [0]   [1]   [2]      [3..63]
       0xFF  0x05  subcmd   payload

0x01 — ValueUpdate

Sends telemetry values for display. Each entry contains a handle, parameter ID, and value:

FF 05 01 <entries...> [00-padded to 64 bytes]

Each entry:

Offset Size Field Description
0 1 Device ID Which display this entry is for (values as in PageSet)
1 1 Handle Parameter handle (dictated by the firmware — see Firmware Subscription Pushes)
2–3 2 Param ID Parameter ID (little-endian). See ITM Parameter IDs.
4 1 Size Value size in bytes — 1, 2, or 4 for numeric types; text-typed parameters use the character count (e.g. ENGINE_MAPPING sends 1–2 bytes)
5+ N Value Parameter value (little-endian, size from above)

The leading byte is the display-device id — which display the entry is for, using the same values as PageSet. Entries addressed to a display that isn't attached are silently ignored.

Multiple entries may be packed into one report; the firmware accepts batched updates.

0x02 — ITM Mode (Enable Gate)

Turns ITM on or off at the firmware level — the same persistent state the Fanatec software's "ITM" switch sets; the small "ITM" text in the lower-right of the display reflects it. This is the persistent FF 05 02 gate, not the session Enable (FF 02 02); bring-up sequences conventionally include both, though only the gate has a measured effect — see Control Model.

  • FF 05 02 00 (off): the display drops to the true legacy 7-segment view and the "ITM" indicator disappears (any cached 7-seg value already on screen persists).
  • FF 05 02 01 (on): ITM turns on and shows the page from the most recent PageSet since the gate-off — or the legacy ITM page if none (page 6 on BME/GTSWX; the number varies by display — see ITM Page Layouts). If that lands on a different page than before, the firmware emits the page's subscription push: an unsubscribe-all clearing the old handles, then the new page's handles. Landing on the legacy ITM page — which carries no telemetry parameters — pushes only the unsubscribe-all. A gate-on that lands where the display already was — e.g. a bare gate cycle from legacy — emits nothing.

A PageSet works even while gated off: it's recorded (no visual, ITM is off) and applied on the next gate-on. So gate off → PageSet(N) → gate on lands directly on page N, whereas a bare gate off → gate on lands on legacy. There is always a current page — the legacy ITM page is the fallback, never "no page." (Hardware-confirmed.)

Don't conflate the two legacy-looking states. The legacy ITM page (gate on, session live) and true legacy (gate off) are easy to mistake for each other: both render 7-segment-style col01 content, and the visible difference is the small "ITM" text in the lower-right of the screen — shown on the legacy ITM page, absent in true legacy. On the wire they are worse than similar: entering either state produces at most an unsubscribe-all, so an unsubscribe-all with no subscriptions following does not identify which state the display entered, and this protocol has no state query. To disambiguate actively, send a PageSet targeting a telemetry page: a subscription push back means the session was live (and the display is now on that page); silence means gated off (the PageSet was recorded, not applied) — or a lost command. The gate off → PageSet → gate on sequence converges from either state, which is why recovery procedures prefer it over diagnosis.

Enable:   FF 05 02 01 [00 x60]
Disable:  FF 05 02 00 [00 x60]
Byte Value Description
3 0x01 / 0x00 ITM on / off

The setting is persistent (survives power cycles — the same state the official software's ITM switch reflects), but the runtime session is not: after a base power cycle or a wheel re-seat the device behaves cold regardless of the persisted setting — PageSets are ignored until a fresh gate off → PageSet → gate on re-establishes the session. Persistence tells you what the display will be set to; it does not save the host from re-running bring-up.

0x03 — ParamDefs

Functionally a unit/suffix report: it attaches a short ASCII decoration to a parameter the firmware has already subscribed — a unit like "C" on a temperature, or a total like "/24" that renders "Position 5 /24". That is all it does. "ParamDefs" is the historical name used throughout these docs, dating from an early misreading that this command defined which parameters a page displays — it does not: page composition and placement are fixed by the firmware and announced via subscription pushes; this command only decorates those slots.

FF 05 03 <entries...> [00-padded to 64 bytes]

Each entry:

Offset Size Field Description
0 1 Device ID Which display this entry is for (values as in PageSet)
1 1 Slot ID The target parameter's handle with the high bit set (0x80 | handle — e.g., 0x82 decorates handle 2)
2 1 Position Lo Low byte of position. Always 0x00 in captures; purpose unknown.
3 1 Position Hi High byte of position. Always 0x00 in captures; purpose unknown.
4 1 Suffix Length Number of suffix bytes following (0 = no suffix)
5+ N Suffix Bytes ASCII text appended after values (e.g., 2F 30 = "/0")

Example — two slots with suffix "/0":

03 82 00 00 02 2F 30   ← slot 0x82, suffix "/0" (2 bytes: '/', '0')
03 83 00 00 02 2F 30   ← slot 0x83, suffix "/0"

The suffix system attaches a unit/total to a slot (e.g. 2F 30 = "/0" total denominator → "Lap 5 / 20"; 43 = "C" for °C on temperature params). The slot ID is 0x80 + handle — i.e. ParamDefs decorates the same handles used by ValueUpdate, but with the high bit set. ParamDefs is cosmetic (units only): a param's value renders from its ValueUpdate alone; only params that carry a unit/total get a ParamDefs entry.

Suffixes are the entire wire-level unit mechanism. Unit conversion happens host-side — the value is converted before sending (°C vs °F, km/h vs mph) and the matching suffix text chosen; the firmware renders whatever number it is given. A total suffix like "/24" is literal text, so the entry must be re-sent whenever the total changes. Suffix frames appear at bring-up, on page changes, and on total changes — not per value tick.

Maximum subscribed parameters per device: 16.

0x04 — PageSet

Selects which ITM page is active on a specific display device. Byte[3] is always the display-device id; byte[4] is the page:

FF 05 04 <deviceId> <page> [00 x59]
Byte Field Description
3 Device ID Target display: 1=Base, 3=BME/GTSWX, 4=Bentley — see ITM Supported Devices. Device 2 ("SmallOLED") exists in the protocol's device numbering but no known hardware implements it; frames addressed to it are seemingly accepted, but have no effect.
4 Page number Page to display (1–6, device-dependent). See ITM Page Layouts.

Space page-change commands at least ~100 ms apart — an established convention that matches official-software pacing; tighter spacing has not been probed.

Dropped PageSets: concurrent traffic is an identified cause. PageSet reliability depends on what else is on the wire. In hardware testing, switches issued on an otherwise-quiet col03 channel were consistently confirmed by a push, while switches issued with ValueUpdates streaming alongside failed a substantial fraction of the time (no push, no page change) — and in one instance, streaming that continued through the retries left the display unresponsive until a quiet gate-off → PageSet → gate-on cycle recovered it. The failure rate likely varies with wheelbase, firmware, and traffic volume; the mechanism — a switch can silently fail to take, and concurrent traffic makes that more likely — should be assumed everywhere. Two rules for reliable host-driven paging follow: (1) suspend ValueUpdate/ParamDefs traffic from shortly before a PageSet until its push confirms; (2) treat the push as the only acknowledgment that a switch happened — never assume a PageSet took, and be prepared to retry.

A second, expected case looks identical to a dropped command: the firmware only pushes on an actual page change. A PageSet for the page the display is already on correctly produces no push (consistent in testing; a preceding Enable makes no difference). The absence of a push is therefore ambiguous — the host cannot tell "already on this page" from "command lost." To convert silence into signal, force a genuine change: switch to a different page and back (each real change must push).

Practical guidance for host-driven paging: suspend value traffic around the switch, treat a missing push as a possible failure only when targeting a different page, retry with ≥100 ms spacing, and escalate to a flip-away-and-back — then to a gate cycle with the PageSet sent while gated off, the most reliable recovery observed in testing (see the gate).

The dev-2 PageSet stream is not a keepalive. Current official software emits PageSet(2, X) at ~10 Hz from its first ITM activity until it exits (X varies by rig/version — pinned to 11, device 2's legacy page, or mirroring the active device-3 page). Since no current hardware implements device 2, the stream has no effect on anything; it runs even while gated off and stops only when the software exits. It serves no protocol function: the display has no idle timeout (see Control Model) and no periodic frame is needed — our recommendation is to not send it.

0x05 — DisplayReset

Resets every rendered ITM field to its per-field placeholder — laps show --- / -, times show --:--.-, and so on. The frame is fixed; it takes no device id and no page (interface-global — it resets the ITM display state as a whole, not one display device):

FF 05 05 01 [00 x60]

What it does and doesn't touch (hardware-verified):

  • Cleared: every field value previously written by ValueUpdate, on every ITM page.
  • Untouched: the ITM Mode gate, the session enable, the active page, and the firmware's current subscriptions — no subscription push is emitted, and value flow resumes at the existing handles. (The reset elicits nothing on the input endpoint; it is a command, not a query.)
  • Untouched: the legacy ITM page — its 7-segment-style content is rendered from col01 display writes and is cleared through that path instead.

This is the only known way to clear already-written field values: gating ITM off and back on does not clear them — the firmware retains every field's value across the off→on cycle and shows it again on re-enable (hardware-verified). The official software issues a reset during its own ITM initialization. Because subscriptions survive the reset, subsequent ValueUpdates repaint the same handles with no re-bring-up.

Control Model

Control of the ITM display is split between the host and the firmware — neither drives it alone:

  • The host selects the page. A host PageSet chooses which page is shown; the wheel's display button does the same thing. The two are equivalent triggers within a live session — neither works before a session exists (a cold display ignores both until brought up).
  • The firmware assigns the handles. On every page change it pushes the new page's parameter subscriptions (see Firmware Subscription Pushes); the host echoes ValueUpdates only at the handles the firmware pushed. Values sent at guessed handles — for a page the firmware hasn't announced — are ignored.

So the host chooses what page; the firmware chooses which handles.

Bring-up sequence (hardware-verified on a PBME):

1. FF 05 02 01 ...        ← ITM Mode gate ON (persistent — 0x02 above)
2. FF 02 02 00 ...        ← session Enable (optional, kept for official parity; see 0x02 — ITM Enable)
3. FF 05 04 <dev> <page>  ← PageSet to establish a page
4. FF 05 01 <entries>     ← ValueUpdates at the handles the PageSet pushes back

Step 3 is what actually brings a page up: in testing, a bare FF 02 02 enable with no PageSet produced no push and no page. Within a live session, a PageSet — or a wheel-button press — is all that's needed to make the firmware push a page's subscription. But note the gate's setting-vs-session distinction (see ITM Mode): after a power cycle or wheel re-seat the device is cold no matter what setting persisted, and the full sequence — with the gate command actually re-sent — is required again.

No keepalive. The display has no idle timeout: it holds its content indefinitely, and the value stream isn't even required to keep it lit. (Hardware-confirmed.) There is no liveness ping in this protocol — the periodic frame current official software emits is a page-set to an absent display device, not a keepalive (see PageSet) — and none is needed.

Firmware Subscription Pushes (Device → Host)

This is the mechanism behind the Control Model above. When the page changes — from a host PageSet or the wheel button — the firmware pushes one or more FF 05 01 reports on the col03 input endpoint, telling the host exactly which parameters to display at which handles. A host PageSet makes the firmware push the new page's subscription within tens of milliseconds (20–70 ms observed in testing; hosts should budget a comfortably larger deadline before treating a push as missed); the host then echoes ValueUpdates for those subscriptions.

Fragmentation varies by setup. One tested setup (Podium DD+ with a PBME) consolidates a push into 1–2 multi-entry reports; another (ClubSport DD with a GTSWX) sends one entry per report — 6–7 reports arriving ~2 ms apart for a single page change. Both wheelbase and wheel differ between those observations, so whether the framing is determined by the base or by the wheel/module is unknown. Accumulate across reports in all cases.

Each pushed report uses the same FF 05 01 framing, with 5-byte entries:

Offset Size Field Description
0 1 Device ID Which display this entry is for (values as in PageSet)
1 1 Handle Firmware handle. The 0x80 bit marks a "slot" param (one with a ParamDefs unit). Host ValueUpdate handle = handle & 0x7F.
2–3 2 Param ID Subscribed parameter (little-endian), or 0xFFFF = unsubscribe that handle.
4 1 Type Value data-type/size selector — low nibble is the wire type (1=text, 2/3=1-byte, 4/5=2-byte, 6/7/9=int32, 8/10=float32); high-nibble meaning unknown (varies per display for the same parameter). Not the display unit. The same parameter can declare different types on different displays — GEAR is a 1-byte int on a PBME (0x12) but text on a GTSWX (0x11) — and the host's value encoding must follow the declared type, or the field renders nothing (capture- and hardware-confirmed).

A page change arrives across several reports — an unsubscribe of the old handles (0xFFFF), plus one or more (often overlapping / partial) reports listing the new page's handles. The host must accumulate them — apply each entry as it arrives (subscribe, or 0xFFFF = unsubscribe) — never treating a single report as the complete set. (One observed instance: page 3's 7 handles arrived as two overlapping reports sharing h1–h5, one adding h6=BRAKE_BIAS, the other h0=SPEED — the specific handles are illustrative only, per the allocation rules below.) Example — switching to the Tyre Temps page:

FF 05 01  03 00 0001 34  03 01 0004 12  03 82 002A 32  03 83 0030 32 ...
          └ h0=SPEED ┘   └ h1=GEAR ┘    └ h2=TYRE_FL ┘ └ h3=TYRE_RL ┘

(0x82 & 0x7F = 2, so the host sends TYRE_FL at handle 2.)

Never assume a fixed page→handle map — handle allocation varies by setup, and handles are not page-owned. The universal rules, consistent across every observation:

  • The firmware announces every assignment via push; assignments are not a property of the page. Whichever page is established first takes the base handles (a session brought up on page 2 lands it at the handles page 1 would otherwise get).
  • A handle number can be re-bound to a different parameter on any page change — observed within a single session: h9 = ERS_LEVEL while Fuel/ERS/DRS was up, then BEST_LAP_TIME after switching to Lap Times. This is the deeper reason to stop sending values the moment a page change begins: entries at the old page's handles may land on re-bound parameters, not just be ignored.
  • Handle numbers live in a small bounded space (≈h0–h13; no growth observed over 60 consecutive switches), every change unsubscribes what it replaces, and a gate cycle resets the table.

On top of those invariants, two different allocation strategies have been observed: one setup (Podium DD+ with a PBME) double-buffers between two regions (≈h0–h5 / h6–h13), subscribing each new page into the currently-free region — the same page lands at different handles on successive visits (6, 0, 6, 0 … across a full cycle), and a 7th parameter spills into the far region. Another setup (ClubSport DD with a GTSWX) reuses one region in place for every page, rebinding the same handle numbers each change. Neither strategy should be assumed: follow the pushed handles after every change and never cache a page→handle map across visits.

Getting the first page. The firmware pushes a subscription only when a page is established or changed. So after enabling, send a PageSet (or wait for a wheel-button press) and populate the handles it pushes back; values sent before that push, at guessed handles, are ignored. See Control Model for the full bring-up.

Timing & Rate Limiting

Not all parameters need to be sent at full rate. Recommended minimum intervals:

Category Delay (ms) Parameters
Real-time 0 SPEED, GEAR, POSITION, LAP, LAST_LAP_TIME, BEST_LAP_TIME, BRAKE_BIAS, FUEL, DRS, ABS, TC
Near real-time 30 LAP_TIME
Moderate 40–50 RPM_MAX, TYRE temps
Low-frequency 100 ENGINE_MAPPING, ERS, others

Automatic Page Changes (Alerts)

The official software supports automatic page switching based on telemetry events (page contents are in ITM Page Layouts):

Value-Change Triggers:

Trigger Target Page
Lap number or Position changed Page 1 (Lap Info)
DRS zone or DRS active changed Page 2 (Fuel/ERS/DRS)
TC, ABS, EngineMap, or BrakeBias changed Page 3 (Car Settings)
Best lap time changed Page 4 (Lap Times)

Threshold Triggers:

Trigger Target Page
Fuel below threshold Page 2
ERS below threshold Page 2
Oil temp above threshold Page 3
Any tyre temp out of range Page 5

Favorite Page:

Each device has a configurable favorite page with a display duration (default 10 seconds, range 3–60 seconds). After a trigger-caused page change, the display reverts to the favorite page after the duration expires.

0x08 — System Report (Identity)

The system report is how a modern col03-capable wheelbase reports its identity and configuration: the connected base, the attached wheel or hub, and any button module on a module-capable hub. It is the primary way to identify connected hardware over pure HID.

Enable / Trigger

Enable push-on-change:  FF 08 01 FF        (disable: FF 08 01 00)
Trigger one report:     FF 08 02

After a single enable, the base pushes a fresh system report on every attachment change (wheel, hub, or module connect/disconnect) and is otherwise silent. A trigger requests one immediate report — used to seed the initial identity on connect. A directly-connected modern base needs no prior handshake to respond.

As far as we've observed, the system report is specific to modern col03-capable wheelbases; older col01-only wheels self-identify through their regular input report instead, and some wheel-side adapters expose col03 without implementing it. Behavior on the oldest bases is inferred, not confirmed.

Report Layout (device → host)

The base replies on col03 with a 64-byte report beginning FF 08. Identity-bearing fields (offsets relative to the leading 0xFF):

Offset Field Description
0x020x03 SystemConfig 16-bit little-endian (byte[3] << 8 | byte[2]). Its only effect is selecting the firmware-version field widths: legacy (< 6) uses 1-byte version fields, extended (≥ 6) uses 4-byte blocks. It does not affect the identity offsets below. Every base observed reports the extended layout with byte[3] = 0x00; the legacy layout is inferred from the field structure, not yet observed. byte[0x02] is the low byte of this word and doubles as a BaseType (wheelbase model) hint — but only while byte[0x03] is 0x00; if a base ever set byte[0x03] nonzero, byte[0x02] would be a version low-byte, not a base code.
0x18 WheelCode Attached wheel or hub, as a single-byte code. 0x00 = nothing attached; 0xFF = EXT_INFO, a reserved escape for devices that report identity through extended fields instead. Code → device: see devices.md.
0x1F Module Button-module presence — meaningful only when a module-capable hub is attached (not all hubs accept modules): 0x00 = none, 0x01 = PBME, 0x02 = PBMR.

The WheelCode (0x18) and Module (0x1F) bytes sit at fixed offsets, outside the firmware-version fields and read independently of SystemConfig, so they decode identically on either layout. Remaining offsets carry firmware-version fields (widths set by SystemConfig) and transient status bytes that are not part of identity and may differ between consecutive reports.

Identity codes

The numeric codes above map to specific hardware. The wheel/hub code (0x18) is a single byte, with 0xFF reserved as the EXT_INFO escape, so treat the published list as the currently-known set, not a closed enumeration. A code that isn't in the known set is not a protocol error: a consumer should fall back to a generic identity and continue rather than reject the report. The wheel/hub and module codes are the reliable identity signals here; the BaseType hint (byte[0x02]) is best treated as advisory, since it is only valid while byte[0x03] is 0x00 (see the row above). The complete code → device tables (BaseType, wheel/hub codes, modules, and which hubs accept a module) live in devices.md.

Worked example. FF 08 0C 00 00 … 0C … 02 … → BaseType 0x0C, WheelCode 0x0C, Module 0x02 = a ClubSport DD+ base with a Podium Hub and a Podium Button Module Rally attached.

Engage Sequence

To bring a modern base online for identity, the host:

  1. Enable the system report: FF 08 01 FF
  2. Trigger one report to seed initial state: FF 08 02
  3. Read col03 and scan the first bytes for the FF 08 signature; decode BaseType / WheelCode / Module.

Thereafter, attachment changes arrive as pushed FF 08 reports — no repeated triggering is needed (re-enable periodically to keep the subscription alive). To force a re-detect of a button module hot-attached to an already-connected hub, send the 0x06 Report Trigger with SubId = 1. A modern wheelbase needs no acquire or host-presence handshake before responding.


Cross-Reference Topics

These sections group related commands across collections for quick navigation.

LEDs

Fanatec steering wheels support several types of LEDs controlled through two distinct protocol generations:

Type Purpose Typical Count Color
Rev LEDs RPM / shift indicator strip 9 Per-LED RGB (modern) or on/off (legacy)
Flag LEDs Status / warning indicators 6 Per-LED RGB (modern) or single color (legacy)
Button LEDs Button backlighting (RGB devices only) Up to 12 Per-LED RGB + intensity
RevStripe Single-color LED strip 1 (entire strip) RGB333, 7 colors plus off

Protocol selection by wheel type:

Capability Protocol Collection Color Depth
RGB LED + col03 support Modern col03 0x01 RGB565 (65K colors)
RGB LED + no col03 Legacy RGB col01 0x0A 3-bit per LED via col01
Non-RGB LED Legacy bitmask col01 0x08 On/off only + global RGB333
RevStripe Legacy color col01 0x08 RGB333, 7 colors plus off

See the devices reference for the per-wheel capability matrix.

Batching behavior: The official software tracks per-LED dirty state internally and batches changes — only sending reports for LEDs whose on/off state or color has changed since the last update.

Displays

Fanatec devices have two display protocols:

Display Type Protocol Collection Devices
3-digit (7-seg / small OLED) col01 group 0x01, subcmd 0x02 col01 Most wheels, hubs, button modules
Multi-page telemetry (ITM) col03 0x05 + 0x02 enable col03 PDD1/PDD2 base, PBME, Bentley, GTSWX

Display preemption: The 7-segment display can be preempted by tuning menu navigation, CBP adjustment, and firmware boot/init. During these periods, host display writes will conflict with firmware output. See Display Ownership for OLED mitigation.

Tuning & Configuration

Tuning spans two collections:

Function Collection Command
Read/write tuning parameters col03 0x03 subcmds
Clutch bite point col01 0x17 Set CBP + 0x06 Trigger
Display ownership during tuning col01 0x18 Display Ownership

When setting both tuning parameters and CBP, always send col03 tuning data first, then col01 CBP. See CBP in Tuning Context.


Appendix

RGB333 Color Encoding

The legacy col01 protocol uses a 9-bit RGB333 color encoding packed into 2 bytes:

byte[5] (data_hi):  [ G1 G0 | R2 R1 R0 | B2 B1 B0 ]   (GG_RRR_BBB)
byte[4] (data_lo):  [  0  0   0  0  0   0  0  G2  ]   (.......G)

Each channel has 3 bits (0–7), so 512 values are representable, but no hardware tested to date renders more than the eight fully saturated combinations — seven colors plus off. Those eight are also the only values official software emits, and at least one of the remainder (01 00) puts the rim into pattern mode, see 0x07.

Color R G B data_lo (byte[4]) data_hi (byte[5])
Off 0 0 0 0x00 0x00
Red 7 0 0 0x00 0x38
Green 0 7 0 0x01 0xC0
Blue 0 0 7 0x00 0x07
Yellow 7 7 0 0x01 0xF8
Magenta 7 0 7 0x00 0x3F
Cyan 0 7 7 0x01 0xC7
White 7 7 7 0x01 0xFF

BGR565 Color Encoding

The modern col03 protocol uses 16-bit colors in big-endian byte order, packed blue-high, red-low:

Bits:  BBBBB GGGGGG RRRRR
       15-11  10-5   4-0
  • Blue: 5 bits (0–31)
  • Green: 6 bits (0–63)
  • Red: 5 bits (0–31)
  • 65,536 possible colors
  • 0x0000 = LED off

Sanity check: pure red is 0x001F, pure blue is 0xF800.

Channel order is per-interface, and no format name carries it:

Path Channel order (high → low)
col03 color (FF 01 00/01/02) B, G, R
col01 0x08 RevStripe color G, R, B
col01 0x0A per-LED 3-bit R, G, B (bits 3n, 3n+1, 3n+2)

Green sits in bits 10-5 under either 565 layout, so a green-only value cannot distinguish them. Verify ordering with red or blue.

BGR555 variant

Some hardware reads only 5 green bits and ignores the sixth. For those devices, quantize green to 5 bits and place it in the low 5 bits of the green field, leaving bit 10 clear:

Bits:  BBBBB _GGGGG RRRRR
       15-11 10 9-5  4-0
        (bit 10 always zero)

7-Segment Encoding Tables

Digit Encoding

Character Hex Binary Segments Active
0 0x3F 0111111 0,1,2,3,4,5
1 0x06 0000110 1,2
2 0x5B 1011011 0,1,3,4,6
3 0x4F 1001111 0,1,2,3,6
4 0x66 1100110 1,2,5,6
5 0x6D 1101101 0,2,3,5,6
6 0x7D 1111101 0,2,3,4,5,6
7 0x07 0000111 0,1,2
8 0x7F 1111111 0,1,2,3,4,5,6
9 0x6F 1101111 0,1,2,3,5,6

Letter Encoding

Char Hex Char Hex Char Hex
A 0x77 J 0x0E S 0x6D
B 0x7C K 0x75 T 0x78
C 0x58 L 0x38 U 0x3E
D 0x5E M 0x37 V 0x18
E 0x79 N 0x54 W 0x7E
F 0x71 O 0x5C X 0x76
G 0x3D P 0x73 Y 0x6E
H 0x76 Q 0x67 Z 0x5B
I 0x06 R 0x50

Symbol Encoding

Symbol Hex Description
Blank 0x00 All segments off
- (Dash) 0x40 Middle segment only
_ (Underscore) 0x08 Bottom segment only
. (Dot) 0x80 Decimal point only

ITM Parameter IDs

The firmware recognizes a vocabulary of parameter IDs. Only a subset is confirmed to render correctly on current firmware — the rest may display no label, show unexpected formatting, or be silently ignored.

Vehicle Telemetry (1–84)

ID Name Size Type Notes
1 SPEED 2 Int16 LE Present on all pages as header
2 RPM 4 Int32
3 RPM_MAX 4 Int32
4 GEAR 1 Per-display Present on all pages as header. The wire form differs by display and is dictated by the Type byte in the subscription push: displays declaring a Uint8 type (e.g. PBME) take numeric — 0x00 renders "n", 1–9 literal digits, 0xFF renders "r" (reverse) — and ignore ASCII bytes; displays declaring a text type (e.g. GTSWX, Formula V3) take a single ASCII char — 'n', '1''9', 'r' (lowercase) — as the official software sends. Both capture-confirmed. Sending the wrong form renders nothing.
5 FUEL 4 Float32 LE Supports total (FUEL_MAX)
6 FUEL_MAX 4 Float32 LE
7 FUEL_PER_LAP 4 Float32 LE
9 ERS_LEVEL 4 Int32 LE Percentage
14 DRS_ZONE 1 Uint8 0 or 1
15 DRS_ACTIVE 1 Uint8 0 or 1
18 ABS_SETTING 1 Uint8
20 TC_SETTING 1 Uint8
25 BRAKE_BIAS 4 Int32 LE Tenths of a percent — 51.2% = 512. Int32, not Float32 (confirmed by capture; sending a float displays a capped 99.9%).
26 ENGINE_MAPPING 1–2 ASCII text The map rendered as text — map 10 travels as bytes '1' '0' (31 30), size = digit count. Not a Uint8 (confirmed by capture; sending the numeric byte hangs the PBME Car Settings page).
33 OIL_TEMP 1 Uint8 Unit-converted (C/F)
42 TYRE_FL_C_TEMP 1 Uint8 Unit-converted (C/F)
45 TYRE_FR_C_TEMP 1 Uint8 Unit-converted (C/F)
48 TYRE_RL_C_TEMP 1 Uint8 Unit-converted (C/F)
51 TYRE_RR_C_TEMP 1 Uint8 Unit-converted (C/F)

Race / Timing (501–536)

ID Name Size Type Notes
501 POSITION 1 Uint8 Supports total (POSITION_TOTAL)
505 LAP 1 Uint8 Supports total (LAP_TOTAL)
509 LAP_TIME 4 Float32 LE Current lap, seconds
510 LAST_LAP_TIME 4 Float32 LE
511 BEST_LAP_TIME 4 Float32 LE
516 DELTA_OWN_BEST 4 Float32 LE Seconds, +/-
519 CAR_AHEAD 4 Float32 LE Gap in seconds
520 CAR_BEHIND 4 Float32 LE Gap in seconds

Additional Defined Parameters

The full parameter vocabulary includes 120+ IDs covering tyre pressures, brake temps, G-forces, pedal positions, flags, system metrics (CPU/GPU), and more. These are defined in the firmware's shared vocabulary but are not confirmed to render on all display types. The complete parameter ID ranges:

  • 0–84: Vehicle telemetry
  • 501–536: Race/timing data and flags
  • 1001–1008: System metrics (CPU load, GPU temp, FPS, etc.)
  • 65535 (0xFFFF): UNSUBSCRIBE sentinel

ITM Page Layouts

SPEED and GEAR appear on every page as persistent header fields.

Base / BME Pages (1–6)

Base and BME use identical page layouts. Page 6 is the legacy/default fallback.

Page 1 — Lap Info:

Param ID Name Size Type
1 SPEED 2 Int16 LE
4 GEAR 1 Uint8
505 LAP 1 Uint8
501 POSITION 1 Uint8
509 LAP_TIME 4 Float32 LE
510 LAST_LAP_TIME 4 Float32 LE

Detection signature: LAP (505) present in subscription.

Page 2 — Fuel / ERS / DRS:

Param ID Name Size Type
1 SPEED 2 Int16 LE
4 GEAR 1 Uint8
5 FUEL 4 Float32 LE
9 ERS_LEVEL 4 Int32 LE
14 DRS_ZONE 1 Uint8
15 DRS_ACTIVE 1 Uint8
516 DELTA_OWN_BEST 4 Float32 LE

Detection signature: ERS_LEVEL (9).

Page 3 — Car Settings:

Param ID Name Size Type
1 SPEED 2 Int16 LE
4 GEAR 1 Uint8
20 TC_SETTING 1 Uint8
18 ABS_SETTING 1 Uint8
25 BRAKE_BIAS 4 Int32 LE (tenths of a percent)
26 ENGINE_MAPPING 1–2 ASCII text
33 OIL_TEMP 1 Uint8

Detection signature: OIL_TEMP (33).

Warning: This page is format-sensitive. BRAKE_BIAS is an Int32 in tenths of a percent (a Float32 shows a capped 99.9%), and ENGINE_MAPPING is ASCII text (map 10 = 31 30) — sending it as a numeric Uint8 hangs the PBME firmware. Rapid value cycling on this page can also cause firmware lockups.

Page 4 — Lap Times:

Param ID Name Size Type
1 SPEED 2 Int16 LE
4 GEAR 1 Uint8
510 LAST_LAP_TIME 4 Float32 LE
511 BEST_LAP_TIME 4 Float32 LE
519 CAR_AHEAD 4 Float32 LE
520 CAR_BEHIND 4 Float32 LE

Detection signature: CAR_AHEAD (519).

Page 5 — Tyre Temps:

Param ID Name Size Type
1 SPEED 2 Int16 LE
4 GEAR 1 Uint8
42 TYRE_FL_C_TEMP 1 Uint8
45 TYRE_FR_C_TEMP 1 Uint8
48 TYRE_RL_C_TEMP 1 Uint8
51 TYRE_RR_C_TEMP 1 Uint8

Detection signature: TYRE_FL_C_TEMP (42).

Page 6 — Legacy / Default:

No telemetry parameters. Fallback page when ITM is inactive.

Bentley Pages (1–5)

Bentley uses the same parameters but with no Car Settings page and only 5 pages total:

Page Content Detection
1 Lap Info LAP (505)
2 Fuel / ERS / DRS ERS_LEVEL (9)
3 Lap Times CAR_AHEAD (519)
4 Tyre Temps TYRE_FL_C_TEMP (42)
5 Legacy / Default

GTSWX Pages (1–6)

GTSWX uses the same page content as Base/BME but with multi-parameter detection signatures — all params in the group must be present before the page is matched:

Page Content Detection (ALL required)
1 Lap Info LAP + POSITION + LAP_TIME + LAST_LAP_TIME
2 Fuel / ERS / DRS FUEL + ERS_LEVEL + DRS_ZONE + DRS_ACTIVE + DELTA_OWN_BEST
3 Car Settings TC_SETTING + ABS_SETTING + ENGINE_MAPPING + OIL_TEMP + BRAKE_BIAS
4 Lap Times (compact) LAST_LAP_TIME + BEST_LAP_TIME + CAR_AHEAD (no CAR_BEHIND)
5 Tyre Temps Any one of: TYRE_FL/FR/RL_C_TEMP
6 Legacy / Default

ITM Supported Devices

ITM display support depends on the wheelbase, steering wheel, and button module combination:

ITM Device Detection Device ID Notes
Base Wheelbase is PDD1, PDD1 (PS4), or PDD2 1 Wheelbase's own display
BME Button Module Endurance connected 3 PBME's large OLED
Bentley Bentley GT3 steering wheel 4 Bentley wheel's built-in display
GTSWX GT Steering Wheel Extreme 3 GTSWX's built-in display

Note: BME and GTSWX share Device ID 3 on the wire. They are mutually exclusive — a setup will have one or the other, never both.

APM-Capable Wheels

Only these steering wheels report APM (Advanced Paddle Mode) via the tuning menu:

Code
CSLESWMCL
CSSWFORMV2
CSLESWMCLV2
CSLSWGT3
CSSWFORMV3

For all other wheels, APM is ignored (always zero).