Skip to content
baelenePublic

About

Live telemetry dashboard and lap analysis for Gran Turismo 7 — Rust, runs on Linux/macOS

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gt7dash

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.

Live dashboard, recorded at Dragon Trail

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.


Contents


Features

Live cockpit (/)

  • 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.

Phone layout

Lap analysis (/analysis)

  • 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.

Lap analysis: lap 2 vs lap 1 at Dragon Trail, 5.17 s faster

Tracks, cars and connection

  • 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.

Naming a new track


Quick start

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 thing

Open http://localhost:5006, or the LAN address printed at start-up, from any device. Then:

  1. 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.
  2. Start GT7 and drive. Laps are recorded automatically.
  3. 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.


Installing on Linux

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 gt7dash

Prebuilt static binaries for other machines:

  • make linux-docker builds dist/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-aarch64 cross-compile with cargo-zigbuild.

Docker:

docker build -t gt7dash .
docker run -d --restart unless-stopped -p 5006:5006 -p 33740:33740/udp \
  -v $PWD/data:/data gt7dash

Containers usually can't broadcast, so set the PS5 address once in the web page; it's saved in /data.

Options

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

Using it

  • 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.

How it works

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"]
Loading

Language and stack

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

Telemetry protocol (src/packet.rs, src/udp.rs)

This is the community-reverse-engineered "Simulator Interface" of GT7. Credit goes to the GTPlanet thread, Nenkai's PDTools and gt7dashboard; see Sources.

  1. Heartbeat: send the byte A to 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.
  2. 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-endian u32 seed at offset 0x40: seed ^ 0xDEADBEAF (sic) followed by seed. A valid packet decrypts to the magic 0x47375330 ("G7S0").
  3. 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.

Lap recording (src/lap.rs)

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.

Analysis (src/analysis.rs)

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

Track recognition (src/track.rs)

GT7 sends no track id, but every circuit sits at fixed world coordinates, so the racing line is a fingerprint:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Live frames (src/state.rs, src/server.rs)

  • 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::watch channel. 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.

Web UI (web/)

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.

Other details

  • 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 -i on macOS, systemd-inhibit --what=idle:sleep on Linux. Normal sleep resumes shortly after telemetry stops.
  • Car names: db/cars.csv and db/maker.csv from ddm999/gt7info (make cars).
  • Track catalog (src/track_catalog.json): all 41 GT7 locations with their countries and layouts, for naming.

HTTP API

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

Project layout

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

Development and tests

cargo run -- --demo        # debug build serves web/ from disk: edit, reload
cargo test                 # ~84 tests, ~10 s, no PlayStation or network needed

58 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 address
  • live.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 behaviour
  • capture.rs: replays a real packet capture through the whole app. Record one with GT7_CAPTURE=session.bin gt7dash, then run GT7_CAPTURE_FILE=session.bin cargo test --release --test capture -- --nocapture

The Docker build runs the whole suite on Linux.


Sources and credits

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

Libraries and assets


License

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.

About

Live telemetry dashboard and lap analysis for Gran Turismo 7 — Rust, runs on Linux/macOS

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages