303 lines
12 KiB
Markdown
303 lines
12 KiB
Markdown
# 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 `<AC>/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 │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
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/<uuid>/
|
|
├── 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"
|
|
}
|
|
```
|
|
|
|
### Shift Point Analysis Response
|
|
|
|
```json
|
|
{
|
|
"gear_ranges": [
|
|
{ "gear": 2, "gear_display": "1", "min_rpm": 3200, "max_rpm": 8500, "avg_rpm": 6100, "sample_count": 1200 },
|
|
{ "gear": 3, "gear_display": "2", "min_rpm": 5800, "max_rpm": 8400, "avg_rpm": 7200, "sample_count": 900 }
|
|
],
|
|
"shift_events": [
|
|
{ "from_gear": 2, "to_gear": 3, "rpm_before_shift": 8400, "rpm_after_shift": 6100, "rpm_drop": 2300, "speed_kmh": 95.0 }
|
|
],
|
|
"recommended_shift_rpm": 8400.0,
|
|
"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
|
|
```
|
|
|
|
## License
|
|
|
|
Private project — not yet licensed for distribution.
|