Skip to content

Protocol LIGHTSYNC

mescon edited this page Jul 20, 2026 · 1 revision

Protocol: LIGHTSYNC LEDs

The RS50's faceplate strip has 10 individually addressable RGB LEDs used as an engine-RPM / shift indicator. LED control spans two HID++ features working as a pair. Part of the Protocol Specification; the user-facing controls are in the Sysfs API Reference and Configuring the Wheel.

Rim differences: this page describes RS50 rim hardware (in both native and compat enumeration; the rim does not change with the PID). The real G PRO rim uses the same 0x807A feature page for a level-based rev-light protocol with no per-LED RGB: after a one-time arm burst, the host repeats a SHORT fn2 + LONG fn6 pair whose byte 9 carries a 0-10 "LEDs lit" level; colors and direction belong to the wheel's onboard profile. The RS50 also accepts the level command (hardware-verified): the fill renders the selected custom slot's stored colors and direction, which is what makes a live RPM display work on the RS50 too (wheel_rev_level). Caution: bursting level writes starves the wheel's shared HID++ command processor; pace them at G HUB's ~160 ms cadence (the driver does this coalescing itself).

Two-feature architecture

Feature Page Purpose Functions used
0x807A LIGHTSYNC effect/slot select and commit fn3 (set effect), fn6 (pre-config/commit), fn7 (enable/refresh)
0x807B RGBZoneConfig per-slot RGB data and names fn2 (set colors), fn1 (get config), fn3 (get name), fn4 (set name)

Feature indices are discovered at runtime (typically 0x0B and 0x0C). fn6/fn7 only exist on 0x807A; sending them to 0x807B returns error 7 (InvalidFunctionId).

Effect values (0x807A fn3)

Value Effect
0x01 Inside to out animation
0x02 Outside to in animation
0x03 Right to left animation
0x04 Left to right animation
0x05-0x09 The five custom slots: 0x05 = CUSTOM 1 .. 0x09 = CUSTOM 5

Effect values 5-9 ARE the custom slots: there is no separate "activate slot" command; selecting a slot is selecting its effect number. fn3 only STAGES the effect; the strip repaints on the following zero-parameter fn6 commit. (0x807B fn3 is GET_NAME, a pure read; an earlier driver mistook it for an activate call.) fn2 GET_STATE returns the current effect, so a value of 5 or more means custom slot effect - 5 is selected.

Set RGB zone config (0x807B fn2, very-long report)

The primary command for colors and animation direction:

Byte    Field           Description
----    -----           -----------
0       Report ID       0x12 (very long)
1       Device Index    0xFF
2       Feature Index   [discovered]
3       Function        0x2C
4       Slot Index      0x00-0x04 (CUSTOM 1-5)
5       Direction       0x01-0x04 (wire value, see below)
6-8     LED10 RGB       R, G, B
9-11    LED9 RGB        ...
...
33-35   LED1 RGB
36-63   Padding

LED order is REVERSED on the wire: LED10 first, LED1 last. A driver must reverse user-facing LED1-10 to protocol order.

Direction values (byte 5, hardware-verified)

Byte 5 carries a 1-based wire value, not the driver's 0-3 enum:

Wire Effect Driver enum
0x01 Inside to Outside (expand) 2
0x02 Outside to Inside (contract) 3
0x03 Left to Right sweep 0
0x04 Right to Left sweep 1

(An earlier driver encoded byte 5 as enum + 2, which mislabelled the sweeps and sent an out-of-range 5 for Outside-In; the firmware NAKs that and the write surfaced as -EIO. All four values are now hardware-verified via live rev-fill sweeps.)

Mirrored pairs

For the two symmetric directions (Inside-Out, Outside-In) G HUB collapses the 10 LEDs into 5 mirrored pairs (LED1-LED10, LED2-LED9, ...), sending a palindrome. This is application behavior, not protocol enforcement; the wire still carries all 10 colors, but a UI mirroring these directions (as G HUB and logi-dd do) must send a palindrome for the animation to look right.

Slot names (0x807B fn3/fn4)

fn3 [slot] returns [slot][len][ASCII name]; fn4 [slot][len][name] sets it (max 8 characters; defaults "CUSTOM 1" through "CUSTOM 5"). Names are stored on the device.

The update sequence

Every color change runs a 5-step sequence spanning both features:

Step  Feature  Function  Purpose
----  -------  --------  ----------------------------------------
1     0x807A   fn3       Select the slot's effect ([0x05 + slot])
2     0x807A   fn6       Pre-config (LONG report)
3     0x807B   fn2       RGB data (64-byte very-long)
4     0x807A   fn6       Commit (LONG report)
5     0x807A   fn7       Enable / refresh display

The fn6 pre-config/commit pair is an SW/FW ownership arbitration: Logitech's published x8070/x8071 specs (this feature pair's documented ancestors) define explicit "software takes control" calls, and re-arbitrating before every update is the officially expected pattern. That also explains the early bring-up mystery where the first LED update worked and later ones silently did not: the firmware reclaims ownership, and G HUB re-runs the arbitration per change.

Events

The wheel broadcasts effect changes (from G HUB-style tools or the wheel itself) as 12 ff <idx> 00 <effect> with SW_ID 0; the driver consumes these and updates wheel_led_effect, notifying poll()ers. Brightness changes made on the wheel's OLED menu broadcast via the x8040 brightnessChangeEvent and are tracked the same way.

Brightness (feature 0x8040)

Global LED brightness is a separate feature (fn2 SET, 0-100). Note 0x8040 is brightness only; G HUB's steering "Sensitivity" slider is an 0x80A4 response-curve upload, not this feature.

Rev-level fill (0x807A fn2 + fn6)

The level-based rev-light command (see the rim note at the top): a one-time arm burst, then SHORT fn2 + LONG fn6 pairs with byte 9 = 0-10. Two hardware-verified behaviors on the RS50:

  • The fill renders the selected custom slot's stored colors and follows its direction.
  • The arm burst's "fn3 param 0x02" is a plain SET_EFFECT, so arming force-switches the strip to Outside-In; the driver re-asserts the pre-arm effect immediately after the one-time burst.

A written level holds until the next write (the wheel eventually reverts an unrefreshed level); writing wheel_led_effect restores the idle pattern.

Clone this wiki locally