diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8ce16d5..443d8f0 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -221,5 +221,6 @@ func! { /// Get service 1 PID support for $21 to $40 fn get_service_1_pid_support_2(0x01, 0x20) -> u32; + // Get the fuel level (out of 255) fn get_fuel_level(0x01, 0x2F) -> u8; } diff --git a/src/device/elm327.rs b/src/device/elm327.rs index 5482193..9b8d9e9 100644 --- a/src/device/elm327.rs +++ b/src/device/elm327.rs @@ -4,9 +4,97 @@ use std::{ io::{Read, Write}, thread, time, }; +use std::time::Duration; use super::{Error, Obd2BaseDevice, Obd2Reader, Result}; +trait SerialDevice { + fn write_all(&mut self, data: &[u8]) -> Result<()>; + fn read(&mut self, data: &mut [u8]) -> Result; + fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()>; + fn purge_buffers(&mut self) -> Result<()>; +} + +pub struct SerialPort { + device: Box +} + +impl SerialPort { + pub fn new(path: &str) -> Result { + let port = serialport::new(path, 38_400) + .timeout(Duration::from_millis(10)) + .parity(serialport::Parity::None) + .data_bits(serialport::DataBits::Eight) + .stop_bits(serialport::StopBits::One) + .path(path) + .open()?; + + let device = SerialPort { + device: port + }; + + Ok(device) + } +} + +impl SerialDevice for SerialPort { + fn write_all(&mut self, data: &[u8]) -> Result<()> { + Ok(self.device.write_all(data)?) + } + + fn read(&mut self, data: &mut [u8]) -> Result { + Ok(self.device.read(data)?) + } + + fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + Ok(self.device.set_baud_rate(baud_rate)?) + } + + fn purge_buffers(&mut self) -> Result<()> { + Ok(self.device.clear(serialport::ClearBuffer::All)?) + } +} + +pub struct FTDIDevice { + device: ftdi::Device +} + +impl FTDIDevice { + pub fn new() -> Result { + let mut ftdi_device = ftdi::find_by_vid_pid(0x0404, 0x6001) + .interface(ftdi::Interface::A) + .open()?; + + ftdi_device.set_baud_rate(38400)?; + ftdi_device.configure(ftdi::Bits::Eight, ftdi::StopBits::One, ftdi::Parity::None)?; + ftdi_device.usb_reset()?; + + let device = FTDIDevice { + device: ftdi_device + }; + + Ok(device) + } +} + +impl SerialDevice for FTDIDevice { + fn write_all(&mut self, data: &[u8]) -> Result<()> { + Ok(self.device.write_all(data)?) + } + + fn read(&mut self, data: &mut [u8]) -> Result { + Ok(self.device.read(data)?) + } + + fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + Ok(self.device.set_baud_rate(baud_rate)?) + } + + fn purge_buffers(&mut self) -> Result<()> { + Ok(self.device.usb_purge_buffers()?) + } +} + /// An ELM327 OBD-II adapter /// /// It communicates with the computer over UART using an FTDI FT232R USB-to-UART converter. @@ -16,13 +104,13 @@ use super::{Error, Obd2BaseDevice, Obd2Reader, Result}; /// /// [Datasheet for v1.4b](https://github.com/rsammelson/obd2/blob/master/docs/ELM327DSH.pdf), and /// the [source](https://www.elmelectronics.com/products/dsheets/). -pub struct Elm327 { - device: ftdi::Device, +pub struct Elm327 { + device: T, buffer: VecDeque, baud_rate: u32, } -impl Obd2BaseDevice for Elm327 { +impl Obd2BaseDevice for Elm327 { fn reset(&mut self) -> Result<()> { self.flush_buffers()?; self.reset_ic()?; @@ -42,7 +130,7 @@ impl Obd2BaseDevice for Elm327 { } } -impl Obd2Reader for Elm327 { +impl Obd2Reader for Elm327 { fn get_line(&mut self) -> Result>> { self.get_until(b'\n', false) } @@ -58,22 +146,12 @@ impl Obd2Reader for Elm327 { } } -impl Elm327 { - fn new() -> Result { - let mut ftdi_device = ftdi::find_by_vid_pid(0x0404, 0x6001) - .interface(ftdi::Interface::A) - .open()?; - - ftdi_device.set_baud_rate(38400)?; - ftdi_device.configure(ftdi::Bits::Eight, ftdi::StopBits::One, ftdi::Parity::None)?; - // device.set_latency_timer(2).unwrap(); - - ftdi_device.usb_reset()?; - +impl Elm327 { + pub fn new(serial_device: T) -> Result { let mut device = Elm327 { - device: ftdi_device, + device: serial_device, buffer: VecDeque::new(), - baud_rate: 38400, + baud_rate: 38_400, }; device.connect(false)?; @@ -92,7 +170,7 @@ impl Elm327 { } fn flush_buffers(&mut self) -> Result<()> { - self.device.usb_purge_buffers()?; + self.device.purge_buffers()?; Ok(()) } @@ -117,9 +195,10 @@ impl Elm327 { fn reset_ic(&mut self) -> Result<()> { info!("Performing IC reset"); self.send_serial_str("ATZ")?; + let response = self.get_response()?; debug!( "reset_ic: got response {:?}", - self.get_response()? + response .as_ref() .map(|l| std::str::from_utf8(l.as_slice())) ); @@ -128,14 +207,18 @@ impl Elm327 { fn reset_protocol(&mut self) -> Result<()> { info!("Performing protocol reset"); + let elm_response = self.serial_cmd("ATSP0")?; debug!( "reset_protocol: got response {:?}", - self.serial_cmd("ATSP0")? + elm_response ); + + let obd_response = self.cmd(&[0x01, 0x00])?; debug!( "reset_protocol: got OBD response {:?}", - self.cmd(&[0x01, 0x00])? + obd_response ); + self.flush_buffers()?; Ok(()) } @@ -252,13 +335,18 @@ impl Elm327 { fn read_into_queue(&mut self) -> Result<()> { let mut buf = [0u8; 16]; loop { - let len = self.device.read(&mut buf)?; - if len > 0 { - self.buffer.extend(&buf[0..len]); - trace!( - "read_into_queue: values {:?}", - std::str::from_utf8(&buf[0..len]) - ); + let len_res = self.device.read(&mut buf); + if let Ok(len) = len_res { + if len > 0 { + self.buffer.extend(&buf[0..len]); + trace!( + "read_into_queue: values {:?}", + std::str::from_utf8(&buf[0..len]) + ); + } else { + trace!("read_into_queue: no values left to read"); + break; + } } else { trace!("read_into_queue: no values left to read"); break; diff --git a/src/device/elm327_linux.rs b/src/device/elm327_linux.rs deleted file mode 100644 index 5d40fad..0000000 --- a/src/device/elm327_linux.rs +++ /dev/null @@ -1,240 +0,0 @@ -use log::{debug, info, trace}; -use std::{ - collections::VecDeque, - io::{Read, Write}, - thread, time, -}; -use std::time::Duration; - -use super::{Error, Obd2BaseDevice, Obd2Reader, Result}; - -/// An ELM327 OBD-II adapter -/// -/// Uses a /dev/ttyUSB* device or similar to communicate -pub struct Elm327Linux { - port: Box, - buffer: VecDeque, - baud_rate: u32, -} - -impl Obd2BaseDevice for Elm327Linux { - fn reset(&mut self) -> Result<()> { - self.flush_buffers()?; - self.reset_ic()?; - thread::sleep(time::Duration::from_millis(500)); - self.reset_protocol()?; - Ok(()) - } - - fn send_cmd(&mut self, data: &[u8]) -> Result<()> { - trace!("send_cmd: sending {:?}", std::str::from_utf8(data)); - self.send_serial_str( - data.into_iter() - .flat_map(|v| format!("{:02X}", v).chars().collect::>()) - .collect::() - .as_str(), - ) - } -} - -impl Obd2Reader for Elm327Linux { - fn get_line(&mut self) -> Result>> { - self.get_until(b'\n', false) - } - - /// Read data until the ELM327's prompt character is printed - /// - /// This will receive the entire OBD-II response. The prompt signifies that the ELM327 is ready - /// for another command. If this is not called after each OBD-II command is sent, the prompt - /// character will come out of the receive queue later and because it is not valid hex this - /// could cause problems. If a timeout occurs, `Ok(None)` will be returned. - fn get_response(&mut self) -> Result>> { - self.get_until(b'>', true) - } -} - -impl Elm327Linux { - pub fn new(serial_path: &str) -> ::anyhow::Result { - let port = serialport::new(serial_path, 38_400) - .timeout(Duration::from_millis(10)) - .parity(serialport::Parity::None) - .data_bits(serialport::DataBits::Eight) - .stop_bits(serialport::StopBits::One) - .path(serial_path) - .open()?; - - let mut device = Elm327Linux { - port, - buffer: VecDeque::new(), - baud_rate: 38_400, - }; - - Ok(device) - } - - /// Flush the device's buffer - 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 connect(&mut self) -> Result<()> { - thread::sleep(time::Duration::from_millis(500)); - self.serial_cmd(" ")?; - thread::sleep(time::Duration::from_millis(500)); - self.reset()?; - - Ok(()) - } - - fn reset_ic(&mut self) -> Result<()> { - info!("Performing IC reset"); - self.send_serial_str("ATZ")?; - self.get_response()?; - //debug!( - // "reset_ic: got response {:?}", - // self.get_response()? - // .as_ref() - // .map(|l| std::str::from_utf8(l.as_slice())) - //); - Ok(()) - } - - fn flush_buffers(&mut self) -> Result<()> { - self.buffer.flush(); - Ok(()) - } - - fn reset_protocol(&mut self) -> Result<()> { - info!("Performing protocol reset"); - self.serial_cmd("ATSP0")?; - //debug!( - // "reset_protocol: got response {:?}", - // self.serial_cmd("ATSP0")? - //); - self.cmd(&[0x01, 0x00])?; - //debug!( - // "reset_protocol: got OBD response {:?}", - // self.cmd(&[0x01, 0x00])? - //); - self.flush_buffers()?; - Ok(()) - } - - - fn get_until(&mut self, end_byte: u8, allow_empty: bool) -> Result>> { - const TIMEOUT: time::Duration = time::Duration::from_secs(5); - - trace!("get_until: getting until {}", end_byte); - - let mut buf = Vec::new(); - let start = time::Instant::now(); - while start.elapsed() < TIMEOUT { - trace!("Getting Byte"); - let Some(b) = self.get_byte()? else { continue }; - trace!("{}", b); - let b = match b { - b'\r' => Some(b'\n'), - b'\n' => None, // no push here - _ => Some(b), - }; - if let Some(b) = b { - buf.push(b); - if b == end_byte { - break; - } - } - } - - trace!( - "get_until: got {:?} ({:?})", - buf, - std::str::from_utf8(buf.as_slice()) - ); - - match buf.pop() { - Some(b) if b == end_byte => { - trace!("got it!"); - if allow_empty || !buf.is_empty() { - Ok(Some(buf)) - } else { - // empty line, try again - self.get_until(end_byte, allow_empty) - } - } // we got it - Some(f) => { - // incomplete line read - for b in buf.iter().rev() { - self.buffer.push_front(*b); - } - self.buffer.push_front(f); - Ok(None) - } - None => Ok(None), - } - } - - fn get_byte(&mut self) -> Result> { - match self.buffer.pop_front() { - Some(b'\0') => Ok(None), - Some(b) => Ok(Some(b)), - None => { - trace!("EmptyBuf"); - self.read_into_queue()?; - Ok(None) - } - } - } - - fn read_into_queue(&mut self) -> Result<()> { - let mut buf = [0u8; 16]; - loop { - let len_res = self.port.read(&mut buf); - if let Ok(len) = len_res { - if len > 0 { - self.buffer.extend(&buf[0..len]); - trace!( - "read_into_queue: values {:?}", - std::str::from_utf8(&buf[0..len]) - ); - } else { - trace!("read_into_queue: no values left to read"); - break; - } - } else { - trace!("read_into_queue: no values left to read"); - break; - } - } - Ok(()) - } - - fn serial_cmd(&mut self, cmd: &str) -> Result> { - self.send_serial_str(cmd)?; - self.get_response() - .map(|o| o.and_then(|resp| String::from_utf8(resp).ok())) - } - - /// Function for sending a raw string, without encoding into ASCII hex - fn send_serial_str(&mut self, data: &str) -> Result<()> { - trace!("send_serial_str: sending {:?}", data); - - let data = data.as_bytes(); - - self.port.write_all(data)?; - self.port.write_all(b"\r\n")?; - let line = self.get_line()?; - if line.as_ref().is_some_and(|v| v == data) { - Ok(()) - } else { - Err(Error::Communication(format!( - "send_serial_str: got {:?} instead of echoed command ({:?})", - line, data - ))) - } - } -} - diff --git a/src/device/mod.rs b/src/device/mod.rs index 9a8d934..cff2463 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -1,10 +1,7 @@ //! Lower level OBD-II interfacing structures mod elm327; -pub use elm327::Elm327; - -mod elm327_linux; -pub use elm327_linux::Elm327Linux; +pub use elm327::{Elm327, SerialPort, FTDIDevice}; type Result = std::result::Result; @@ -56,6 +53,9 @@ pub enum Error { /// An error with the underlying [FTDI device](ftdi::Device) #[error("FTDI error: `{0:?}`")] Ftdi(ftdi::Error), + + #[error("Serialport error: `{0:?}`")] + Serialport(serialport::Error), /// An I/O error in a low-level [std::io] stream operation #[error("IO error: `{0:?}`")] @@ -72,6 +72,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: serialport::Error) -> Self { + Error::Serialport(e) + } +} + impl From for Error { fn from(e: std::io::Error) -> Self { Error::IO(e) diff --git a/src/interface.rs b/src/interface.rs index e1ec870..cc083dd 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -41,7 +41,7 @@ impl Obd2Device for Obd2 { impl Obd2 { pub fn new(dev: T) -> ::anyhow::Result { - let mut device = Obd2 { + let device = Obd2 { device: dev };