obd2/device/
elm327.rs

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        // device.set_latency_timer(2).unwrap();
73
74        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                // validate new baud rate
137                let validation_response = self.get_line()?;
138                if validation_response == Some(b"ELM327 v1.5".to_vec()) {
139                    // reply that it is okay
140                    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                        // our TX is bad
149                        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                    // reset baud rate and keep looking
155                    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, // no push here
187                _ => 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                    // empty line, try again
209                    self.get_until(end_byte, allow_empty)
210                }
211            } // we got it
212            Some(f) => {
213                // incomplete line read
214                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}