Skip to main content

obd2_core/protocol/
dtc.rs

1//! DTC types and status definitions.
2
3/// A Diagnostic Trouble Code read from the vehicle.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct Dtc {
6    pub code: String,
7    pub category: DtcCategory,
8    pub status: DtcStatus,
9    pub description: Option<String>,
10    pub severity: Option<Severity>,
11    pub source_module: Option<String>,
12    pub notes: Option<String>,
13}
14
15impl Default for Dtc {
16    fn default() -> Self {
17        Self {
18            code: String::new(),
19            category: DtcCategory::Powertrain,
20            status: DtcStatus::Stored,
21            description: None,
22            severity: None,
23            source_module: None,
24            notes: None,
25        }
26    }
27}
28
29impl Dtc {
30    /// Decode a DTC from two raw bytes (Mode 03/07/0A response).
31    /// Bits 15-14 = category, bits 13-12 = second char, bits 11-8 = third, 7-4 = fourth, 3-0 = fifth.
32    pub fn from_bytes(a: u8, b: u8) -> Self {
33        let category = match (a >> 6) & 0x03 {
34            0 => DtcCategory::Powertrain,
35            1 => DtcCategory::Chassis,
36            2 => DtcCategory::Body,
37            _ => DtcCategory::Network,
38        };
39        let prefix = match category {
40            DtcCategory::Powertrain => 'P',
41            DtcCategory::Chassis => 'C',
42            DtcCategory::Body => 'B',
43            DtcCategory::Network => 'U',
44        };
45        let d2 = (a >> 4) & 0x03;
46        let d3 = a & 0x0F;
47        let d4 = (b >> 4) & 0x0F;
48        let d5 = b & 0x0F;
49        let code = format!("{}{}{:X}{:X}{:X}", prefix, d2, d3, d4, d5);
50
51        let description = universal_dtc_description(&code).map(String::from);
52
53        Self {
54            code,
55            category,
56            status: DtcStatus::Stored,
57            description,
58            severity: None,
59            source_module: None,
60            notes: None,
61        }
62    }
63
64    /// Create a DTC from a code string (e.g., "P0420").
65    pub fn from_code(code: &str) -> Self {
66        let category = match code.chars().next() {
67            Some('P') | Some('p') => DtcCategory::Powertrain,
68            Some('C') | Some('c') => DtcCategory::Chassis,
69            Some('B') | Some('b') => DtcCategory::Body,
70            Some('U') | Some('u') => DtcCategory::Network,
71            _ => DtcCategory::Powertrain,
72        };
73        let upper = code.to_uppercase();
74        let description = universal_dtc_description(&upper).map(String::from);
75        Self {
76            code: upper,
77            category,
78            status: DtcStatus::Stored,
79            description,
80            severity: None,
81            source_module: None,
82            notes: None,
83        }
84    }
85}
86
87/// Category prefix of a DTC (P, C, B, U).
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89pub enum DtcCategory {
90    Powertrain,
91    Chassis,
92    Body,
93    Network,
94}
95
96/// Lifecycle status of a DTC.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum DtcStatus {
99    Stored,
100    Pending,
101    Permanent,
102}
103
104/// Severity level of a DTC.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
106pub enum Severity {
107    Info,
108    Low,
109    Medium,
110    High,
111    Critical,
112}
113
114/// GM/UDS Mode 19 extended status byte -- 8 flags per DTC.
115#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
116pub struct DtcStatusByte {
117    pub test_failed: bool,
118    pub test_failed_this_cycle: bool,
119    pub pending: bool,
120    pub confirmed: bool,
121    pub test_not_completed_since_clear: bool,
122    pub test_failed_since_clear: bool,
123    pub test_not_completed_this_cycle: bool,
124    pub warning_indicator_requested: bool,
125}
126
127impl DtcStatusByte {
128    /// Decode a Mode 19 DTC status byte into individual flags.
129    pub fn from_byte(b: u8) -> Self {
130        Self {
131            test_failed: (b & 0x01) != 0,
132            test_failed_this_cycle: (b & 0x02) != 0,
133            pending: (b & 0x04) != 0,
134            confirmed: (b & 0x08) != 0,
135            test_not_completed_since_clear: (b & 0x10) != 0,
136            test_failed_since_clear: (b & 0x20) != 0,
137            test_not_completed_this_cycle: (b & 0x40) != 0,
138            warning_indicator_requested: (b & 0x80) != 0,
139        }
140    }
141
142    /// Encode the status flags back into a single byte.
143    pub fn to_byte(&self) -> u8 {
144        let mut b = 0u8;
145        if self.test_failed {
146            b |= 0x01;
147        }
148        if self.test_failed_this_cycle {
149            b |= 0x02;
150        }
151        if self.pending {
152            b |= 0x04;
153        }
154        if self.confirmed {
155            b |= 0x08;
156        }
157        if self.test_not_completed_since_clear {
158            b |= 0x10;
159        }
160        if self.test_failed_since_clear {
161            b |= 0x20;
162        }
163        if self.test_not_completed_this_cycle {
164            b |= 0x40;
165        }
166        if self.warning_indicator_requested {
167            b |= 0x80;
168        }
169        b
170    }
171}
172
173/// Look up the universal (SAE J2012) description for a DTC code.
174///
175/// Covers ~200 common OBD-II codes across powertrain, chassis, body, and network categories.
176pub fn universal_dtc_description(code: &str) -> Option<&'static str> {
177    match code {
178        // VVT / Camshaft
179        "P0010" => Some("Intake Camshaft Position Actuator Circuit (Bank 1)"),
180        "P0011" => Some("Intake Camshaft Position Timing Over-Advanced (Bank 1)"),
181        "P0012" => Some("Intake Camshaft Position Timing Over-Retarded (Bank 1)"),
182        "P0014" => Some("Exhaust Camshaft Position Timing Over-Advanced (Bank 1)"),
183        "P0016" => Some("Crankshaft Position / Camshaft Position Correlation (Bank 1 Sensor A)"),
184        // O2 Sensor Heaters
185        "P0030" => Some("HO2S Heater Control Circuit (Bank 1 Sensor 1)"),
186        "P0036" => Some("HO2S Heater Control Circuit (Bank 1 Sensor 2)"),
187        // Diesel / Fuel Rail
188        "P0087" => Some("Fuel Rail/System Pressure Too Low"),
189        "P0088" => Some("Fuel Rail/System Pressure Too High"),
190        "P0093" => Some("Fuel System Leak Detected (Large)"),
191        // Fuel & Air Metering
192        "P0100" => Some("MAF/VAF Circuit Malfunction"),
193        "P0101" => Some("MAF/VAF Circuit Range/Performance"),
194        "P0102" => Some("MAF/VAF Circuit Low Input"),
195        "P0103" => Some("MAF/VAF Circuit High Input"),
196        "P0106" => Some("MAP/Barometric Pressure Circuit Range/Performance"),
197        "P0107" => Some("MAP/Barometric Pressure Circuit Low Input"),
198        "P0108" => Some("MAP/Barometric Pressure Circuit High Input"),
199        "P0110" => Some("Intake Air Temperature Sensor 1 Circuit"),
200        "P0111" => Some("Intake Air Temperature Sensor 1 Circuit Range/Performance"),
201        "P0112" => Some("Intake Air Temperature Sensor 1 Circuit Low"),
202        "P0113" => Some("Intake Air Temperature Sensor 1 Circuit High"),
203        "P0115" => Some("Engine Coolant Temperature Circuit"),
204        "P0116" => Some("Engine Coolant Temperature Circuit Range/Performance"),
205        "P0117" => Some("Engine Coolant Temperature Circuit Low"),
206        "P0118" => Some("Engine Coolant Temperature Circuit High"),
207        "P0120" => Some("Throttle Position Sensor A Circuit"),
208        "P0121" => Some("Throttle Position Sensor A Circuit Range/Performance"),
209        "P0122" => Some("Throttle Position Sensor A Circuit Low"),
210        "P0123" => Some("Throttle Position Sensor A Circuit High"),
211        "P0125" => Some("Insufficient Coolant Temperature for Closed Loop"),
212        "P0128" => Some("Coolant Thermostat Below Operating Temperature"),
213        "P0130" => Some("O2 Sensor Circuit Bank 1 Sensor 1"),
214        "P0131" => Some("O2 Sensor Circuit Low Voltage B1S1"),
215        "P0132" => Some("O2 Sensor Circuit High Voltage B1S1"),
216        "P0133" => Some("O2 Sensor Circuit Slow Response B1S1"),
217        "P0134" => Some("O2 Sensor Circuit No Activity B1S1"),
218        "P0135" => Some("O2 Sensor Heater Circuit B1S1"),
219        "P0136" => Some("O2 Sensor Circuit Bank 1 Sensor 2"),
220        "P0137" => Some("O2 Sensor Circuit Low Voltage B1S2"),
221        "P0138" => Some("O2 Sensor Circuit High Voltage B1S2"),
222        "P0139" => Some("O2 Sensor Circuit Slow Response B1S2"),
223        "P0140" => Some("O2 Sensor Circuit No Activity B1S2"),
224        "P0141" => Some("O2 Sensor Heater Circuit B1S2"),
225        "P0150" => Some("O2 Sensor Circuit Bank 2 Sensor 1"),
226        "P0151" => Some("O2 Sensor Circuit Low Voltage B2S1"),
227        "P0152" => Some("O2 Sensor Circuit High Voltage B2S1"),
228        "P0153" => Some("O2 Sensor Circuit Slow Response B2S1"),
229        "P0154" => Some("O2 Sensor Circuit No Activity B2S1"),
230        "P0155" => Some("O2 Sensor Heater Circuit B2S1"),
231        "P0156" => Some("O2 Sensor Circuit Bank 2 Sensor 2"),
232        "P0157" => Some("O2 Sensor Circuit Low Voltage B2S2"),
233        "P0158" => Some("O2 Sensor Circuit High Voltage B2S2"),
234        "P0159" => Some("O2 Sensor Circuit Slow Response B2S2"),
235        "P0160" => Some("O2 Sensor Circuit No Activity B2S2"),
236        "P0161" => Some("O2 Sensor Heater Circuit B2S2"),
237        "P0171" => Some("System Too Lean Bank 1"),
238        "P0172" => Some("System Too Rich Bank 1"),
239        "P0174" => Some("System Too Lean Bank 2"),
240        "P0175" => Some("System Too Rich Bank 2"),
241        // Fuel Rail Pressure Sensor
242        "P0192" => Some("Fuel Rail Pressure Sensor Circuit Low"),
243        "P0193" => Some("Fuel Rail Pressure Sensor Circuit High"),
244        // Injector Circuits
245        "P0201" => Some("Injector Circuit/Open Cylinder 1"),
246        "P0202" => Some("Injector Circuit/Open Cylinder 2"),
247        "P0203" => Some("Injector Circuit/Open Cylinder 3"),
248        "P0204" => Some("Injector Circuit/Open Cylinder 4"),
249        "P0205" => Some("Injector Circuit/Open Cylinder 5"),
250        "P0206" => Some("Injector Circuit/Open Cylinder 6"),
251        "P0207" => Some("Injector Circuit/Open Cylinder 7"),
252        "P0208" => Some("Injector Circuit/Open Cylinder 8"),
253        // Turbo / Boost
254        "P0234" => Some("Turbo/Supercharger Overboost Condition"),
255        "P0236" => Some("Turbocharger Boost Sensor A Circuit Range/Performance"),
256        "P0299" => Some("Turbo/Supercharger Underboost"),
257        // Ignition / Misfire
258        "P0300" => Some("Random/Multiple Cylinder Misfire Detected"),
259        "P0301" => Some("Cylinder 1 Misfire Detected"),
260        "P0302" => Some("Cylinder 2 Misfire Detected"),
261        "P0303" => Some("Cylinder 3 Misfire Detected"),
262        "P0304" => Some("Cylinder 4 Misfire Detected"),
263        "P0305" => Some("Cylinder 5 Misfire Detected"),
264        "P0306" => Some("Cylinder 6 Misfire Detected"),
265        "P0307" => Some("Cylinder 7 Misfire Detected"),
266        "P0308" => Some("Cylinder 8 Misfire Detected"),
267        "P0325" => Some("Knock Sensor 1 Circuit Bank 1"),
268        "P0335" => Some("Crankshaft Position Sensor A Circuit"),
269        "P0336" => Some("Crankshaft Position Sensor A Range/Performance"),
270        "P0340" => Some("Camshaft Position Sensor A Circuit Bank 1"),
271        "P0341" => Some("Camshaft Position Sensor A Range/Performance Bank 1"),
272        // Camshaft Position Sensors
273        "P0365" => Some("Camshaft Position Sensor B Circuit (Bank 1)"),
274        "P0366" => Some("Camshaft Position Sensor B Circuit Range/Performance (Bank 1)"),
275        // Glow Plug
276        "P0380" => Some("Glow Plug/Heater Circuit A Malfunction"),
277        // Emission Controls
278        "P0400" => Some("EGR Flow Malfunction"),
279        "P0401" => Some("EGR Flow Insufficient"),
280        "P0402" => Some("EGR Flow Excessive"),
281        "P0404" => Some("EGR System Range/Performance"),
282        "P0405" => Some("EGR Position Sensor A Circuit Low"),
283        "P0406" => Some("EGR Position Sensor A Circuit High"),
284        "P0420" => Some("Catalyst System Efficiency Below Threshold Bank 1"),
285        "P0430" => Some("Catalyst System Efficiency Below Threshold Bank 2"),
286        "P0440" => Some("Evaporative Emission System Malfunction"),
287        "P0441" => Some("Evaporative Emission System Incorrect Purge Flow"),
288        "P0442" => Some("Evaporative Emission System Leak Detected (Small)"),
289        "P0443" => Some("Evaporative Emission System Purge Control Valve Circuit"),
290        "P0446" => Some("Evaporative Emission System Vent Control Circuit"),
291        "P0449" => Some("Evaporative Emission System Vent Valve/Solenoid Circuit"),
292        "P0451" => Some("Evaporative Emission System Pressure Sensor Range/Performance"),
293        "P0455" => Some("Evaporative Emission System Leak Detected (Large)"),
294        "P0456" => Some("Evaporative Emission System Leak Detected (Very Small)"),
295        "P0496" => Some("Evaporative Emission System High Purge Flow"),
296        // Speed / Idle
297        "P0500" => Some("Vehicle Speed Sensor A Malfunction"),
298        "P0503" => Some("Vehicle Speed Sensor Intermittent/Erratic/High"),
299        "P0505" => Some("Idle Air Control System Malfunction"),
300        "P0506" => Some("Idle Air Control System RPM Lower Than Expected"),
301        "P0507" => Some("Idle Air Control System RPM Higher Than Expected"),
302        // Electrical
303        "P0562" => Some("System Voltage Low"),
304        "P0563" => Some("System Voltage High"),
305        // Thermostat
306        "P0597" => Some("Thermostat Heater Control Circuit Open"),
307        "P0598" => Some("Thermostat Heater Control Circuit Low"),
308        "P0599" => Some("Thermostat Heater Control Circuit High"),
309        // ECM
310        "P0600" => Some("Serial Communication Link Malfunction"),
311        "P0601" => Some("Internal Control Module Memory Check Sum Error"),
312        "P0602" => Some("Control Module Programming Error"),
313        "P0606" => Some("ECM/PCM Processor Fault"),
314        // Transmission
315        "P0700" => Some("Transmission Control System Malfunction"),
316        "P0705" => Some("Transmission Range Sensor Circuit Malfunction"),
317        "P0710" => Some("Transmission Fluid Temperature Sensor Circuit"),
318        "P0711" => Some("Transmission Fluid Temperature Sensor Circuit Range/Performance"),
319        "P0715" => Some("Input/Turbine Speed Sensor Circuit"),
320        "P0717" => Some("Input/Turbine Speed Sensor Circuit No Signal"),
321        "P0720" => Some("Output Speed Sensor Circuit"),
322        "P0725" => Some("Engine Speed Input Circuit"),
323        "P0730" => Some("Incorrect Gear Ratio"),
324        "P0731" => Some("Gear 1 Incorrect Ratio"),
325        "P0732" => Some("Gear 2 Incorrect Ratio"),
326        "P0733" => Some("Gear 3 Incorrect Ratio"),
327        "P0734" => Some("Gear 4 Incorrect Ratio"),
328        "P0735" => Some("Gear 5 Incorrect Ratio"),
329        "P0740" => Some("Torque Converter Clutch Circuit Malfunction"),
330        "P0741" => Some("Torque Converter Clutch System Stuck Off"),
331        "P0742" => Some("Torque Converter Clutch System Stuck On"),
332        "P0743" => Some("Torque Converter Clutch System Electrical"),
333        "P0747" => Some("Pressure Control Solenoid A Stuck On"),
334        "P0748" => Some("Pressure Control Solenoid A Electrical"),
335        "P0750" => Some("Shift Solenoid A Malfunction"),
336        "P0751" => Some("Shift Solenoid A Performance/Stuck Off"),
337        "P0752" => Some("Shift Solenoid A Stuck On"),
338        "P0753" => Some("Shift Solenoid A Electrical"),
339        "P0755" => Some("Shift Solenoid B Malfunction"),
340        "P0756" => Some("Shift Solenoid B Performance/Stuck Off"),
341        "P0757" => Some("Shift Solenoid B Stuck On"),
342        "P0758" => Some("Shift Solenoid B Electrical"),
343        // GM-Specific / Manufacturer
344        "P1101" => Some("Intake Airflow System Performance"),
345        "P2097" => Some("Post Catalyst Fuel Trim System Too Rich (Bank 1)"),
346        "P2227" => Some("Barometric Pressure Circuit Range/Performance"),
347        "P2270" => Some("O2 Sensor Signal Stuck Lean (Bank 1 Sensor 2)"),
348        "P2271" => Some("O2 Sensor Signal Stuck Rich (Bank 1 Sensor 2)"),
349        "P2797" => Some("Auxiliary Transmission Fluid Pump Performance"),
350        // Body (B) Codes
351        "B0083" => Some("Left Side/Front Impact Sensor Circuit"),
352        "B0092" => Some("Left Side/Rear Impact Sensor Circuit"),
353        "B0096" => Some("Right Side/Rear Impact Sensor Circuit"),
354        "B0408" => Some("Temperature Control A Circuit"),
355        "B1325" => Some("Control Module General Memory Failure"),
356        "B1517" => Some("Steering Wheel Controls Switch 1 Circuit"),
357        // Chassis (C) Codes
358        "C0035" => Some("Left Front Wheel Speed Sensor Circuit"),
359        "C0040" => Some("Right Front Wheel Speed Sensor Circuit"),
360        "C0045" => Some("Left Rear Wheel Speed Sensor Circuit"),
361        "C0050" => Some("Right Rear Wheel Speed Sensor Circuit"),
362        "C0110" => Some("Pump Motor Circuit Malfunction"),
363        "C0161" => Some("ABS/TCS Brake Switch Circuit Malfunction"),
364        "C0186" => Some("Lateral Accelerometer Circuit"),
365        "C0196" => Some("Yaw Rate Sensor Circuit"),
366        "C0550" => Some("ECU Malfunction (Stability System)"),
367        "C0899" => Some("Device Voltage Low"),
368        "C0900" => Some("Device Voltage High"),
369        // Network (U) Codes
370        "U0001" => Some("High Speed CAN Communication Bus"),
371        "U0073" => Some("Control Module Communication Bus Off"),
372        "U0100" => Some("Lost Communication with ECM/PCM"),
373        "U0101" => Some("Lost Communication with TCM"),
374        "U0121" => Some("Lost Communication with ABS"),
375        "U0140" => Some("Lost Communication with BCM"),
376        "U0146" => Some("Lost Communication with Gateway A"),
377        "U0151" => Some("Lost Communication with SIR Module"),
378        "U0155" => Some("Lost Communication with Instrument Panel Cluster"),
379        "U0168" => Some("Lost Communication with HVAC Control Module"),
380        "U0184" => Some("Lost Communication with Radio"),
381        "U0401" => Some("Invalid Data Received from ECM/PCM A"),
382        _ => None,
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[test]
391    fn test_dtc_default() {
392        let dtc = Dtc::default();
393        assert_eq!(dtc.category, DtcCategory::Powertrain);
394        assert_eq!(dtc.status, DtcStatus::Stored);
395        assert!(dtc.description.is_none());
396    }
397
398    #[test]
399    fn test_severity_ordering() {
400        assert!(Severity::Critical > Severity::High);
401        assert!(Severity::High > Severity::Medium);
402        assert!(Severity::Medium > Severity::Low);
403        assert!(Severity::Low > Severity::Info);
404    }
405
406    #[test]
407    fn test_dtc_status_byte_default() {
408        let status = DtcStatusByte::default();
409        assert!(!status.test_failed);
410        assert!(!status.confirmed);
411        assert!(!status.warning_indicator_requested);
412    }
413
414    #[test]
415    fn test_dtc_from_bytes_powertrain() {
416        let dtc = Dtc::from_bytes(0x04, 0x20);
417        assert_eq!(dtc.code, "P0420");
418        assert_eq!(dtc.category, DtcCategory::Powertrain);
419        assert!(dtc.description.is_some());
420        assert!(dtc.description.unwrap().contains("Catalyst"));
421    }
422
423    #[test]
424    fn test_dtc_from_bytes_chassis() {
425        let dtc = Dtc::from_bytes(0x40, 0x35);
426        assert_eq!(dtc.code, "C0035");
427        assert_eq!(dtc.category, DtcCategory::Chassis);
428    }
429
430    #[test]
431    fn test_dtc_from_bytes_body() {
432        let dtc = Dtc::from_bytes(0x80, 0x83);
433        assert_eq!(dtc.code, "B0083");
434        assert_eq!(dtc.category, DtcCategory::Body);
435    }
436
437    #[test]
438    fn test_dtc_from_bytes_network() {
439        let dtc = Dtc::from_bytes(0xC1, 0x00);
440        assert_eq!(dtc.code, "U0100");
441        assert_eq!(dtc.category, DtcCategory::Network);
442    }
443
444    #[test]
445    fn test_dtc_from_code() {
446        let dtc = Dtc::from_code("P0420");
447        assert_eq!(dtc.code, "P0420");
448        assert_eq!(dtc.category, DtcCategory::Powertrain);
449        assert!(dtc.description.is_some());
450    }
451
452    #[test]
453    fn test_dtc_from_code_lowercase() {
454        let dtc = Dtc::from_code("p0171");
455        assert_eq!(dtc.code, "P0171");
456    }
457
458    #[test]
459    fn test_universal_description_known() {
460        assert!(universal_dtc_description("P0420").is_some());
461        assert!(universal_dtc_description("P0300").unwrap().contains("Misfire"));
462    }
463
464    #[test]
465    fn test_universal_description_unknown() {
466        assert!(universal_dtc_description("P9999").is_none());
467    }
468
469    #[test]
470    fn test_status_byte_decode() {
471        let status = DtcStatusByte::from_byte(0x0B); // bits 0,1,3
472        assert!(status.test_failed);
473        assert!(status.test_failed_this_cycle);
474        assert!(!status.pending);
475        assert!(status.confirmed);
476    }
477
478    #[test]
479    fn test_status_byte_roundtrip() {
480        let original: u8 = 0xAF;
481        let status = DtcStatusByte::from_byte(original);
482        assert_eq!(status.to_byte(), original);
483    }
484
485    #[test]
486    fn test_status_byte_all_flags() {
487        let status = DtcStatusByte::from_byte(0xFF);
488        assert!(status.test_failed);
489        assert!(status.test_failed_this_cycle);
490        assert!(status.pending);
491        assert!(status.confirmed);
492        assert!(status.test_not_completed_since_clear);
493        assert!(status.test_failed_since_clear);
494        assert!(status.test_not_completed_this_cycle);
495        assert!(status.warning_indicator_requested);
496    }
497
498    #[test]
499    fn test_status_byte_mil_on() {
500        let status = DtcStatusByte::from_byte(0x80);
501        assert!(status.warning_indicator_requested); // MIL bit
502        assert!(!status.test_failed);
503    }
504}