A fast, good-looking live telemetry dashboard and lap analyser for Gran Turismo 7. It's one small Rust program that runs on Linux, macOS and Windows. Open it in any browser on your network, on the PC, a tablet next to the wheel or your phone.
gt7dash is a Rust rewrite and redesign of snipem/gt7dashboard (Python/Bokeh). It keeps that project's proven telemetry handling and lap analysis, and adds a 60 fps cockpit UI, automatic track recognition, car names, and a single dependency-free binary.
The screenshots show real laps driven in GT7 (Renault Megane Gr.4, Dragon Trail), replayed with
--demo-file.
- Features
- Quick start
- Installing on Linux
- Using it
- How it works
- Development and tests
- Sources and credits
- License
- Shift lights: they flash at the shift point and rev limiter.
- Gauge: glowing rev arc, a big gear number, and speed with an inner top-speed ring.
- Pedals and inputs: throttle and brake bars, plus a 6-second input trace.
- Live delta to your best lap on this track, measured at the same distance, with a centred gain/loss bar.
- Tyres: four temperatures colour-coded cold → working → hot, with wheelspin and lock-up warnings.
- Track map: your line, coloured by throttle, brake or coasting, and a moving car dot.
- G-meter with a trail.
- Fuel: level, use per lap and laps left.
- Engine and session: oil and water temperature, oil pressure, boost, ride height, session top speed.
- Flags and indicators: track name with country flag, car name and photo, TCS/ASM/handbrake lights.
- Fits any screen: fullscreen mode with a screen wake lock, and a layout for phones and tablets.
- Race line coloured by inputs or by speed, with apex and straight-end speeds labelled.
- Comparison charts on one distance axis: time gap to a reference lap, speed, throttle, brake, RPM and gear, all with a linked cursor. Hovering any chart moves both cars around the map, and zooming one chart zooms them all.
- Median lap of your laps within 10 s of your best.
- Speed consistency: speed spread across your fastest laps, showing where you vary most.
- Corners & straights table: lap vs reference speed at every apex and straight end.
- Fuel-mixture map: power vs consumption trade-off for mixture settings −5 to +5.
- Lap table with throttle, brake, coasting and wheelspin statistics.
- Lap files: save, load and append, compatible with gt7dashboard in both directions.
- Track recognition. GT7 doesn't send which track you're on, so gt7dash recognises it from the car's position: usually within 400–600 m, and it tells reverse layouts apart. Name a new track once and it's recognised from then on. See Track recognition.
- Car names with the maker ("Ferrari 296 GT3 '23"), and a car photo from Wikipedia where one exists.
- PS5 address set in the browser, saved for every later session, with live connection status. Leaving it empty auto-detects the console by broadcast.
- Stays awake while you race. It keeps the computer from idle-sleeping while telemetry flows. A laptop dozing off mid-race once cost three laps during testing.
You need Rust 1.85 or newer, and on Linux a C linker (build-essential on Debian/Ubuntu).
git clone https://github.com/baelene/gt7dash.git && cd gt7dash
cargo run --release -- --demo # try it now, no PlayStation needed
cargo run --release # the real thingOpen http://localhost:5006, or the LAN address printed at start-up, from any device. Then:
- Click the 📶 button (PlayStation connection), enter your PS5's IP address and click Save. You'll find it on the PS5 under Settings → Network → Connection Status → View Connection Status. It's remembered from now on. Leave it empty to auto-detect.
- Start GT7 and drive. Laps are recorded automatically.
- After your first lap on a new track, click Unknown track · Name it.
Optional: make cars downloads the car and maker lists, so cars show by name instead of CAR-ID-1234.
Firewall: allow incoming UDP 33740. gt7dash sends a heartbeat to the console on UDP 33739.
make release # → target/release/gt7dash (≈4 MB, no runtime dependencies)
sudo make install # binary + systemd unit
sudo useradd -r -m -d /var/lib/gt7dash gt7dash
sudo systemctl enable --now gt7dashPrebuilt static binaries for other machines:
make linux-dockerbuildsdist/gt7dash-linux-{amd64,arm64}; it needs only Docker. The arm64 build runs on a Raspberry Pi next to the TV.make linux-x86_64/make linux-aarch64cross-compile withcargo-zigbuild.
Docker:
docker build -t gt7dash .
docker run -d --restart unless-stopped -p 5006:5006 -p 33740:33740/udp \
-v $PWD/data:/data gt7dashContainers usually can't broadcast, so set the PS5 address once in the web page; it's saved in /data.
Everything has a sensible default; the PS5 address normally comes from the web page.
| Option | Environment | Default |
|---|---|---|
--ps-ip <ip> |
GT7_PLAYSTATION_IP |
the address saved in the web page, else auto-detect. Given here, it overrides the saved one. |
--bind <addr> |
GT7_BIND |
0.0.0.0:5006 |
--data-dir <dir> |
GT7_DATA_DIR |
data (laps, tracks.json, settings.json) |
--cars <file> |
GT7_CARS_CSV |
db/cars.csv (with maker.csv next to it) |
--always-record |
GT7_ALWAYS_RECORD |
off. Also records replays. |
--demo [--demo-file laps.json] |
replays recorded laps at 60 Hz |
- Recording: a lap is stored when GT7's lap counter advances, using GT7's own lap time. Restarts, quitting mid-lap, the grid and the out lap never produce partial or junk laps. The pre-race screen shows your car lapping in the background, and replays are ignored unless Always record is on.
- Reference lap: the delta and the analysis compare with your best lap on the same track by default. Pick any lap with L (analyse) or R (reference) in the lap table.
- Lap files: Save laps writes
data/<date>_<car>.json. Load replaces the session and Append adds to it. Files from gt7dashboard load as-is, and get tagged with their track if it's known. - Log lap now: closes the current lap manually, e.g. on a track without a start line.
flowchart LR
PS5["PS5 · GT7"] -- "UDP 33740, 60 Hz<br/>Salsa20-encrypted" --> UDP
UDP -- "heartbeat 'A' on 33739<br/>every ~100 packets" --> PS5
subgraph gt7dash["gt7dash (one process)"]
UDP["udp.rs<br/>link, reconnect"] --> PKT["packet.rs<br/>decrypt + parse"]
PKT --> REC["lap.rs<br/>Recorder"]
PKT --> TRK["track.rs<br/>live recognition"]
REC --> ST["state.rs<br/>delta, G-forces, fuel"]
TRK --> ST
ST -- "watch channel" --> WS["/ws<br/>live frames"]
REC --> AN["analysis.rs"] --> API["/api/*"]
end
WS --> LIVE["Live page<br/>canvas, 60 fps"]
API --> ANA["Analysis page<br/>uPlot charts"]
| Part | Choice | Why |
|---|---|---|
| Server | Rust with tokio (async I/O) and axum (HTTP + WebSocket) |
A single static binary using a few MB of RAM and under 1 % CPU at 60 Hz, easy to run on a Pi |
| Web UI | Plain HTML, CSS and JavaScript, no build step | Embedded into the binary with rust-embed; debug builds read web/ from disk for instant reloads |
| Gauges, maps | Canvas 2D, one requestAnimationFrame loop |
Smooth 60 fps with light smoothing between packets |
| Charts | uPlot, vendored | Fast enough for 25 000-point laps (the Nordschleife) with cursor sync |
This is the community-reverse-engineered "Simulator Interface" of GT7. Credit goes to the GTPlanet thread, Nenkai's PDTools and gt7dashboard; see Sources.
- Heartbeat: send the byte
Ato the console on UDP 33739. GT7 then streams 296-byte packets to the sender on UDP 33740 at 60 Hz. It stops after ~100 packets without another heartbeat, so one is sent every 100. - Decryption: Salsa20. The key is the first 32 bytes of
"Simulator Interface Packet GT7 ver 0.0". The 8-byte nonce comes from the little-endianu32seed at offset0x40:seed ^ 0xDEADBEAF(sic) followed byseed. A valid packet decrypts to the magic0x47375330("G7S0"). - Layout (little-endian; selection):
| Offset | Field | Offset | Field |
|---|---|---|---|
0x04 |
position x, y, z (f32) | 0x74 |
current lap / total laps (i16) |
0x10 |
velocity x, y, z | 0x78 |
best lap / last lap (i32 ms) |
0x1C |
rotation pitch, yaw, roll | 0x84 |
race position / total cars |
0x2C |
angular velocity | 0x88 |
RPM shift warning / rev limiter (u16) |
0x38 |
ride height (m) | 0x8E |
flags: in race, paused, loading, in gear, turbo, rev-limit, handbrake, lights |
0x3C |
RPM | 0x8F |
flags: ASM, TCS active |
0x44 |
fuel / capacity | 0x90 |
gear (low 4 bits) / suggested gear (high 4) |
0x4C |
speed (m/s) | 0x91, 0x92 |
throttle, brake (0–255) |
0x50 |
boost (+1 bar) | 0xA4 |
wheel rotation speed ×4 (rad/s) |
0x58, 0x5C |
water, oil temperature | 0xB4 |
tyre radius ×4 |
0x60 |
tyre temperature ×4 | 0xC4 |
suspension travel ×4 |
0x70 |
packet id (i32) | 0x104 |
gear ratios ×8, 0x124 car id |
Packets with an older id than the last one are dropped. The link reconnects on errors and retargets immediately when you change the address in the web page. It logs state changes once, instead of "no route to host" every 5 seconds while the console sleeps.
A port of gt7dashboard's _log_data / finish_lap:
- Recording: every packet while in race (and not paused) appends to per-tick arrays: speed, inputs, RPM, gear, position, boost and yaw. It also counts full-throttle, full-brake, coasting, throttle-and-brake, overheated-tyre and wheelspin ticks.
- Lap end: when the lap counter advances, the lap is closed with GT7's official lap time and fuel used.
Live testing against a PS5 found two cases the original handles badly, now fixed:
- Restarting an event (lap 1 → 0 → 1): the next lap starts fresh, with no stale fuel figure (it showed −100) or grid samples.
- Grid starts: no junk lap is stored when GT7 still reports the previous race's time.
Pure Rust, with no pandas or scipy:
| Function | How |
|---|---|
| Distance axis | integrate speed per tick: d += v/3.6 · 16.668 ms (GT7's measured tick) |
| Time gap | resample both laps' time onto a common 5 m distance grid, then take lap − reference |
| Apex / straight-end speeds | a port of scipy.signal.find_peaks(x, width=100): plateau-aware local maxima, prominence, width at half prominence. Tested to give exactly scipy's indices on real laps. |
| Median lap | per-tick median of laps within 10 s of the best |
| Consistency | sample standard deviation of speed across the 3 fastest non-replay laps within 5 % |
| Fuel map | gt7dashboard's model: −8 % consumption and −4 % power per mixture step |
GT7 sends no track id, but every circuit sits at fixed world coordinates, so the racing line is a fingerprint:
- Learning: a lap is resampled every 10 m into an outline, stored in driving direction.
Broad Bean Raceway ships built in; anything you name is saved to
data/tracks.json. - Matching: twice a second, the last ≤1.5 km of the car's path (10 m steps) is matched against each outline. A point counts if it lies within 20 m of the outline (track width plus different lines) and travels in the same direction, which keeps reverse layouts apart. A track matches when ≥ 90 % of points fit, after ≥ 400 m.
- Layouts sharing a section (e.g. Full vs East): while several layouts of one location still fit, the location is shown with "identifying layout…" until they split.
- Stability: a confirmed track is kept through restarts and teleports, and dropped only when the recent 150 m clearly leave it. Finished laps are matched both ways (lap → outline and outline → lap), so a short layout never matches a longer one.
- Uses: laps are tagged with their track, including laps in loaded files. The live delta and BEST time use the best lap on the current track.
On real data it recognised Yas Marina 600 m into a lap, and again with a different car from the race grid. Over 100 000+ packets it never flickered.
- What each packet produces: one JSON frame with the telemetry plus derived values:
- the live delta, interpolating the reference lap's time at the current distance
- G-forces, from the speed change and speed × yaw rate, smoothed
- fuel per lap and laps left
- the recognised track and the car name
- Delivery: frames go into a
tokio::sync::watchchannel. Each browser gets the newest frame and never a backlog, so a slow tablet can't fall behind, and bursts over Wi-Fi are absorbed.
| File | Role |
|---|---|
index.html, live.js |
cockpit: canvas gauge, trace, map and G-meter in one animation loop; DOM text only updates when it changes |
analysis.html, analysis.js |
resamples every lap onto one 2 m distance grid so all charts share an x-axis and a cursor |
tracks.js |
flags, country names (Intl.DisplayNames), the naming dialog with the GT7 track catalog |
settings.js |
PlayStation connection dialog |
car.js |
car photo lookup (Wikipedia, cached per car in the browser) |
style.css |
dark cockpit theme, design tokens, responsive grid |
Lap colours use a palette validated for colour-blind safety (last / reference / median) on the dark surface.
- Settings (
src/settings.rs):data/settings.json, written by the web page. - Keep awake (
src/keepawake.rs): while telemetry flows, it holds a 90-second idle-sleep assertion, renewed every 60 s:caffeinate -ion macOS,systemd-inhibit --what=idle:sleepon Linux. Normal sleep resumes shortly after telemetry stops. - Car names:
db/cars.csvanddb/maker.csvfrom ddm999/gt7info (make cars). - Track catalog (
src/track_catalog.json): all 41 GT7 locations with their countries and layouts, for naming.
| Endpoint | |
|---|---|
GET /ws |
live frames (JSON, ≤ 60 per second) |
GET /api/laps |
lap list, best lap, session, tracks |
GET /api/analysis?last=<uid>&reference=<uid> |
series, time gap, apexes, median, consistency, fuel map |
GET /api/track |
outline for the live map |
POST /api/save · GET /api/files · POST /api/load {name, mode} · POST /api/reset |
lap files and session |
POST /api/record {enabled} · POST /api/lap/finish |
recording |
GET /api/tracks · POST /api/tracks {name, layout, country, lap_uid?} · DELETE /api/tracks/<id> |
tracks |
GET /api/settings · POST /api/settings {playstation_ip} |
PlayStation connection |
src/
main.rs CLI, start-up
packet.rs decryption, packet layout, encoder (for tests/simulators)
udp.rs link to the console
lap.rs Lap model (gt7dashboard-compatible JSON), Recorder, car names
analysis.rs distance, time gap, find_peaks, median, consistency, fuel map
track.rs track recognition + track_catalog.json
state.rs shared state, live frames, link status
server.rs HTTP/WebSocket routes, embedded web UI
settings.rs saved settings
keepawake.rs prevent idle sleep during sessions
demo.rs replay recorded laps as live telemetry
web/ the browser UI (embedded at build time)
tests/ integration tests
packaging/ systemd unit
cargo run -- --demo # debug build serves web/ from disk: edit, reload
cargo test # ~84 tests, ~10 s, no PlayStation or network needed58 unit tests, kept next to the code:
- the packet encrypt → decrypt → parse round trip, with every field and flag
- recorder edge cases: restarts, pause, replays, the grid
- analysis, including the scipy parity check
- track recognition: reverse and shifted paths are rejected, there's no flicker, and learning works
- settings, and the keep-awake rate limit
26 integration tests (tests/) run the real server on random ports:
api.rs: every endpoint, the lap file round trip, path-traversal rejection, track naming and the saved PS5 addresslive.rs: WebSocket frames, demo replay, and a simulated PlayStation over real UDP (heartbeats, encryption, junk and out-of-order packets, switching console at runtime)cli.rs: the compiled binary's options and start-up behaviourcapture.rs: replays a real packet capture through the whole app. Record one withGT7_CAPTURE=session.bin gt7dash, then runGT7_CAPTURE_FILE=session.bin cargo test --release --test capture -- --nocapture
The Docker build runs the whole suite on Linux.
gt7dash stands on the work of the GT7 telemetry community. Thank you!
Telemetry protocol and dashboard logic
- snipem/gt7dashboard (GPL-3.0): the original dashboard. gt7dash ports its
lap recording, lap statistics, time-gap, median-lap, consistency and fuel-map logic and its lap file format.
The demo laps in
test_data/come from this project. - Bornhall/gt7telemetry (GPL-3.0): the script gt7dashboard started from.
- GTPlanet: "GT7 is compatible with motion rig": where the Simulator Interface, its encryption and the 16.668 ms tick were first worked out.
- Nenkai/PDTools (MIT): documentation of the simulator packet, used for the flag bits.
Data
- ddm999/gt7info (MIT-0):
cars.csvandmaker.csvwith car and maker names. Downloaded bymake cars, not bundled. - GT7 track and country list, for the naming catalog: ginx.tv, samurai-gamers.com, and the official GT7 update notes (Grand Valley, Spec II / Lake Louise, later tracks).
- Car photos: Wikipedia and Wikimedia Commons. Looked up live in the browser; only freely licensed images, credited with a link to the image page (on the live page) or in a tooltip (analysis).
- Track recognition algorithm: my own approach (position fingerprints). The idea of fingerprinting tracks by position is also discussed in the GTPlanet telemetry software thread.
Libraries and assets
- Rust crates: tokio, axum, salsa20 (RustCrypto), serde / serde_json, clap, rust-embed, tracing, chrono, futures-util, mime_guess, anyhow; for tests reqwest, tokio-tungstenite, tempfile. All MIT or Apache-2.0.
- uPlot (MIT), vendored in
web/vendor/. - flag-icons (MIT), 15 flags vendored in
web/vendor/flags/with their licence. - Fonts: Barlow Condensed and Inter via Google Fonts (SIL Open Font License); system fonts are used offline.
- The
find_peaksport follows the algorithm of SciPy'sscipy.signal.find_peaks(BSD-3-Clause).
GPL-3.0, see LICENSE. gt7dash is a derivative of gt7dashboard (GPL-3.0): it ports that project's logic and includes its sample laps, so it carries the same licence.
Gran Turismo is a trademark of Sony Interactive Entertainment Inc. This project is not affiliated with or endorsed by Sony Interactive Entertainment or Polyphony Digital. It only reads the telemetry the game sends on your local network.



