Split some files and add more documentation
This commit is contained in:
parent
877722ed1b
commit
cd0242b796
|
@ -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-pre1"
|
version = "0.2.0-pre2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use obd2::Obd2Device;
|
use obd2::commands::Obd2DataRetrieval;
|
||||||
|
|
||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
|
|
155
src/commands.rs
Normal file
155
src/commands.rs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
//! High level OBD-II interface
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::{Error, Obd2Device, Result};
|
||||||
|
|
||||||
|
/// Trait for devices that can retrieve data over OBD-II
|
||||||
|
///
|
||||||
|
/// Automatically impelemted for implementors of [Obd2Device].
|
||||||
|
pub trait Obd2DataRetrieval: private::Sealed {
|
||||||
|
/// 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(&mut self) -> Result<String>;
|
||||||
|
|
||||||
|
/// Get DTC (diagnostic trouble code) metadata for each ECU
|
||||||
|
fn get_dtc_info(&mut self) -> Result<Vec<DtcsInfo>>;
|
||||||
|
|
||||||
|
/// Get DTCs for each ECU
|
||||||
|
fn get_dtcs(&mut self) -> Result<Vec<Vec<Dtc>>>;
|
||||||
|
|
||||||
|
/// Get the RPM in increments of 0.25
|
||||||
|
fn get_rpm(&mut self) -> Result<f32>;
|
||||||
|
|
||||||
|
/// Get the speed in km/h
|
||||||
|
fn get_speed(&mut self) -> Result<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Obd2Device> Obd2DataRetrieval for T {
|
||||||
|
fn get_vin(&mut self) -> Result<String> {
|
||||||
|
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)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dtc_info(&mut self) -> Result<Vec<DtcsInfo>> {
|
||||||
|
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<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_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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DTC (diagnostic trouble code) metadata
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct DtcsInfo {
|
||||||
|
/// Whether the "check engine" light is illuminated
|
||||||
|
pub malfunction_indicator_light: bool,
|
||||||
|
|
||||||
|
/// Number of DTCs for this ECU
|
||||||
|
pub dtc_count: u8,
|
||||||
|
|
||||||
|
/// Bit field showing availability of seven common tests; the upper bit is currently unused.
|
||||||
|
pub common_test_availability: u8,
|
||||||
|
|
||||||
|
/// Whether the engine is Diesel
|
||||||
|
pub is_compression_engine: bool,
|
||||||
|
|
||||||
|
/// Bit field showing availability of sixteen engine-specific tests. What the tests are is
|
||||||
|
/// based on the value of `is_compression_engine`.
|
||||||
|
pub specific_test_availability: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An individual trouble code from an ECU
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Dtc {
|
||||||
|
Powertrain(u16),
|
||||||
|
Chassis(u16),
|
||||||
|
Body(u16),
|
||||||
|
Network(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Dtc {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let (c, n) = match self {
|
||||||
|
Self::Powertrain(n) => ('P', n),
|
||||||
|
Self::Chassis(n) => ('C', n),
|
||||||
|
Self::Body(n) => ('B', n),
|
||||||
|
Self::Network(n) => ('U', n),
|
||||||
|
};
|
||||||
|
f.write_fmt(format_args!("{}{:03X}", c, n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
impl<T: super::Obd2Device> Sealed for T {}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Lower level OBD-II interfacing structures
|
||||||
|
|
||||||
mod elm327;
|
mod elm327;
|
||||||
pub use elm327::Elm327;
|
pub use elm327::Elm327;
|
||||||
|
|
||||||
|
|
32
src/error.rs
Normal file
32
src/error.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Device error: `{0:?}`")]
|
||||||
|
Device(DeviceError),
|
||||||
|
#[error("Other OBD2 error: `{0}`")]
|
||||||
|
Other(String),
|
||||||
|
#[error("Incorrect length (`{0}`): expected `{1}`, got `{2}`")]
|
||||||
|
IncorrectResponseLength(&'static str, usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeviceError(super::device::Error);
|
||||||
|
|
||||||
|
impl From<super::device::Error> for Error {
|
||||||
|
fn from(e: super::device::Error) -> Self {
|
||||||
|
Error::Device(DeviceError(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for Error {
|
||||||
|
fn from(e: std::num::ParseIntError) -> Self {
|
||||||
|
Error::Other(format!("invalid data recieved: {:?}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::FromUtf8Error> for Error {
|
||||||
|
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||||
|
Error::Other(format!("invalid string recieved: {:?}", e))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,10 @@ use log::{debug, trace};
|
||||||
|
|
||||||
use super::{device::Obd2BaseDevice, Error, Obd2Device, Result};
|
use super::{device::Obd2BaseDevice, Error, Obd2Device, Result};
|
||||||
|
|
||||||
|
/// An OBD-II interface
|
||||||
|
///
|
||||||
|
/// Wraps an implementor of [Obd2BaseDevice] to allow for higher-level usage of the OBD-II
|
||||||
|
/// interface.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Obd2<T: Obd2BaseDevice> {
|
pub struct Obd2<T: Obd2BaseDevice> {
|
||||||
device: T,
|
device: T,
|
||||||
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -1,10 +1,28 @@
|
||||||
|
//! Crate for communicating with OBD-II (on-board diagnostics) interfaces on cars
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//! ```
|
||||||
|
//! use obd2::{commands::Obd2DataRetrieval, device::Elm327, Obd2};
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), obd2::Error> {
|
||||||
|
//! let mut device = Obd2::<Elm327>::default();
|
||||||
|
//! println!("VIN: {}", device.get_vin()?);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
pub mod device;
|
pub mod device;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
use error::Result;
|
||||||
|
|
||||||
mod interface;
|
mod interface;
|
||||||
pub use interface::Obd2;
|
pub use interface::Obd2;
|
||||||
|
|
||||||
mod obd2_device;
|
mod obd2_device;
|
||||||
use obd2_device::Result;
|
pub use obd2_device::Obd2Device;
|
||||||
pub use obd2_device::{Error, Obd2Device};
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use core::fmt;
|
use crate::{Error, Result};
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// A higher-level API for using an OBD-II device
|
/// A higher-level API for using an OBD-II device
|
||||||
pub trait Obd2Device {
|
pub trait Obd2Device {
|
||||||
|
@ -58,151 +56,4 @@ pub trait Obd2Device {
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| Error::IncorrectResponseLength("count", RESPONSE_COUNT, count))
|
.map_err(|_| Error::IncorrectResponseLength("count", RESPONSE_COUNT, count))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(&mut self) -> Result<String> {
|
|
||||||
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)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get DTC (diagnostic trouble code) metadata for each ECU
|
|
||||||
fn get_dtc_info(&mut self) -> Result<Vec<DtcsInfo>> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get DTCs for each ECU
|
|
||||||
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>>>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the RPM in increments of 0.25
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the speed in km/h
|
|
||||||
fn get_speed(&mut self) -> Result<u8> {
|
|
||||||
Ok(self.obd_command_cnt_len::<1, 1>(0x01, 0x0C)?[0][0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DtcsInfo {
|
|
||||||
malfunction_indicator_light: bool,
|
|
||||||
dtc_count: u8,
|
|
||||||
common_test_availability: u8,
|
|
||||||
is_compression_engine: bool,
|
|
||||||
specific_test_availability: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Dtc {
|
|
||||||
Powertrain(u16),
|
|
||||||
Chassis(u16),
|
|
||||||
Body(u16),
|
|
||||||
Network(u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Dtc {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let (c, n) = match self {
|
|
||||||
Self::Powertrain(n) => ('P', n),
|
|
||||||
Self::Chassis(n) => ('C', n),
|
|
||||||
Self::Body(n) => ('B', n),
|
|
||||||
Self::Network(n) => ('U', n),
|
|
||||||
};
|
|
||||||
f.write_fmt(format_args!("{}{:03X}", c, n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Device error: `{0:?}`")]
|
|
||||||
Device(DeviceError),
|
|
||||||
#[error("Other OBD2 error: `{0}`")]
|
|
||||||
Other(String),
|
|
||||||
#[error("Incorrect length (`{0}`): expected `{1}`, got `{2}`")]
|
|
||||||
IncorrectResponseLength(&'static str, usize, usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeviceError(super::device::Error);
|
|
||||||
|
|
||||||
impl From<super::device::Error> for Error {
|
|
||||||
fn from(e: super::device::Error) -> Self {
|
|
||||||
Error::Device(DeviceError(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::num::ParseIntError> for Error {
|
|
||||||
fn from(e: std::num::ParseIntError) -> Self {
|
|
||||||
Error::Other(format!("invalid data recieved: {:?}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::string::FromUtf8Error> for Error {
|
|
||||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
|
||||||
Error::Other(format!("invalid string recieved: {:?}", e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue