From 839e1ab30bec1d5d0f67217227e2b5401dd82f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20P=C4=99dzich?= Date: Tue, 18 Feb 2025 21:34:16 +0100 Subject: [PATCH] feat: add 2022 spec car status packet parser --- src/constants/mod.rs | 172 ++++++++++++++++++++++++++++++++++- src/lib.rs | 8 +- src/packets/car_status.rs | 72 +++++++++++++++ src/packets/car_telemetry.rs | 6 +- src/packets/mod.rs | 42 +++++---- 5 files changed, 272 insertions(+), 28 deletions(-) create mode 100644 src/packets/car_status.rs diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 78b5862..5f57286 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,14 +1,11 @@ -pub mod wheel_index; pub mod driver_id; +pub mod wheel_index; use binrw::BinRead; use bitflags::bitflags; use serde::{Deserialize, Serialize}; pub(crate) const MAX_NUM_CARS: usize = 22; -pub(crate) const MAX_NUM_MARSHAL_ZONES: usize = 21; -pub(crate) const MAX_NUM_WEATHER_FORECAST_SAMPLES: usize = 56; -pub(crate) const MAX_AI_DIFFICULTY: u8 = 110; #[non_exhaustive] #[derive( @@ -983,3 +980,170 @@ pub enum MfdPanelIndex { Temperatures = 4, Closed = 255, } + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum TractionControl { + Off = 0, + Medium = 1, + Full = 2, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum FuelMix { + Lean = 0, + Standard = 1, + Rich = 2, + Max = 3, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum ErsDeployMode { + None = 0, + Medium = 1, + Overtake = 2, + Hotlap = 3, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(i8))] +pub enum VehicleFiaFlag { + Unknown = -1, + None = 0, + Green = 1, + Blue = 2, + Yellow = 3, + Red = 4, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(i8))] +pub enum DrsAllowed { + Unknown = -1, + NotAllowed = 0, + Allowed = 1, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum ActualTyreCompound { + Unknown = 0, + F1C5 = 16, + F1C4 = 17, + F1C3 = 18, + F1C2 = 19, + F1C1 = 20, + F1Inter = 7, + F1Wet = 8, + ClassicDry = 9, + ClassicWet = 10, + F2SuperSoft = 11, + F2Soft = 12, + F2Medium = 13, + F2Hard = 14, + F2Wet = 15, +} + +#[non_exhaustive] +#[derive( + BinRead, + Eq, + PartialEq, + Ord, + PartialOrd, + Copy, + Clone, + Debug, + Serialize, + Deserialize, +)] +#[br(little, repr(u8))] +pub enum VisualTyreCompound { + Unknown = 0, + F1Soft = 16, + F1Medium = 17, + F1Hard = 18, + F1Inter = 7, + F1Wet = 8, + ClassicDry = 9, + ClassicWet = 10, + F2SuperSoft = 19, + F2Soft = 20, + F2Medium = 21, + F2Hard = 22, + F2Wet = 15, +} diff --git a/src/lib.rs b/src/lib.rs index 504d338..63fd9e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,9 @@ pub mod packets; use crate::constants::PacketId; use crate::packets::{ - F1PacketCarSetupsData, F1PacketCarTelemetryData, F1PacketEventData, - F1PacketLapData, F1PacketMotionData, F1PacketParticipantsData, - F1PacketSessionData, + F1PacketCarSetupsData, F1PacketCarStatusData, F1PacketCarTelemetryData, + F1PacketEventData, F1PacketLapData, F1PacketMotionData, + F1PacketParticipantsData, F1PacketSessionData, }; use binrw::io::Cursor; @@ -93,4 +93,6 @@ pub struct F1PacketBody { pub car_setups: Option, #[br(if(packet_id == PacketId::CarTelemetry), args(packet_format))] pub car_telemetry: Option, + #[br(if(packet_id == PacketId::CarStatus), args(packet_format))] + pub car_status: Option, } diff --git a/src/packets/car_status.rs b/src/packets/car_status.rs new file mode 100644 index 0000000..af8472e --- /dev/null +++ b/src/packets/car_status.rs @@ -0,0 +1,72 @@ +use super::u8_to_bool; +use crate::constants::{ + ActualTyreCompound, DrsAllowed, ErsDeployMode, FuelMix, TractionControl, + VehicleFiaFlag, VisualTyreCompound, +}; + +use binrw::BinRead; +use serde::{Deserialize, Serialize}; + +#[derive( + BinRead, PartialEq, PartialOrd, Copy, Clone, Debug, Serialize, Deserialize, +)] +#[br( + little, + import(_packet_format: u16), + assert( + max_gears <= 8, + "Car status entry has an invalid max number of gears: {}", + max_gears + ) +)] +pub struct CarStatusData { + /// How much traction control is enabled. + pub traction_control: TractionControl, + /// Whether ABS is enabled. + #[br(map(u8_to_bool))] + pub anti_lock_brakes: bool, + /// Fuel mix currently in use. + pub fuel_mix: FuelMix, + /// Front brake bias (percentage). + pub front_brake_bias: u8, + /// Whether the pit limiter is enabled. + #[br(map(u8_to_bool))] + pub pit_limiter_enabled: bool, + /// Current fuel mass. + pub fuel_in_tank: f32, + /// Fuel capacity. + pub fuel_capacity: f32, + /// Fuel remaining in terms of laps. + pub fuel_remaining_laps: f32, + /// Car's max RPM, point of rev limiter. + pub max_rpm: u16, + /// Car's idle RPM. + pub idle_rpm: u16, + /// Maximum number of gears. + pub max_gears: u8, + /// Whether DRS can be used (might be unknown). + pub drs_allowed: DrsAllowed, + /// 0 = DRS is unavailable, Non-zero = DRS will be available in X metres. + pub drs_activation_distance: u16, + /// Actual tyre compound currently in use. + pub actual_tyre_compound: ActualTyreCompound, + /// Visible tyre compound currently in use. + pub visual_tyre_compound: VisualTyreCompound, + /// Age of the current set of tyres in laps. + pub tyres_age_laps: u8, + /// Flag the driver is currently being shown. + pub vehicle_fia_flag: VehicleFiaFlag, + /// ERS energy store in Joules. + pub ers_store_energy: f32, + /// ERS deployment mode. + pub ers_deploy_mode: ErsDeployMode, + /// ERS energy harvested this lap by the MGU-K. + pub ers_harvested_this_lap_mguk: f32, + /// ERS energy harvested this lap by the MGU-H. + pub ers_harvested_this_lap_mguh: f32, + /// ERS energy deployed this lap. + pub ers_deployed_this_lap: f32, + /// Whether the car has paused in a network game. + #[br(map(u8_to_bool))] + pub network_paused: bool, +} diff --git a/src/packets/car_telemetry.rs b/src/packets/car_telemetry.rs index 65d3c1f..758ba15 100644 --- a/src/packets/car_telemetry.rs +++ b/src/packets/car_telemetry.rs @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize}; brake ), assert( - (0..=100).contains(&clutch), + clutch <= 100, "Car telemetry entry has an invalid clutch value: {}", clutch ), @@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize}; gear ), assert( - (0..=100).contains(&rev_lights_percent), + rev_lights_percent <= 100, "Car telemetry entry has an invalid rev lights percentage: {}", rev_lights_percent ), @@ -51,7 +51,7 @@ pub struct CarTelemetryData { pub steer: f32, /// Amount of brake applied. Value in range `(0.0..=1.0)`. pub brake: f32, - /// Amount of clutch applied. Value in range `(0..=100)`. + /// Amount of clutch applied (percentage). pub clutch: u8, /// Selected gear. Neutral = 0, reverse = -1. pub gear: i8, diff --git a/src/packets/mod.rs b/src/packets/mod.rs index ca0d2f7..4d95bfa 100644 --- a/src/packets/mod.rs +++ b/src/packets/mod.rs @@ -1,4 +1,5 @@ pub mod car_setups; +pub mod car_status; pub mod car_telemetry; pub mod event; pub mod lap_data; @@ -9,8 +10,7 @@ pub mod session; use crate::constants::{ BrakingAssist, DynamicRacingLine, DynamicRacingLineType, ForecastAccuracy, Formula, GameMode, GearboxAssist, MfdPanelIndex, Ruleset, SafetyCarStatus, - SessionLength, SessionType, TrackId, Weather, MAX_AI_DIFFICULTY, - MAX_NUM_CARS, MAX_NUM_MARSHAL_ZONES, MAX_NUM_WEATHER_FORECAST_SAMPLES, + SessionLength, SessionType, TrackId, Weather, MAX_NUM_CARS, }; use crate::packets::event::EventDataDetails; use crate::packets::lap_data::LapData; @@ -19,6 +19,7 @@ use crate::packets::participants::ParticipantsData; use crate::packets::session::{MarshalZone, WeatherForecastSample}; use crate::packets::car_setups::CarSetupData; +use crate::packets::car_status::CarStatusData; use crate::packets::car_telemetry::CarTelemetryData; use binrw::BinRead; use serde::{Deserialize, Serialize}; @@ -49,17 +50,17 @@ pub struct F1PacketMotionData { little, import(packet_format: u16), assert( - num_marshal_zones <= MAX_NUM_MARSHAL_ZONES, + num_marshal_zones <= 21, "Session packet has an invalid number of marshal zones: {}", num_marshal_zones ), assert( - num_weather_forecast_samples <= MAX_NUM_WEATHER_FORECAST_SAMPLES, + num_weather_forecast_samples <= 56, "Session packet has an invalid number of weather forecast samples: {}", num_weather_forecast_samples ), assert( - ai_difficulty <= MAX_AI_DIFFICULTY, + ai_difficulty <= 110, "Session packet has an invalid AI difficulty value: {}", ai_difficulty ), @@ -104,7 +105,7 @@ pub struct F1PacketSessionData { pub num_marshal_zones: usize, /// List of marshal zones. /// Has a size of 21 regardless of the `num_marshal_zones` value. - #[br(count(MAX_NUM_MARSHAL_ZONES), args{ inner: (packet_format,) })] + #[br(count(21), args{ inner: (packet_format,) })] pub marshal_zones: Vec, /// Safety car deployment status. pub safety_car_status: SafetyCarStatus, @@ -116,10 +117,7 @@ pub struct F1PacketSessionData { pub num_weather_forecast_samples: usize, /// List of up to weather forecast samples. /// Has a size of 56 regardless of the `num_weather_forecast_samples` value. - #[br( - count(MAX_NUM_WEATHER_FORECAST_SAMPLES), - args{ inner: (packet_format,) } - )] + #[br(count(56), args{ inner: (packet_format,) })] pub weather_forecast_samples: Vec, /// Weather forecast accuracy. pub forecast_accuracy: ForecastAccuracy, @@ -224,8 +222,7 @@ pub struct F1PacketParticipantsData { #[br(map(u8_to_usize))] pub num_active_cars: usize, /// Data for all participants. - /// Should have a size equal to - /// [`num_active_cars`](field@F1PacketParticipantsData::num_active_cars) + /// Should have a size equal to `num_active_cars`. #[br(count(num_active_cars), args{ inner: (packet_format,) })] pub participants: Vec, } @@ -261,20 +258,29 @@ pub struct F1PacketCarSetupsData { )] pub struct F1PacketCarTelemetryData { /// Telemetry data for all cars on track. - /// Should have a size of 22 - #[br( - count(MAX_NUM_CARS), - args{ inner: (packet_format,) } - )] + /// Should have a size of 22. + #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })] pub car_telemetry_data: Vec, /// Index of currently open MFD panel. pub mfd_panel_index: MfdPanelIndex, - /// See [`mfd_panel_index`](field@F1PacketCarTelemetryData::mfd_panel_index) + /// See [`mfd_panel_index`](field@F1PacketCarTelemetryData::mfd_panel_index). pub mfd_panel_index_secondary_player: MfdPanelIndex, /// Suggested gear (0 if no gear suggested). pub suggested_gear: i8, } +/// Car status data for each car in the race. +#[non_exhaustive] +#[derive( + BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize, +)] +#[br(little, import(packet_format: u16))] +pub struct F1PacketCarStatusData { + /// Status data for all cars. Should have a size of 22. + #[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })] + pub car_status_data: Vec, +} + /// Extended motion data for player's car. Available as a: /// - part of [`F1PacketMotionData`] in the 2022 format /// - standalone packet from the 2023 format onwards