Jump to content Jump to content

Recommended Posts

  • Hoo changed the title to F1 2021 UDP Specification
  • Codemasters Staff
16 minutes ago, AndyHamp said:

Thanks for publishing ... But can you add the spec to this post so that we can see updates ... like you have done in previous years 

The spec is attached to the original post in the thread. It's a downloadable Word doc.

  • Like 1
Link to post
Share on other sites
Posted (edited)

Thanks for publishing the spec, I was eagerly waiting for it!

As a side note, would you be open to use a more developer friendly format than docx? A plain text file would probably be better imo as I could open it in an IDE.
Even wilder idea: publish the .h files on github!

Edited by acidrain42
  • Like 3
Link to post
Share on other sites
Posted (edited)

Thanks a lot, in the end my urge to do some python last few weeks will be responsible for me getting this game. 😁 I like some of the updates listed above, helps a lot. Especially AI value and session identifier.

 

EDIT: @Hoo in participant packet, myTeam field. That is 1 only for me and my teammate. Do I understand it correct?

Edited by XanderSJX
Link to post
Share on other sites

I really appreciate your help gparent. You made some great work with f1-2020-telemetry and cleaned up a lot. I appreciate your contributions.

 

I also have something I've been working on for a while but my focus was to basically take a C or C++ structures and unions and turn it into python code + tests for the code. So far it works with pcars2, f12019, f12020. I plan to add support for f12021 and once that is done. I plan to open source it as soon as it has support for f12021 which I should be able to do before the weekend is over.

  • Like 1
Link to post
Share on other sites

As promised here is the output of copying and pasting the udp-specification into my parsing library. This is just the python code for the udp specification packet structs but I hope that it helps others get started quickly on writing the UDP handler and what to do with the packets once they are received. As promised, I will open source this and post the link to a official page, but my time for that is limited.

 

class PacketHeader(object):
    _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(object):
    _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)
        ('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(object):
    _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(object):
    _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(object):
    _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(object):
    _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(object):
    _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(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_lap_data', LapData * 22), # Lap data for all cars on track
    ]

class FastestLap(object):
    _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(object):
    _fields_ = [
        ('vehicle_idx', ctypes.c_uint8), # Vehicle index of car retiring
    ]

class TeamMateInPits(object):
    _fields_ = [
        ('vehicle_idx', ctypes.c_uint8), # Vehicle index of team mate
    ]

class RaceWinner(object):
    _fields_ = [
        ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the race winner
    ]

class Penalty(object):
    _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(object):
    _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(object):
    _fields_ = [
        ('num_lights', ctypes.c_uint8), # Number of lights showing
    ]

class DriveThroughPenaltyServed(object):
    _fields_ = [
        ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the vehicle serving drive through
    ]

class StopGoPenaltyServed(object):
    _fields_ = [
        ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the vehicle serving stop go
    ]

class Flashback(object):
    _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(object):
    _fields_ = [
        ('m_button_status', ctypes.c_uint32), # Bit flags specifying which buttons are being pressed
        # currently - see appendices
    ]

class PacketEventData(object):
    _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(object):
    _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(object):
    _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(object):
    _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(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_setups', CarSetupData * 22),
    ]

class CarTelemetryData(object):
    _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(object):
    _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(object):
    _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(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_status_data', CarStatusData * 22),
    ]

class FinalClassificationData(object):
    _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
        ('m_best_lap_time_in_ms', ctypes.c_uint32), # Best lap time of the session in milliseconds
        ('m_total_race_time', ctypes.c_double), # Total race time in seconds without penalties
        ('m_penalties_time', ctypes.c_uint8), # Total penalties accumulated in seconds
        ('m_num_penalties', ctypes.c_uint8), # Number of penalties applied to this driver
        ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyres stints up to maximum
        ('m_tyre_stints_actual', ctypes.c_uint8 * 8), # Actual tyres used by this driver
        ('m_tyre_stints_visual', ctypes.c_uint8 * 8), # Visual tyres used by this driver
    ]

class PacketFinalClassificationData(object):
    _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(object):
    _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
        ('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_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(object):
    _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(object):
    _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)
        ('m_front_left_wing_damage', ctypes.c_uint8), # Front left wing damage (percentage)
        ('m_front_right_wing_damage', ctypes.c_uint8), # Front right wing damage (percentage)
        ('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(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_damage_data', CarDamageData * 22),
    ]

class LapHistoryData(object):
    _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(object):
    _fields_ = [
        ('m_end_lap', ctypes.c_uint8), # Lap the tyre usage ends on (255 of current tyre)
        ('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(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_idx', ctypes.c_uint8), # Index of the car this lap data relates to
        ('m_num_laps', ctypes.c_uint8), # Num laps in the data (including current partial lap)
        ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyre stints in the data
        ('m_best_lap_time_lap_num', ctypes.c_uint8), # Lap the best lap time was achieved on
        ('m_best_sector1_lap_num', ctypes.c_uint8), # Lap the best Sector 1 time was achieved on
        ('m_best_sector2_lap_num', ctypes.c_uint8), # Lap the best Sector 2 time was achieved on
        ('m_best_sector3_lap_num', ctypes.c_uint8), # Lap the best Sector 3 time was achieved on
        ('m_lap_history_data', LapHistoryData * 100), # 100 laps of data max
        ('m_tyre_stints_history_data', TyreStintHistoryData * 8),
    ]

 

And finally, here are the pytest tests for the above code.

 

class Test_PacketHeader(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_PacketHeader__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketHeader()

        assert (type_name, type_class) in packet_type._fields_


class Test_CarMotionData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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)
            ('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
        ]
    )
    def test_CarMotionData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = CarMotionData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketMotionData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_PacketMotionData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketMotionData()

        assert (type_name, type_class) in packet_type._fields_


class Test_MarshalZone(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_MarshalZone__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = MarshalZone()

        assert (type_name, type_class) in packet_type._fields_


class Test_WeatherForecastSample(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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)
        ]
    )
    def test_WeatherForecastSample__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = WeatherForecastSample()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketSessionData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_PacketSessionData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketSessionData()

        assert (type_name, type_class) in packet_type._fields_


class Test_LapData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_LapData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = LapData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketLapData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_lap_data', LapData * 22), # Lap data for all cars on track
        ]
    )
    def test_PacketLapData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketLapData()

        assert (type_name, type_class) in packet_type._fields_


class Test_FastestLap(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of car achieving fastest lap
            ('lap_time', ctypes.c_float), # Lap time is in seconds
        ]
    )
    def test_FastestLap__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = FastestLap()

        assert (type_name, type_class) in packet_type._fields_


class Test_Retirement(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of car retiring
        ]
    )
    def test_Retirement__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = Retirement()

        assert (type_name, type_class) in packet_type._fields_


class Test_TeamMateInPits(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of team mate
        ]
    )
    def test_TeamMateInPits__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = TeamMateInPits()

        assert (type_name, type_class) in packet_type._fields_


class Test_RaceWinner(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the race winner
        ]
    )
    def test_RaceWinner__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = RaceWinner()

        assert (type_name, type_class) in packet_type._fields_


class Test_Penalty(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_Penalty__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = Penalty()

        assert (type_name, type_class) in packet_type._fields_


class Test_SpeedTrap(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_SpeedTrap__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = SpeedTrap()

        assert (type_name, type_class) in packet_type._fields_


class Test_StartLIghts(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('num_lights', ctypes.c_uint8), # Number of lights showing
        ]
    )
    def test_StartLIghts__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = StartLIghts()

        assert (type_name, type_class) in packet_type._fields_


class Test_DriveThroughPenaltyServed(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the vehicle serving drive through
        ]
    )
    def test_DriveThroughPenaltyServed__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = DriveThroughPenaltyServed()

        assert (type_name, type_class) in packet_type._fields_


class Test_StopGoPenaltyServed(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('vehicle_idx', ctypes.c_uint8), # Vehicle index of the vehicle serving stop go
        ]
    )
    def test_StopGoPenaltyServed__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = StopGoPenaltyServed()

        assert (type_name, type_class) in packet_type._fields_


class Test_Flashback(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('flashback_frame_identifier', ctypes.c_uint32), # Frame identifier flashed back to
            ('flashback_session_time', ctypes.c_float), # Session time flashed back to
        ]
    )
    def test_Flashback__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = Flashback()

        assert (type_name, type_class) in packet_type._fields_


class Test_Buttons(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_button_status', ctypes.c_uint32), # Bit flags specifying which buttons are being pressed
            # currently - see appendices
        ]
    )
    def test_Buttons__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = Buttons()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketEventData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_PacketEventData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketEventData()

        assert (type_name, type_class) in packet_type._fields_


class Test_ParticipantData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_ParticipantData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = ParticipantData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketParticipantsData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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),
        ]
    )
    def test_PacketParticipantsData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketParticipantsData()

        assert (type_name, type_class) in packet_type._fields_


class Test_CarSetupData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_CarSetupData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = CarSetupData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketCarSetupData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_car_setups', CarSetupData * 22),
        ]
    )
    def test_PacketCarSetupData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketCarSetupData()

        assert (type_name, type_class) in packet_type._fields_


class Test_CarTelemetryData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_CarTelemetryData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = CarTelemetryData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketCarTelemetryData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_PacketCarTelemetryData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketCarTelemetryData()

        assert (type_name, type_class) in packet_type._fields_


class Test_CarStatusData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_CarStatusData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = CarStatusData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketCarStatusData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_car_status_data', CarStatusData * 22),
        ]
    )
    def test_PacketCarStatusData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketCarStatusData()

        assert (type_name, type_class) in packet_type._fields_


class Test_FinalClassificationData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
            ('m_best_lap_time_in_ms', ctypes.c_uint32), # Best lap time of the session in milliseconds
            ('m_total_race_time', ctypes.c_double), # Total race time in seconds without penalties
            ('m_penalties_time', ctypes.c_uint8), # Total penalties accumulated in seconds
            ('m_num_penalties', ctypes.c_uint8), # Number of penalties applied to this driver
            ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyres stints up to maximum
            ('m_tyre_stints_actual', ctypes.c_uint8 * 8), # Actual tyres used by this driver
            ('m_tyre_stints_visual', ctypes.c_uint8 * 8), # Visual tyres used by this driver
        ]
    )
    def test_FinalClassificationData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = FinalClassificationData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketFinalClassificationData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_num_cars', ctypes.c_uint8), # Number of cars in the final classification
            ('m_classification_data', FinalClassificationData * 22),
        ]
    )
    def test_PacketFinalClassificationData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketFinalClassificationData()

        assert (type_name, type_class) in packet_type._fields_


class Test_LobbyInfoData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
            ('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_car_number', ctypes.c_uint8), # Car number of the player
            ('m_ready_status', ctypes.c_uint8), # 0 = not ready, 1 = ready, 2 = spectating
        ]
    )
    def test_LobbyInfoData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = LobbyInfoData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketLobbyInfoData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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),
        ]
    )
    def test_PacketLobbyInfoData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketLobbyInfoData()

        assert (type_name, type_class) in packet_type._fields_


class Test_CarDamageData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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)
            ('m_front_left_wing_damage', ctypes.c_uint8), # Front left wing damage (percentage)
            ('m_front_right_wing_damage', ctypes.c_uint8), # Front right wing damage (percentage)
            ('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)
        ]
    )
    def test_CarDamageData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = CarDamageData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketCarDamageData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_car_damage_data', CarDamageData * 22),
        ]
    )
    def test_PacketCarDamageData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketCarDamageData()

        assert (type_name, type_class) in packet_type._fields_


class Test_LapHistoryData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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
        ]
    )
    def test_LapHistoryData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = LapHistoryData()

        assert (type_name, type_class) in packet_type._fields_


class Test_TyreStintHistoryData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_end_lap', ctypes.c_uint8), # Lap the tyre usage ends on (255 of current tyre)
            ('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
        ]
    )
    def test_TyreStintHistoryData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = TyreStintHistoryData()

        assert (type_name, type_class) in packet_type._fields_


class Test_PacketSessionHistoryData(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('m_header', PacketHeader), # Header
            ('m_car_idx', ctypes.c_uint8), # Index of the car this lap data relates to
            ('m_num_laps', ctypes.c_uint8), # Num laps in the data (including current partial lap)
            ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyre stints in the data
            ('m_best_lap_time_lap_num', ctypes.c_uint8), # Lap the best lap time was achieved on
            ('m_best_sector1_lap_num', ctypes.c_uint8), # Lap the best Sector 1 time was achieved on
            ('m_best_sector2_lap_num', ctypes.c_uint8), # Lap the best Sector 2 time was achieved on
            ('m_best_sector3_lap_num', ctypes.c_uint8), # Lap the best Sector 3 time was achieved on
            ('m_lap_history_data', LapHistoryData * 100), # 100 laps of data max
            ('m_tyre_stints_history_data', TyreStintHistoryData * 8),
        ]
    )
    def test_PacketSessionHistoryData__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = PacketSessionHistoryData()

        assert (type_name, type_class) in packet_type._fields_

 

  • Like 1
Link to post
Share on other sites

One thing I forgot to mention is that EventDataDetails needs to be defined on it's own. Here's the missing code for EventDataDetails

 

# coding: utf-8

import ctypes

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 Test_EventDataDetails(object):
    @pytest.mark.parametrize(
        'type_name, type_class',
        [
            ('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),
        ]
    )
    def test_EventDataDetails__field__types(self, type_name, type_class):  # noqa: E501
        packet_type = EventDataDetails()

        assert (type_name, type_class) in packet_type._fields_

 

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),
    ]

 

 

Edited by codylane482
Link to post
Share on other sites

I apologize for the missing code, but this comment is here to help and replace all (object) references with (Packet) for all classes that do not have the word Test_ in them, in the following post.

I apologize for the confusion and I do hope this helps others. Please reach out if you need help.

 

For example this code

 

class PacketSessionHistoryData(object):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_idx', ctypes.c_uint8), # Index of the car this lap data relates to
        ('m_num_laps', ctypes.c_uint8), # Num laps in the data (including current partial lap)
        ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyre stints in the data
        ('m_best_lap_time_lap_num', ctypes.c_uint8), # Lap the best lap time was achieved on
        ('m_best_sector1_lap_num', ctypes.c_uint8), # Lap the best Sector 1 time was achieved on
        ('m_best_sector2_lap_num', ctypes.c_uint8), # Lap the best Sector 2 time was achieved on
        ('m_best_sector3_lap_num', ctypes.c_uint8), # Lap the best Sector 3 time was achieved on
        ('m_lap_history_data', LapHistoryData * 100), # 100 laps of data max
        ('m_tyre_stints_history_data', TyreStintHistoryData * 8),
    ]

becomes this code, assuming the Packet class is defined before this class.

class PacketSessionHistoryData(Packet):
    _fields_ = [
        ('m_header', PacketHeader), # Header
        ('m_car_idx', ctypes.c_uint8), # Index of the car this lap data relates to
        ('m_num_laps', ctypes.c_uint8), # Num laps in the data (including current partial lap)
        ('m_num_tyre_stints', ctypes.c_uint8), # Number of tyre stints in the data
        ('m_best_lap_time_lap_num', ctypes.c_uint8), # Lap the best lap time was achieved on
        ('m_best_sector1_lap_num', ctypes.c_uint8), # Lap the best Sector 1 time was achieved on
        ('m_best_sector2_lap_num', ctypes.c_uint8), # Lap the best Sector 2 time was achieved on
        ('m_best_sector3_lap_num', ctypes.c_uint8), # Lap the best Sector 3 time was achieved on
        ('m_lap_history_data', LapHistoryData * 100), # 100 laps of data max
        ('m_tyre_stints_history_data', TyreStintHistoryData * 8),
    ]

 

Edited by codylane482
Link to post
Share on other sites
On 7/8/2021 at 9:38 PM, BarryBL said:

Fixed in Future Patch Release (date tbc)

  • Add extra byte to indicate if this is the final packet in a batch => we didn’t want to change the packet structure at this stage, so instead have made a change so that the final session history packets are sent AFTER the final classification packet, so if you watch for the session ended packet. This change is expected in a future patch of the game.  One thing I would like to query though (related to general output of SessionHistory, not specific to end of session) is what happens with those SessionHistory packets when the telemetry frequency is not 20Hz. To explain further, when using 20Hz telemetry the SessionHistory packet gets included as part of the batches of other packets that fire at the regular telemetry interval, they all share a common m_frameIdentifier. The docs for SessionHistory says "This packet works slightly differently to other packets. To reduce CPU and bandwidth, each packet relates to a specific vehicle and is sent every 1/20 s, and the vehicle being sent is cycled through", which indicates it runs at 20Hz, so what happens if the telemetry rate is set to 30Hz, do those SessionHistory packets just get sent separately at 20Hz (with their own m_frameIdentifier), or will they still get included intermittantly in the regular telemetry batches? (as obviously the update intervals no longer overlap perfectly between telemetry batches and session history 20hz).

Still Open

  • Session History info from previous session is sent following a restart. Just to clarify this isn't just related to restarts, it happens when the game transitions between session types in normal use as well, ie prac to qual, or qual to race. 

Just a couple of comments in red regaring the above points. Cheers

Edited by cjorgens79
Link to post
Share on other sites

Can someone give me an updated list of which driver numbers are user-selectable for a custom driver? I see a full list of drivers, but I can't remember the rules for which custom driver numbers you can actually select.

 

Thanks!

Link to post
Share on other sites

Just started using the deluxe edition on PC and I’m experiencing weird, mid-lap session type changes in TT sessions (haven’t tried other session types yet) and my teleportation detection code triggers randomly with “teleportation distances” being all over the shop, sometimes a few meters to hundreds of meters.

the teleportation thing might be on me but is anyone else seeing that kind of behaviour?
 

While I know that there is no guarantee for UDP to arrive/arrive in order, this never happened to me in F1 2020, only in the 2021 Version.

I’ll debug my own stuff some more tomorrow but wanted to see if others see this issue as well.

Link to post
Share on other sites
1 hour ago, cgfdoo said:

Just started using the deluxe edition on PC and I’m experiencing weird, mid-lap session type changes in TT sessions (haven’t tried other session types yet) and my teleportation detection code triggers randomly with “teleportation distances” being all over the shop, sometimes a few meters to hundreds of meters.

the teleportation thing might be on me but is anyone else seeing that kind of behaviour?
 

While I know that there is no guarantee for UDP to arrive/arrive in order, this never happened to me in F1 2020, only in the 2021 Version.

I’ll debug my own stuff some more tomorrow but wanted to see if others see this issue as well.

Well, my tool worked with the beta in all modes with no issues. But there was no TT in beta, so I could not test that.

I also just did quick testing with the deluxe version.

In race (solo race, did not enter an online race yet), my tool works as expected and all data seems to work ok.

BUT, in TT there is something odd going on, which breaks my tool. I haven't dived into the data feed details, but hopefully will have tomorrow some time to look, what is going on there.

Cheers.

Link to post
Share on other sites

Thanks staff, really happy with the udp updates you made. I wanted your advice for calculating the gaps between cars in real time. The only way I have found to perform the calculation is to identify the position of the player and check the distance in meters traveled by the car player, I do the same thing with the car that precedes and follows, obtained the distances thanks to the variable m_speed , I calculate time using the inverse formula speed = space / time subtracting the result from my time I get the front gap, adding I get the rear one. Small problem if the two cars are in different sectors, one slower and the other faster, the gap becomes fluctuating. has anyone found a better solution than mine?

Link to post
Share on other sites
14 hours ago, LonelyRacer said:

Well, my tool worked with the beta in all modes with no issues. But there was no TT in beta, so I could not test that.

I also just did quick testing with the deluxe version.

In race (solo race, did not enter an online race yet), my tool works as expected and all data seems to work ok.

BUT, in TT there is something odd going on, which breaks my tool. I haven't dived into the data feed details, but hopefully will have tomorrow some time to look, what is going on there.

Cheers.

Turns out I was just being a massive doofus, a little bit of moving some of my logic around and handling "late" packets better, solved the issue for me. Best guess is that it was a pairing of a bug in my code with packets being out of order more often compared to F1 2020, at least on my hardware.

Carry on, nothing else to see here.

Edited by cgfdoo
Link to post
Share on other sites

I'm investigating two issues that occur inconsistently for me. It could just be an implementation issue on our side, but I wonder if anyone else is seeing the same things:

  1. In some sessions, I see the Lap Data packet being sent after crossing the finish line without incrementing the currentLapNum. As an example, in a race, after crossing the finish line on the last lap, I get a few more Lap Data packet with currentLapNum = last lap number. I don't think this occurred in F1 2020. This is an issue because we need to map telemetry data to lap distance & time data. With the Lap Data packet not consistently incrementing the currentLapNum, we'll have to implement some custom logic to handle these instances.  
  2. In some cases, it seems like the Session History packet is not sent during the last lap. This means that we receive the Session History packet data for the last lap, and the previous lap (second last), only with the final classification screen. This is not a huge issue to us, because we do get the data eventually, but it would be nice for it to be consistent. 

We'll keep investigating this on our side, but I'd appreciate any feedback if anyone else sees the same behavior. 

  • Agree 1
Link to post
Share on other sites
On 7/13/2021 at 12:36 AM, LonelyRacer said:

Well, my tool worked with the beta in all modes with no issues. But there was no TT in beta, so I could not test that.

I also just did quick testing with the deluxe version.

In race (solo race, did not enter an online race yet), my tool works as expected and all data seems to work ok.

BUT, in TT there is something odd going on, which breaks my tool. I haven't dived into the data feed details, but hopefully will have tomorrow some time to look, what is going on there.

Cheers.

I found out, that the error was on my side.

As the id of TT is now 13, instead of 12 (as in F1 2020), my tool got stuck in "Event changed" loop, and it only happened with TT, as for other events the ids are same as with F1 2020.

Cheers.

Link to post
Share on other sites

One of our users just completed a race session with Team ID = 41, which I can't find in the appendix. Is it missing from the table? Does anyone know what team ID 41 maps to? 

Link to post
Share on other sites
2 hours ago, krz9 said:

One of our users just completed a race session with Team ID = 41, which I can't find in the appendix. Is it missing from the table? Does anyone know what team ID 41 maps to? 

I believe in previous versions of the protocol Team 41 indicated it was a "Multiplayer" team, so I assume it is still the same even though its not documented in the latest spec by looks of it.

  • Thanks 1
Link to post
Share on other sites
7 hours ago, Lopensky said:

Am i the only one who has a fixed zero into the sessionTime value in the header?

I was looking at that field in the data yesterday and it was working fine for me on windows version. It is zero for a bit before session starts and after session ends, but during it was ticking over just fine. I was looking at data from single player sessions at the time though.

  • Like 1
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...