Administrator Popular Post BarryBL Posted July 8, 2021 Administrator Popular Post Share Posted July 8, 2021 (edited) Here is the latest specification for the UDP telemetry system for F1 2021. Please note that we've opted for posting a document with this info rather than adding this into the forum as this is easier to work with. The spec should be downloaded from here: Below is a summary of the various updates and bug fixes added to this year's game. A big thanks to everyone who has contributed suggestions and reported bugs with system over the past year, plus an extra shout out to the beta group who helped test this out ahead of release. Fixed Pre-Release Add number of warnings in a lap into LapData packet => We have added the number of warnings a driver has to the lap data Add a means of detecting when a stop-go penalty has been served => We have added an event which fires when stop go and drive through penalties have been served Add a way of indicating when a player is in a paused state => This will trigger after 3 seconds of being paused in an online race Add a means of detecting frames being replayed due to flashbacks => We have added a new flashback event that gives enough info to work this out Update UDP to include new Unreliable Weather Forecasts feature => Done Add engine wear/damage to the telemetry output => Done Add info to show which rev lights are illuminated => Done Add new damage parts to the data => Added floor, diffuser and sidepod damage Add the driver number to UDP output within the lobby => Done Tyre wear values should be floats not ints => Done Driver IDs are now conflicting => We have added a new network Id so that we don't need to offset by 100 now Traction Control is only showing values of 0 or 2 => This was working, but not shown in the docs. These have been update now Time for ghost cars are not displayed in telemetry output => Re-enabled now No enum value 0 => We have now made the values 255 for invalid items Provide information about all assists used => We have added Steering, Braking, Gearbox, Pit, Pit Release, ERS & DRS Add AI difficulty information => Done Add identifier to link all sessions within a race weekend => Done Add identifier to link all sessions within a career season => Done Add an identifier to link restarts of the same (offline) session => Done Add lap and sector info for the previous lap => We have added a new Session History packet which gives a list of all the laps in the session Add info for pit stop strategy / windows => Pit window and rejoin position info added to the data Show number of pit stops taken => Done Show tyres used in session history => We have added a new Session History packet which gives a list of all the tyre stints in the session Add data to show status of start light sequence => We have added "Start lights" and "Lights out" events to the UDP Set m_tyreAgeLaps as public => We have removed the tyres age from the restricted list Add the PIT stop time => We have added pit timings for all vehicles Add number of warnings in a lap into LapData packet => We have added the number of warnings a driver has to the lap data Add a means of detecting when a stop-go penalty has been served => We have added an event which fires when stop go and drive through penalties have been served Add a way of indicating when a player is in a paused state => This will trigger after 3 seconds of being paused in an online race Add a means of detecting frames being replayed due to flashbacks => We have added a new flashback event that gives enough info to work this out Add DRL assist info into SessionData packet => this has now been added Convert all time-based units to the same type and unit => time-based units will use milliseconds as ints where possible (note the a few of these still work in seconds and total lap time is a double). Please let us know if there are any issues or changes needed to this. Wear data is not updating in solo play => we tested this one and think it should be working as expected. Let us know if you are still having issues Add an invalid lap flag to LapHistoryData => added the invalid lap, together with the sector validity as well which gives a bit more information. m_buttonStatus bits for L2 and R2 are over-written if using steering wheel Send PacketCarTelemetryData and m_buttonStatus data after race has ended Unable to retrieve LapHistoryData for the final lap 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. Incorrect number of laps reported in Final Classification info => fixed and due for release in a future patch m_resultStatus shows as "active" after the race has finished LapHistoryData is not sent for an active car following retirement of another car => we will now update this data for all cars, even if they are retired Still Open Session History info from previous session is sent following a restart Refresh rate requires "hz" suffix Some values not updating on the correct frame when crossing lap boundary No way of linking player data using m_name => This is an old request. We are working with our legal teams to find a way of providing this functionality in the future Won’t Fix Sum of sector times don't match total lap time in SessionHistory data => we believe this is a result of rounding / precision issues. The current UDP data for SessionHistory matches our in-game lap time UI output, so we will leave this as it is for the time being. We have a task on our backlog to investigate these precision issues in the future. Add lap tyre info into the final classification packet for the best lap => would require a change to the format which we don’t want to do now, so we’ll look at this for a future project Consider moving assistOn packets to the PacketLobbyInfoData => this is not really possible to support due to how the network game works. If you see any instances of assist restrictions not working then please let us know. Add time delta to car in front and behind => This is an old request. As previously stated, players have enough data to produce their own delta time system so we will not be implementing this Inlap times are not displayed in the telemetry output => We are unable to accommodate this request the behaviour is based on how the game handles inlaps and outlaps. We can't provide the requested functionality without re-writing much of the game logic in this area (which we aren't able to do). Driver number does not update when taking control of a vehicle => We are unable to fix this as driver customisations are handled when first launching a session and do not get updated when players drop into a race mid-session. Increment laps to accommodate outlaps and inlaps => We are unable to accommodate this request the behaviour is based on how the game handles inlaps and outlaps. We can't provide the requested functionality without re-writing much of the game logic in this area (which we aren't able to do). Increase the number of buttons sent in m_buttonStatus to 32 => We aren't able to accommodate this request. It would require us to expose axes, of which there are many and they would be different on the many wheels we support, so this would be too large a task to look at now. Data_Output_from_F1_2021#51a.docx Edited December 6, 2021 by Hoo Updated doc with F2 2021 drivers and teams. 6 5 Quote Link to comment Share on other sites More sharing options...
AndyHamp Posted July 9, 2021 Share Posted July 9, 2021 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 Quote Link to comment Share on other sites More sharing options...
Codemasters Staff Hoo Posted July 9, 2021 Codemasters Staff Share Posted July 9, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
acidrain42 Posted July 9, 2021 Share Posted July 9, 2021 (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 July 9, 2021 by acidrain42 3 Quote Link to comment Share on other sites More sharing options...
XanderSJX Posted July 9, 2021 Share Posted July 9, 2021 (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 July 9, 2021 by XanderSJX Quote Link to comment Share on other sites More sharing options...
gparent Posted July 9, 2021 Share Posted July 9, 2021 (edited) I will do my best to continue updating the spec and publish it in a format that's easy to work with for the community, as opposed to a proprietary document. I'm a little late but I haven't given up. https://gitlab.com/gparent/f1-2020-telemetry/ Example for 2020: https://f1-2020-telemetry.readthedocs.io/en/latest/telemetry-specification.html Edited July 9, 2021 by gparent 2 4 Quote Link to comment Share on other sites More sharing options...
codylane482 Posted July 9, 2021 Share Posted July 9, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
codylane482 Posted July 9, 2021 Share Posted July 9, 2021 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_ 1 Quote Link to comment Share on other sites More sharing options...
codylane482 Posted July 10, 2021 Share Posted July 10, 2021 (edited) 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 July 10, 2021 by codylane482 Quote Link to comment Share on other sites More sharing options...
codylane482 Posted July 10, 2021 Share Posted July 10, 2021 (edited) 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 July 10, 2021 by codylane482 Quote Link to comment Share on other sites More sharing options...
cjorgens79 Posted July 10, 2021 Share Posted July 10, 2021 (edited) 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 July 10, 2021 by cjorgens79 Quote Link to comment Share on other sites More sharing options...
Rawrocopter Posted July 11, 2021 Share Posted July 11, 2021 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! Quote Link to comment Share on other sites More sharing options...
cgfdoo Posted July 12, 2021 Share Posted July 12, 2021 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. Quote Link to comment Share on other sites More sharing options...
LonelyRacer Posted July 12, 2021 Share Posted July 12, 2021 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. Quote Link to comment Share on other sites More sharing options...
bruschetta85 Posted July 13, 2021 Share Posted July 13, 2021 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? Quote Link to comment Share on other sites More sharing options...
cgfdoo Posted July 13, 2021 Share Posted July 13, 2021 (edited) 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 July 13, 2021 by cgfdoo Quote Link to comment Share on other sites More sharing options...
ericgvmaia Posted July 13, 2021 Share Posted July 13, 2021 (edited) Just downloaded the file. Thanks. Edited July 13, 2021 by ericgvmaia Wasn't logged in. Quote Link to comment Share on other sites More sharing options...
sim913 Posted July 13, 2021 Share Posted July 13, 2021 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: 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. 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. 1 Quote Link to comment Share on other sites More sharing options...
SimRacingStudio Posted July 13, 2021 Share Posted July 13, 2021 @SimRacingStudio just updated their software, ready to use with F1 2021: Version 2.16.0.6 is LIVE with F1 2021 support (simracingstudio.com) 1 Quote Link to comment Share on other sites More sharing options...
LonelyRacer Posted July 13, 2021 Share Posted July 13, 2021 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. Quote Link to comment Share on other sites More sharing options...
sim913 Posted July 14, 2021 Share Posted July 14, 2021 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? Quote Link to comment Share on other sites More sharing options...
cjorgens79 Posted July 14, 2021 Share Posted July 14, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
Lopensky Posted July 14, 2021 Share Posted July 14, 2021 Am i the only one who has a fixed zero into the sessionTime value in the header? Quote Link to comment Share on other sites More sharing options...
cjorgens79 Posted July 15, 2021 Share Posted July 15, 2021 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. 1 Quote Link to comment Share on other sites More sharing options...
Lopensky Posted July 15, 2021 Share Posted July 15, 2021 Ok thanks! So I'm probably missing something within the released game packets' format, I'll check it out and let you know what the problem was, just for completeness. ☺️ 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.