f1_api/nineteen/
status.rs

1//! Decoder for car status packets sent by F1 2019
2//!
3//! F1 2019 is the first game to differentiate between a physical tyre compound (e.g. C1) and a
4//! visual tyre compound (e.g. hard). This makes it packet format and decoder incompatible with
5//! earlier F1 games.
6
7use std::io::{Cursor, Error, ErrorKind};
8
9use bytes::{Buf, BytesMut};
10
11use crate::nineteen::flag::decode_flag;
12use crate::nineteen::header::decode_header;
13use crate::packet::ensure_packet_size;
14use crate::packet::status::{
15    CarStatus, CarStatusPacket, DrsSetting, ErsDeployMode, FuelMix, PhysicalTyreCompound,
16    TractionControl, VisualTyreCompound,
17};
18use crate::types::CornerProperty;
19
20/// Size of the car status packet in bytes
21pub const PACKET_SIZE: usize = 1143;
22
23/// Decode the car status packet sent by F1 2019
24///
25/// The car status packet by F1 2019 introduces the differentiation between a physical and a visual
26/// tyre compound.
27pub fn decode_statuses(cursor: &mut Cursor<&mut BytesMut>) -> Result<CarStatusPacket, Error> {
28    ensure_packet_size(PACKET_SIZE, cursor)?;
29
30    let header = decode_header(cursor)?;
31    let mut car_status = Vec::with_capacity(20);
32
33    for _ in 0..20 {
34        car_status.push(CarStatus::new(
35            decode_traction_control(cursor)?,
36            cursor.get_u8() > 0,
37            decode_fuel_mix(cursor)?,
38            cursor.get_u8(),
39            cursor.get_u8() > 0,
40            cursor.get_f32_le(),
41            cursor.get_f32_le(),
42            cursor.get_f32_le(),
43            cursor.get_u16_le(),
44            cursor.get_u16_le(),
45            cursor.get_u8(),
46            decode_drs(cursor)?,
47            decode_tyre_wear(cursor),
48            decode_physical_tyre_compound(cursor)?,
49            decode_visual_tyre_compound(cursor)?,
50            decode_tyre_damage(cursor),
51            cursor.get_u8(),
52            cursor.get_u8(),
53            cursor.get_u8(),
54            cursor.get_u8(),
55            cursor.get_u8(),
56            decode_flag(cursor)?,
57            cursor.get_f32_le(),
58            decode_ers_deploy_mode(cursor)?,
59            cursor.get_f32_le(),
60            cursor.get_f32_le(),
61            cursor.get_f32_le(),
62        ));
63    }
64
65    Ok(CarStatusPacket::new(header, car_status))
66}
67
68fn decode_traction_control(cursor: &mut Cursor<&mut BytesMut>) -> Result<TractionControl, Error> {
69    let value = cursor.get_u8();
70
71    match value {
72        0 => Ok(TractionControl::Off),
73        1 => Ok(TractionControl::Low),
74        2 => Ok(TractionControl::High),
75        _ => Err(Error::new(
76            ErrorKind::InvalidData,
77            "Failed to decode transaction control.",
78        )),
79    }
80}
81
82fn decode_fuel_mix(cursor: &mut Cursor<&mut BytesMut>) -> Result<FuelMix, Error> {
83    let value = cursor.get_u8();
84
85    match value {
86        0 => Ok(FuelMix::Lean),
87        1 => Ok(FuelMix::Standard),
88        2 => Ok(FuelMix::Rich),
89        3 => Ok(FuelMix::Max),
90        _ => Err(Error::new(
91            ErrorKind::InvalidData,
92            "Failed to decode fuel mix.",
93        )),
94    }
95}
96
97fn decode_drs(cursor: &mut Cursor<&mut BytesMut>) -> Result<DrsSetting, Error> {
98    let value = cursor.get_i8();
99
100    match value {
101        -1 => Ok(DrsSetting::Unknown),
102        0 => Ok(DrsSetting::NotAllowed),
103        1 => Ok(DrsSetting::Allowed),
104        _ => Err(Error::new(
105            ErrorKind::InvalidData,
106            "Failed to decode DRS status.",
107        )),
108    }
109}
110
111fn decode_tyre_wear(cursor: &mut Cursor<&mut BytesMut>) -> CornerProperty<u8> {
112    CornerProperty::new(
113        cursor.get_u8(),
114        cursor.get_u8(),
115        cursor.get_u8(),
116        cursor.get_u8(),
117    )
118}
119
120fn decode_physical_tyre_compound(
121    cursor: &mut Cursor<&mut BytesMut>,
122) -> Result<PhysicalTyreCompound, Error> {
123    let value = cursor.get_u8();
124
125    match value {
126        7 => Ok(PhysicalTyreCompound::F1Intermediate),
127        8 => Ok(PhysicalTyreCompound::F1Wet),
128        9 => Ok(PhysicalTyreCompound::ClassicDry),
129        10 => Ok(PhysicalTyreCompound::ClassicWet),
130        11 => Ok(PhysicalTyreCompound::F2SuperSoft),
131        12 => Ok(PhysicalTyreCompound::F2Soft),
132        13 => Ok(PhysicalTyreCompound::F2Medium),
133        14 => Ok(PhysicalTyreCompound::F2Hard),
134        15 => Ok(PhysicalTyreCompound::F2Wet),
135        16 => Ok(PhysicalTyreCompound::F1C5),
136        17 => Ok(PhysicalTyreCompound::F1C4),
137        18 => Ok(PhysicalTyreCompound::F1C3),
138        19 => Ok(PhysicalTyreCompound::F1C2),
139        20 => Ok(PhysicalTyreCompound::F1C1),
140        _ => Err(Error::new(
141            ErrorKind::InvalidData,
142            "Failed to decode physical tyre compound.",
143        )),
144    }
145}
146
147fn decode_visual_tyre_compound(
148    cursor: &mut Cursor<&mut BytesMut>,
149) -> Result<VisualTyreCompound, Error> {
150    let value = cursor.get_u8();
151
152    match value {
153        7 => Ok(VisualTyreCompound::F1Intermediate),
154        8 => Ok(VisualTyreCompound::F1Wet),
155        9 => Ok(VisualTyreCompound::ClassicDry),
156        10 => Ok(VisualTyreCompound::ClassicWet),
157        11 => Ok(VisualTyreCompound::F2SuperSoft),
158        12 => Ok(VisualTyreCompound::F2Soft),
159        13 => Ok(VisualTyreCompound::F2Medium),
160        14 => Ok(VisualTyreCompound::F2Hard),
161        15 => Ok(VisualTyreCompound::F2Wet),
162        16 => Ok(VisualTyreCompound::F1Soft),
163        17 => Ok(VisualTyreCompound::F1Medium),
164        18 => Ok(VisualTyreCompound::F1Hard),
165        _ => Err(Error::new(
166            ErrorKind::InvalidData,
167            "Failed to decode visual tyre compound.",
168        )),
169    }
170}
171
172fn decode_tyre_damage(cursor: &mut Cursor<&mut BytesMut>) -> CornerProperty<u8> {
173    CornerProperty::new(
174        cursor.get_u8(),
175        cursor.get_u8(),
176        cursor.get_u8(),
177        cursor.get_u8(),
178    )
179}
180
181fn decode_ers_deploy_mode(cursor: &mut Cursor<&mut BytesMut>) -> Result<ErsDeployMode, Error> {
182    let value = cursor.get_u8();
183
184    match value {
185        0 => Ok(ErsDeployMode::None),
186        1 => Ok(ErsDeployMode::Low),
187        2 => Ok(ErsDeployMode::Medium),
188        3 => Ok(ErsDeployMode::High),
189        4 => Ok(ErsDeployMode::Overtake),
190        5 => Ok(ErsDeployMode::Hotlap),
191        _ => Err(Error::new(
192            ErrorKind::InvalidData,
193            "Failed to decode ERS deployment mode.",
194        )),
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use std::io::Cursor;
201
202    use assert_approx_eq::assert_approx_eq;
203    use bytes::{BufMut, BytesMut};
204
205    use crate::nineteen::status::{decode_statuses, PACKET_SIZE};
206    use crate::packet::status::{
207        DrsSetting, ErsDeployMode, FuelMix, PhysicalTyreCompound, TractionControl,
208        VisualTyreCompound,
209    };
210    use crate::types::Flag;
211
212    fn put_packet_header(mut bytes: BytesMut) -> BytesMut {
213        bytes.put_u16_le(2019);
214        bytes.put_u8(1);
215        bytes.put_u8(2);
216        bytes.put_u8(3);
217        bytes.put_u8(7);
218        bytes.put_u64_le(u64::max_value());
219        bytes.put_f32_le(1.0);
220        bytes.put_u32_le(u32::max_value());
221        bytes.put_u8(0);
222
223        bytes
224    }
225
226    #[test]
227    fn decode_statuses_with_error() {
228        let mut bytes = BytesMut::with_capacity(0);
229        let mut cursor = Cursor::new(&mut bytes);
230
231        let packet = decode_statuses(&mut cursor);
232        assert!(packet.is_err());
233    }
234
235    #[test]
236    #[allow(clippy::cognitive_complexity)]
237    fn decode_statuses_with_success() {
238        let mut bytes = BytesMut::with_capacity(PACKET_SIZE);
239        bytes = put_packet_header(bytes);
240
241        for _ in 0..20 {
242            bytes.put_u8(1);
243            bytes.put_u8(1);
244            bytes.put_u8(3);
245            bytes.put_u8(4);
246            bytes.put_u8(1);
247            bytes.put_f32_le(6.0);
248            bytes.put_f32_le(7.0);
249            bytes.put_f32_le(8.0);
250            bytes.put_u16_le(9);
251            bytes.put_u16_le(10);
252            bytes.put_u8(11);
253            bytes.put_i8(-1);
254            bytes.put_u8(13);
255            bytes.put_u8(14);
256            bytes.put_u8(15);
257            bytes.put_u8(16);
258            bytes.put_u8(17);
259            bytes.put_u8(18);
260            bytes.put_u8(19);
261            bytes.put_u8(20);
262            bytes.put_u8(21);
263            bytes.put_u8(22);
264            bytes.put_u8(23);
265            bytes.put_u8(24);
266            bytes.put_u8(25);
267            bytes.put_u8(26);
268            bytes.put_u8(27);
269            bytes.put_i8(-1);
270            bytes.put_f32_le(29.0);
271            bytes.put_u8(5);
272            bytes.put_f32_le(31.0);
273            bytes.put_f32_le(32.0);
274            bytes.put_f32_le(33.0);
275        }
276
277        let mut cursor = Cursor::new(&mut bytes);
278
279        let packet = decode_statuses(&mut cursor).unwrap();
280        let status = packet.statuses()[0];
281
282        assert_eq!(TractionControl::Low, status.traction_control());
283        assert!(status.abs());
284        assert_eq!(FuelMix::Max, status.fuel_mix());
285        assert_eq!(4, status.brake_bias());
286        assert!(status.pit_limiter());
287        assert_approx_eq!(6.0, status.fuel_remaining());
288        assert_approx_eq!(7.0, status.fuel_capacity());
289        assert_approx_eq!(8.0, status.fuel_remaining_laps());
290        assert_eq!(9, status.max_rpm());
291        assert_eq!(10, status.idle_rpm());
292        assert_eq!(11, status.gear_count());
293        assert_eq!(DrsSetting::Unknown, status.drs());
294        assert_eq!(13, status.tyre_wear().front_left());
295        assert_eq!(PhysicalTyreCompound::F1C4, status.physical_tyre_compound());
296        assert_eq!(VisualTyreCompound::F1Hard, status.visual_tyre_compound());
297        assert_eq!(19, status.tyre_damage().front_left());
298        assert_eq!(23, status.front_left_wing_damage());
299        assert_eq!(24, status.front_right_wing_damage());
300        assert_eq!(25, status.rear_wing_damage());
301        assert_eq!(26, status.engine_damage());
302        assert_eq!(27, status.gear_box_damage());
303        assert_eq!(Flag::Invalid, status.vehicle_flags());
304        assert_approx_eq!(29.0, status.ers_energy());
305        assert_eq!(ErsDeployMode::Hotlap, status.ers_deploy_mode());
306        assert_approx_eq!(31.0, status.ers_harvest_mgu_k());
307        assert_approx_eq!(32.0, status.ers_harvest_mgu_h());
308        assert_approx_eq!(33.0, status.ers_deployed());
309    }
310}