Switch to using macros for data retrieval commands
This commit is contained in:
parent
8d521ff46d
commit
fb35987b28
|
@ -3,7 +3,7 @@ name = "obd2"
|
||||||
description = "Utility for reading data from a vehicle over OBD-II"
|
description = "Utility for reading data from a vehicle over OBD-II"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/rsammelson/obd2"
|
repository = "https://github.com/rsammelson/obd2"
|
||||||
version = "0.2.0-pre2"
|
version = "0.2.0-pre3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -1,99 +1,97 @@
|
||||||
use crate::{Error, Obd2Device, Result};
|
use crate::{Error, Obd2Device, Result};
|
||||||
|
|
||||||
use super::{Dtc, DtcsInfo, Obd2DataRetrieval};
|
use super::{Dtc, DtcsInfo};
|
||||||
|
|
||||||
impl<T: Obd2Device> Obd2DataRetrieval for T {
|
pub(crate) trait GetObd2Values<T>
|
||||||
fn get_vin(&mut self) -> Result<String> {
|
where
|
||||||
let mut result = self.obd_command(0x09, 0x02)?.pop().unwrap();
|
Self: Sized,
|
||||||
result.remove(0); // do not know what this byte is
|
{
|
||||||
Ok(String::from_utf8(result)?)
|
fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result<Vec<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_dtc_info(&mut self) -> Result<Vec<DtcsInfo>> {
|
impl<T: Obd2Device> GetObd2Values<T> for u8 {
|
||||||
let result = self.obd_command(0x01, 0x01)?;
|
fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result<Vec<Self>> {
|
||||||
|
Ok(device
|
||||||
result
|
.obd_command_len::<1>(service, pid)?
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|response| {
|
.map(|r| r[0])
|
||||||
if response.len() == 4 {
|
.collect())
|
||||||
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<Vec<Vec<Dtc>>> {
|
|
||||||
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::<Result<Vec<Vec<Dtc>>>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_engine_load(&mut self) -> Result<u8> {
|
|
||||||
Ok(self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_engine_coolant_temperature(&mut self) -> Result<i16> {
|
|
||||||
Ok((self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0] as i16) - 40)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_fuel_pressure(&mut self) -> Result<i16> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_engine_manifold_pressure(&mut self) -> Result<f32> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rpm(&mut self) -> Result<f32> {
|
|
||||||
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<u8> {
|
|
||||||
Ok(self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Obd2Device> GetObd2Values<T> for u16 {
|
||||||
|
fn get_obd2_val(device: &mut T, service: u8, pid: u8) -> Result<Vec<Self>> {
|
||||||
|
Ok(device
|
||||||
|
.obd_command_len::<2>(service, pid)?
|
||||||
|
.into_iter()
|
||||||
|
.map(u16::from_be_bytes)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_vin<T: Obd2Device>(device: &mut T) -> Result<String> {
|
||||||
|
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<T: Obd2Device>(device: &mut T) -> Result<Vec<DtcsInfo>> {
|
||||||
|
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<T: Obd2Device>(device: &mut T) -> Result<Vec<Vec<Dtc>>> {
|
||||||
|
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::<Result<Vec<Vec<Dtc>>>>()
|
||||||
|
}
|
||||||
|
|
119
src/commands/macros.rs
Normal file
119
src/commands/macros.rs
Normal file
|
@ -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<Vec<$retrieve_type>>;
|
||||||
|
};
|
||||||
|
{
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
fn $name:ident($service:expr, $pid:expr, $map:expr) -> $retrieve_type:ty;
|
||||||
|
} => {
|
||||||
|
$(#[$attr])*
|
||||||
|
fn $name(&mut self) -> Result<Vec<$retrieve_type>>;
|
||||||
|
};
|
||||||
|
{
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
fn $name:ident<$retrieve_type:ty>($service:expr, $pid:expr, $map:expr) -> $out_type:ty;
|
||||||
|
} => {
|
||||||
|
$(#[$attr])*
|
||||||
|
fn $name(&mut self) -> Result<Vec<$out_type>>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_func {
|
||||||
|
{
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
fn $name:ident($service:expr, $pid:expr) -> $retrieve_type:ty;
|
||||||
|
} => {
|
||||||
|
fn $name(&mut self) -> Result<Vec<$retrieve_type>> {
|
||||||
|
<$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<Vec<$retrieve_type>> {
|
||||||
|
$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<Vec<$out_type>> {
|
||||||
|
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<T: Obd2Device> $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)
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,55 +1,63 @@
|
||||||
//! High level OBD-II interface
|
//! High level OBD-II interface
|
||||||
|
|
||||||
mod implementation;
|
mod implementation;
|
||||||
|
use implementation::GetObd2Values;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
|
use types::private;
|
||||||
pub use types::{Dtc, DtcsInfo};
|
pub use types::{Dtc, DtcsInfo};
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Obd2Device, Result};
|
||||||
|
|
||||||
/// Trait for devices that can retrieve data over OBD-II
|
func! {
|
||||||
///
|
/// 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)
|
|
||||||
///
|
///
|
||||||
/// Service 0x09, PID 0x01. This should match the number printed on the vehicle, and is a good
|
/// Automatically implemented for implementors of [Obd2Device](crate::Obd2Device).
|
||||||
/// command for checking that the OBD-II interface is working correctly.
|
trait Obd2DataRetrieval;
|
||||||
fn get_vin(&mut self) -> Result<String>;
|
|
||||||
|
|
||||||
/// Get DTC (diagnostic trouble code) metadata for each ECU
|
{
|
||||||
fn get_dtc_info(&mut self) -> Result<Vec<DtcsInfo>>;
|
/// 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<String> {
|
||||||
|
implementation::get_vin(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get DTCs for each ECU
|
/// Get DTCs for each ECU
|
||||||
fn get_dtcs(&mut self) -> Result<Vec<Vec<Dtc>>>;
|
fn get_dtcs(self, 0x03) -> Result<Vec<Vec<Dtc>>> {
|
||||||
|
implementation::get_dtcs(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get DTC (diagnostic trouble code) metadata for each ECU
|
||||||
|
fn get_dtc_info(self, 0x01, 0x01) -> Result<Vec<DtcsInfo>> {
|
||||||
|
implementation::get_dtc_info(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the calculated engine load (out of 255)
|
/// Get the calculated engine load (out of 255)
|
||||||
fn get_engine_load(&mut self) -> Result<u8>;
|
fn get_engine_load(0x01, 0x04) -> u8;
|
||||||
|
|
||||||
/// Get the temperature of the engine's coolant in ºC
|
/// Get the temperature of the engine's coolant in ºC
|
||||||
fn get_engine_coolant_temperature(&mut self) -> Result<i16>;
|
fn get_engine_coolant_temperature<u8>(0x01, 0x05, |v: i16| v - 40) -> i16;
|
||||||
|
|
||||||
/// Get the fuel pressure in kPa
|
/// Get the fuel pressure in kPa
|
||||||
///
|
///
|
||||||
/// This measurement is gauge pressure (measured relative to the atmosphere)
|
/// This measurement is gauge pressure (measured relative to the atmosphere)
|
||||||
fn get_fuel_pressure(&mut self) -> Result<i16>;
|
fn get_fuel_pressure<u8>(0x01, 0x0A, |v: i16| v * 3) -> i16;
|
||||||
|
|
||||||
/// Get the intake manifold pressure in kPa
|
/// Get the intake manifold pressure in kPa
|
||||||
///
|
///
|
||||||
/// This measurement is absolute pressure.
|
/// This measurement is absolute pressure.
|
||||||
fn get_engine_manifold_pressure(&mut self) -> Result<f32>;
|
fn get_engine_manifold_pressure<u16>(0x01, 0x0B, |v: f32| v) -> f32;
|
||||||
|
|
||||||
/// Get the RPM in increments of 0.25
|
/// Get the RPM in increments of 0.25
|
||||||
fn get_rpm(&mut self) -> Result<f32>;
|
fn get_rpm<u16>(0x01, 0x0C, |v: f32| v / 4.0) -> f32;
|
||||||
|
|
||||||
/// Get the speed in km/h
|
/// Get the speed in km/h
|
||||||
fn get_speed(&mut self) -> Result<u8>;
|
fn get_speed(0x01, 0x0D) -> u8;
|
||||||
}
|
|
||||||
|
|
||||||
mod private {
|
|
||||||
pub trait Sealed {}
|
|
||||||
impl<T: crate::Obd2Device> Sealed for T {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,3 +41,8 @@ impl fmt::Display for Dtc {
|
||||||
f.write_fmt(format_args!("{}{:03X}", c, n))
|
f.write_fmt(format_args!("{}{:03X}", c, n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
impl<T: crate::Obd2Device> Sealed for T {}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue