1use log::{debug, info, trace};
2use std::{
3 collections::VecDeque,
4 io::{Read, Write},
5 thread, time,
6};
7
8use super::{Error, Obd2BaseDevice, Obd2Reader, Result};
9
10pub struct Elm327 {
11 device: ftdi::Device,
12 buffer: VecDeque<u8>,
13 baud_rate: u32,
14}
15
16impl Default for Elm327 {
17 fn default() -> Self {
18 Elm327::new().unwrap()
19 }
20}
21
22impl Obd2BaseDevice for Elm327 {
23 fn reset(&mut self) -> Result<()> {
24 self.flush_buffers()?;
25 self.reset_ic()?;
26 thread::sleep(time::Duration::from_millis(500));
27 self.reset_protocol()?;
28 Ok(())
29 }
30
31 fn flush(&mut self) -> Result<()> {
32 thread::sleep(time::Duration::from_millis(500));
33 self.read_into_queue()?;
34 self.buffer.clear();
35 thread::sleep(time::Duration::from_millis(500));
36 Ok(())
37 }
38
39 fn send_serial_cmd(&mut self, data: &str) -> Result<()> {
40 self.device.write_all(data.as_bytes())?;
41 self.device.write_all(b"\r\n")?;
42 let line = self.get_line()?;
43 if line.as_ref().is_some_and(|v| v == data.as_bytes()) {
44 Ok(())
45 } else {
46 Err(Error::Communication(format!(
47 "send_serial_cmd: got {:?} instead of echoed command ({})",
48 line, data
49 )))
50 }
51 }
52}
53
54impl Obd2Reader for Elm327 {
55 fn get_line(&mut self) -> Result<Option<Vec<u8>>> {
56 self.get_until(b'\n', false)
57 }
58
59 fn get_until_prompt(&mut self) -> Result<Option<Vec<u8>>> {
60 self.get_until(b'>', true)
61 }
62}
63
64impl Elm327 {
65 fn new() -> Result<Self> {
66 let mut ftdi_device = ftdi::find_by_vid_pid(0x0403, 0x6001)
67 .interface(ftdi::Interface::A)
68 .open()?;
69
70 ftdi_device.set_baud_rate(38400)?;
71 ftdi_device.configure(ftdi::Bits::Eight, ftdi::StopBits::One, ftdi::Parity::None)?;
72 ftdi_device.usb_reset()?;
75
76 let mut device = Elm327 {
77 device: ftdi_device,
78 buffer: VecDeque::new(),
79 baud_rate: 38400,
80 };
81
82 device.connect(false)?;
83 device.flush()?;
84
85 Ok(device)
86 }
87
88 fn connect(&mut self, check_baud_rate: bool) -> Result<()> {
89 self.flush_buffers()?;
90 thread::sleep(time::Duration::from_millis(500));
91 self.send_serial_str(" ")?;
92 thread::sleep(time::Duration::from_millis(500));
93
94 self.reset()?;
95
96 if check_baud_rate {
97 match self.find_baud_rate_divisor()? {
98 Some((rate, div)) => info!("Found baud rate {} (divisor {})", rate, div),
99 None => info!("Could not find better baud rate"),
100 }
101 }
102
103 Ok(())
104 }
105
106 fn reset_ic(&mut self) -> Result<()> {
107 info!("Performing IC reset");
108 self.send_serial_cmd("ATZ")?;
109 debug!(
110 "reset_ic: got response {:?}",
111 self.get_until_prompt()?
112 .as_ref()
113 .map(|l| std::str::from_utf8(l.as_slice()))
114 );
115 Ok(())
116 }
117
118 fn reset_protocol(&mut self) -> Result<()> {
119 info!("Performing protocol reset");
120 debug!("reset_protocol: got response {:?}", self.cmd("ATSP0")?);
121 debug!("reset_protocol: got OBD response {:?}", self.cmd("0100")?);
122 self.flush_buffers()?;
123 Ok(())
124 }
125
126 fn find_baud_rate_divisor(&mut self) -> Result<Option<(u8, u32)>> {
127 for div in 90..104u8 {
128 let new_baud = 4000000 / u32::from(div);
129
130 debug!("Trying baud rate {} (divisor {})", new_baud, div);
131 self.send_serial_cmd(&format!("ATBRD{:02X}", div))?;
132
133 if self.get_line()? == Some(b"OK".to_vec()) {
134 self.device.set_baud_rate(new_baud)?;
135
136 let validation_response = self.get_line()?;
138 if validation_response == Some(b"ELM327 v1.5".to_vec()) {
139 self.send_serial_str("\r")
141 .expect("Device left in unknown state");
142 if self.get_line().expect("Device left in unknown state")
143 == Some(b"OK".to_vec())
144 {
145 self.baud_rate = new_baud;
146 return Ok(Some((div, new_baud)));
147 } else {
148 self.device.set_baud_rate(self.baud_rate)?;
150 debug!("Baud rate bad - device did not receive response");
151 self.get_until_prompt()?;
152 }
153 } else {
154 self.device.set_baud_rate(self.baud_rate)?;
156 debug!(
157 "Baud rate bad - did get correct string (got {:?} - {:?})",
158 validation_response,
159 validation_response
160 .as_ref()
161 .map(|r| String::from_utf8_lossy(r))
162 );
163 self.get_until_prompt()?;
164 }
165 } else {
166 debug!("Baud rate bad - did not ok initially");
167 self.get_until_prompt()?;
168 }
169
170 thread::sleep(time::Duration::from_millis(200));
171 }
172 Ok(None)
173 }
174
175 fn get_until(&mut self, end_byte: u8, allow_empty: bool) -> Result<Option<Vec<u8>>> {
176 const TIMEOUT: time::Duration = time::Duration::from_secs(5);
177
178 trace!("get_until: getting until {}", end_byte);
179
180 let mut buf = Vec::new();
181 let start = time::Instant::now();
182 while start.elapsed() < TIMEOUT {
183 let Some(b) = self.get_byte()? else { continue };
184 let b = match b {
185 b'\r' => Some(b'\n'),
186 b'\n' => None, _ => Some(b),
188 };
189 if let Some(b) = b {
190 buf.push(b);
191 if b == end_byte {
192 break;
193 }
194 }
195 }
196
197 trace!(
198 "get_until: got {:?} ({:?})",
199 buf,
200 std::str::from_utf8(buf.as_slice())
201 );
202
203 match buf.pop() {
204 Some(b) if b == end_byte => {
205 if allow_empty || !buf.is_empty() {
206 Ok(Some(buf))
207 } else {
208 self.get_until(end_byte, allow_empty)
210 }
211 } Some(f) => {
213 for b in buf.iter().rev() {
215 self.buffer.push_front(*b);
216 }
217 self.buffer.push_front(f);
218 Ok(None)
219 }
220 None => Ok(None),
221 }
222 }
223
224 fn get_byte(&mut self) -> Result<Option<u8>> {
225 self.read_into_queue()?;
226 loop {
227 let b = self.buffer.pop_front();
228 if b != Some(b'\0') {
229 return Ok(b);
230 }
231 }
232 }
233
234 fn flush_buffers(&mut self) -> Result<()> {
235 self.device.usb_purge_buffers()?;
236 Ok(())
237 }
238
239 fn send_serial_str(&mut self, data: &str) -> Result<()> {
240 self.device.write_all(data.as_bytes())?;
241 Ok(())
242 }
243
244 fn read_into_queue(&mut self) -> Result<()> {
245 let mut buf = [0u8; 16];
246 loop {
247 let len = self.device.read(&mut buf)?;
248 if len > 0 {
249 self.buffer.extend(&buf[0..len]);
250 trace!(
251 "read_into_queue: values {:?}",
252 std::str::from_utf8(&buf[0..len])
253 );
254 } else {
255 trace!("read_into_queue: no values left to read");
256 break;
257 }
258 }
259 Ok(())
260 }
261}