Skip to content

Telemetry capture

GT7 streams encrypted telemetry over UDP at roughly 60 Hz while driving is on screen. This page describes exactly how the datalogger captures and decodes it.

The handshake

The console only streams to a client that keeps asking for data:

  1. The datalogger binds UDP port 33740 and sends a heartbeat — a single character — to the console's port 33739 every 1.6 s. (GT7 stops sending after ~100 packets without a heartbeat.) The character selects the packet format: A, B, ~, or C (see packet formats below); the datalogger sends C by default (GT7_PACKET_FORMAT, changeable live in Settings › Connection).
  2. The heartbeat is addressed to GT7_PS_IP if configured. If not, it is broadcast (255.255.255.255), and the console's address is learned from the source address of the first telemetry packet that arrives — that's the auto-discovery mechanism.
  3. If no packet arrives for 5 s the connection is considered stale and the status indicator drops to "waiting for telemetry" while heartbeats continue. Without GT7_PS_IP they go back to being broadcast (and sent to the last known address as well), so a console that wakes from rest mode on a new DHCP address is found again on its own. With an IP configured the heartbeat only ever goes to it; only Find console broadcasts.

The console streams to every client that keeps sending heartbeats, not only the latest one. Two dataloggers on different machines can capture the same drive at once, and so can a datalogger and the phone app talking to the console directly (#144).

Packet formats

Four packet formats exist, each a strict superset of the previous. Parsing keys off the datagram length, so whatever the console answers is decoded correctly:

Heartbeat Size Adds
A 296 B base telemetry (everything in the table below)
B 316 B steering wheel rotation, sway / heave / surge accelerations
~ 344 B filtered throttle & brake (the brake applied after ABS, live driving only), per-wheel torque vectors, energy recovery, and three fields the community decoded in 2026: a powertrain byte, an energy-recovery flag and the delivered engine torque (#150)
C 368 B per-wheel surface type, live lap timer, front-wheel steering angles, wheelbase, car category (needs GT7 v1.68+)

The extended fields are parsed into the typed packet model (as None when the console sends a smaller format). Three of the ~ fields were unnamed in the community layout until late 2026; what they mean comes from community reverse-engineering (GTPlanet's telemetry thread, Nenkai/PDTools#14), not from Polyphony:

Offset Field Meaning
0x13E powertrain_flags (u8) powertrain / motor layout: 0 on every combustion car checked, 4 electric, 9 four-motor cars with active torque vectoring, 2 also seen. Possibly a bitmask
0x13F energy_recovery_available (u8 → bool) whether energy_recovery carries anything for this car: set on most hybrids and EVs. PDTools calls it NoGasConsumption
0x154 engine_torque_norm (f32) the engine torque being delivered, normalized: tracks boost and throttle, negative on the overrun, exactly 0 with the clutch in or in neutral, not bounded to ±1. Recorded per tick as the engine_torque column
set Packet format to A in Settings › Connection if no data arrives.

Decryption

Each datagram is encrypted with Salsa20:

  • Key — the first 32 bytes of the ASCII string Simulator Interface Packet GT7 ver 0.0 (i.e. Simulator Interface Packet GT7 v).
  • Nonce — a 4-byte little-endian seed iv1 is read unencrypted at offset 0x40 of the datagram. A second value is derived as iv2 = iv1 XOR <constant>, and the 8-byte Salsa20 nonce is iv2 followed by iv1 (both little-endian). The XOR constant depends on the packet format: 0xDEADBEAF for A (yes, BEAF — that is the real GT7 constant), 0xDEADBEEF for B and C, 0x55FABB4F for ~. The decoder picks the constant matching the datagram size and falls back to trying the others.
  • Validation — after decryption, the first four bytes must equal the magic 0x47375330 ("G7S0"). Anything else is counted as a decode error and dropped.

Decoded fields

Every packet decodes to a typed structure with ~50 fields. The important ones:

Offset Field Units / notes
0x04 position X/Y/Z m, GT7 world coordinates
0x10 velocity X/Y/Z m/s, world axes
0x1C orientation x/y/z the car's orientation as a unit quaternion, with 0x28. The community layout names these rotation pitch/yaw/roll; they are not angles (how that was settled)
0x28 orientation w named relative orientation to north; not a heading
0x2C angular velocity X/Y/Z rad/s (Y = yaw rate)
0x38 body height m
0x3C engine RPM rpm
0x44 fuel level / capacity L (capacity 0 for EVs)
0x4C speed m/s
0x50 boost raw − 1.0 → bar
0x54–0x5C oil pressure, water temp, oil temp bar, °C, °C
0x60 tire temps FL/FR/RL/RR °C
0x74 current lap / total laps total = 0 in time trial
0x78 best / last lap time ms, −1 when unset
0x80 in-game time of day ms
0x84 race position / total
0x88 RPM alert min/max shift-light thresholds
0x8E flags bitmask, see below
0x90 current gear (low nibble) / suggested gear (high nibble) current: 0 = reverse, 15 = neutral; suggested: 15 = no suggestion, recorded as suggested_gear
0x91 throttle / brake 0–255 → % via ÷2.55
0xA4 wheel angular speed FL/FR/RL/RR rad/s, signed
0xB4 tire radius FL/FR/RL/RR m
0xC4 suspension travel FL/FR/RL/RR m
0xF4 clutch, clutch engagement, RPM after clutch
0x104 gear ratios (8) + transmission top speed
0x124 car ID maps to the car inventory (app/data/cars.json)

Flags bitmask — CAR_ON_TRACK (bit 0), PAUSED (1), LOADING (2), IN_GEAR (3), HAS_TURBO (4), REV_LIMITER (5), HANDBRAKE (6), lights (7–9), ASM_ACTIVE (10), TCS_ACTIVE (11).

For storage, the aid flags are remapped into a stable, compact aids bitmask persisted with every sample: TCS=1, ASM=2, HANDBRAKE=4, REV_LIMITER=8. This keeps recorded laps immune to any future changes in GT7's flag layout.

Wheel slip (computed at decode time)

GT7 doesn't send slip directly; it sends wheel angular speed and tire radius. Per wheel:

slip = |wheel_rad_per_s| × tire_radius / car_speed_mps
  • slip < 1 under braking → the wheel is turning slower than the car is moving — locking
  • slip > 1 under power → the wheel is spinning faster than the car — wheelspin
  • Below 1 m/s car speed the ratio is meaningless, so all four are forced to 1.0.

This is a slip-ratio proxy (wheel surface speed ÷ car speed), unsigned — not a signed SAE slip ratio — but it is exactly what's needed to spot lockups and wheelspin.

From packet to dashboard

Decoded packets flow through a bounded queue (600 packets ≈ 10 s) into a single ordered consumer:

  • if recording is on, the packet feeds lap detection;
  • the latest frame is broadcast to all WebSocket clients, throttled to GT7_WS_RATE (default 30 Hz) — capture stays at 60 Hz regardless.

If the queue ever fills (slow disk), the oldest packet is dropped so the live view never lags behind reality. Received/dropped/decode-error counters are visible in GET /api/status and Settings › Health.

Protocol references

The GT7 telemetry format is community-reverse-engineered. Useful companion projects: snipem/gt7dashboard (Python dashboard this project reached parity with) and MacManley/gt7-udp (a C++ parser for ESP32 / ESP8266 boards with well-documented packet offsets).

The simulated source

GT7_SOURCE=sim swaps the UDP listener for a synthetic source that emits the same packet stream at exactly 60 Hz: a 3,200 m circuit, ~223 km/h baseline with two braking zones, deliberate front lockups, rear wheelspin on slow launches, a kerb strike at ~21 % of the lap, and TCS/ASM/rev-limiter activity. It uses a fixed random seed, so demo data is reproducible. It also suggests gears, as a stand-in for GT7's own hint, which has not been checked against a console: the gear the next corner calls for, shown from up to 150 m before it while the car is in a higher one. On about half its laps the driver holds fourth through the tight right-hander at ~1,600 m where third is suggested, so the gear choice figures differ from lap to lap.