From ab604f3c517247cf8c89a537fb3d4ddb8b1deae9 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Tue, 18 Mar 2025 17:13:03 -0400 Subject: [PATCH] last before fix --- src/device/elm327_linux.rs | 240 +++++++++++++++++++++++++++++++++++++ src/interface.rs | 4 + 2 files changed, 244 insertions(+) create mode 100644 src/device/elm327_linux.rs diff --git a/src/device/elm327_linux.rs b/src/device/elm327_linux.rs new file mode 100644 index 0000000..5d40fad --- /dev/null +++ b/src/device/elm327_linux.rs @@ -0,0 +1,240 @@ +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/interface.rs b/src/interface.rs index d9beb4c..e1ec870 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -48,6 +48,10 @@ impl Obd2 { Ok(device) } + pub fn reset(&mut self) -> Result<()> { + Ok(self.device.reset()?) + } + fn command(&mut self, command: &[u8]) -> Result>> { let response = self .device