From cd82a02efbd6e38a35bb1e0c3c399cc5533086a1 Mon Sep 17 00:00:00 2001 From: Robert Sammelson Date: Sat, 13 May 2023 20:21:01 -0400 Subject: [PATCH] Use traits for basic reader --- src/main.rs | 2 +- src/obd2/{device.rs => device/elm327.rs} | 156 ++++++++++------------- src/obd2/device/mod.rs | 42 ++++++ src/obd2/mod.rs | 12 +- 4 files changed, 116 insertions(+), 96 deletions(-) rename src/obd2/{device.rs => device/elm327.rs} (82%) create mode 100644 src/obd2/device/mod.rs diff --git a/src/main.rs b/src/main.rs index 9edee86..4f2200c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ mod obd2; fn main() { env_logger::init(); - let mut device = obd2::Obd2::default(); + let mut device: obd2::Obd2 = obd2::Obd2::default(); println!("VIN: {:?}", device.get_vin()); println!("DTC Info: {:#?}", device.get_dtc_info()); diff --git a/src/obd2/device.rs b/src/obd2/device/elm327.rs similarity index 82% rename from src/obd2/device.rs rename to src/obd2/device/elm327.rs index eb288b9..2a7aa4d 100644 --- a/src/obd2/device.rs +++ b/src/obd2/device/elm327.rs @@ -5,20 +5,80 @@ use std::{ thread, time, }; -type Result = std::result::Result; +use super::{Error, Obd2BaseDevice, Obd2Reader, Result}; -pub struct Obd2Basic { +pub struct Elm327 { device: ftdi::Device, buffer: VecDeque, } -impl Default for Obd2Basic { +impl Default for Elm327 { fn default() -> Self { - Obd2Basic::new().unwrap() + Elm327::new().unwrap() } } -impl Obd2Basic { +impl Obd2BaseDevice for Elm327 { + fn reset(&mut self) -> Result<()> { + self.flush_buffers()?; + self.reset_ic()?; + thread::sleep(time::Duration::from_millis(500)); + self.reset_protocol()?; + Ok(()) + } + + fn flush(&mut self) -> Result<()> { + thread::sleep(time::Duration::from_millis(500)); + self.read_into_queue()?; + self.buffer.clear(); + thread::sleep(time::Duration::from_millis(500)); + Ok(()) + } + + fn send_serial_cmd(&mut self, data: &str) -> Result<()> { + self.device.write_all(data.as_bytes())?; + self.device.write_all(b"\r\n")?; + let line = self.get_line()?; + if line.as_ref().is_some_and(|v| v == data.as_bytes()) { + Ok(()) + } else { + Err(Error::Communication(format!( + "send_serial_cmd: got {:?} instead of echoed command ({})", + line, data + ))) + } + } +} + +impl Obd2Reader for Elm327 { + fn get_line(&mut self) -> Result>> { + let result = self.get_until(b'\n')?; + let Some(mut line) = result else { + return Ok(result); + }; + if line.pop() == Some(b'\n') { + Ok(Some(line)) + } else { + Err(Error::Communication("get_line no line ending".to_owned())) + } + } + + fn get_until_prompt(&mut self) -> Result>> { + let result = self.get_until(b'>')?; + let Some(mut line) = result else { + return Ok(result); + }; + if line.pop() == Some(b'>') { + Ok(Some(line)) + } else { + Err(Error::Communication( + "get_until_prompt no ending".to_owned(), + )) + } + } +} + +impl Elm327 { fn new() -> Result { let mut ftdi_device = ftdi::find_by_vid_pid(0x0403, 0x6001) .interface(ftdi::Interface::A) @@ -30,7 +90,7 @@ impl Obd2Basic { ftdi_device.usb_reset()?; - let mut device = Obd2Basic { + let mut device = Elm327 { device: ftdi_device, buffer: VecDeque::new(), }; @@ -52,20 +112,6 @@ impl Obd2Basic { Ok(()) } - pub fn reset(&mut self) -> Result<()> { - self.flush_buffers()?; - self.reset_ic()?; - thread::sleep(time::Duration::from_millis(500)); - self.reset_protocol()?; - Ok(()) - } - - pub fn cmd(&mut self, cmd: &str) -> Result> { - self.send_serial_cmd(cmd)?; - self.get_until_prompt() - .map(|o| o.and_then(|resp| String::from_utf8(resp).ok())) - } - fn reset_ic(&mut self) -> Result<()> { info!("Performing IC reset"); self.send_serial_cmd("atz")?; @@ -86,32 +132,6 @@ impl Obd2Basic { Ok(()) } - pub fn get_line(&mut self) -> Result>> { - let result = self.get_until(b'\n')?; - let Some(mut line) = result else { - return Ok(result); - }; - if line.pop() == Some(b'\n') { - Ok(Some(line)) - } else { - Err(Error::Communication("get_line no line ending".to_owned())) - } - } - - pub fn get_until_prompt(&mut self) -> Result>> { - let result = self.get_until(b'>')?; - let Some(mut line) = result else { - return Ok(result); - }; - if line.pop() == Some(b'>') { - Ok(Some(line)) - } else { - Err(Error::Communication( - "get_until_prompt no ending".to_owned(), - )) - } - } - fn get_until(&mut self, end_byte: u8) -> Result>> { const TIMEOUT: time::Duration = time::Duration::from_secs(5); @@ -166,33 +186,11 @@ impl Obd2Basic { } } - pub fn flush(&mut self) -> Result<()> { - thread::sleep(time::Duration::from_millis(500)); - self.read_into_queue()?; - self.buffer.clear(); - thread::sleep(time::Duration::from_millis(500)); - Ok(()) - } - fn flush_buffers(&mut self) -> Result<()> { self.device.usb_purge_buffers()?; Ok(()) } - pub fn send_serial_cmd(&mut self, data: &str) -> Result<()> { - self.device.write_all(data.as_bytes())?; - self.device.write_all(b"\r\n")?; - let line = self.get_line()?; - if line.as_ref().is_some_and(|v| v == data.as_bytes()) { - Ok(()) - } else { - Err(Error::Communication(format!( - "send_serial_cmd: got {:?} instead of echoed command ({})", - line, data - ))) - } - } - fn send_serial_str(&mut self, data: &str) -> Result<()> { self.device.write_all(data.as_bytes())?; Ok(()) @@ -216,25 +214,3 @@ impl Obd2Basic { Ok(()) } } - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("FTDI error: `{0:?}`")] - Ftdi(ftdi::Error), - #[error("IO error: `{0:?}`")] - IO(std::io::Error), - #[error("Communication error: `{0}`")] - Communication(String), -} - -impl From for Error { - fn from(e: ftdi::Error) -> Self { - Error::Ftdi(e) - } -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IO(e) - } -} diff --git a/src/obd2/device/mod.rs b/src/obd2/device/mod.rs new file mode 100644 index 0000000..f54d36d --- /dev/null +++ b/src/obd2/device/mod.rs @@ -0,0 +1,42 @@ +mod elm327; +pub use elm327::Elm327; + +type Result = std::result::Result; + +pub trait Obd2BaseDevice: Obd2Reader { + fn reset(&mut self) -> Result<()>; + fn flush(&mut self) -> Result<()>; + fn send_serial_cmd(&mut self, data: &str) -> Result<()>; + fn cmd(&mut self, cmd: &str) -> Result> { + self.send_serial_cmd(cmd)?; + self.get_until_prompt() + .map(|o| o.and_then(|resp| String::from_utf8(resp).ok())) + } +} + +pub trait Obd2Reader { + fn get_line(&mut self) -> Result>>; + fn get_until_prompt(&mut self) -> Result>>; +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("FTDI error: `{0:?}`")] + Ftdi(ftdi::Error), + #[error("IO error: `{0:?}`")] + IO(std::io::Error), + #[error("Communication error: `{0}`")] + Communication(String), +} + +impl From for Error { + fn from(e: ftdi::Error) -> Self { + Error::Ftdi(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IO(e) + } +} diff --git a/src/obd2/mod.rs b/src/obd2/mod.rs index ceeba88..850b9c9 100644 --- a/src/obd2/mod.rs +++ b/src/obd2/mod.rs @@ -1,17 +1,19 @@ +mod device; +pub use device::Elm327; +use device::Obd2BaseDevice; + use core::fmt; use log::{debug, trace}; -mod device; - type Result = std::result::Result; #[derive(Default)] -pub struct Obd2 { - device: device::Obd2Basic, +pub struct Obd2 { + device: T, } -impl Obd2 { +impl Obd2 { pub fn obd_command(&mut self, mode: u8, pid: u8) -> Result>> { let result = self.command(&format!("{:02x}{:02x}", mode, pid))?;