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> {
203 let vin = self.read_vin().await?;
204 let supported = self.supported_pids().await.unwrap_or_default();
205
206 let decoded = crate::vehicle::vin::decode(&vin);
208
209 let spec = self.specs.match_vin(&vin).cloned();
211
212 let profile = VehicleProfile {
213 vin: vin.clone(),
214 decoded_vin: Some(decoded),
215 info: Some(VehicleInfo {
216 vin: vin.clone(),
217 calibration_ids: vec![],
218 cvns: vec![],
219 ecu_name: None,
220 }),
221 spec,
222 supported_pids: supported,
223 };
224
225 self.profile = Some(profile.clone());
226 Ok(profile)
227 }
228
229 pub async fn read_enhanced(&mut self, did: u16, module: ModuleId) -> Result<Reading, Obd2Error> {
233 let service_id = self.lookup_enhanced_service_id(did, &module);
235
236 let req = ServiceRequest::enhanced_read(
237 service_id,
238 did,
239 Target::Module(module.0.clone()),
240 );
241 let data = self.adapter.request(&req).await?;
242
243 Ok(Reading {
245 value: Value::Raw(data.clone()),
246 unit: "",
247 timestamp: Instant::now(),
248 raw_bytes: data,
249 source: ReadingSource::Live,
250 })
251 }
252
253 fn lookup_enhanced_service_id(&self, did: u16, module: &ModuleId) -> u8 {
255 enhanced::find_service_id_from_spec(self.spec(), did, module)
256 }
257
258 pub fn module_pids(&self, module: ModuleId) -> Vec<&crate::protocol::enhanced::EnhancedPid> {
260 enhanced::list_module_pids(self.spec(), &module)
261 }
262
263 pub async fn read_o2_monitoring(
267 &mut self,
268 test_id: u8,
269 ) -> Result<Vec<crate::protocol::service::O2TestResult>, Obd2Error> {
270 modes::read_o2_monitoring(&mut self.adapter, test_id).await
271 }
272
273 pub async fn read_all_o2_monitoring(
275 &mut self,
276 ) -> Result<Vec<crate::protocol::service::O2TestResult>, Obd2Error> {
277 modes::read_all_o2_monitoring(&mut self.adapter).await
278 }
279
280 pub async fn read_j1939_pgn(
304 &mut self,
305 pgn: crate::protocol::j1939::Pgn,
306 ) -> Result<Vec<u8>, Obd2Error> {
307 let pgn_bytes = [
311 (pgn.0 & 0xFF) as u8,
312 ((pgn.0 >> 8) & 0xFF) as u8,
313 ((pgn.0 >> 16) & 0xFF) as u8,
314 ];
315 self.raw_request(0xEA, &pgn_bytes, Target::Broadcast).await
317 }
318
319 pub async fn read_j1939_dtcs(&mut self) -> Result<Vec<crate::protocol::j1939::J1939Dtc>, Obd2Error> {
323 let data = self.read_j1939_pgn(crate::protocol::j1939::Pgn::DM1).await?;
324 Ok(crate::protocol::j1939::decode_dm1(&data))
325 }
326
327 pub fn evaluate_threshold(&self, pid: Pid, value: f64) -> Option<crate::vehicle::ThresholdResult> {
350 threshold::evaluate_pid_threshold(self.spec(), pid, value)
351 }
352
353 pub fn evaluate_enhanced_threshold(&self, did: u16, value: f64) -> Option<crate::vehicle::ThresholdResult> {
355 threshold::evaluate_enhanced_threshold(self.spec(), did, value)
356 }
357
358 pub fn vehicle(&self) -> Option<&VehicleProfile> {
362 self.profile.as_ref()
363 }
364
365 pub fn spec(&self) -> Option<&VehicleSpec> {
367 self.profile.as_ref().and_then(|p| p.spec.as_ref())
368 }
369
370 pub fn adapter_info(&self) -> &crate::adapter::AdapterInfo {
372 self.adapter.info()
373 }
374
375 pub async fn battery_voltage(&mut self) -> Result<Option<f64>, Obd2Error> {
377 self.adapter.battery_voltage().await
378 }
379
380 pub async fn raw_request(&mut self, service: u8, data: &[u8], target: Target) -> Result<Vec<u8>, Obd2Error> {
382 let req = ServiceRequest {
383 service_id: service,
384 data: data.to_vec(),
385 target,
386 };
387 self.adapter.request(&req).await
388 }
389}
390
391impl<A: Adapter> std::fmt::Debug for Session<A> {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 f.debug_struct("Session")
394 .field("profile", &self.profile)
395 .field("specs_loaded", &self.specs.specs().len())
396 .finish()
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::adapter::mock::MockAdapter;
404
405 #[tokio::test]
406 async fn test_session_read_pid() {
407 let adapter = MockAdapter::new();
408 let mut session = Session::new(adapter);
409 let reading = session.read_pid(Pid::ENGINE_RPM).await.unwrap();
410 assert_eq!(reading.value.as_f64().unwrap(), 680.0);
412 assert_eq!(reading.unit, "RPM");
413 assert_eq!(reading.source, ReadingSource::Live);
414 }
415
416 #[tokio::test]
417 async fn test_session_read_multiple_pids() {
418 let adapter = MockAdapter::new();
419 let mut session = Session::new(adapter);
420 let results = session.read_pids(&[Pid::ENGINE_RPM, Pid::COOLANT_TEMP, Pid::VEHICLE_SPEED]).await.unwrap();
421 assert_eq!(results.len(), 3);
422 }
423
424 #[tokio::test]
425 async fn test_session_supported_pids() {
426 let adapter = MockAdapter::new();
427 let mut session = Session::new(adapter);
428 let pids = session.supported_pids().await.unwrap();
429 assert!(pids.contains(&Pid::ENGINE_RPM));
430 assert!(pids.contains(&Pid::VEHICLE_SPEED));
431 }
432
433 #[tokio::test]
434 async fn test_session_supported_pids_cached() {
435 let adapter = MockAdapter::new();
436 let mut session = Session::new(adapter);
437 let pids1 = session.supported_pids().await.unwrap();
438 let pids2 = session.supported_pids().await.unwrap();
439 assert_eq!(pids1, pids2); }
441
442 #[tokio::test]
443 async fn test_session_read_vin() {
444 let adapter = MockAdapter::with_vin("1GCHK23224F000001");
445 let mut session = Session::new(adapter);
446 let vin = session.read_vin().await.unwrap();
447 assert_eq!(vin, "1GCHK23224F000001");
448 }
449
450 #[tokio::test]
451 async fn test_session_identify_vehicle() {
452 let adapter = MockAdapter::with_vin("1GCHK23224F000001");
453 let mut session = Session::new(adapter);
454 let profile = session.identify_vehicle().await.unwrap();
455 assert_eq!(profile.vin, "1GCHK23224F000001");
456 assert!(profile.spec.is_some(), "should match Duramax spec by VIN");
458 assert_eq!(profile.spec.as_ref().unwrap().identity.engine.code, "LLY");
459 }
460
461 #[tokio::test]
462 async fn test_session_identify_no_spec() {
463 let adapter = MockAdapter::with_vin("JH4KA7660PC000001"); let mut session = Session::new(adapter);
465 let profile = session.identify_vehicle().await.unwrap();
466 assert!(profile.spec.is_none());
467 }
468
469 #[tokio::test]
470 async fn test_session_read_dtcs() {
471 let mut adapter = MockAdapter::new();
472 adapter.set_dtcs(vec![
473 Dtc::from_code("P0420"),
474 Dtc::from_code("P0171"),
475 ]);
476 let mut session = Session::new(adapter);
477 let dtcs = session.read_dtcs().await.unwrap();
478 assert_eq!(dtcs.len(), 2);
479 assert!(dtcs.iter().any(|d| d.code == "P0420"));
480 assert!(dtcs.iter().any(|d| d.code == "P0171"));
481 }
482
483 #[tokio::test]
484 async fn test_session_clear_dtcs() {
485 let mut adapter = MockAdapter::new();
486 adapter.set_dtcs(vec![Dtc::from_code("P0420")]);
487 let mut session = Session::new(adapter);
488
489 session.clear_dtcs().await.unwrap();
490 let dtcs = session.read_dtcs().await.unwrap();
491 assert!(dtcs.is_empty());
492 }
493
494 #[tokio::test]
495 async fn test_session_battery_voltage() {
496 let adapter = MockAdapter::new();
497 let mut session = Session::new(adapter);
498 let voltage = session.battery_voltage().await.unwrap();
499 assert_eq!(voltage, Some(14.4));
500 }
501
502 #[tokio::test]
503 async fn test_session_no_spec_still_reads_pids() {
504 let adapter = MockAdapter::with_vin("JH4KA7660PC000001");
505 let mut session = Session::new(adapter);
506 let reading = session.read_pid(Pid::ENGINE_RPM).await.unwrap();
508 assert!(reading.value.as_f64().is_ok());
509 }
510
511 #[tokio::test]
512 async fn test_session_raw_request() {
513 let adapter = MockAdapter::new();
514 let mut session = Session::new(adapter);
515 let data = session.raw_request(0x09, &[0x02], Target::Broadcast).await.unwrap();
516 assert!(!data.is_empty()); }
518}