thrustmaster-solaris
v0.1.0
Published
LED control and joystick input for the Thrustmaster Solaris Base, over libusb
Maintainers
Readme
thrustmaster-solaris
LED control and joystick input for the Thrustmaster Solaris Base, over libusb. TypeScript, ESM, Linux.
import { Device } from 'thrustmaster-solaris';
const device = await Device.open('044f:042a', { baseline: '#9bff37' });
await device.apply({ button5: '#ff0000', logo: '#0000ff' });
await device.close();- docs/API.md — the API
- docs/PROTOCOL.md — the wire format, written to port from
- TODO.md — what is missing, tests first
Requirements
Node 20 or newer; developed and tested on Node 25.
Linux. The LED path is plain libusb and would port elsewhere, but reading input
resolves the device through /sys/class/hidraw, and the permission setup below
is udev — neither has been tried on another OS.
Running the examples straight from .ts needs Node 23.6+ (or 22.6+ with
--experimental-strip-types). The library itself is compiled, so it has no such
requirement.
Setup
1. udev rule
The LED interface is reached over raw USB, and /dev/bus/usb/* is root-only by
default. Edit the rule for your own device ids if they differ:
sudo cp 70-thrustmaster-solaris.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm triggerReading input needs no rule — logind already grants an ACL on hidraw nodes.
2. Build
npm install
npm run build| Script | |
|---|---|
| npm run build | Compile src/ to dist/ |
| npm run lint | ESLint |
| npm run typecheck | Types, library and examples |
Examples
Node runs TypeScript directly, so these need no build of their own — only the library has to be built.
npm run build
node examples/set-colors.ts # the ways to set colours, then resetThe input side (button, axis and hat events) has no example yet — see
TODO.md.
Two things worth knowing
Every write resets the LEDs you did not set. The device wants the whole LED
table on every write and offers no way to read the current one back, so anything
you are not explicitly setting becomes the Station baseline colour. If your unit
has custom colours stored, the first write flattens them — pass a baseline that
matches.
The commit packets probably write flash. Each unit keeps its own profile across power cycles, and the commit pair appears to persist into it. Writes are serialised, rate limited to one per 100 ms, and skipped when they would change nothing. Whether it truly is flash is unconfirmed (PROTOCOL.md §7) — until it is, avoid frame-rate animation.
Design
Nothing about a specific unit is hardcoded. You describe your device — its USB id and idle colour — and pass it in. Any device speaking this protocol works.
Product (abstract) zones, LED packet layout, input report layout
└── Solaris src/products/solaris.ts
Station ──┐ which product + which device + its idle colour
├── Device ─────┬── LedTable ── Color / Zone staged state
UsbId ────┘ ├── LedWriter libusb out
├── HidInputReader ── InputReport hidraw in
└── WriteQueue serialise, rate limitStation describes a device; Device is an open connection to one. They are
separate because they have different lifetimes — a Station is an immutable
value you can build from config with no hardware present, a Device owns a
claimed USB interface and an open stream.
Everything device-specific lives on the product, including the wire format, and it drives the types too:
await Device.open('044f:042a', { baseline: '#9bff37' }); // Solaris by default
await Device.open(0x1234, { product: Warthog }); // class or instanceAdding a joystick means subclassing Product in src/products/ — no edits to
the library. src/products/solaris.ts is the worked example and the first file
to port; docs/PROTOCOL.md explains every value in it.
Status
The output path is verified against hardware: colours apply, per-LED painting works, reset works. Input is verified to open, read and decode, including correctly ignoring the latched toggle switch that sits permanently on.
Not implemented: HandleRing, which needs a separate report variant the protocol doc is itself unsure about.
There are no automated tests yet. Everything above was checked by hand. The byte-level fixture in PROTOCOL.md §4.7 is the obvious starting point and needs no hardware — see TODO.md.
The zone names come from the Windows Control Panel's labels. Which HID button
index sits under which labelled LED was never captured — log button events on
your own unit rather than assuming index 5 is button5.
