Open-source, local-first sim racing telemetry pipeline for EA F1 25. Captures UDP telemetry from the game, streams it through Kafka into TimescaleDB, and visualises everything in Grafana — all running locally via Docker Compose.
Where this is heading: Domain microservices will consume Kafka streams for real-time analysis — tyres, engine, strategy, driver performance — just like the specialist roles on a real F1 pit wall. An LLM-powered AI Race Engineer orchestrates sub-agents for each domain, synthesising their insights into live strategy calls, tyre recommendations, and post-session debriefs over your pit wall.
- Real-time telemetry — speed, throttle, brake, gear, RPM, DRS, tyre temps, G-forces at up to 60 Hz
- 13 Grafana dashboards — 6 live + 7 post-session analysis views, all pre-provisioned
- High-throughput ingestion — Npgsql COPY binary protocol for batch inserts into TimescaleDB
- Single-car or full-grid — track just the player or all 20 cars
- Zero-config local dev —
docker compose upand you're running - Fully typed pipeline — F1Game.UDP packet structs mapped to canonical C# records, serialised through Kafka, into typed DB columns
Speed, throttle/brake traces, steering, gear shifts, RPM, ERS and fuel — all updating in real time.
Four-corner tyre surface/inner temperatures, brake temps, wear, compound info, and ERS deploy mode.
Side-by-side telemetry overlay with speed, throttle, brake, gear, and steering traces plotted against lap distance, plus cumulative time delta and sector breakdown.
- Docker Desktop (or Docker Engine + Compose)
- .NET 10 SDK
- EA F1 25 with UDP telemetry enabled (Settings → Telemetry → UDP, port
20777)
cd infra
docker compose up -dThis starts Kafka (KRaft), TimescaleDB (PostgreSQL 16), and Grafana with all dashboards pre-provisioned.
dotnet run --project src/TelemetryIngesterThe service listens on UDP port 20777 and will begin processing telemetry as soon as you start a session in F1 25.
Navigate to http://localhost:3000 — dashboards are ready under the pre-provisioned folder.
| Dashboard | Description |
|---|---|
| Pit Wall Overview | Leader board, session state, weather conditions |
| Live Timing | Current lap, position, sector times |
| Race Engineer | Speed, throttle, brake, gear, RPM, DRS, tyre temps, G-forces |
| Tyre & Car Health | Tyre temperatures, wear, brake temps, fuel level |
| Driver Analysis | Throttle/brake inputs vs car outputs |
| Strategy | Fuel consumption rate, pit window, tyre age |
| Dashboard | Description |
|---|---|
| Session Debrief | Lap summary, penalties, pit stop history |
| Lap Comparison | Best vs last lap telemetry overlay |
| Tyre Degradation | Temperature and wear trends across laps |
| Driver Performance | Lap time distribution, consistency metrics |
| Strategy Retrospective | Fuel usage, compound history, stint analysis |
| Session Progress | Lap-by-lap position changes and pace |
| ERS & Fuel Review | ERS deployment modes, MGU harvesting, fuel consumption |
src/TelemetryIngester/
├── Services/ # UdpListenerService, KafkaConsumerService
├── Mapping/ # PacketMapper — F1Game.UDP → canonical events
├── Events/ # 10 canonical event records
├── Kafka/ # KafkaProducer, serialisation
├── Storage/ # TimescaleWriter (COPY binary batch inserts)
└── Configuration/ # Strongly-typed IOptions<T> classes
infra/
├── docker-compose.yml
├── timescaledb/init.sql # 8 hypertables with indexes
└── grafana/
├── dashboards/ # 13 pre-provisioned dashboards
└── provisioning/ # Datasource & dashboard config
- UdpListenerService receives raw UDP packets from F1 25
- PacketMapper decodes via F1Game.UDP and maps to canonical event records
- Events are published to Kafka (9 topics, keyed by session UID)
- KafkaConsumerService batches events (100 count / 500ms timeout)
- TimescaleWriter flushes batches to TimescaleDB using Npgsql COPY protocol
- Grafana queries TimescaleDB for live and historical visualisation
| Event | Frequency | Key Data |
|---|---|---|
| CarTelemetry | ~60 Hz | Speed, throttle, brake, gear, RPM, DRS, tyre/brake temps |
| LapData | ~60 Hz | Lap time, sectors, position, pit status |
| CarStatus | ~60 Hz | Tyre wear, compound, fuel, ERS |
| CarMotion | ~60 Hz | G-force (lateral, longitudinal, vertical) |
| SessionHistory | ~1/sec | Best lap, sector breakdown |
| Participant | ~1/5sec | Driver name, team, nationality |
| Session | ~2/sec | Track, weather, pit window |
| WeatherForecast | periodic | Forecast samples |
| FinalClassification | end of session | Final standings, points, tyre stints |
All config is in appsettings.json / appsettings.Development.json:
| Section | Key | Default | Description |
|---|---|---|---|
Telemetry |
ListenPort |
20777 |
UDP port for F1 telemetry |
Telemetry |
AllCars |
false |
Emit all 20 cars (vs player only) |
Kafka |
BootstrapServers |
localhost:9092 |
Kafka broker address |
Kafka |
GroupId |
telemetry-ingester |
Consumer group ID |
TimescaleDb |
ConnectionString |
(dev default set) | Npgsql connection string |
Ingester |
BatchSize |
100 |
Events before flush |
Ingester |
FlushIntervalMs |
500 |
Max ms between flushes |
Development defaults are pre-configured — dotnet run works immediately after docker compose up -d.
# Build
dotnet build src/TelemetryIngester
# Run tests (71 total — unit + integration)
dotnet test src/TelemetryIngester.Tests
# Run a specific test class
dotnet test src/TelemetryIngester.Tests --filter "ClassName~PacketMapperTests"
# Format
dotnet format src/TelemetryIngester- .NET 10 / C# 14 — worker service with BackgroundService pattern
- F1Game.UDP v25.1.0 — packet decoding
- Confluent.Kafka — message bus
- Npgsql — TimescaleDB writes (COPY binary protocol)
- Serilog — structured logging
- xUnit + Testcontainers — testing
- Docker Compose — Kafka (KRaft), TimescaleDB (PG 16), Grafana
- Open F1 25 → Settings → Telemetry Settings
- Set UDP Telemetry to On
- Set UDP Port to 20777
- Set UDP Send Rate to 60 Hz (recommended)
- Set UDP Broadcast Mode to the IP of the machine running the ingester (or
localhostif same machine)
This repo is the data infrastructure layer — the foundation for a larger AI-powered race engineer system.
The next phase introduces dedicated microservices that consume Kafka streams and perform real-time processing, analysis, and aggregation for specific domains:
- Tyre Service — degradation modelling, optimal pit window prediction, compound performance comparison
- Engine & ERS Service — fuel consumption forecasting, ERS deployment strategy, MGU-K/H harvesting efficiency
- Strategy Service — undercut/overcut analysis, gap management, weather-adjusted race planning
- Driver Performance Service — consistency metrics, braking point analysis, corner-by-corner benchmarking
Each service reads from the relevant Kafka topics, maintains its own domain state, and writes enriched insights back to the database.
An AI Race Engineer desktop app (separate repo) will sit on top of these services — modelled on how a real F1 pit wall operates. Just like in a real team, a lead race engineer delegates to specialists: a tyre engineer monitoring degradation and grip, a performance engineer tracking driver inputs and lap consistency, a strategist running pit window scenarios, and a power unit engineer managing fuel and ERS. Each sub-agent queries its own domain service, and the lead engineer synthesises their reports into clear, actionable calls over your pit wall — when to box, which compound to take, when to push, and when to manage.
Contributions welcome. Please open an issue first to discuss what you'd like to change.


