1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Module for common Diagnostic trouble code data

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// DTC name interpretation format specifier
pub enum DTCFormatType {
    /// ISO15031-6 DTC Format
    Iso15031_6,
    /// ISO14229-1 DTC Format
    Iso14229_1,
    /// SAE J1939-73 DTC Format
    SaeJ1939_73,
    /// ISO11992-4 DTC Format
    Iso11992_4,
    /// Unknown DTC Format
    Unknown(u8),
    /// 2 byte hex (KWP2000)
    TwoByteHexKwp,
}

pub(crate) fn dtc_format_from_uds(fmt: u8) -> DTCFormatType {
    match fmt {
        0x00 => DTCFormatType::Iso15031_6,
        0x01 => DTCFormatType::Iso14229_1,
        0x02 => DTCFormatType::SaeJ1939_73,
        0x03 => DTCFormatType::Iso11992_4,
        x => DTCFormatType::Unknown(x),
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Storage state of the DTC
pub enum DTCStatus {
    /// No DTC is stored in non volatile memory
    None,
    /// DTC has not met criteria for it to become active or stored,
    /// but a failure condition has been met
    Pending,
    /// DTC is no longer present, but is stored in non volatile memory
    Stored,
    /// DTC is present and stored in non volatile memory
    Active,
    /// Permanent (Can NOT be cleared from the ECU!)
    Permanent,
    /// Unknown DTC Status
    Unknown(u8),
}

impl DTCStatus {
    pub(crate) fn from_kwp_status(x: u8) -> DTCStatus {
        match (x & 0b01100000) >> 5 {
            0b00 => Self::None,
            0b01 => Self::Stored,
            0b10 => Self::Pending,
            0b11 => Self::Active,
            _ => Self::Unknown(x & 0b01100000), // Should never happen
        }
    }
}

/// Diagnostic trouble code (DTC) storage struct
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct DTC {
    /// The [DTCFormatType] of the DTC. This is used
    /// to interpret the raw value of the DTC   
    pub format: DTCFormatType,
    /// The raw value of the DTC according to the ECU
    pub raw: u32,
    /// Status of the DTC
    pub status: DTCStatus,
    /// Indication if the DTC turns on the MIL lamp (Malfunction indicator lamp).
    /// This usually means that the Check engine light is illuminated on the
    /// vehicles instrument cluster
    pub mil_on: bool,
    /// Indication if the DTC conditions have been met since the last clear.
    pub readiness_flag: bool,
}

impl DTC {
    /// Returns the error in a string format. EG: raw of 8276 = error P
    pub fn get_name_as_string(&self) -> String {
        match self.format {
            DTCFormatType::Iso15031_6 => {
                // 2 bytes
                let b0 = (self.raw >> 8) as u8;
                let b1 = self.raw as u8;
                let component_prefix = match b0 >> 6 {
                    0 => "P",
                    1 => "C",
                    2 => "B",
                    3 => "U",
                    _ => "N", // Should never happen
                };
                format!(
                    "{}{:01X}{:01X}{:01X}{:01X}",
                    component_prefix,
                    ((b0 & 0x30) >> 4),
                    b0 & 0x0F,
                    b1 >> 4,
                    b1 & 0x0F
                )
            }
            DTCFormatType::TwoByteHexKwp => {
                let component_prefix = match (self.raw as u16 & 0b110000000000000) >> 14 {
                    0b00 => "P",
                    0b01 => "C",
                    0b10 => "B",
                    0b11 => "U",
                    _ => "", // Should never happen
                };
                format!("{}{:04X}", component_prefix, self.raw & 0b11111111111111)
                // 14 bits
            }
            _ => format!("{}", self.raw),
        }
    }
}

#[cfg(test)]
pub mod test {
    use super::DTC;

    #[test]
    pub fn test_dtc_parse_raw() {
        let iso15031_6_dtc = DTC {
            format: super::DTCFormatType::Iso15031_6,
            raw: 8276,
            status: super::DTCStatus::None,
            mil_on: false,
            readiness_flag: false,
        };
        println!("{:04X}", iso15031_6_dtc.raw);
        println!("{}", iso15031_6_dtc.get_name_as_string());
    }
}