Skip to main content

Race-Telemetry-Package

A single Python package for reading real-time telemetry from a wide range of racing and driving simulation games.

Racing sims each expose telemetry (speed, tyre temps, lap times, car position, and more) through their own UDP or shared-memory format, and every one of those formats is slightly different. Race-Telemetry-Package gives you one consistent interface for all of them, so you can build dashboards, overlays, data loggers, or motion-rig controllers without writing a separate decoder for every title.

  • One API, many games — Assetto Corsa, BeamNG.drive, the F1 series (2016–2026), the Forza series, Gran Turismo, Project CARS 2, and more.
  • UDP and shared memory support, depending on what each game offers.
  • Single-threaded or multi-threaded operation, so it fits both quick scripts and always-on applications.
  • Extensible — add support for a new game by defining its packet structure; no changes to the core package required.

Table of Contents


Quick Start

pip install RaceTelemetry
from RaceTelemetry import TelemetryManager
from RaceTelemetry.data_structures.F1_2024_struct import MetaData

# Create the manager and tell it which game protocol to expect
telemetry = TelemetryManager()
telemetry.updateMeta(MetaData)

# Start pulling packets — this blocks until data arrives
for packet, packetID, headerPacket in telemetry.GetTelemetry():
    if not packet:
        continue

    if packetID == 6:
        print("Received a car telemetry packet")

That's the whole setup for basic, single-threaded use. See Usage below for the multi-threaded version, and Supported Games for the full list of protocols and structure modules available.

Installation

Prerequisites

  • Python 3.8 or later
  • The telemetry-sending device (console or PC running the game) must be reachable on the network — either the same machine (loopback) or the same local network
  • The game must be configured to send telemetry to the correct IP and port (UDP), or to write to shared memory, depending on the title

Install from PyPI

pip install RaceTelemetry

Features

  • Single package covering multiple racing game telemetry protocols
  • Single-threaded and multi-threaded operating modes
  • Extensible packet structure system for adding new games without touching the core library
  • Real-time UDP or shared-memory reception and decoding
  • Thread-safe data storage for concurrent access from multiple worker threads

How It Works

In multi-threaded mode, the package runs three kinds of thread:

Thread Responsibility
Main thread Creates and manages the telemetry system, starts worker threads, and waits for a stop signal
Network listener thread Continuously receives UDP or shared-memory packets, decodes them using the game's protocol, and stores the latest data inCentralStorage
Worker thread(s) Your own code, which reads telemetry via read-only snapshots — you never touchCentralStorage directly

Data lives in CentralStorage, which is protected by thread-safe locking. Worker threads never get direct access to it; instead they receive a ReadOnlyStorage interface that only allows taking immutable snapshots. This means you get thread safety for free, without managing any locks yourself.

Usage

Single-Threaded Mode

A simple, blocking function that listens for packets and returns decoded telemetry as they arrive. Best for scripts and simple applications that don't need background processing.

Defined in main.py as telemetryManager.GetTelemetry():

  • Blocks until a packet is received
  • No threading overhead — the simplest way to get started

More examples: tests/Game_Specific

from RaceTelemetry import TelemetryManager
from RaceTelemetry.data_structures.F1_2024_struct import MetaData

# Initialise the manager
telemetry = TelemetryManager()

# Tell it which game's protocol to use
telemetry.updateMeta(MetaData)

# Start receiving telemetry
for packet, packetID, headerPacket in telemetry.GetTelemetry():
    if not packet:
        continue

    # Check by packet ID, if the protocol provides one
    if packetID == 6:
        pass  # Process data here

    # Or check by packet name
    packetName = packet.__name__
    if packetName == 'PacketCarTelemetryData':
        pass  # Process data here

Multi-Threaded Mode

Runs a full telemetry server with a dedicated network listener thread plus any number of worker threads, so you can process data continuously without blocking on network I/O. Best for dashboards, overlays, and long-running applications.

Defined in main.py as telemetryManager.StartTelemetry():

  • Starts a network listener thread that continuously receives data
  • Provides a thread-safe central store (CentralStorage)
  • Lets multiple worker threads process data concurrently via read-only access

More examples: tests/Game_Specific

from RaceTelemetry import TelemetryManager
from RaceTelemetry.data_structures.F1_2024_struct import MetaData

# Define a worker thread function
def my_worker_thread(worker_id: int, ro_storage, stop_event):
    while not stop_event.is_set():
        snapshot = ro_storage.snapshot()

        # Access telemetry data
        data = snapshot.get("latestData")
        if data:
            telemetry = data.get("PacketCarTelemetryData")
            if telemetry:
                pass  # Process data here

# Initialise the manager
activeThreads = TelemetryManager()

# Tell it which game's protocol to use
activeThreads.updateMeta(MetaData)

# Register one or more worker threads
activeThreads.addWorkerThread(my_worker_thread)

# Start the telemetry system — blocks until stopped
activeThreads.StartTelemetry()

API Reference

Method Parameters Description
TelemetryManager() None Creates a new telemetry manager instance, which handles network communication, data storage, and threading.
.updateMeta() MetaData (class) — see Adding Support for a New Game Applies game-specific metadata to configure packet structures, ports, and data handling.Must be called before starting telemetry.
.updateLocalIP() ip (str), e.g. "192.168.1.100", "127.0.0.1" Sets the local IP address the telemetry server listens on for incoming packets.
.updateSendIP() ip (str), e.g. "192.168.1.100", "127.0.0.1" Sets the destination IP address used for heartbeats and handshake packets.
.addWorkerThread() mainFunc (callable) with signature def worker_function(worker_id: int, ro_storage, stop_event): Registers a worker thread function to process telemetry data concurrently. Worker threads receive read-only snapshots, keeping access thread-safe.
.manualStop() target (bool) — True to stop Manually triggers a stop signal from outside the main thread or telemetry loop.
.isSharedMemory() target (bool) — True for shared memory, False for UDP Switches between UDP and shared memory as the data source. Shared memory is faster but only available on the local machine.
.setEnumMode() target (int): 0 (default) returns full enum members with name and value; 1 returns raw integer values; 2 returns enum names as strings Configures how enum fields are represented in decoded packet data.
.GetTelemetry() None Yields telemetry packets one at a time, forsingle-threaded use.
.StartTelemetry() None Starts the telemetry system with all configured settings, creating the network listener and any registered worker threads.Blocks until a stop signal is received (Ctrl+C or .manualStop()).

Adding Support for a New Game

Support for a new game is added by defining its packet structure — no changes to the core package are needed.

Step 1: Define the Packet Structure

from enum import Enum
# Swap depending on the data types the game's protocol uses
import ctypes

class DataTypes:
    STRUCTURE = ctypes.LittleEndianStructure
    UNION = ctypes.Union

    SIGNED_INT8 = ctypes.c_int8
    SIGNED_INT16 = ctypes.c_int16

    UNSIGNED_INT8 = ctypes.c_uint8
    UNSIGNED_INT16 = ctypes.c_uint16

    FLOAT = ctypes.c_float
    CHAR = ctypes.c_char

# Define a header packet, if the protocol uses one
class PacketHeader(DataTypes.STRUCTURE):
    _pack_ = 1  # May be required depending on the game
    _fields_ = [
        ("m_packetFormat",              DataTypes.UNSIGNED_INT16),
        ("m_gameYear",                  DataTypes.UNSIGNED_INT8),
        # ...
    ]

# Define any sub-packets
class CarMotionData(DataTypes.STRUCTURE):
    # _pack_ = 1  # May be required depending on the game
    _fields_ = [
        ("m_worldPositionX",        DataTypes.FLOAT),
        ("m_worldVelocityX",        DataTypes.FLOAT),
        # ...
    ]

# Define a main packet
class PacketMotionData(DataTypes.STRUCTURE):
    _pack_ = 1  # May be required depending on the game
    _fields_ = [
        ("m_header",        PacketHeader),          # Header
        ("m_carMotionData", CarMotionData * 22),     # Data for all cars on track
    ]

Step 2: Set Up Enums (Optional)

Create enum classes for any fields that have a defined set of values.

from enum import Enum, IntEnum, StrEnum, Flag

class SessionType(IntEnum):
    UNKNOWN = 0
    PRACTICE = 1
    QUALIFYING = 2
    RACE = 3

class Gear(IntEnum):
    NEUTRAL = 0
    FIRST = 1
    SECOND = 2

class TelemetryData(DataTypes.STRUCTURE):
    # Map each enum type to the field names it applies to
    _enums_: dict[type, tuple[str, ...]] = {
        SessionType: ("session",),
        Gear: ("current_gear", "recommended_gear"),
    }
    _fields_ = [
        ("speed",               DataTypes.UNSIGNED_INT8),
        ("current_gear",        DataTypes.UNSIGNED_INT8),
        ("recommended_gear",    DataTypes.UNSIGNED_INT8),
        # ...
    ]

Before starting telemetry, choose how enum fields should be returned:

activeThreads = TelemetryManager()
activeThreads.updateMeta(MetaData)
activeThreads.addWorkerThread(displayTime)

# Mode 0 (default): return full enum members, e.g. <AC_STATUS.AC_PAUSE: 3>
activeThreads.setEnumMode(0)

# Mode 1: return raw values, e.g. 3
activeThreads.setEnumMode(1)

# Mode 2: return the enum name as a string, e.g. 'AC_PAUSE'
activeThreads.setEnumMode(2)

activeThreads.StartTelemetry()

Step 3: Define the MetaData Class

Field Type Description
port int UDP port the data is received on
heartBeatPort int UDP port to send a heartbeat to
heartBeatFunc function Heartbeat function
handShakePort int UDP port to send a handshake to
handShakeFunc tuple[function, function] Start and stop handshake functions
decryptionFunc function Data decryption function
headerInfo type The header struct class, if the protocol uses one
packetIDAttribute str The header packet attribute that identifies the packet ID
sharedMemoryName str or dict[str, str] Name of the shared memory segment, or a dictionary mapping packet name to segment name
packetInfo dict[int, tuple[type, ...]] Game packet mapping — see below

PacketInfo

A dictionary where:

  • key — the packet ID, or 0 if the protocol doesn't use one
  • value — a tuple of packet structure class(es) associated with that ID

Standard mapping:

packetInfo = {
    0: (PacketMotionData,),
    1: (PacketSessionData,),
    # ...
}

One packet ID mapping to multiple packet types:

packetInfo = {
    0: (TelemetryData,),
    7: (TimeStatsData,),
    8: (VehicleClassNamesData, ParticipantVehicleNamesData),
    # ...
}

Protocols with no packet ID:

packetInfo = {
    0: (PacketAData, PacketBData, PacketTildaData, PacketCData),
    # ...
}

Full MetaData Example

class MetaData:
    # Standard network info
    port: int | None = 20777  # UDP port for your game

    # Only needed if the game expects a heartbeat
    heartBeatPort: int | None = 33739
    heartBeatFunc = heartBeat

    # Only needed for an initial handshake
    handShakePort: int | None = None
    handShakeFunc: tuple | None = None  # (startHandShakeFunc, stopHandShakeFunc)

    # Only needed if the data requires decryption
    decryptionFunc = decrypt_data

    # Only needed if the protocol uses a header packet
    headerInfo: type | None = PacketHeader
    packetIDAttribute: str = "m_packetId"

    # Only needed for shared memory
    sharedMemoryName: str | None | dict[str, str] = "Local\\SCSTelemetry"

    # Standard packet mapping
    packetInfo: dict[int, tuple[type, ...]] = {
        0: (PacketMotionData,),  # Packet ID: (packet_class,)
        # Add more packet types as needed
    }

Step 4: Import and Use It

from RaceTelemetry import TelemetryManager
from your_game_struct import MetaData

# Setup, works for both modes
activeThreads = TelemetryManager()
activeThreads.updateMeta(MetaData)

# Single-threaded use
for packet, packetID, headerPacket in activeThreads.GetTelemetry():
    if not packet:
        continue

# Or multi-threaded use
activeThreads.addWorkerThread(your_worker_function)
activeThreads.StartTelemetry()

Step 5: Check Packet Decoding

Decoding is driven entirely by the packetInfo dictionary you defined. If packets aren't decoding correctly, check:

  • Packet sizes match the protocol spec exactly (use _pack_ = 1 for correct byte alignment)
  • Packet IDs correspond to the correct packet types
  • All nested structures are fully and correctly defined

Supported Games

UDP

Game Status
Assetto Corsa ✅
BeamNG.drive ✅
Dirt 4 ⚠️ Untested
Dirt Rally ⚠️ Untested
F1 2016 ⚠️ Untested
F1 2017 ✅
F1 2018 ✅
F1 2019 ✅
F1 2020 ✅
F1 2021 ✅
F1 2022 ✅
F1 2023 ✅
F1 2024 ✅
F1 2025 ⚠️ Untested
F1 2026 (2025 DLC) ⚠️ Untested
Forza Horizon 4 ✅
Forza Horizon 5 ✅
Forza Horizon 6 ✅
Forza Motorsport 7 ⚠️ Untested
Forza Motorsport 8 ✅
Gran Turismo 7 ✅
Project CARS ✅
Project CARS 2 ✅

Shared Memory

Game Status
Assetto Corsa ✅
Assetto Corsa Competizione ⚠️ Untested
Assetto Corsa EVO ⚠️ Untested
Euro Truck Simulator 2 ✅
Project CARS ⚠️ Untested

Don't see your game listed? See Adding Support for a New Game — contributions of new packet structures are welcome.

Troubleshooting

  • No data arriving? Check that the game is actually configured to send telemetry, and that no other running game is using the same port. On Xbox, a game left in Quick Resume can hold onto a port (this has been seen with Forza Horizon 5 and Forza Motorsport 8).
  • Wrong IP? Double-check the local and destination IP addresses configured on both the game and in your script.
  • Still nothing? Use a packet capture tool such as Wireshark, filtering on UDP, the relevant port, and the incoming/source IP, to confirm data is actually reaching your machine.
  • Firewall blocking traffic? Make sure your firewall allows inbound UDP traffic on the configured port.

Game-Specific Notes

  • Forza (Microsoft Store versions): loopback needs to be configured correctly — see forza debug.txt in the supporting docs.
  • Euro Truck Simulator 2: requires the scs-sdk-plugin to be installed in the game's plugins folder — see the supporting docs for details.

Official Documents

Document Covers
ACSharedMemoryDocumentation.pdf Assetto Corsa shared memory (official)
ACRemoteTelemetryDocumentation.pdf Assetto Corsa UDP remote telemetry (official)
ACCSharedMemoryDocumentationV1.8.12.pdf Assetto Corsa Competizione shared memory, v1.8.12 (official)
ACE_SharedFileOut_Documentation_V1.pdf Assetto Corsa EVO shared memory, v1 (official)
Data Output from F1 22 v16.docx F1 2022 packet structures, v16 (official)
Data Output from F1 23 v29x3.docx F1 2023 packet structures, v29x3 (official)
Data Output from F1 24 v27.2x.docx F1 2024 packet structures, v27.2x (official)
Data Output from F1 25 v3.pdf F1 2025 packet structures, v3 (official)
Data Output from F1 25 2026 Season Pack.pdf F1 2026 packet structures (official)

More debugging guides live in Supporting_Docs/, including forza debug.txt for Forza loopback setup.

Game Link Notes
Assetto Corsa (UDP) AC Remote Telemetry Documentation Official Link
Assetto Corsa (UDP) AC UDP Remote Telemetry PDF Download
Assetto Corsa (shared memory) Shared Memory Reference
Assetto Corsa Competizione ACC Shared Memory Documentation
Assetto Corsa EVO Shared Memory API Documentation
Assetto Corsa EVO ACE_SharedFileOut_Documentation_v1
BeamNG.drive Protocols Official Link
Dirt 4 Setting up UDP output
Dirt 4 Configuring UDP Output
Dirt Rally UDP Telemetry
Euro Truck Simulator 2 scs-sdk-plugin on GitHub Including installation instructions
F1 2016 D-Box and UDP Telemetry Information
F1 2017 D-Box and UDP Output Specification
F1 2018 UDP Specification
F1 2019 UDP Specification
F1 2020 UDP Specification
F1 2021 UDP Specification Dead download link
F1 2021 raweceek-telemetry/f1-2021-udp
F1 2022 UDP Specification Official Link
F1 2023 UDP Specification Official Link
F1 2024 UDP Specification Official Link
F1 2025 / F1 2026 2026 Season Pack UDP Specification Official Link
Forza Horizon 4 Forza-data-tools on GitHub
Forza Horizon 5 Data Out format Pastebin Link
Forza Horizon 6 Data Out Documentation Official Link
Forza Motorsport 7 Data Out feature details
Forza Motorsport 8 Data Out Documentation Structure and Car ID
Forza Motorsport 8 Data Out Documentation - Data Out Documentation Archive Just output structure
Gran Turismo 7 gt7-udp on GitHub
Project CARS (UDP) Companion App UDP Streaming
Project CARS (shared memory) Shared Memory API discussion
Project CARS 2 project-cars-2-udp on GitHub

Other Titles

Game Link Notes
EA Sports WRC 2023 How to use UDP on PC
Le Mans Ultimate Telemetry Socket – JSON Telemetry Plugin
RaceRoom Shared Memory API
iRacing pyirsdk on GitHub currently unsupported due to dynamic packet structure
Richard Burns Rally rbr-udp-telem on GitHub
KartKraft kartkraft-telemetry schema on GitHub
Project CARS 3 likely shares a protocol with Project CARS 2, not yet confirmed
MotoGP 18 MotoGP-18-UDP-Telemetry still missing some data

Contributing

Contributions are welcome, whether that's a bug fix, a new packet structure for an unsupported game, or an improvement to these docs.

  • Found a bug? Open an issue describing what you expected to happen, what actually happened, and which game/protocol you were using.
  • Want to add a game? Follow the steps in Adding Support for a New Game and open a pull request — please include a short test under tests/Game_Specific if you can.
  • Improving sources? PRs to this README or the files in Supporting_Docs/ are welcome.

Metadata

Release files for RaceTelemetry 4.7.8.post1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for RaceTelemetry 4.7.8.post1
File Size Uploaded
racetelemetry-4.7.8.post1.tar.gz 221.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for RaceTelemetry 4.7.8.post1
File Interpreter ABI Platform
racetelemetry-4.7.8.post1-py3-none-any.whl Python 3 none any Details

Total release size: 442.3 kB

Release files / racetelemetry-4.7.8.post1.tar.gz

Download URL racetelemetry-4.7.8.post1.tar.gz
Size 221.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f93455b35c692f9163eef8922332a799f0cb570896efef27025ac4bc2069d67f
BLAKE2b-256 checksum
How to use checksums
bf928b0343255655be0bd7b8b37b3deee3785f30027b5cf2cd12583193f3af34
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / racetelemetry-4.7.8.post1-py3-none-any.whl

Download URL racetelemetry-4.7.8.post1-py3-none-any.whl
Size 220.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7e1a20372fd0121e795c7c9e1015ef6d742fdd1742c31577c4e90d42c2ad81d
BLAKE2b-256 checksum
How to use checksums
3d6d65c9da2328c90ead5d8a90e1a4ebba43553d096b86b21d9e89e4956f7e67
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page