Skip to content

Instantly share code, notes, and snippets.

@chrishannam
Created July 13, 2021 20:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrishannam/15911744edc7de963d458cb8c570aaf9 to your computer and use it in GitHub Desktop.
Save chrishannam/15911744edc7de963d458cb8c570aaf9 to your computer and use it in GitHub Desktop.
import ctypes
import json
import numbers
import socket
import logging
from cassandra.telemetry.constants import PACKET_MAPPER
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
class Feed:
def __init__(self, port: int = None):
if not port:
port = 20777
self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.socket.bind(("", port))
def get_latest(self):
packet = self.socket.recv(2048)
header = PacketHeader.from_buffer_copy(packet)
key = (header.m_packet_format, header.m_packet_version, header.m_packet_id)
HeaderFieldsToPacketType = {
(2021, 1, 0): PacketMotionData,
(2021, 1, 1): PacketSessionData,
(2021, 1, 2): PacketLapData,
(2021, 1, 3): PacketEventData,
(2021, 1, 4): PacketParticipantsData,
(2021, 1, 5): PacketCarSetupData,
(2021, 1, 6): PacketCarTelemetryData,
(2021, 1, 7): PacketCarStatusData,
(2021, 1, 8): PacketFinalClassificationData,
(2021, 1, 9): PacketLobbyInfoData,
(2021, 1, 10): PacketCarDamageData,
(2021, 1, 11): PacketSessionHistoryData,
}
packet_type = HeaderFieldsToPacketType[key]
data = packet_type.unpack(packet)
return data.to_dict()
def to_json(*args, **kwargs):
kwargs.setdefault('indent', 2)
kwargs['sort_keys'] = True
kwargs['ensure_ascii'] = False
kwargs['separators'] = (',', ': ')
return json.dumps(*args, **kwargs)
class PacketMixin(object):
"""A base set of helper methods for ctypes based packets
"""
def get_value(self, field):
"""Returns the field's value and formats the types value
"""
return self._format_type(getattr(self, field))
def pack(self):
"""Packs the current data structure into a compressed binary
Returns:
(bytes):
- The packed binary
"""
return bytes(self)
@classmethod
def size(cls):
return ctypes.sizeof(cls)
@classmethod
def unpack(cls, buffer):
"""Attempts to unpack the binary structure into a python structure
Args:
buffer (bytes):
- The encoded buffer to decode
"""
return cls.from_buffer_copy(buffer)
def to_dict(self):
"""Returns a ``dict`` with key-values derived from _fields_
"""
return {k: self.get_value(k) for k, _ in self._fields_}
def to_json(self):
"""Returns a ``str`` of sorted JSON derived from _fields_
"""
return to_json(self.to_dict())
def _format_type(self, value):
"""A type helper to format values
"""
class_name = type(value).__name__
if class_name == 'float':
return round(value, 3)
if class_name == 'bytes':
return value.decode()
if isinstance(value, ctypes.Array):
return self._format_array_type(value)
if hasattr(value, 'to_dict'):
return value.to_dict()
return value
def _format_array_type(self, value):
results = []
for item in value:
if isinstance(item, Packet):
results.append(item.to_dict())
else:
results.append(item)
return results
class Packet(ctypes.LittleEndianStructure, PacketMixin):
"""The base packet class for API version 2021
"""
_pack_ = 1
def __repr__(self):
return self.to_json()
class PacketHeader(Packet):
_fields_ = [
('m_packet_format', ctypes.c_uint16), # 2021
('m_game_major_version', ctypes.c_uint8), # Game major version - "X.00"
('m_game_minor_version', ctypes.c_uint8), # Game minor version - "1.XX"
('m_packet_version', ctypes.c_uint8),
# Version of this packet type, all start from 1
('m_packet_id', ctypes.c_uint8), # Identifier for the packet type, see below
('m_session_uid', ctypes.c_uint64), # Unique identifier for the session
('m_session_time', ctypes.c_float), # Session timestamp
('m_frame_identifier', ctypes.c_uint32),
# Identifier for the frame the data was retrieved on
('m_player_car_index', ctypes.c_uint8), # Index of player's car in the array
('m_secondary_player_car_index', ctypes.c_uint8),
# Index of secondary player's car in the array (splitscreen)
# 255 if no second player
]
class CarMotionData(Packet):
_fields_ = [
('m_world_position_x', ctypes.c_float), # World space X position
('m_world_position_y', ctypes.c_float), # World space Y position
('m_world_position_z', ctypes.c_float), # World space Z position
('m_world_velocity_x', ctypes.c_float), # Velocity in world space X
('m_world_velocity_y', ctypes.c_float), # Velocity in world space Y
('m_world_velocity_z', ctypes.c_float), # Velocity in world space Z
('m_world_forward_dir_x', ctypes.c_int16), # World space forward X direction
# (normalised)a
('m_world_forward_dir_y', ctypes.c_int16),
# World space forward Y direction (normalised)
('m_world_forward_dir_z', ctypes.c_int16),
# World space forward Z direction (normalised)
('m_world_right_dir_x', ctypes.c_int16),
# World space right X direction (normalised)
('m_world_right_dir_y', ctypes.c_int16),
# World space right Y direction (normalised)
('m_world_right_dir_z', ctypes.c_int16),
# World space right Z direction (normalised)
('m_g_force_lateral', ctypes.c_float), # Lateral G-Force component
('m_g_force_longitudinal', ctypes.c_float), # Longitudinal G-Force component
('m_g_force_vertical', ctypes.c_float), # Vertical G-Force component
('m_yaw', ctypes.c_float), # Yaw angle in radians
('m_pitch', ctypes.c_float), # Pitch angle in radians
('m_roll', ctypes.c_float), # Roll angle in radians
]
class PacketMotionData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_motion_data', CarMotionData * 22), # Data for all cars on track
# Extra player car ONLY data
('m_suspension_position', ctypes.c_float * 4),
# Note: All wheel arrays have the following order:
('m_suspension_velocity', ctypes.c_float * 4), # RL, RR, FL, FR
('m_suspension_acceleration', ctypes.c_float * 4), # RL, RR, FL, FR
('m_wheel_speed', ctypes.c_float * 4), # Speed of each wheel
('m_wheel_slip', ctypes.c_float * 4), # Slip ratio for each wheel
('m_local_velocity_x', ctypes.c_float), # Velocity in local space
('m_local_velocity_y', ctypes.c_float), # Velocity in local space
('m_local_velocity_z', ctypes.c_float), # Velocity in local space
('m_angular_velocity_x', ctypes.c_float), # Angular velocity x-component
('m_angular_velocity_y', ctypes.c_float), # Angular velocity y-component
('m_angular_velocity_z', ctypes.c_float), # Angular velocity z-component
('m_angular_acceleration_x', ctypes.c_float), # Angular velocity x-component
('m_angular_acceleration_y', ctypes.c_float), # Angular velocity y-component
('m_angular_acceleration_z', ctypes.c_float), # Angular velocity z-component
('m_front_wheels_angle', ctypes.c_float),
# Current front wheels angle in radians
]
class MarshalZone(Packet):
_fields_ = [
('m_zone_start', ctypes.c_float),
# Fraction (0..1) of way through the lap the marshal zone starts
('m_zone_flag', ctypes.c_int8),
# -1 = invalid/unknown, 0 = none, 1 = green, 2 = blue, 3 = yellow, 4 = red
]
class WeatherForecastSample(Packet):
_fields_ = [
('m_session_type', ctypes.c_uint8),
# 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1
# 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2
# 12 = Time Trial
('m_time_offset', ctypes.c_uint8), # Time in minutes the forecast is for
('m_weather', ctypes.c_uint8),
# Weather - 0 = clear, 1 = light cloud, 2 = overcast
# 3 = light rain, 4 = heavy rain, 5 = storm
('m_track_temperature', ctypes.c_int8), # Track temp. in degrees Celsius
('m_track_temperature_change', ctypes.c_int8),
# Track temp. change – 0 = up, 1 = down, 2 = no change
('m_air_temperature', ctypes.c_int8), # Air temp. in degrees celsius
('m_air_temperature_change', ctypes.c_int8),
# Air temp. change – 0 = up, 1 = down, 2 = no change
('m_rain_percentage', ctypes.c_uint8), # Rain percentage (0-100)
]
class PacketSessionData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_weather', ctypes.c_uint8),
# Weather - 0 = clear, 1 = light cloud, 2 = overcast
# 3 = light rain, 4 = heavy rain, 5 = storm
('m_track_temperature', ctypes.c_int8), # Track temp. in degrees celsius
('m_air_temperature', ctypes.c_int8), # Air temp. in degrees celsius
('m_total_laps', ctypes.c_uint8), # Total number of laps in this race
('m_track_length', ctypes.c_uint16), # Track length in metres
('m_session_type', ctypes.c_uint8),
# 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P
# 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ
# 10 = R, 11 = R2, 12 = R3, 13 = Time Trial
('m_track_id', ctypes.c_int8), # -1 for unknown, 0-21 for tracks, see appendix
('m_formula', ctypes.c_uint8),
# Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2,
# 3 = F1 Generic
('m_session_time_left', ctypes.c_uint16), # Time left in session in seconds
('m_session_duration', ctypes.c_uint16), # Session duration in seconds
('m_pit_speed_limit', ctypes.c_uint8), # Pit speed limit in kilometres per hour
('m_game_paused', ctypes.c_uint8), # Whether the game is paused
('m_is_spectating', ctypes.c_uint8), # Whether the player is spectating
('m_spectator_car_index', ctypes.c_uint8), # Index of the car being spectated
('m_sli_pro_native_support', ctypes.c_uint8),
# SLI Pro support, 0 = inactive, 1 = active
('m_num_marshal_zones', ctypes.c_uint8), # Number of marshal zones to follow
('m_marshal_zones', MarshalZone * 21), # List of marshal zones – max 21
('m_safety_car_status', ctypes.c_uint8), # 0 = no safety car, 1 = full
# 2 = virtual, 3 = formation lap
('m_network_game', ctypes.c_uint8), # 0 = offline, 1 = online
('m_num_weather_forecast_samples', ctypes.c_uint8),
# Number of weather samples to follow
('m_weather_forecast_samples', WeatherForecastSample * 56),
# Array of weather forecast samples
('m_forecast_accuracy', ctypes.c_uint8), # 0 = Perfect, 1 = Approximate
('m_ai_difficulty', ctypes.c_uint8), # AI Difficulty rating – 0-110
('m_season_link_identifier', ctypes.c_uint32),
# Identifier for season - persists across saves
('m_weekend_link_identifier', ctypes.c_uint32),
# Identifier for weekend - persists across saves
('m_session_link_identifier', ctypes.c_uint32),
# Identifier for session - persists across saves
('m_pit_stop_window_ideal_lap', ctypes.c_uint8),
# Ideal lap to pit on for current strategy (player)
('m_pit_stop_window_latest_lap', ctypes.c_uint8),
# Latest lap to pit on for current strategy (player)
('m_pit_stop_rejoin_position', ctypes.c_uint8),
# Predicted position to rejoin at (player)
('m_steering_assist', ctypes.c_uint8), # 0 = off, 1 = on
('m_braking_assist', ctypes.c_uint8), # 0 = off, 1 = low, 2 = medium, 3 = high
('m_gearbox_assist', ctypes.c_uint8),
# 1 = manual, 2 = manual & suggested gear, 3 = auto
('m_pit_assist', ctypes.c_uint8), # 0 = off, 1 = on
('m_pit_release_assist', ctypes.c_uint8), # 0 = off, 1 = on
('m_ersassist', ctypes.c_uint8), # 0 = off, 1 = on
('m_drsassist', ctypes.c_uint8), # 0 = off, 1 = on
('m_dynamic_racing_line', ctypes.c_uint8),
# 0 = off, 1 = corners only, 2 = full
('m_dynamic_racing_line_type', ctypes.c_uint8), # 0 = 2D, 1 = 3D
]
class LapData(Packet):
_fields_ = [
('m_last_lap_time_in_ms', ctypes.c_uint32), # Last lap time in milliseconds
('m_current_lap_time_in_ms', ctypes.c_uint32),
# Current time around the lap in milliseconds
('m_sector1_time_in_ms', ctypes.c_uint16), # Sector 1 time in milliseconds
('m_sector2_time_in_ms', ctypes.c_uint16), # Sector 2 time in milliseconds
('m_lap_distance', ctypes.c_float),
# Distance vehicle is around current lap in metres – could
# be negative if line hasn’t been crossed yet
('m_total_distance', ctypes.c_float),
# Total distance travelled in session in metres – could
# be negative if line hasn’t been crossed yet
('m_safety_car_delta', ctypes.c_float), # Delta in seconds for safety car
('m_car_position', ctypes.c_uint8), # Car race position
('m_current_lap_num', ctypes.c_uint8), # Current lap number
('m_pit_status', ctypes.c_uint8), # 0 = none, 1 = pitting, 2 = in pit area
('m_num_pit_stops', ctypes.c_uint8), # Number of pit stops taken in this race
('m_sector', ctypes.c_uint8), # 0 = sector1, 1 = sector2, 2 = sector3
('m_current_lap_invalid', ctypes.c_uint8),
# Current lap invalid - 0 = valid, 1 = invalid
('m_penalties', ctypes.c_uint8),
# Accumulated time penalties in seconds to be added
('m_warnings', ctypes.c_uint8), # Accumulated number of warnings issued
('m_num_unserved_drive_through_pens', ctypes.c_uint8),
# Num drive through pens left to serve
('m_num_unserved_stop_go_pens', ctypes.c_uint8),
# Num stop go pens left to serve
('m_grid_position', ctypes.c_uint8),
# Grid position the vehicle started the race in
('m_driver_status', ctypes.c_uint8),
# Status of driver - 0 = in garage, 1 = flying lap
# 2 = in lap, 3 = out lap, 4 = on track
('m_result_status', ctypes.c_uint8),
# Result status - 0 = invalid, 1 = inactive, 2 = active
# 3 = finished, 4 = didnotfinish, 5 = disqualified
# 6 = not classified, 7 = retired
('m_pit_lane_timer_active', ctypes.c_uint8),
# Pit lane timing, 0 = inactive, 1 = active
('m_pit_lane_time_in_lane_in_ms', ctypes.c_uint16),
# If active, the current time spent in the pit lane in ms
('m_pit_stop_timer_in_ms', ctypes.c_uint16),
# Time of the actual pit stop in ms
('m_pit_stop_should_serve_pen', ctypes.c_uint8),
# Whether the car should serve a penalty at this stop
]
class PacketLapData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_lap_data', LapData * 22), # Lap data for all cars on track
]
class FastestLap(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8), # Vehicle index of car achieving fastest lap
('lap_time', ctypes.c_float), # Lap time is in seconds
]
class Retirement(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8), # Vehicle index of car retiring
]
class TeamMateInPits(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8), # Vehicle index of team mate
]
class RaceWinner(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8), # Vehicle index of the race winner
]
class Penalty(Packet):
_fields_ = [
('penalty_type', ctypes.c_uint8), # Penalty type – see Appendices
('infringement_type', ctypes.c_uint8), # Infringement type – see Appendices
('vehicle_idx', ctypes.c_uint8),
# Vehicle index of the car the penalty is applied to
('other_vehicle_idx', ctypes.c_uint8),
# Vehicle index of the other car involved
('time', ctypes.c_uint8), # Time gained, or time spent doing action in seconds
('lap_num', ctypes.c_uint8), # Lap the penalty occurred on
('places_gained', ctypes.c_uint8), # Number of places gained by this
]
class SpeedTrap(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8),
# Vehicle index of the vehicle triggering speed trap
('speed', ctypes.c_float), # Top speed achieved in kilometres per hour
('overall_fastest_in_session', ctypes.c_uint8),
# Overall fastest speed in session = 1, otherwise 0
('driver_fastest_in_session', ctypes.c_uint8),
# Fastest speed for driver in session = 1, otherwise 0
]
class StartLIghts(Packet):
_fields_ = [
('num_lights', ctypes.c_uint8), # Number of lights showing
]
class DriveThroughPenaltyServed(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8),
# Vehicle index of the vehicle serving drive through
]
class StopGoPenaltyServed(Packet):
_fields_ = [
('vehicle_idx', ctypes.c_uint8), # Vehicle index of the vehicle serving stop go
]
class Flashback(Packet):
_fields_ = [
('flashback_frame_identifier', ctypes.c_uint32),
# Frame identifier flashed back to
('flashback_session_time', ctypes.c_float), # Session time flashed back to
]
class Buttons(Packet):
_fields_ = [
('m_button_status', ctypes.c_uint32),
# Bit flags specifying which buttons are being pressed
# currently - see appendices
]
class EventDataDetails(ctypes.Union, PacketMixin):
_fields_ = [
('fastest_lap', FastestLap),
('retirement', Retirement),
('team_mate_in_pits', TeamMateInPits),
('race_winner', RaceWinner),
('penalty', Penalty),
('speed_trap', SpeedTrap),
('start_lights', StartLIghts),
('drive_through_penalty_served', DriveThroughPenaltyServed),
('stop_go_penalty_served', StopGoPenaltyServed),
('flashback', Flashback),
('buttons', Buttons),
]
class PacketEventData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_event_string_code', ctypes.c_uint8 * 4), # Event string code, see below
('m_event_details', EventDataDetails),
# Event details - should be interpreted differently
# for each type
]
class ParticipantData(Packet):
_fields_ = [
('m_ai_controlled', ctypes.c_uint8),
# Whether the vehicle is AI (1) or Human (0) controlled
('m_driver_id', ctypes.c_uint8),
# Driver id - see appendix, 255 if network human
('m_network_id', ctypes.c_uint8),
# Network id – unique identifier for network players
('m_team_id', ctypes.c_uint8), # Team id - see appendix
('m_my_team', ctypes.c_uint8), # My team flag – 1 = My Team, 0 = otherwise
('m_race_number', ctypes.c_uint8), # Race number of the car
('m_nationality', ctypes.c_uint8), # Nationality of the driver
('m_name', ctypes.c_char * 48),
# Name of participant in UTF-8 format – null terminated
# Will be truncated with … (U+2026) if too long
('m_your_telemetry', ctypes.c_uint8),
# The player's UDP setting, 0 = restricted, 1 = public
]
class PacketParticipantsData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_num_active_cars', ctypes.c_uint8),
# Number of active cars in the data – should match number of
# cars on HUD
('m_participants', ParticipantData * 22),
]
class CarSetupData(Packet):
_fields_ = [
('m_front_wing', ctypes.c_uint8), # Front wing aero
('m_rear_wing', ctypes.c_uint8), # Rear wing aero
('m_on_throttle', ctypes.c_uint8),
# Differential adjustment on throttle (percentage)
('m_off_throttle', ctypes.c_uint8),
# Differential adjustment off throttle (percentage)
('m_front_camber', ctypes.c_float), # Front camber angle (suspension geometry)
('m_rear_camber', ctypes.c_float), # Rear camber angle (suspension geometry)
('m_front_toe', ctypes.c_float), # Front toe angle (suspension geometry)
('m_rear_toe', ctypes.c_float), # Rear toe angle (suspension geometry)
('m_front_suspension', ctypes.c_uint8), # Front suspension
('m_rear_suspension', ctypes.c_uint8), # Rear suspension
('m_front_anti_roll_bar', ctypes.c_uint8), # Front anti-roll bar
('m_rear_anti_roll_bar', ctypes.c_uint8), # Front anti-roll bar
('m_front_suspension_height', ctypes.c_uint8), # Front ride height
('m_rear_suspension_height', ctypes.c_uint8), # Rear ride height
('m_brake_pressure', ctypes.c_uint8), # Brake pressure (percentage)
('m_brake_bias', ctypes.c_uint8), # Brake bias (percentage)
('m_rear_left_tyre_pressure', ctypes.c_float), # Rear left tyre pressure (PSI)
('m_rear_right_tyre_pressure', ctypes.c_float),
# Rear right tyre pressure (PSI)
('m_front_left_tyre_pressure', ctypes.c_float),
# Front left tyre pressure (PSI)
('m_front_right_tyre_pressure', ctypes.c_float),
# Front right tyre pressure (PSI)
('m_ballast', ctypes.c_uint8), # Ballast
('m_fuel_load', ctypes.c_float), # Fuel load
]
class PacketCarSetupData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_setups', CarSetupData * 22),
]
class CarTelemetryData(Packet):
_fields_ = [
('m_speed', ctypes.c_uint16), # Speed of car in kilometres per hour
('m_throttle', ctypes.c_float), # Amount of throttle applied (0.0 to 1.0)
('m_steer', ctypes.c_float),
# Steering (-1.0 (full lock left) to 1.0 (full lock right))
('m_brake', ctypes.c_float), # Amount of brake applied (0.0 to 1.0)
('m_clutch', ctypes.c_uint8), # Amount of clutch applied (0 to 100)
('m_gear', ctypes.c_int8), # Gear selected (1-8, N=0, R=-1)
('m_engine_rpm', ctypes.c_uint16), # Engine RPM
('m_drs', ctypes.c_uint8), # 0 = off, 1 = on
('m_rev_lights_percent', ctypes.c_uint8), # Rev lights indicator (percentage)
('m_rev_lights_bit_value', ctypes.c_uint16),
# Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED)
('m_brakes_temperature', ctypes.c_uint16 * 4), # Brakes temperature (celsius)
('m_tyres_surface_temperature', ctypes.c_uint8 * 4),
# Tyres surface temperature (celsius)
('m_tyres_inner_temperature', ctypes.c_uint8 * 4),
# Tyres inner temperature (celsius)
('m_engine_temperature', ctypes.c_uint16), # Engine temperature (celsius)
('m_tyres_pressure', ctypes.c_float * 4), # Tyres pressure (PSI)
('m_surface_type', ctypes.c_uint8 * 4), # Driving surface, see appendices
]
class PacketCarTelemetryData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_telemetry_data', CarTelemetryData * 22),
('m_mfd_panel_index', ctypes.c_uint8),
# Index of MFD panel open - 255 = MFD closed
# Single player, race – 0 = Car setup, 1 = Pits
# 2 = Damage, 3 = Engine, 4 = Temperatures
# May vary depending on game mode
('m_mfd_panel_index_secondary_player', ctypes.c_uint8), # See above
('m_suggested_gear', ctypes.c_int8), # Suggested gear for the player (1-8)
# 0 if no gear suggested
]
class CarStatusData(Packet):
_fields_ = [
('m_traction_control', ctypes.c_uint8),
# Traction control - 0 = off, 1 = medium, 2 = full
('m_anti_lock_brakes', ctypes.c_uint8), # 0 (off) - 1 (on)
('m_fuel_mix', ctypes.c_uint8),
# Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max
('m_front_brake_bias', ctypes.c_uint8), # Front brake bias (percentage)
('m_pit_limiter_status', ctypes.c_uint8),
# Pit limiter status - 0 = off, 1 = on
('m_fuel_in_tank', ctypes.c_float), # Current fuel mass
('m_fuel_capacity', ctypes.c_float), # Fuel capacity
('m_fuel_remaining_laps', ctypes.c_float),
# Fuel remaining in terms of laps (value on MFD)
('m_max_rpm', ctypes.c_uint16), # Cars max RPM, point of rev limiter
('m_idle_rpm', ctypes.c_uint16), # Cars idle RPM
('m_max_gears', ctypes.c_uint8), # Maximum number of gears
('m_drs_allowed', ctypes.c_uint8), # 0 = not allowed, 1 = allowed
('m_drs_activation_distance', ctypes.c_uint16),
# 0 = DRS not available, non-zero - DRS will be available
# in [X] metres
('m_actual_tyre_compound', ctypes.c_uint8),
# F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1
# 7 = inter, 8 = wet
# F1 Classic - 9 = dry, 10 = wet
# F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard
# 15 = wet
('m_visual_tyre_compound', ctypes.c_uint8),
# F1 visual (can be different from actual compound)
# 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet
# F1 Classic – same as above
# F2 ‘19, 15 = wet, 19 – super soft, 20 = soft
# 21 = medium , 22 = hard
('m_tyres_age_laps', ctypes.c_uint8), # Age in laps of the current set of tyres
('m_vehicle_fia_flags', ctypes.c_int8),
# -1 = invalid/unknown, 0 = none, 1 = green
# 2 = blue, 3 = yellow, 4 = red
('m_ers_store_energy', ctypes.c_float), # ERS energy store in Joules
('m_ers_deploy_mode', ctypes.c_uint8),
# ERS deployment mode, 0 = none, 1 = medium
# 2 = hotlap, 3 = overtake
('m_ers_harvested_this_lap_mguk', ctypes.c_float),
# ERS energy harvested this lap by MGU-K
('m_ers_harvested_this_lap_mguh', ctypes.c_float),
# ERS energy harvested this lap by MGU-H
('m_ers_deployed_this_lap', ctypes.c_float), # ERS energy deployed this lap
('m_network_paused', ctypes.c_uint8),
# Whether the car is paused in a network game
]
class PacketCarStatusData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_status_data', CarStatusData * 22),
]
class FinalClassificationData(Packet):
_fields_ = [
('m_position', ctypes.c_uint8), # Finishing position
('m_num_laps', ctypes.c_uint8), # Number of laps completed
('m_grid_position', ctypes.c_uint8), # Grid position of the car
('m_points', ctypes.c_uint8), # Number of points scored
('m_num_pit_stops', ctypes.c_uint8), # Number of pit stops made
('m_result_status', ctypes.c_uint8),
# Result status - 0 = invalid, 1 = inactive, 2 = active
# 3 = finished, 4 = didnotfinish, 5 = disqualified
# 6 = not classified, 7 = retired
# Best lap time of the session in milliseconds
('m_best_lap_time_in_ms', ctypes.c_uint32),
# Total race time in seconds without penalties
('m_total_race_time', ctypes.c_double),
('m_penalties_time', ctypes.c_uint8), # Total penalties accumulated in seconds
# Number of penalties applied to this driver
('m_num_penalties', ctypes.c_uint8),
('m_num_tyre_stints', ctypes.c_uint8), # Number of tyres stints up to maximum
# Actual tyres used by this driver
('m_tyre_stints_actual', ctypes.c_uint8 * 8),
# Visual tyres used by this driver
('m_tyre_stints_visual', ctypes.c_uint8 * 8),
]
class PacketFinalClassificationData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_num_cars', ctypes.c_uint8), # Number of cars in the final classification
('m_classification_data', FinalClassificationData * 22),
]
class LobbyInfoData(Packet):
_fields_ = [
('m_ai_controlled', ctypes.c_uint8),
# Whether the vehicle is AI (1) or Human (0) controlled
('m_team_id', ctypes.c_uint8),
# Team id - see appendix (255 if no team currently selected)
('m_nationality', ctypes.c_uint8), # Nationality of the driver
# Name of participant in UTF-8 format – null terminated
('m_name', ctypes.c_char * 48),
# Will be truncated with ... (U+2026) if too long
('m_car_number', ctypes.c_uint8), # Car number of the player
('m_ready_status', ctypes.c_uint8), # 0 = not ready, 1 = ready, 2 = spectating
]
class PacketLobbyInfoData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
# Packet specific data
('m_num_players', ctypes.c_uint8), # Number of players in the lobby data
('m_lobby_players', LobbyInfoData * 22),
]
class CarDamageData(Packet):
_fields_ = [
('m_tyres_wear', ctypes.c_float * 4), # Tyre wear (percentage)
('m_tyres_damage', ctypes.c_uint8 * 4), # Tyre damage (percentage)
('m_brakes_damage', ctypes.c_uint8 * 4), # Brakes damage (percentage)
# Front left wing damage (percentage)
('m_front_left_wing_damage', ctypes.c_uint8),
# Front right wing damage (percentage)
('m_front_right_wing_damage', ctypes.c_uint8),
('m_rear_wing_damage', ctypes.c_uint8), # Rear wing damage (percentage)
('m_floor_damage', ctypes.c_uint8), # Floor damage (percentage)
('m_diffuser_damage', ctypes.c_uint8), # Diffuser damage (percentage)
('m_sidepod_damage', ctypes.c_uint8), # Sidepod damage (percentage)
('m_drs_fault', ctypes.c_uint8), # Indicator for DRS fault, 0 = OK, 1 = fault
('m_gear_box_damage', ctypes.c_uint8), # Gear box damage (percentage)
('m_engine_damage', ctypes.c_uint8), # Engine damage (percentage)
('m_engine_mguhwear', ctypes.c_uint8), # Engine wear MGU-H (percentage)
('m_engine_eswear', ctypes.c_uint8), # Engine wear ES (percentage)
('m_engine_cewear', ctypes.c_uint8), # Engine wear CE (percentage)
('m_engine_icewear', ctypes.c_uint8), # Engine wear ICE (percentage)
('m_engine_mgukwear', ctypes.c_uint8), # Engine wear MGU-K (percentage)
('m_engine_tcwear', ctypes.c_uint8), # Engine wear TC (percentage)
]
class PacketCarDamageData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_damage_data', CarDamageData * 22),
]
class LapHistoryData(Packet):
_fields_ = [
('m_lap_time_in_ms', ctypes.c_uint32), # Lap time in milliseconds
('m_sector1_time_in_ms', ctypes.c_uint16), # Sector 1 time in milliseconds
('m_sector2_time_in_ms', ctypes.c_uint16), # Sector 2 time in milliseconds
('m_sector3_time_in_ms', ctypes.c_uint16), # Sector 3 time in milliseconds
('m_lap_valid_bit_flags', ctypes.c_uint8),
# 0x01 bit set-lap valid, 0x02 bit set-sector 1 valid
# 0x04 bit set-sector 2 valid, 0x08 bit set-sector 3 valid
]
class TyreStintHistoryData(Packet):
_fields_ = [
# Lap the tyre usage ends on (255 of current tyre)
('m_end_lap', ctypes.c_uint8),
('m_tyre_actual_compound', ctypes.c_uint8), # Actual tyres used by this driver
('m_tyre_visual_compound', ctypes.c_uint8), # Visual tyres used by this driver
]
class PacketSessionHistoryData(Packet):
_fields_ = [
('m_header', PacketHeader), # Header
('m_car_idx', ctypes.c_uint8), # Index of the car this lap data relates to
# Num laps in the data (including current partial lap)
('m_num_laps', ctypes.c_uint8),
('m_num_tyre_stints', ctypes.c_uint8), # Number of tyre stints in the data
# Lap the best lap time was achieved on
('m_best_lap_time_lap_num', ctypes.c_uint8),
# Lap the best Sector 1 time was achieved on
('m_best_sector1_lap_num', ctypes.c_uint8),
# Lap the best Sector 2 time was achieved on
('m_best_sector2_lap_num', ctypes.c_uint8),
# Lap the best Sector 3 time was achieved on
('m_best_sector3_lap_num', ctypes.c_uint8),
('m_lap_history_data', LapHistoryData * 100), # 100 laps of data max
('m_tyre_stints_history_data', TyreStintHistoryData * 8),
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment