From 273c4159be37ed9885a06f2e8f2c4409bbab0782 Mon Sep 17 00:00:00 2001 From: Robert Sammelson Date: Fri, 12 May 2023 19:04:18 -0400 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 378 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++ src/main.rs | 7 + src/obd2/device.rs | 197 +++++++++++++++++++++++ src/obd2/mod.rs | 72 +++++++++ 6 files changed, 665 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/obd2/device.rs create mode 100644 src/obd2/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..defe0e6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,378 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "ftdi" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9c8c625c6b8634ce70cd8cbb2ab8a103e4c2b4185c86d9954d2e16b1ef7c4a" +dependencies = [ + "ftdi-mpsse", + "libftdi1-sys", + "thiserror", +] + +[[package]] +name = "ftdi-mpsse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7cfcda69930a8d2fdcdd7ffb9234fe4c79a8c73934ed4904327d77bfb5078a" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libftdi1-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff6928872c7d13bec3c8a60c4c92f41f6252f3369b7552a5b4f9c90c8ba2338" +dependencies = [ + "cfg-if", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "obd" +version = "0.0.0" +dependencies = [ + "env_logger", + "ftdi", + "log", + "thiserror", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b655587 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "obd" +version = "0.0.0" +edition = "2021" + +[dependencies] +env_logger = "0.10" +ftdi = "0.1" +log = "0.4" +thiserror = "1" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6b04279 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +mod obd2; + +fn main() { + env_logger::init(); + let mut device = obd2::Obd2::default(); + println!("VIN: {:?}", device.get_vin()); +} diff --git a/src/obd2/device.rs b/src/obd2/device.rs new file mode 100644 index 0000000..723398e --- /dev/null +++ b/src/obd2/device.rs @@ -0,0 +1,197 @@ +use log::{debug, info, trace}; +use std::{ + collections::VecDeque, + io::{Read, Write}, + thread, time, +}; + +type Result = std::result::Result; + +pub struct Obd2Basic { + device: ftdi::Device, + buffer: VecDeque, +} + +impl Default for Obd2Basic { + fn default() -> Self { + Obd2Basic::new().unwrap() + } +} + +impl Obd2Basic { + fn new() -> Result { + let mut ftdi_device = ftdi::find_by_vid_pid(0x0403, 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()?; + + let mut device = Obd2Basic { + device: ftdi_device, + buffer: VecDeque::new(), + }; + + device.connect()?; + + Ok(device) + } + + fn connect(&mut self) -> Result<()> { + self.flush_buffers()?; + thread::sleep(time::Duration::from_millis(500)); + self.send_serial_str(" ")?; + thread::sleep(time::Duration::from_millis(500)); + + self.reset()?; + + 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")?; + debug!( + "reset_ic: got response {:?}", + self.get_until_prompt()? + .as_ref() + .map(|l| std::str::from_utf8(l.as_slice())) + ); + Ok(()) + } + + fn reset_protocol(&mut self) -> Result<()> { + info!("Performing protocol reset"); + debug!("reset_protocol: got response {:?}", self.cmd("atsp0")?); + debug!("reset_protocol: got response {:?}", self.cmd("0100")?); + self.flush_buffers()?; + Ok(()) + } + + pub fn get_line(&mut self) -> Result>> { + self.get_until(b'\r') + } + + pub fn get_until_prompt(&mut self) -> Result>> { + self.get_until(b'>') + } + + fn get_until(&mut self, end_byte: u8) -> 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 { + let Some(b) = self.get_byte()? else { continue }; + match b { + b'\r' => { + buf.push(b'\n'); + } + b'\n' => {} + _ => buf.push(b), + } + if b == end_byte { + break; + } + } + + trace!( + "get_until: got {:?} ({:?})", + buf, + std::str::from_utf8(buf.as_slice()) + ); + + match buf.last() { + Some(b) if b == &end_byte => Ok(Some(buf)), // we got it + Some(_) => { + // incomplete line read + for b in buf.iter().rev() { + self.buffer.push_front(*b); + } + Ok(None) + } + None => Ok(None), + } + } + + fn get_byte(&mut self) -> Result> { + self.read_into_queue()?; + loop { + let b = self.buffer.pop_front(); + if b != Some(b'\0') { + return Ok(b); + } + } + } + + 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")?; + Ok(()) + } + + fn send_serial_str(&mut self, data: &str) -> Result<()> { + self.device.write_all(data.as_bytes())?; + Ok(()) + } + + fn read_into_queue(&mut self) -> Result<()> { + let mut buf = [0u8; 16]; + loop { + let len = self.device.read(&mut buf)?; + self.buffer.extend(&buf[0..len]); + trace!( + "read_into_queue: values {:?}", + std::str::from_utf8(&buf[0..len]) + ); + if len == 0 { + break; + } + } + Ok(()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("FTDI error: `{0:?}`")] + Ftdi(ftdi::Error), + #[error("IO error: `{0:?}`")] + IO(std::io::Error), +} + +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 new file mode 100644 index 0000000..5d46302 --- /dev/null +++ b/src/obd2/mod.rs @@ -0,0 +1,72 @@ +mod device; + +type Result = std::result::Result; + +#[derive(Default)] +pub struct Obd2 { + device: device::Obd2Basic, +} + +impl Obd2 { + pub fn get_vin(&mut self) -> Result { + let response = self + .device + .cmd("0902")? + .ok_or(Error::Other("no response to get vin command".to_owned()))?; + let mut n_idx = 0; + let data: Vec = response + .split('\n') + .filter_map(|l| l.split_once(':')) + .flat_map(|(idx, data)| { + if u8::from_str_radix(idx, 16) != Ok(n_idx) { + todo!() + } + n_idx = (n_idx + 1) % 0x10; + data.split_whitespace().map(|s| s.to_owned()) + }) + .collect(); + + if data.get(0).map(|s| s.as_str()) != Some("49") { + todo!() + } + if data.get(1).map(|s| s.as_str()) != Some("02") { + todo!() + } + + let vin = String::from_utf8( + data.split_at(3) + .1 + .iter() + .map(|s| u8::from_str_radix(s, 16).map_err(|e| e.into())) + .collect::>()?, + )?; + + Ok(vin) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Communication error: `{0:?}`")] + Communication(device::Error), + #[error("Other OBD2 error: `{0}`")] + Other(String), +} + +impl From for Error { + fn from(e: device::Error) -> Self { + Error::Communication(e) + } +} + +impl From for Error { + fn from(e: std::num::ParseIntError) -> Self { + Error::Other(format!("Invalid data recieved: {:?}", e)) + } +} + +impl From for Error { + fn from(e: std::string::FromUtf8Error) -> Self { + Error::Other(format!("Invalid string recieved: {:?}", e)) + } +}