-
Notifications
You must be signed in to change notification settings - Fork 5
Simulated TrueForce Internals
How logi-tf-sim works inside. The user-facing guide is
Simulated TrueForce; the crate is userspace/logi-dd/crates/tf-sim
(binary logi-tf-sim).
The crate is deliberately layered so each piece is testable pure:
| Module | Role |
|---|---|
codemasters, pcars
|
pure &[u8] -> Option<(game id, Telemetry)> packet parsers, fixture-tested |
telemetry |
the normalized sample all parsers decode into |
synth |
pure engine-note generator (1 kHz sample stream) |
config |
the ~/.config/logi-dd/tf-sim.conf key=value store |
tf |
safe wrapper over the libtrueforce FFI (stream lifecycle) |
leds |
the rev-display feeder (wheel_rev_level via sysfs) |
daemon |
the UDP listen / synthesize / watchdog loop |
sweep |
the --sweep hardware-test mode (synthetic RPM sweep) |
Two wire formats, each on its own UDP port, both parsed permissively (a malformed packet is skipped, never fatal):
- Codemasters / EA float array (default port 20777): the classic format spoken by DiRT Rally 2.0 and the older Codemasters titles.
- Project CARS 2 (default port 5606): also spoken by Automobilista 2.
Both normalize into one Telemetry sample:
pub struct Telemetry {
pub rpm: f32, // engine speed
pub max_rpm: f32, // redline (> 0 for a valid sample)
pub throttle: f32, // 0.0 to 1.0
pub speed: f32, // m/s
}The daemon auto-detects the game from the packet format and applies that game's per-game config (enable, intensity).
EngineSynth generates the 1 kHz sample stream the wheel's DSP consumes:
- A fundamental at
rpm / 60 * pitch_scaleHz plus 2x and 3x harmonics at falling gains (1, 1/2, 1/4). The gains factor so the summed waveform crosses zero exactly twice per fundamental cycle, keeping the felt pitch equal to the engine rate (and making the spectral unit test exact). - Amplitude is
idle_floor + throttle * gain(0.15 + 0.85·throttle), everything scaled by the effective intensity (master × per-game). - The generator is stateful only in its oscillator phase, so frequency changes are click-free. Frequency is capped below Nyquist.
-
pitch_scale(0.1-2.0, configpitchas percent) scales the felt rev rate. The default is 50%: at 100% (crank-rate fundamental) the hardware feel test read as an alarmingly fast engine; half rate feels like an engine.
The libtrueforce stream thread does the packetizing (4 samples per 250 Hz packet, see TrueForce Protocol); the synth only produces samples.
leds::RevLeds mirrors telemetry RPM onto the wheel's rev display through
the driver's wheel_rev_level sysfs attribute (0-10 LEDs lit; on the RS50
the fill uses the active LIGHTSYNC slot's colors and direction, on a real
G PRO rim the onboard profile owns the colors).
- Level =
round(10 * rpm / max_rpm), clamped 0-10; a missing or zero redline reads as 0 so a car that never reported its limiter shows a dark strip instead of a division artifact. - Pacing: writes are rate-limited to one per ~160 ms and skipped while the level is unchanged. Faster bursts would starve the wheel's shared HID++ command processor and can cut FFB out (see Protocol LIGHTSYNC); the driver additionally coalesces on its side.
- Discovery honors
LOGI_DD_SYSFS_DIR(the same development override the logi-dd frontends use), falling back to a scan of/sys/bus/hid/devices/*/wheel_rev_level. - Everything is best-effort: a wheel without the attribute, a failed write or a missing driver never disturbs the TrueForce stream riding alongside.
- Listens on both UDP ports simultaneously; idle until telemetry arrives.
- On the first valid sample from an enabled game, ensures a libtrueforce session (which runs the 68-packet two-pass init, see TrueForce Protocol) and starts streaming synthesized samples.
- A watchdog stops the stream when telemetry goes quiet, releasing the wheel; the rev display is restored to the idle LED pattern.
- Config changes from the Setup page (master switch, intensities, pitch, per-game enables) are picked up without restarting the daemon.
logi-tf-sim --sweep (also reachable from the Setup page) runs a synthetic
RPM run-up through the exact same synth, stream and LED paths as real
telemetry, letting a user feel and calibrate the effect without a game. It
is consent-gated in the UIs because it moves a strong direct-drive wheel.
The parsers and synth are pure and covered by fixture tests
(crates/tf-sim/tests); the LED feeder takes an injected clock and an
explicit attribute path so tests drive it against a temp file. Run the lot
with cargo test -p tf-sim in userspace/logi-dd.