1pub mod diag_session;
4pub mod diagnostics;
5pub mod enhanced;
6pub mod modes;
7pub mod poller;
8pub mod threshold;
9
10use std::collections::HashSet;
11use crate::adapter::Adapter;
12use crate::error::Obd2Error;
13use crate::protocol::pid::Pid;
14use crate::protocol::dtc::{Dtc, DtcStatus};
15use crate::protocol::enhanced::{Value, Reading, ReadingSource};
16use crate::protocol::service::{ServiceRequest, Target, VehicleInfo};
17use crate::vehicle::{VehicleSpec, VehicleProfile, SpecRegistry, ModuleId};
18use std::time::Instant;
19
20pub struct Session<A: Adapter> {
41 adapter: A,
42 specs: SpecRegistry,
43 profile: Option<VehicleProfile>,
44 supported_pids_cache: Option<HashSet<Pid>>,
45}
46
47impl<A: Adapter> Session<A> {
48 pub fn new(adapter: A) -> Self {
50 Self {
51 adapter,
52 specs: SpecRegistry::with_defaults(),
53 profile: None,
54 supported_pids_cache: None,
55 }
56 }
57
58 pub fn load_spec(&mut self, path: &std::path::Path) -> Result<(), Obd2Error> {
62 self.specs.load_file(path)
63 }
64
65 pub fn load_spec_dir(&mut self, dir: &std::path::Path) -> Result<usize, Obd2Error> {
67 self.specs.load_directory(dir)
68 }
69
70 pub fn specs(&self) -> &SpecRegistry {
72 &self.specs
73 }
74
75 pub async fn read_pid(&mut self, pid: Pid) -> Result<Reading, Obd2Error> {
79 let req = ServiceRequest::read_pid(pid);
80 let data = self.adapter.request(&req).await?;
81 let value = pid.parse(&data)?;
82 Ok(Reading {
83 value,
84 unit: pid.unit(),
85 timestamp: Instant::now(),
86 raw_bytes: data,
87 source: ReadingSource::Live,
88 })
89 }
90
91 pub async fn read_pids(&mut self, pids: &[Pid]) -> Result<Vec<(Pid, Reading)>, Obd2Error> {
93 let mut results = Vec::with_capacity(pids.len());
94 for &pid in pids {
95 match self.read_pid(pid).await {
96 Ok(reading) => results.push((pid, reading)),
97 Err(Obd2Error::NoData) => continue, Err(e) => return Err(e),
99 }
100 }
101 Ok(results)
102 }
103
104 pub async fn supported_pids(&mut self) -> Result<HashSet<Pid>, Obd2Error> {
106 if let Some(cached) = &self.supported_pids_cache {
107 return Ok(cached.clone());
108 }
109 let pids = self.adapter.supported_pids().await?;
110 self.supported_pids_cache = Some(pids.clone());
111 Ok(pids)
112 }
113
114 pub async fn read_dtcs(&mut self) -> Result<Vec<Dtc>, Obd2Error> {
118 let req = ServiceRequest::read_dtcs();
119 let data = self.adapter.request(&req).await?;
120 Ok(Self::decode_dtc_response(&data, DtcStatus::Stored))
121 }
122
123 pub async fn read_pending_dtcs(&mut self) -> Result<Vec<Dtc>, Obd2Error> {
125 let req = ServiceRequest {
126 service_id: 0x07,
127 data: vec![],
128 target: Target::Broadcast,
129 };
130 let data = self.adapter.request(&req).await?;
131 Ok(Self::decode_dtc_response(&data, DtcStatus::Pending))
132 }
133
134 pub async fn read_permanent_dtcs(&mut self) -> Result<Vec<Dtc>, Obd2Error> {
136 let req = ServiceRequest {
137 service_id: 0x0A,
138 data: vec![],
139 target: Target::Broadcast,
140 };
141 let data = self.adapter.request(&req).await?;
142 Ok(Self::decode_dtc_response(&data, DtcStatus::Permanent))
143 }
144
145 fn decode_dtc_response(data: &[u8], status: DtcStatus) -> Vec<Dtc> {
147 let mut dtcs = Vec::new();
148 let mut i = 0;
149 while i + 1 < data.len() {
150 if data[i] == 0 && data[i + 1] == 0 {
152 i += 2;
153 continue;
154 }
155 let mut dtc = Dtc::from_bytes(data[i], data[i + 1]);
156 dtc.status = status;
157 dtcs.push(dtc);
158 i += 2;
159 }
160 dtcs
161 }
162
163 pub async fn clear_dtcs(&mut self) -> Result<(), Obd2Error> {
167 tracing::warn!("Clearing all DTCs -- readiness monitors will be reset");
168 let req = ServiceRequest {
169 service_id: 0x04,
170 data: vec![],
171 target: Target::Broadcast,
172 };
173 self.adapter.request(&req).await?;
174 Ok(())
175 }
176
177 pub async fn read_vin(&mut self) -> Result<String, Obd2Error> {
181 let req = ServiceRequest::read_vin();
182 let data = self.adapter.request(&req).await?;
183 let vin: String = data.iter()
185 .filter(|&&b| (0x20..=0x7E).contains(&b))
186 .map(|&b| b as char)
187 .take(17)
188 .collect();
189 if vin.len() == 17 {
190 Ok(vin)
191 } else {
192 Err(Obd2Error::ParseError(format!("VIN too short: {} chars", vin.len())))
193 }
194 }
195
196 pub async fn identify_vehicle(&mut self) -> Result<VehicleProfile, Obd2Error> {
198 let vin = self.read_vin().await?;
199 let supported = self.supported_pids().await.unwrap_or_default();
200
201 let _decoded = crate::vehicle::vin::decode(&vin);
203
204 let spec = self.specs.match_vin(&vin).cloned();
206
207 let profile = VehicleProfile {
208 vin: vin.clone(),
209 info: Some(VehicleInfo {
210 vin: vin.clone(),
211 calibration_ids: vec![],
212 cvns: vec![],
213 ecu_name: None,
214 }),
215 spec,
216 supported_pids: supported,
217 };
218
219 self.profile = Some(profile.clone());
220 Ok(profile)
221 }
222
223 pub async fn read_enhanced(&mut self, did: u16, module: ModuleId) -> Result<Reading, Obd2Error> {
227 let service_id = self.lookup_enhanced_service_id(did, &module);
229
230 let req = ServiceRequest::enhanced_read(
231 service_id,
232 did,
233 Target::Module(module.0.clone()),
234 );
235 let data = self.adapter.request(&req).await?;
236
237 Ok(Reading {
239 value: Value::Raw(data.clone()),
240 unit: "",
241 timestamp: Instant::now(),
242 raw_bytes: data,
243 source: ReadingSource::Live,
244 })
245 }
246
247 fn lookup_enhanced_service_id(&self, did: u16, module: &ModuleId) -> u8 {
249 enhanced::find_service_id_from_spec(self.spec(), did, module)
250 }
251
252 pub fn module_pids(&self, module: ModuleId) -> Vec<&crate::protocol::enhanced::EnhancedPid> {
254 enhanced::list_module_pids(self.spec(), &module)
255 }
256
257 pub async fn read_o2_monitoring(
261 &mut self,
262 test_id: u8,
263 ) -> Result<Vec<crate::protocol::service::O2TestResult>, Obd2Error> {
264 modes::read_o2_monitoring(&mut self.adapter, test_id).await
265 }
266
267 pub async fn read_all_o2_monitoring(
269 &mut self,
270 ) -> Result<Vec<crate::protocol::service::O2TestResult>, Obd2Error> {
271 modes::read_all_o2_monitoring(&mut self.adapter).await
272 }
273
274 pub fn vehicle(&self) -> Option<&VehicleProfile> {
278 self.profile.as_ref()
279 }
280
281 pub fn spec(&self) -> Option<&VehicleSpec> {
283 self.profile.as_ref().and_then(|p| p.spec.as_ref())
284 }
285
286 pub fn adapter_info(&self) -> &crate::adapter::AdapterInfo {
288 self.adapter.info()
289 }
290
291 pub async fn battery_voltage(&mut self) -> Result<Option<f64>, Obd2Error> {
293 self.adapter.battery_voltage().await
294 }
295
296 pub async fn raw_request(&mut self, service: u8, data: &[u8], target: Target) -> Result<Vec<u8>, Obd2Error> {
298 let req = ServiceRequest {
299 service_id: service,
300 data: data.to_vec(),
301 target,
302 };
303 self.adapter.request(&req).await
304 }
305}
306
307impl<A: Adapter> std::fmt::Debug for Session<A> {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 f.debug_struct("Session")
310 .field("profile", &self.profile)
311 .field("specs_loaded", &self.specs.specs().len())
312 .finish()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::adapter::mock::MockAdapter;
320
321 #[tokio::test]
322 async fn test_session_read_pid() {
323 let adapter = MockAdapter::new();
324 let mut session = Session::new(adapter);
325 let reading = session.read_pid(Pid::ENGINE_RPM).await.unwrap();
326 assert_eq!(reading.value.as_f64().unwrap(), 680.0);
328 assert_eq!(reading.unit, "RPM");
329 assert_eq!(reading.source, ReadingSource::Live);
330 }
331
332 #[tokio::test]
333 async fn test_session_read_multiple_pids() {
334 let adapter = MockAdapter::new();
335 let mut session = Session::new(adapter);
336 let results = session.read_pids(&[Pid::ENGINE_RPM, Pid::COOLANT_TEMP, Pid::VEHICLE_SPEED]).await.unwrap();
337 assert_eq!(results.len(), 3);
338 }
339
340 #[tokio::test]
341 async fn test_session_supported_pids() {
342 let adapter = MockAdapter::new();
343 let mut session = Session::new(adapter);
344 let pids = session.supported_pids().await.unwrap();
345 assert!(pids.contains(&Pid::ENGINE_RPM));
346 assert!(pids.contains(&Pid::VEHICLE_SPEED));
347 }
348
349 #[tokio::test]
350 async fn test_session_supported_pids_cached() {
351 let adapter = MockAdapter::new();
352 let mut session = Session::new(adapter);
353 let pids1 = session.supported_pids().await.unwrap();
354 let pids2 = session.supported_pids().await.unwrap();
355 assert_eq!(pids1, pids2); }
357
358 #[tokio::test]
359 async fn test_session_read_vin() {
360 let adapter = MockAdapter::with_vin("1GCHK23224F000001");
361 let mut session = Session::new(adapter);
362 let vin = session.read_vin().await.unwrap();
363 assert_eq!(vin, "1GCHK23224F000001");
364 }
365
366 #[tokio::test]
367 async fn test_session_identify_vehicle() {
368 let adapter = MockAdapter::with_vin("1GCHK23224F000001");
369 let mut session = Session::new(adapter);
370 let profile = session.identify_vehicle().await.unwrap();
371 assert_eq!(profile.vin, "1GCHK23224F000001");
372 assert!(profile.spec.is_some(), "should match Duramax spec by VIN");
374 assert_eq!(profile.spec.as_ref().unwrap().identity.engine.code, "LLY");
375 }
376
377 #[tokio::test]
378 async fn test_session_identify_no_spec() {
379 let adapter = MockAdapter::with_vin("JH4KA7660PC000001"); let mut session = Session::new(adapter);
381 let profile = session.identify_vehicle().await.unwrap();
382 assert!(profile.spec.is_none());
383 }
384
385 #[tokio::test]
386 async fn test_session_read_dtcs() {
387 let mut adapter = MockAdapter::new();
388 adapter.set_dtcs(vec![
389 Dtc::from_code("P0420"),
390 Dtc::from_code("P0171"),
391 ]);
392 let mut session = Session::new(adapter);
393 let dtcs = session.read_dtcs().await.unwrap();
394 assert_eq!(dtcs.len(), 2);
395 assert!(dtcs.iter().any(|d| d.code == "P0420"));
396 assert!(dtcs.iter().any(|d| d.code == "P0171"));
397 }
398
399 #[tokio::test]
400 async fn test_session_clear_dtcs() {
401 let mut adapter = MockAdapter::new();
402 adapter.set_dtcs(vec![Dtc::from_code("P0420")]);
403 let mut session = Session::new(adapter);
404
405 session.clear_dtcs().await.unwrap();
406 let dtcs = session.read_dtcs().await.unwrap();
407 assert!(dtcs.is_empty());
408 }
409
410 #[tokio::test]
411 async fn test_session_battery_voltage() {
412 let adapter = MockAdapter::new();
413 let mut session = Session::new(adapter);
414 let voltage = session.battery_voltage().await.unwrap();
415 assert_eq!(voltage, Some(14.4));
416 }
417
418 #[tokio::test]
419 async fn test_session_no_spec_still_reads_pids() {
420 let adapter = MockAdapter::with_vin("JH4KA7660PC000001");
421 let mut session = Session::new(adapter);
422 let reading = session.read_pid(Pid::ENGINE_RPM).await.unwrap();
424 assert!(reading.value.as_f64().is_ok());
425 }
426
427 #[tokio::test]
428 async fn test_session_raw_request() {
429 let adapter = MockAdapter::new();
430 let mut session = Session::new(adapter);
431 let data = session.raw_request(0x09, &[0x02], Target::Broadcast).await.unwrap();
432 assert!(!data.is_empty()); }
434}