1use super::enhanced::{Value, Bitfield};
4use crate::error::Obd2Error;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
8pub enum ValueType {
9 Scalar,
11 Bitfield,
13 State,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct Pid(pub u8);
20
21impl Pid {
22 pub const SUPPORTED_PIDS_01_20: Pid = Pid(0x00);
24 pub const MONITOR_STATUS: Pid = Pid(0x01);
25 pub const FUEL_SYSTEM_STATUS: Pid = Pid(0x03);
26
27 pub const ENGINE_LOAD: Pid = Pid(0x04);
29 pub const COOLANT_TEMP: Pid = Pid(0x05);
30 pub const SHORT_FUEL_TRIM_B1: Pid = Pid(0x06);
31 pub const LONG_FUEL_TRIM_B1: Pid = Pid(0x07);
32 pub const SHORT_FUEL_TRIM_B2: Pid = Pid(0x08);
33 pub const LONG_FUEL_TRIM_B2: Pid = Pid(0x09);
34 pub const FUEL_PRESSURE: Pid = Pid(0x0A);
35 pub const INTAKE_MAP: Pid = Pid(0x0B);
36 pub const ENGINE_RPM: Pid = Pid(0x0C);
37 pub const VEHICLE_SPEED: Pid = Pid(0x0D);
38 pub const TIMING_ADVANCE: Pid = Pid(0x0E);
39 pub const INTAKE_AIR_TEMP: Pid = Pid(0x0F);
40 pub const MAF: Pid = Pid(0x10);
41 pub const THROTTLE_POSITION: Pid = Pid(0x11);
42
43 pub const OBD_STANDARD: Pid = Pid(0x1C);
45 pub const RUN_TIME: Pid = Pid(0x1F);
46
47 pub const SUPPORTED_PIDS_21_40: Pid = Pid(0x20);
49 pub const DISTANCE_WITH_MIL: Pid = Pid(0x21);
50 pub const FUEL_RAIL_GAUGE_PRESSURE: Pid = Pid(0x23);
51 pub const COMMANDED_EGR: Pid = Pid(0x2C);
52 pub const EGR_ERROR: Pid = Pid(0x2D);
53 pub const COMMANDED_EVAP_PURGE: Pid = Pid(0x2E);
54 pub const FUEL_TANK_LEVEL: Pid = Pid(0x2F);
55 pub const WARMUPS_SINCE_CLEAR: Pid = Pid(0x30);
56 pub const DISTANCE_SINCE_CLEAR: Pid = Pid(0x31);
57 pub const BAROMETRIC_PRESSURE: Pid = Pid(0x33);
58
59 pub const CATALYST_TEMP_B1S1: Pid = Pid(0x3C);
61 pub const CATALYST_TEMP_B2S1: Pid = Pid(0x3D);
62 pub const CATALYST_TEMP_B1S2: Pid = Pid(0x3E);
63 pub const CATALYST_TEMP_B2S2: Pid = Pid(0x3F);
64
65 pub const SUPPORTED_PIDS_41_60: Pid = Pid(0x40);
67 pub const CONTROL_MODULE_VOLTAGE: Pid = Pid(0x42);
68 pub const ABSOLUTE_LOAD: Pid = Pid(0x43);
69 pub const COMMANDED_EQUIV_RATIO: Pid = Pid(0x44);
70 pub const RELATIVE_THROTTLE_POS: Pid = Pid(0x45);
71 pub const AMBIENT_AIR_TEMP: Pid = Pid(0x46);
72 pub const ABS_THROTTLE_POS_B: Pid = Pid(0x47);
73 pub const ACCEL_PEDAL_POS_D: Pid = Pid(0x49);
74 pub const ACCEL_PEDAL_POS_E: Pid = Pid(0x4A);
75 pub const COMMANDED_THROTTLE_ACTUATOR: Pid = Pid(0x4C);
76
77 pub const ENGINE_OIL_TEMP: Pid = Pid(0x5C);
79 pub const ENGINE_FUEL_RATE: Pid = Pid(0x5E);
80 pub const FUEL_RAIL_ABS_PRESSURE: Pid = Pid(0x59);
81
82 pub const SUPPORTED_PIDS_61_80: Pid = Pid(0x60);
84 pub const DEMANDED_TORQUE: Pid = Pid(0x61);
85 pub const ACTUAL_TORQUE: Pid = Pid(0x62);
86 pub const REFERENCE_TORQUE: Pid = Pid(0x63);
87
88 pub fn name(&self) -> &'static str {
90 match self.0 {
91 0x00 => "Supported PIDs [01-20]",
92 0x01 => "Monitor Status",
93 0x03 => "Fuel System Status",
94 0x04 => "Engine Load",
95 0x05 => "Coolant Temperature",
96 0x06 => "Short Term Fuel Trim (Bank 1)",
97 0x07 => "Long Term Fuel Trim (Bank 1)",
98 0x08 => "Short Term Fuel Trim (Bank 2)",
99 0x09 => "Long Term Fuel Trim (Bank 2)",
100 0x0A => "Fuel Pressure",
101 0x0B => "Intake MAP",
102 0x0C => "Engine RPM",
103 0x0D => "Vehicle Speed",
104 0x0E => "Timing Advance",
105 0x0F => "Intake Air Temperature",
106 0x10 => "MAF Air Flow Rate",
107 0x11 => "Throttle Position",
108 0x1C => "OBD Standard",
109 0x1F => "Run Time Since Start",
110 0x20 => "Supported PIDs [21-40]",
111 0x21 => "Distance with MIL On",
112 0x23 => "Fuel Rail Gauge Pressure",
113 0x2C => "Commanded EGR",
114 0x2D => "EGR Error",
115 0x2E => "Commanded Evaporative Purge",
116 0x2F => "Fuel Tank Level",
117 0x30 => "Warm-ups Since Clear",
118 0x31 => "Distance Since DTC Clear",
119 0x33 => "Barometric Pressure",
120 0x3C => "Catalyst Temp B1S1",
121 0x3D => "Catalyst Temp B2S1",
122 0x3E => "Catalyst Temp B1S2",
123 0x3F => "Catalyst Temp B2S2",
124 0x40 => "Supported PIDs [41-60]",
125 0x42 => "Control Module Voltage",
126 0x43 => "Absolute Load",
127 0x44 => "Commanded Equivalence Ratio",
128 0x45 => "Relative Throttle Position",
129 0x46 => "Ambient Air Temperature",
130 0x47 => "Absolute Throttle Position B",
131 0x49 => "Accelerator Pedal Position D",
132 0x4A => "Accelerator Pedal Position E",
133 0x4C => "Commanded Throttle Actuator",
134 0x59 => "Fuel Rail Absolute Pressure",
135 0x5C => "Engine Oil Temperature",
136 0x5E => "Engine Fuel Rate",
137 0x60 => "Supported PIDs [61-80]",
138 0x61 => "Demanded Torque",
139 0x62 => "Actual Torque",
140 0x63 => "Reference Torque",
141 _ => "Unknown PID",
142 }
143 }
144
145 pub fn unit(&self) -> &'static str {
147 match self.0 {
148 0x00 | 0x01 | 0x03 | 0x20 | 0x40 | 0x60 => "bitfield",
149 0x04 | 0x06..=0x09 | 0x11 | 0x2C | 0x2D | 0x2E | 0x2F
150 | 0x43 | 0x45 | 0x47 | 0x49 | 0x4A | 0x4C | 0x61 | 0x62 => "%",
151 0x05 | 0x0F | 0x3C..=0x3F | 0x46 | 0x5C => "\u{00B0}C",
152 0x0A | 0x0B | 0x23 | 0x33 | 0x59 => "kPa",
153 0x0C => "RPM",
154 0x0D => "km/h",
155 0x0E => "\u{00B0}",
156 0x10 => "g/s",
157 0x1F => "s",
158 0x21 | 0x31 => "km",
159 0x30 => "count",
160 0x42 => "V",
161 0x44 => "\u{03BB}",
162 0x5E => "L/h",
163 0x63 => "Nm",
164 _ => "",
165 }
166 }
167
168 pub fn response_bytes(&self) -> u8 {
170 match self.0 {
171 0x00 | 0x01 | 0x20 | 0x40 | 0x60 => 4, 0x0C | 0x10 | 0x1F | 0x21 | 0x23 | 0x31 | 0x3C..=0x3F
173 | 0x42 | 0x43 | 0x44 | 0x59 | 0x5E | 0x63 => 2,
174 _ => 1, }
176 }
177
178 pub fn value_type(&self) -> ValueType {
180 match self.0 {
181 0x00 | 0x01 | 0x03 | 0x20 | 0x40 | 0x60 => ValueType::Bitfield,
182 0x1C => ValueType::State,
183 _ => ValueType::Scalar,
184 }
185 }
186
187 pub fn all() -> &'static [Pid] {
189 &[
190 Self::SUPPORTED_PIDS_01_20, Self::MONITOR_STATUS, Self::FUEL_SYSTEM_STATUS,
191 Self::ENGINE_LOAD, Self::COOLANT_TEMP,
192 Self::SHORT_FUEL_TRIM_B1, Self::LONG_FUEL_TRIM_B1,
193 Self::SHORT_FUEL_TRIM_B2, Self::LONG_FUEL_TRIM_B2,
194 Self::FUEL_PRESSURE, Self::INTAKE_MAP, Self::ENGINE_RPM,
195 Self::VEHICLE_SPEED, Self::TIMING_ADVANCE, Self::INTAKE_AIR_TEMP,
196 Self::MAF, Self::THROTTLE_POSITION, Self::OBD_STANDARD, Self::RUN_TIME,
197 Self::SUPPORTED_PIDS_21_40, Self::DISTANCE_WITH_MIL,
198 Self::FUEL_RAIL_GAUGE_PRESSURE, Self::COMMANDED_EGR, Self::EGR_ERROR,
199 Self::COMMANDED_EVAP_PURGE, Self::FUEL_TANK_LEVEL, Self::WARMUPS_SINCE_CLEAR,
200 Self::DISTANCE_SINCE_CLEAR, Self::BAROMETRIC_PRESSURE,
201 Self::CATALYST_TEMP_B1S1, Self::CATALYST_TEMP_B2S1,
202 Self::CATALYST_TEMP_B1S2, Self::CATALYST_TEMP_B2S2,
203 Self::SUPPORTED_PIDS_41_60, Self::CONTROL_MODULE_VOLTAGE,
204 Self::ABSOLUTE_LOAD, Self::COMMANDED_EQUIV_RATIO,
205 Self::RELATIVE_THROTTLE_POS, Self::AMBIENT_AIR_TEMP,
206 Self::ABS_THROTTLE_POS_B, Self::ACCEL_PEDAL_POS_D,
207 Self::ACCEL_PEDAL_POS_E, Self::COMMANDED_THROTTLE_ACTUATOR,
208 Self::FUEL_RAIL_ABS_PRESSURE, Self::ENGINE_OIL_TEMP,
209 Self::ENGINE_FUEL_RATE, Self::SUPPORTED_PIDS_61_80,
210 Self::DEMANDED_TORQUE, Self::ACTUAL_TORQUE, Self::REFERENCE_TORQUE,
211 ]
212 }
213
214 pub fn from_code(code: u8) -> Pid {
216 Pid(code)
217 }
218
219 pub fn parse(&self, data: &[u8]) -> Result<Value, Obd2Error> {
223 let expected = self.response_bytes() as usize;
225 if data.len() < expected {
226 return Err(Obd2Error::ParseError(format!(
227 "PID {:#04X} expects {} bytes, got {}", self.0, expected, data.len()
228 )));
229 }
230
231 let a = data[0] as f64;
232 let b = if data.len() > 1 { data[1] as f64 } else { 0.0 };
233 let _c = if data.len() > 2 { data[2] as f64 } else { 0.0 };
234 let _d = if data.len() > 3 { data[3] as f64 } else { 0.0 };
235
236 match self.0 {
237 0x00 | 0x20 | 0x40 | 0x60 => {
239 let raw = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
240 Ok(Value::Bitfield(Bitfield { raw, flags: vec![] }))
241 }
242
243 0x01 => {
245 let raw = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
246 let mil_on = (data[0] & 0x80) != 0;
247 let dtc_count = data[0] & 0x7F;
248 let flags = vec![
249 ("MIL".into(), mil_on),
250 (format!("{} DTCs", dtc_count), dtc_count > 0),
251 ("Compression Ignition".into(), (data[1] & 0x08) != 0),
252 ];
253 Ok(Value::Bitfield(Bitfield { raw, flags }))
254 }
255
256 0x03 => {
258 let raw = u32::from(data[0]);
259 Ok(Value::Bitfield(Bitfield { raw, flags: vec![] }))
260 }
261
262 0x04 | 0x11 | 0x2C | 0x2E | 0x2F | 0x45 | 0x47 | 0x49 | 0x4A | 0x4C => {
264 Ok(Value::Scalar(a * 100.0 / 255.0))
265 }
266
267 0x05 | 0x0F | 0x46 | 0x5C => {
269 Ok(Value::Scalar(a - 40.0))
270 }
271
272 0x06 | 0x07 | 0x08 | 0x09 | 0x2D => {
274 Ok(Value::Scalar((a - 128.0) * 100.0 / 128.0))
275 }
276
277 0x0A => Ok(Value::Scalar(a * 3.0)),
279
280 0x0B | 0x33 => Ok(Value::Scalar(a)),
282
283 0x0C => Ok(Value::Scalar((256.0 * a + b) / 4.0)),
285
286 0x0D => Ok(Value::Scalar(a)),
288
289 0x0E => Ok(Value::Scalar(a / 2.0 - 64.0)),
291
292 0x10 => Ok(Value::Scalar((256.0 * a + b) / 100.0)),
294
295 0x1C => Ok(Value::State(format!("Standard {}", data[0]))),
297
298 0x1F | 0x21 | 0x31 => Ok(Value::Scalar(256.0 * a + b)),
300
301 0x23 => Ok(Value::Scalar((256.0 * a + b) * 10.0)),
303
304 0x30 => Ok(Value::Scalar(a)),
306
307 0x3C..=0x3F => {
309 Ok(Value::Scalar((256.0 * a + b) / 10.0 - 40.0))
310 }
311
312 0x42 => Ok(Value::Scalar((256.0 * a + b) / 1000.0)),
314
315 0x43 => Ok(Value::Scalar((256.0 * a + b) * 100.0 / 255.0)),
317
318 0x44 => Ok(Value::Scalar((256.0 * a + b) / 32768.0)),
320
321 0x59 => Ok(Value::Scalar((256.0 * a + b) * 10.0)),
323
324 0x5E => Ok(Value::Scalar((256.0 * a + b) / 20.0)),
326
327 0x61 | 0x62 => Ok(Value::Scalar(a - 125.0)),
329
330 0x63 => Ok(Value::Scalar(256.0 * a + b)),
332
333 _ => Err(Obd2Error::ParseError(format!("no parse formula for PID {:#04X}", self.0))),
334 }
335 }
336}
337
338impl std::fmt::Display for Pid {
339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340 write!(f, "{} ({:#04X})", self.name(), self.0)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_pid_constants() {
350 assert_eq!(Pid::ENGINE_RPM.0, 0x0C);
351 assert_eq!(Pid::COOLANT_TEMP.0, 0x05);
352 assert_eq!(Pid::ENGINE_OIL_TEMP.0, 0x5C);
353 }
354
355 #[test]
356 fn test_pid_names() {
357 assert_eq!(Pid::ENGINE_RPM.name(), "Engine RPM");
358 assert_eq!(Pid::COOLANT_TEMP.name(), "Coolant Temperature");
359 }
360
361 #[test]
362 fn test_pid_units() {
363 assert_eq!(Pid::ENGINE_RPM.unit(), "RPM");
364 assert_eq!(Pid::COOLANT_TEMP.unit(), "\u{00B0}C");
365 assert_eq!(Pid::VEHICLE_SPEED.unit(), "km/h");
366 }
367
368 #[test]
369 fn test_pid_response_bytes() {
370 assert_eq!(Pid::ENGINE_RPM.response_bytes(), 2);
371 assert_eq!(Pid::COOLANT_TEMP.response_bytes(), 1);
372 assert_eq!(Pid::MONITOR_STATUS.response_bytes(), 4);
373 }
374
375 #[test]
376 fn test_pid_value_types() {
377 assert_eq!(Pid::ENGINE_RPM.value_type(), ValueType::Scalar);
378 assert_eq!(Pid::MONITOR_STATUS.value_type(), ValueType::Bitfield);
379 }
380
381 #[test]
382 fn test_all_pids_have_names() {
383 for pid in Pid::all() {
384 assert_ne!(pid.name(), "Unknown PID", "PID {:#04x} has no name", pid.0);
385 }
386 }
387
388 #[test]
389 fn test_pid_display() {
390 let s = format!("{}", Pid::ENGINE_RPM);
391 assert!(s.contains("Engine RPM"));
392 assert!(s.contains("0x0C"));
393 }
394
395 #[test]
396 fn test_parse_rpm() {
397 let data = [0x0C, 0x00]; let val = Pid::ENGINE_RPM.parse(&data).unwrap();
399 assert_eq!(val.as_f64().unwrap(), 768.0);
400 }
401
402 #[test]
403 fn test_parse_rpm_idle() {
404 let data = [0x0A, 0xA0]; let val = Pid::ENGINE_RPM.parse(&data).unwrap();
406 assert_eq!(val.as_f64().unwrap(), 680.0);
407 }
408
409 #[test]
410 fn test_parse_speed() {
411 let data = [0x3C]; let val = Pid::VEHICLE_SPEED.parse(&data).unwrap();
413 assert_eq!(val.as_f64().unwrap(), 60.0);
414 }
415
416 #[test]
417 fn test_parse_coolant_temp() {
418 let data = [0x7E]; let val = Pid::COOLANT_TEMP.parse(&data).unwrap();
420 assert_eq!(val.as_f64().unwrap(), 86.0);
421 }
422
423 #[test]
424 fn test_parse_coolant_temp_freezing() {
425 let data = [0x00]; let val = Pid::COOLANT_TEMP.parse(&data).unwrap();
427 assert_eq!(val.as_f64().unwrap(), -40.0);
428 }
429
430 #[test]
431 fn test_parse_fuel_trim_zero() {
432 let data = [0x80]; let val = Pid::SHORT_FUEL_TRIM_B1.parse(&data).unwrap();
434 assert!((val.as_f64().unwrap()).abs() < 0.01);
435 }
436
437 #[test]
438 fn test_parse_fuel_trim_rich() {
439 let data = [0x90]; let val = Pid::SHORT_FUEL_TRIM_B1.parse(&data).unwrap();
441 assert!((val.as_f64().unwrap() - 12.5).abs() < 0.01);
442 }
443
444 #[test]
445 fn test_parse_control_module_voltage() {
446 let data = [0x38, 0x5C]; let val = Pid(0x42).parse(&data).unwrap();
448 assert!((val.as_f64().unwrap() - 14.428).abs() < 0.001);
449 }
450
451 #[test]
452 fn test_parse_maf() {
453 let data = [0x01, 0xF4]; let val = Pid::MAF.parse(&data).unwrap();
455 assert!((val.as_f64().unwrap() - 5.0).abs() < 0.01);
456 }
457
458 #[test]
459 fn test_parse_catalyst_temp() {
460 let data = [0x11, 0x0E]; let val = Pid::CATALYST_TEMP_B1S1.parse(&data).unwrap();
462 assert!((val.as_f64().unwrap() - 396.6).abs() < 0.1);
463 }
464
465 #[test]
466 fn test_parse_fuel_rate() {
467 let data = [0x00, 0xC8]; let val = Pid::ENGINE_FUEL_RATE.parse(&data).unwrap();
469 assert!((val.as_f64().unwrap() - 10.0).abs() < 0.01);
470 }
471
472 #[test]
473 fn test_parse_monitor_status_bitfield() {
474 let data = [0x00, 0x07, 0x65, 0x00];
475 let val = Pid::MONITOR_STATUS.parse(&data).unwrap();
476 let bf = val.as_bitfield().unwrap();
477 assert!(!bf.flags.iter().find(|(n, _)| n == "MIL").unwrap().1); }
479
480 #[test]
481 fn test_parse_insufficient_bytes() {
482 let data = [0x0C]; let result = Pid::ENGINE_RPM.parse(&data);
484 assert!(result.is_err());
485 }
486
487 #[test]
488 fn test_parse_timing_advance() {
489 let data = [0x8C]; let val = Pid::TIMING_ADVANCE.parse(&data).unwrap();
491 assert!((val.as_f64().unwrap() - 6.0).abs() < 0.01);
492 }
493
494 #[test]
495 fn test_parse_torque_percent() {
496 let data = [0xAF]; let val = Pid::ACTUAL_TORQUE.parse(&data).unwrap();
498 assert_eq!(val.as_f64().unwrap(), 50.0);
499 }
500
501 #[test]
502 fn test_parse_reference_torque() {
503 let data = [0x03, 0x7F]; let val = Pid::REFERENCE_TORQUE.parse(&data).unwrap();
505 assert_eq!(val.as_f64().unwrap(), 895.0);
506 }
507}