diff --git a/Cargo.toml b/Cargo.toml index 286d7f8..ec43f5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "obd2" description = "Utility for reading data from a vehicle over OBD-II" license = "MIT OR Apache-2.0" repository = "https://github.com/rsammelson/obd2" -version = "0.2.0-pre2" +version = "0.2.0-pre3" edition = "2021" [dependencies] diff --git a/src/commands/implementation.rs b/src/commands/implementation.rs index 0dcb3ba..795bd77 100644 --- a/src/commands/implementation.rs +++ b/src/commands/implementation.rs @@ -1,99 +1,97 @@ use crate::{Error, Obd2Device, Result}; -use super::{Dtc, DtcsInfo, Obd2DataRetrieval}; +use super::{Dtc, DtcsInfo}; -impl Obd2DataRetrieval for T { - fn get_vin(&mut self) -> Result { - let mut result = self.obd_command(0x09, 0x02)?.pop().unwrap(); - result.remove(0); // do not know what this byte is - Ok(String::from_utf8(result)?) - } +pub(crate) trait GetObd2Values +where + Self: Sized, +{ + fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result>; +} - fn get_dtc_info(&mut self) -> Result> { - let result = self.obd_command(0x01, 0x01)?; - - result - .iter() - .map(|response| { - if response.len() == 4 { - Ok(DtcsInfo { - malfunction_indicator_light: (response[0] & 0x80) == 0x80, - dtc_count: response[0] & 0x7f, - common_test_availability: ((response[1] & 0xf0) >> 1) - | (response[1] & 0x07), - is_compression_engine: (response[1] & 0x08) == 0x08, - specific_test_availability: ((response[3] as u16) << 8) - | (response[2] as u16), - }) - } else { - Err(Error::Other(format!( - "get_dtc_info: expected length 4, got {}", - response.len() - ))) - } - }) - .collect() - } - - fn get_dtcs(&mut self) -> Result>> { - let result = self.obd_mode_command(0x03)?; - result - .iter() - .map(|response| match response.first() { - Some(0) => { - if response.len() % 2 == 1 { - let mut ret = Vec::new(); - for i in (1..response.len()).step_by(2) { - ret.push(match response[i] >> 6 { - 0 => Dtc::Powertrain(0), - 1 => Dtc::Chassis(0), - 2 => Dtc::Body(0), - 3 => Dtc::Network(0), - _ => unreachable!(), - }); - } - Ok(ret) - } else { - Err(Error::Other(format!( - "invalid response when getting DTCs {:?}", - response - ))) - } - } - Some(n) if *n <= 3 => todo!(), - Some(_) => Err(Error::Other(format!( - "invalid response {:?} when getting DTCs", - response - ))), - None => Err(Error::Other( - "no response bytes when getting DTCs".to_owned(), - )), - }) - .collect::>>>() - } - - fn get_engine_load(&mut self) -> Result { - Ok(self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0]) - } - - fn get_engine_coolant_temperature(&mut self) -> Result { - Ok((self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0] as i16) - 40) - } - - fn get_fuel_pressure(&mut self) -> Result { - todo!() - } - - fn get_engine_manifold_pressure(&mut self) -> Result { - todo!() - } - - fn get_rpm(&mut self) -> Result { - let result = self.obd_command_cnt_len::<1, 2>(0x01, 0x0C)?[0]; - Ok(f32::from(u16::from_be_bytes(result)) / 4.0) - } - - fn get_speed(&mut self) -> Result { - Ok(self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0]) +impl GetObd2Values for u8 { + fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result> { + Ok(device + .obd_command_len::<1>(service, pid)? + .into_iter() + .map(|r| r[0]) + .collect()) } } + +impl GetObd2Values for u16 { + fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result> { + Ok(device + .obd_command_len::<2>(service, pid)? + .into_iter() + .map(u16::from_be_bytes) + .collect()) + } +} + +pub(super) fn get_vin(device: &mut T) -> Result { + let mut result = device.obd_command(0x09, 0x02)?.pop().unwrap(); + result.remove(0); // do not know what this byte is + Ok(String::from_utf8(result)?) +} + +pub(super) fn get_dtc_info(device: &mut T) -> Result> { + let result = device.obd_command(0x01, 0x01)?; + + result + .iter() + .map(|response| { + if response.len() == 4 { + Ok(DtcsInfo { + malfunction_indicator_light: (response[0] & 0x80) == 0x80, + dtc_count: response[0] & 0x7f, + common_test_availability: ((response[1] & 0xf0) >> 1) | (response[1] & 0x07), + is_compression_engine: (response[1] & 0x08) == 0x08, + specific_test_availability: ((response[3] as u16) << 8) | (response[2] as u16), + }) + } else { + Err(Error::Other(format!( + "get_dtc_info: expected length 4, got {}", + response.len() + ))) + } + }) + .collect() +} + +pub(super) fn get_dtcs(device: &mut T) -> Result>> { + let result = device.obd_mode_command(0x03)?; + result + .iter() + .map(|response| match response.first() { + Some(0) => { + if response.len() % 2 == 1 { + let mut ret = Vec::new(); + for i in (1..response.len()).step_by(2) { + ret.push(match response[i] >> 6 { + 0 => Dtc::Powertrain(0), + 1 => Dtc::Chassis(0), + 2 => Dtc::Body(0), + 3 => Dtc::Network(0), + _ => unreachable!(), + }); + } + Ok(ret) + } else { + Err(Error::Other(format!( + "invalid response when getting DTCs {:?}", + response + ))) + } + } + Some(n) if *n <= 3 => todo!(), + Some(_) => Err(Error::Other(format!( + "invalid response {:?} when getting DTCs", + response + ))), + None => Err(Error::Other( + "no response bytes when getting DTCs".to_owned(), + )), + }) + .collect::>>>() +} diff --git a/src/commands/macros.rs b/src/commands/macros.rs new file mode 100644 index 0000000..c0ccaa4 --- /dev/null +++ b/src/commands/macros.rs @@ -0,0 +1,119 @@ +macro_rules! trait_func { + { + $(#[$attr:meta])* + fn $name:ident($service:expr, $pid:expr) -> $retrieve_type:ty; + } => { + $(#[$attr])* + fn $name(&mut self) -> Result>; + }; + { + $(#[$attr:meta])* + fn $name:ident($service:expr, $pid:expr, $map:expr) -> $retrieve_type:ty; + } => { + $(#[$attr])* + fn $name(&mut self) -> Result>; + }; + { + $(#[$attr:meta])* + fn $name:ident<$retrieve_type:ty>($service:expr, $pid:expr, $map:expr) -> $out_type:ty; + } => { + $(#[$attr])* + fn $name(&mut self) -> Result>; + }; +} + +macro_rules! impl_func { + { + $(#[$attr:meta])* + fn $name:ident($service:expr, $pid:expr) -> $retrieve_type:ty; + } => { + fn $name(&mut self) -> Result> { + <$retrieve_type>::get_obd2_val(self, $service, $pid) + } + }; + { + $(#[$attr:meta])* + fn $name:ident($service:expr, $pid:expr, $map:expr) -> $retrieve_type:ty; + } => { + fn $name(&mut self) -> Result> { + $map(<$retrieve_type>::get_obd2_val(self, $service, $pid)) + } + }; + { + $(#[$attr:meta])* + fn $name:ident<$retrieve_type:ty>($service:expr, $pid:expr, $map:expr) -> $out_type:ty; + } => { + fn $name(&mut self) -> Result> { + Ok( + <$retrieve_type>::get_obd2_val(self, $service, $pid)? + .into_iter() + .map(|v| $map(v.into())) + .collect() + ) + } + }; +} + +macro_rules! func { + { + $(#[$attr:meta])* + trait $trait_name:ident; + + $({ + $( + $(#[$f_attr_inner:meta])* + fn $f_name:ident($self:ident, $f_service:expr$(, $f_pid:expr)?) -> $f_output:ty + $inside:block + )+ + })? + + $( + $(#[$attr_inner:meta])* + fn $name:ident$(<$retrieve_type:ty>)?($service:expr, $pid:expr$(, $map:expr)?) -> $output:ty; + )* + } => { + $(#[$attr])* + pub trait $trait_name: private::Sealed { + $($( + $(#[$f_attr_inner])* + fn $f_name(&mut self) -> $f_output; + )+)? + + $( + trait_func! { + $(#[$attr_inner])* + /// + #[doc=concat!( + "Details: service ", $service, + ", PID ", $pid, + ", read type: `", decode_type!($output $(, $retrieve_type)?), "`" + )] + fn $name$(<$retrieve_type>)?($service, $pid$(, $map)?) -> $output; + } + )* + } + + impl $trait_name for T { + $($( + fn $f_name(&mut $self) -> $f_output + $inside + )+)? + + $( + impl_func! { + $(#[$attr_inner])* + fn $name$(<$retrieve_type>)?($service, $pid$(, $map)?) -> $output; + } + )* + } + }; +} + +macro_rules! decode_type { + ($_:ty, $t:ty) => { + stringify!($t) + }; + ($t:ty) => { + stringify!($t) + }; +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 64a10b0..0a65e4b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,55 +1,63 @@ //! High level OBD-II interface mod implementation; +use implementation::GetObd2Values; + +#[macro_use] +mod macros; mod types; +use types::private; pub use types::{Dtc, DtcsInfo}; -use crate::Result; +use crate::{Obd2Device, Result}; -/// Trait for devices that can retrieve data over OBD-II -/// -/// Automatically implemented for implementors of [Obd2Device](crate::Obd2Device). -pub trait Obd2DataRetrieval: private::Sealed { - /// Check which getters are supported by the current vehicle - // fn get_support() -> Obd2FunctionSupport; - - /// Retreive the VIN (vehicle identification number) +func! { + /// Trait for devices that can retrieve data over OBD-II /// - /// Service 0x09, PID 0x01. This should match the number printed on the vehicle, and is a good - /// command for checking that the OBD-II interface is working correctly. - fn get_vin(&mut self) -> Result; + /// Automatically implemented for implementors of [Obd2Device](crate::Obd2Device). + trait Obd2DataRetrieval; - /// Get DTC (diagnostic trouble code) metadata for each ECU - fn get_dtc_info(&mut self) -> Result>; + { + /// Retreive the VIN (vehicle identification number) + /// + /// This should match the number printed on the vehicle, and is a good + /// command for checking that the OBD-II interface is working correctly. + fn get_vin(self, 0x09, 0x02) -> Result { + implementation::get_vin(self) + } - /// Get DTCs for each ECU - fn get_dtcs(&mut self) -> Result>>; + /// Get DTCs for each ECU + fn get_dtcs(self, 0x03) -> Result>> { + implementation::get_dtcs(self) + } + + /// Get DTC (diagnostic trouble code) metadata for each ECU + fn get_dtc_info(self, 0x01, 0x01) -> Result> { + implementation::get_dtc_info(self) + } + + } /// Get the calculated engine load (out of 255) - fn get_engine_load(&mut self) -> Result; + fn get_engine_load(0x01, 0x04) -> u8; /// Get the temperature of the engine's coolant in ÂșC - fn get_engine_coolant_temperature(&mut self) -> Result; + fn get_engine_coolant_temperature(0x01, 0x05, |v: i16| v - 40) -> i16; /// Get the fuel pressure in kPa /// /// This measurement is gauge pressure (measured relative to the atmosphere) - fn get_fuel_pressure(&mut self) -> Result; + fn get_fuel_pressure(0x01, 0x0A, |v: i16| v * 3) -> i16; /// Get the intake manifold pressure in kPa /// /// This measurement is absolute pressure. - fn get_engine_manifold_pressure(&mut self) -> Result; + fn get_engine_manifold_pressure(0x01, 0x0B, |v: f32| v) -> f32; /// Get the RPM in increments of 0.25 - fn get_rpm(&mut self) -> Result; + fn get_rpm(0x01, 0x0C, |v: f32| v / 4.0) -> f32; /// Get the speed in km/h - fn get_speed(&mut self) -> Result; -} - -mod private { - pub trait Sealed {} - impl Sealed for T {} + fn get_speed(0x01, 0x0D) -> u8; } diff --git a/src/commands/types.rs b/src/commands/types.rs index 011cdb4..db7ad8e 100644 --- a/src/commands/types.rs +++ b/src/commands/types.rs @@ -41,3 +41,8 @@ impl fmt::Display for Dtc { f.write_fmt(format_args!("{}{:03X}", c, n)) } } + +pub(super) mod private { + pub trait Sealed {} + impl Sealed for T {} +}