1use 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
20pub const PACKET_SIZE: usize = 1143;
22
23pub 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}