# rusty-telemetry Multi-port UDP telemetry listener and analysis platform for sim racing games. Receives real-time telemetry from **Assetto Corsa** and **Project CARS 1 & 2** via UDP, parses binary packet formats, and provides live data via a terminal dashboard and REST API. Supports use-case driven analysis sessions for shift point optimization and track mapping. ## Supported Games | Game | Feed | Port | Protocol | Data | |------|------|------|----------|------| | Assetto Corsa | KSUDP (built-in) | 9996 | 3-phase handshake, binary RTCarInfo | Speed, RPM, gear, inputs, G-forces, lap times, per-wheel slip, suspension | | Assetto Corsa | Telemetry Tool Plugin | 10101 | Binary per-driver structs | All of KSUDP + tyre core temps, driver names, ERS, DRS, all cars | | Project CARS 1 | UDP Telemetry | 5606 | Binary V1 format (1367 bytes) | Speed, RPM, gear, inputs, engine temps, damage, weather, tyre wear | | Project CARS 2 | UDP Telemetry | 5606 | Binary V2 format (559 bytes) | All of PCARS1 + full 3D position, turbo boost, tyre compounds | All feeds are optional. If a feed fails to bind (e.g. the game isn't running), it shows as "not available" in the TUI and the other feeds continue normally. ## Build and Run ```bash cargo run --release ``` Press **q** or **Ctrl+C** to quit. ## Configuration ### Environment Variables | Variable | Purpose | Default | |----------|---------|---------| | `RUSTY_TELEMETRY_GAME_HOST` | IP of the machine running the sim game (used for KSUDP handshake target) | `127.0.0.1` | | `RUSTY_TELEMETRY_LISTEN` | Local address to bind for incoming UDP feeds (Telemetry Tool, PCARS) | `0.0.0.0` | | `RUSTY_TELEMETRY_BIND` | Fallback for both of the above (backward compatibility) | — | | `RUSTY_TELEMETRY_API_PORT` | REST API server port | `8080` | | `RUSTY_TELEMETRY_API_HOST` | REST API server bind address | `127.0.0.1` | ### Same Machine No configuration needed — defaults work out of the box: ```bash cargo run --release ``` ### Remote Setup (game on a different machine) Run rusty-telemetry on a separate machine (e.g. `192.168.1.100`) while the game runs on another (e.g. `192.168.1.2`): ```bash # On the analysis machine: RUSTY_TELEMETRY_GAME_HOST=192.168.1.2 cargo run --release ``` Then point the games at the analysis machine: - **KSUDP**: Works automatically — AC sends telemetry back to the handshake's source address - **Telemetry Tool**: Set `to_ip = 192.168.1.100` in `config.ini` - **PCARS**: Set target IP to `192.168.1.100` in the in-game UDP settings ## Feed Setup ### KSUDP (AC built-in, port 9996) No additional setup. AC's built-in Remote Telemetry UDP server listens on port 9996 at startup. rusty-telemetry performs a 3-phase handshake automatically: 1. Sends `HANDSHAKE` to AC's port 9996 2. Receives 408-byte response (car name, driver name, track name in UTF-16LE) 3. Sends `SUBSCRIBE_UPDATE` to start continuous streaming at ~20 Hz **Important**: Start AC before rusty-telemetry so AC can bind port 9996 first. ### Telemetry Tool Plugin (AC, port 10101) Optional third-party Python plugin by IkoRein. Provides richer data than KSUDP: tyre core temperatures, driver names, ERS, DRS, and data for all cars in the session. 1. Download from [RaceDepartment](https://www.racedepartment.com/downloads/telemetry-tool-for-ac.37231/) 2. Extract to `/apps/python/Telemetry_Tool_plugin/` 3. Edit `config.ini`: ```ini [telemetry_tool_plugin] to_ip = 127.0.0.1 to_port = 10101 ``` 4. Enable in `python.ini`: `[TELEMETRY_TOOL_PLUGIN] ACTIVE=1` 5. Restart AC ### Project CARS 1 & 2 (port 5606) Configure in-game to send telemetry to your machine's IP on port 5606. **PCARS 2**: Options → System → UDP Protocol → set target IP/port, format **2** recommended. **PCARS 1**: Options → Visual → UDP Protocol → set target IP/port. The parser auto-detects the format: PCARS 1 (1367-byte packets, 3-byte header) or PCARS 2 (559-byte car physics packets, 12-byte header). ## TUI The terminal UI shows one panel per active feed: ``` ┌──────────────────────────────────────────────────────────┐ │ rusty-telemetry v0.5.0 -- Telemetry Listener │ ├──────────────────────────────────────────────────────────┤ │ ╔ * Receiving ╗ │ │ ║ 20.0 Hz 1200 packets 15.2 KB/s last: 0.05s ║ │ ║ 245 km/h Gear:5 8750 RPM Lap:1:23.456 ║ │ ║ THR: 85% BRK: 0% Pos:0.453 ║ │ ╚→ :9996 recv :42132 [subscribed]╝ │ │ │ │ ╔ * Receiving ╗ │ │ ║ 60.0 Hz 3600 packets 45.8 KB/s last: 0.01s ║ │ ║ 180 km/h Gear:4 7200 RPM Lap:1:18.123 ║ │ ║ THR: 72% BRK: 0% Pos:0.500 ║ │ ╚ Telemetry Tool Port :10101 ╝ │ │ │ │ ╔ . Bound, waiting for data ╗ │ │ ║ 0.0 Hz 0 packets 0.0 KB/s last: never ║ │ ╚ PCARS Port :5606 ╝ │ ├──────────────────────────────────────────────────────────┤ │ Recording: 550e8400-… Ctrl+C Quit │ │ ⚠ Handshake response timed out (5s) — 12s ago │ └──────────────────────────────────────────────────────────┘ ``` Each panel shows: - Health indicator and status (`*` green/alive, `~` yellow/stale, `.` gray/waiting, `!` red/error) - Packet frequency, total count, data rate, time since last packet - Live telemetry: speed, gear, RPM, lap time, throttle/brake percentage, track position - Connection mode (handshake with target/recv ports, or bind-listen) ## Data Capture All feeds are continuously recorded into in-memory ring buffers (5 minutes, 18,000 frames per feed). A recording is auto-started on launch; on shutdown, ring buffer contents are flushed to per-feed JSONL files: ``` ./data// ├── manifest.json ├── feed_9996_ksudp.jsonl ├── feed_10101_telemetry_tool.jsonl └── feed_5606_pcars.jsonl ``` Each recording has a `manifest.json` with metadata (game, track, car, use case, status, timestamps) and JSONL files for each feed. Each line is a serialized `TelemetryFrame` object. ### Session-Based Analysis Use-case sessions allow targeted data capture and analysis: 1. **Create a session** with a use case (`shift_points` or `track_map`) 2. **Start runs** — each run captures frames from the ring buffer between start and stop 3. **Complete the session** — auto-analyzes captured data and stores results 4. **Retrieve analysis** — shift point RPM ranges or track boundary coordinates Sessions are in-memory and independent of persistent recordings. ## REST API A built-in HTTP API server runs on port 8080 for recording management and live telemetry access. ### Endpoints | Method | Path | Description | |--------|------|-------------| | GET | `/api/live` | Live telemetry snapshot (all feeds) | | GET | `/api/recordings` | List all recordings | | POST | `/api/recordings` | Start new recording | | GET | `/api/recordings/{id}` | Get recording details | | POST | `/api/recordings/stop` | Stop active recording | | POST | `/api/recordings/{id}/stop` | Stop specific recording | | DELETE | `/api/recordings/{id}` | Delete recording | | GET | `/api/recordings/{id}/data/{feed_name}` | Download raw feed JSONL | | GET | `/api/use-cases` | List valid use cases | | GET | `/api/games` | List supported games | | GET | `/api/sessions` | List all sessions | | POST | `/api/sessions` | Create analysis session | | GET | `/api/sessions/{id}` | Get session details | | DELETE | `/api/sessions/{id}` | Delete session | | POST | `/api/sessions/{id}/runs` | Start a run | | POST | `/api/sessions/{id}/runs/{run_id}/stop` | Stop a run | | POST | `/api/sessions/{id}/complete` | Complete session + auto-analyze | | GET | `/api/sessions/{id}/analysis` | Get analysis results | | POST | `/api/sessions/{id}/analyze` | Re-run analysis on completed session | ### Start Recording Body ```json { "name": "My Session", "game": "assetto_corsa", "track": "nurburgring", "car": "ferrari_488", "use_case": "general", "notes": "Testing session" } ``` All fields are optional. ### Live Telemetry Response ```json { "feeds": [ { "label": "KSUDP", "port": 9996, "health": "alive", "frequency_hz": 20.0, "total_packets": 1200, "data_rate_kb_s": 15.2, "speed_kmh": 245.0, "gear": "5", "engine_rpm": 8750.0, "throttle": 0.85, "brake": 0.0, "lap_time": "1:23.456", "spline_position": 0.453 } ], "active_recording": "550e8400-e29b-41d4-a716-446655440000" } ``` ### Create Session Body ```json { "use_case": "shift_points", "label": "Shift Point Analysis Run 1" } ``` ### Start Run Body ```json { "feed_name": "ksudp", "label": "Acceleration runs" } ``` ### Use Cases Response ```json [ { "id": "shift_points", "name": "ShiftPoints", "guidance": "1. Drive to a long straight..." }, { "id": "track_map", "name": "TrackMap", "guidance": "1. Drive a complete lap..." } ] ``` Each object contains: - `id` — use case identifier (use in session creation) - `name` — human-readable name - `guidance` — step-by-step instructions for the use case ### Shift Point Analysis Response ```json { "gear_curves": [ { "gear": 2, "gear_display": "1", "run_label": "Run 1, Run 2 (2 runs)", "data_points": [ { "speed_kmh": 45.2, "rpm": 3200, "acceleration_g": 0.85 }, { "speed_kmh": 55.8, "rpm": 4100, "acceleration_g": 0.82 } ], "min_rpm": 3200, "max_rpm": 8500, "min_speed_kmh": 45.0, "max_speed_kmh": 120.0, "sample_count": 1200 } ], "crossover_points": [ { "from_gear": 2, "to_gear": 3, "from_gear_display": "1", "to_gear_display": "2", "crossover_speed_kmh": 95.0, "crossover_rpm": 7500 } ], "recommended_shift_rpm": 7500.0, "runs_analyzed": 3, "total_frames_analyzed": 2100 } ``` ## Web Client A companion web application ([Racecraft](https://codeberg.org/jhunnius/racecraft)) provides a browser-based interface for live telemetry, session management, and data analysis visualization. It connects to the REST API and renders shift point charts, track maps, and real-time gauges. To use with the web client, start the API on all interfaces: ```bash RUSTY_TELEMETRY_API_HOST=0.0.0.0 cargo run --release ``` Then start the web client on the same machine — it proxies `/api/*` to `localhost:8080` automatically. Access from any machine on the LAN via the Vite dev server's network URL. ## Testing ```bash cargo test ``` 32 unit tests covering: - Binary parsers for all three feed formats (KSUDP, Telemetry Tool, PCARS V1/V2) - Ring buffer frequency calculation and edge cases - Feed health state machine transitions (Waiting → Alive → Stale → Alive) - Packet validation, error handling, boundary conditions ## Architecture ``` src/ ├── main.rs Multi-port UDP listener, TUI event loop, KSUDP handshake ├── api.rs REST API server (axum) — recordings, sessions, live telemetry ├── recording.rs Recording manager — ring buffers, manifest CRUD, flush to JSONL ├── session.rs Session manager — use-case sessions, runs, frame extraction ├── analysis.rs Analysis algorithms — shift points, track mapping ├── binary_utils.rs Shared safe binary read helpers (bounds-checked, zero fallback) ├── telemetry_frame.rs Unified telemetry data model (TelemetryFrame) ├── feed_state.rs Per-feed health/frequency state machine with ring buffer ├── parser_ksudp.rs AC KSUDP binary parser (RTCarInfo, 6 tests) ├── parser_telemetry_tool.rs AC Telemetry Tool binary parser (357-byte driver struct, 7 tests) ├── parser_pcars.rs PCARS 1 & 2 binary parser with auto-detection (5 tests) ├── ui.rs Ratatui TUI rendering └── warnings.rs Thread-safe warning buffer with TUI bar + stderr fallback ``` ## License Private project — not yet licensed for distribution.