1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum DiagSession {
6 Default,
7 Programming,
8 Extended,
9}
10
11#[derive(Debug, Clone)]
13pub enum ActuatorCommand {
14 ReturnToEcu,
15 Adjust(Vec<u8>),
16 Activate,
17}
18
19#[derive(Debug, Clone)]
21pub struct ReadinessStatus {
22 pub mil_on: bool,
23 pub dtc_count: u8,
24 pub compression_ignition: bool,
25 pub monitors: Vec<MonitorStatus>,
26}
27
28#[derive(Debug, Clone)]
30pub struct MonitorStatus {
31 pub name: String,
32 pub supported: bool,
33 pub complete: bool,
34}
35
36#[derive(Debug, Clone)]
38pub struct TestResult {
39 pub test_id: u8,
40 pub name: String,
41 pub value: f64,
42 pub min: Option<f64>,
43 pub max: Option<f64>,
44 pub passed: bool,
45 pub unit: String,
46}
47
48#[derive(Debug, Clone)]
50pub struct VehicleInfo {
51 pub vin: String,
52 pub calibration_ids: Vec<String>,
53 pub cvns: Vec<u32>,
54 pub ecu_name: Option<String>,
55}
56
57#[derive(Debug, Clone)]
59pub struct DtcDetail {
60 pub code: String,
61 pub occurrence_count: u16,
62 pub aging_counter: u16,
63}
64
65#[derive(Debug, Clone)]
67pub struct O2TestResult {
68 pub test_id: u8,
69 pub test_name: &'static str,
70 pub sensor: O2SensorLocation,
71 pub value: f64,
72 pub unit: &'static str,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum O2SensorLocation {
78 Bank1Sensor1,
79 Bank1Sensor2,
80 Bank2Sensor1,
81 Bank2Sensor2,
82 Bank3Sensor1,
83 Bank3Sensor2,
84 Bank4Sensor1,
85 Bank4Sensor2,
86}
87
88impl O2SensorLocation {
89 pub fn from_byte(b: u8) -> Option<Self> {
91 match b {
92 0x01 => Some(Self::Bank1Sensor1),
93 0x02 => Some(Self::Bank1Sensor2),
94 0x03 => Some(Self::Bank2Sensor1),
95 0x04 => Some(Self::Bank2Sensor2),
96 0x05 => Some(Self::Bank3Sensor1),
97 0x06 => Some(Self::Bank3Sensor2),
98 0x07 => Some(Self::Bank4Sensor1),
99 0x08 => Some(Self::Bank4Sensor2),
100 _ => None,
101 }
102 }
103}
104
105impl std::fmt::Display for O2SensorLocation {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match self {
108 Self::Bank1Sensor1 => write!(f, "B1S1"),
109 Self::Bank1Sensor2 => write!(f, "B1S2"),
110 Self::Bank2Sensor1 => write!(f, "B2S1"),
111 Self::Bank2Sensor2 => write!(f, "B2S2"),
112 Self::Bank3Sensor1 => write!(f, "B3S1"),
113 Self::Bank3Sensor2 => write!(f, "B3S2"),
114 Self::Bank4Sensor1 => write!(f, "B4S1"),
115 Self::Bank4Sensor2 => write!(f, "B4S2"),
116 }
117 }
118}
119
120pub fn o2_test_info(tid: u8) -> (&'static str, &'static str, fn(u16) -> f64) {
122 match tid {
123 0x01 => ("Rich-to-Lean Threshold Voltage", "V", |v| v as f64 * 0.005),
124 0x02 => ("Lean-to-Rich Threshold Voltage", "V", |v| v as f64 * 0.005),
125 0x03 => ("Low Sensor Voltage for Switch Time", "V", |v| v as f64 * 0.005),
126 0x04 => ("High Sensor Voltage for Switch Time", "V", |v| v as f64 * 0.005),
127 0x05 => ("Rich-to-Lean Switch Time", "s", |v| v as f64 * 0.004),
128 0x06 => ("Lean-to-Rich Switch Time", "s", |v| v as f64 * 0.004),
129 0x07 => ("Minimum Sensor Voltage", "V", |v| v as f64 * 0.005),
130 0x08 => ("Maximum Sensor Voltage", "V", |v| v as f64 * 0.005),
131 0x09 => ("Time Between Transitions", "s", |v| v as f64 * 0.04),
132 _ => ("Unknown O2 Test", "", |v| v as f64),
133 }
134}
135
136#[derive(Debug, Clone)]
138pub struct ServiceRequest {
139 pub service_id: u8,
140 pub data: Vec<u8>,
141 pub target: Target,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum Target {
147 Broadcast,
148 Module(String),
149}
150
151impl ServiceRequest {
152 pub fn read_pid(pid: super::pid::Pid) -> Self {
154 Self {
155 service_id: 0x01,
156 data: vec![pid.0],
157 target: Target::Broadcast,
158 }
159 }
160
161 pub fn read_vin() -> Self {
163 Self {
164 service_id: 0x09,
165 data: vec![0x02],
166 target: Target::Broadcast,
167 }
168 }
169
170 pub fn read_dtcs() -> Self {
172 Self {
173 service_id: 0x03,
174 data: vec![],
175 target: Target::Broadcast,
176 }
177 }
178
179 pub fn enhanced_read(service_id: u8, did: u16, target: Target) -> Self {
181 Self {
182 service_id,
183 data: vec![(did >> 8) as u8, (did & 0xFF) as u8],
184 target,
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::protocol::pid::Pid;
193
194 #[test]
195 fn test_service_request_read_pid() {
196 let req = ServiceRequest::read_pid(Pid::ENGINE_RPM);
197 assert_eq!(req.service_id, 0x01);
198 assert_eq!(req.data, vec![0x0C]);
199 assert_eq!(req.target, Target::Broadcast);
200 }
201
202 #[test]
203 fn test_service_request_read_vin() {
204 let req = ServiceRequest::read_vin();
205 assert_eq!(req.service_id, 0x09);
206 assert_eq!(req.data, vec![0x02]);
207 }
208
209 #[test]
210 fn test_service_request_enhanced_read() {
211 let req = ServiceRequest::enhanced_read(0x22, 0x162F, Target::Module("ecm".into()));
212 assert_eq!(req.service_id, 0x22);
213 assert_eq!(req.data, vec![0x16, 0x2F]);
214 }
215
216 #[test]
217 fn test_service_request_read_dtcs() {
218 let req = ServiceRequest::read_dtcs();
219 assert_eq!(req.service_id, 0x03);
220 assert!(req.data.is_empty());
221 }
222
223 #[test]
224 fn test_o2_sensor_location_roundtrip() {
225 for b in 0x01..=0x08u8 {
226 assert!(O2SensorLocation::from_byte(b).is_some());
227 }
228 }
229
230 #[test]
231 fn test_o2_test_info_all_standard_tids() {
232 for tid in 0x01..=0x09u8 {
233 let (name, unit, _) = o2_test_info(tid);
234 assert!(!name.contains("Unknown"), "TID {:#04X} should be known", tid);
235 assert!(!unit.is_empty(), "TID {:#04X} should have a unit", tid);
236 }
237 }
238
239 #[test]
240 fn test_o2_test_info_unknown_tid() {
241 let (name, _, _) = o2_test_info(0xFF);
242 assert!(name.contains("Unknown"));
243 }
244}