1use std::collections::HashSet;
7use async_trait::async_trait;
8use tracing::debug;
9use crate::error::Obd2Error;
10use crate::protocol::pid::Pid;
11use crate::protocol::service::{ServiceRequest, Target};
12use crate::transport::Transport;
13use super::{Adapter, AdapterInfo, Chipset, Capabilities};
14use crate::vehicle::Protocol;
15
16fn default_adapter_info() -> AdapterInfo {
18 AdapterInfo {
19 chipset: Chipset::Unknown,
20 firmware: String::new(),
21 protocol: Protocol::Auto,
22 capabilities: Capabilities::default(),
23 }
24}
25
26pub struct Elm327Adapter {
28 transport: Box<dyn Transport>,
29 info: AdapterInfo,
30 initialized: bool,
31 current_header: Option<String>,
32}
33
34impl Elm327Adapter {
35 pub fn new(transport: Box<dyn Transport>) -> Self {
37 Self {
38 transport,
39 info: default_adapter_info(),
40 initialized: false,
41 current_header: None,
42 }
43 }
44
45 async fn send_command(&mut self, cmd: &str) -> Result<String, Obd2Error> {
47 debug!(cmd = cmd, "ELM327 send");
48 self.transport.write(format!("{}\r", cmd).as_bytes()).await?;
49 let response_bytes = self.transport.read().await?;
50 let response = String::from_utf8_lossy(&response_bytes).to_string();
51 debug!(response = response.trim(), "ELM327 recv");
52 Ok(response)
53 }
54
55 #[allow(dead_code)]
57 async fn set_header(&mut self, header: &str) -> Result<(), Obd2Error> {
58 if self.current_header.as_deref() == Some(header) {
59 return Ok(()); }
61 let response = self.send_command(&format!("AT SH {}", header)).await?;
62 if !response.contains("OK") {
63 return Err(Obd2Error::Adapter(format!("AT SH failed: {}", response.trim())));
64 }
65 self.current_header = Some(header.to_string());
66 Ok(())
67 }
68
69 fn parse_hex_response(response: &str, skip_bytes: usize) -> Result<Vec<u8>, Obd2Error> {
73 let cleaned = response
74 .replace(['\r', '\n'], " ")
75 .replace('>', "");
76
77 let bytes: Result<Vec<u8>, _> = cleaned
78 .split_whitespace()
79 .filter(|s| !s.is_empty())
80 .map(|s| u8::from_str_radix(s, 16))
81 .collect();
82
83 match bytes {
84 Ok(all_bytes) => {
85 if all_bytes.len() > skip_bytes {
86 Ok(all_bytes[skip_bytes..].to_vec())
87 } else {
88 Ok(vec![])
89 }
90 }
91 Err(_) => Err(Obd2Error::ParseError(format!(
92 "invalid hex: {}",
93 response.trim()
94 ))),
95 }
96 }
97
98 pub fn parse_supported_pids(data: &[u8], base_pid: u8) -> Vec<u8> {
102 let mut pids = Vec::new();
103 for (byte_idx, &byte) in data.iter().enumerate() {
104 for bit in 0..8 {
105 if byte & (0x80 >> bit) != 0 {
106 let pid = base_pid + (byte_idx as u8 * 8) + bit + 1;
107 pids.push(pid);
108 }
109 }
110 }
111 pids
112 }
113
114 fn check_response_error(response: &str) -> Result<(), Obd2Error> {
116 let trimmed = response.trim();
117 if trimmed.contains("NO DATA") {
118 return Err(Obd2Error::NoData);
119 }
120 if trimmed.contains("UNABLE TO CONNECT") {
121 return Err(Obd2Error::Adapter("unable to connect to vehicle".into()));
122 }
123 if trimmed.contains("BUS INIT") && trimmed.contains("ERROR") {
124 return Err(Obd2Error::Adapter("bus initialization error".into()));
125 }
126 if trimmed.contains("CAN ERROR") {
127 return Err(Obd2Error::Adapter("CAN bus error".into()));
128 }
129 if trimmed == "?" {
130 return Err(Obd2Error::Adapter("unknown command".into()));
131 }
132 if trimmed.starts_with("7F") {
134 let parts: Vec<&str> = trimmed.split_whitespace().collect();
135 if parts.len() >= 3 {
136 if let (Ok(service), Ok(nrc_byte)) = (
137 u8::from_str_radix(parts[1], 16),
138 u8::from_str_radix(parts[2], 16),
139 ) {
140 if let Some(nrc) = crate::error::NegativeResponse::from_byte(nrc_byte) {
141 return Err(Obd2Error::NegativeResponse { service, nrc });
142 }
143 }
144 }
145 }
146 Ok(())
147 }
148}
149
150impl std::fmt::Debug for Elm327Adapter {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct("Elm327Adapter")
153 .field("info", &self.info)
154 .field("initialized", &self.initialized)
155 .field("current_header", &self.current_header)
156 .finish()
157 }
158}
159
160#[async_trait]
161impl Adapter for Elm327Adapter {
162 async fn initialize(&mut self) -> Result<AdapterInfo, Obd2Error> {
163 let atz_response = self.send_command("ATZ").await?;
165
166 let sti_response = match self.send_command("STI").await {
168 Ok(r) if !r.contains("?") => Some(r),
169 _ => None,
170 };
171
172 let mut info = AdapterInfo::detect(
174 &atz_response,
175 sti_response.as_deref(),
176 );
177
178 self.send_command("ATE0").await?; self.send_command("ATL0").await?; self.send_command("ATH0").await?; self.send_command("ATSP0").await?; let response = self.send_command("0100").await?;
186 if response.contains("41 00") || response.contains("4100") {
187 if let Ok(protocol_response) = self.send_command("ATDPN").await {
190 let proto_char = protocol_response
191 .trim()
192 .replace('>', "")
193 .trim()
194 .chars()
195 .last()
196 .unwrap_or('0');
197 info.protocol = match proto_char {
198 '1' => Protocol::J1850Pwm,
199 '2' => Protocol::J1850Vpw,
200 '3' => Protocol::Iso9141(crate::vehicle::KLineInit::SlowInit),
201 '4' => Protocol::Kwp2000(crate::vehicle::KLineInit::SlowInit),
202 '5' => Protocol::Kwp2000(crate::vehicle::KLineInit::FastInit),
203 '6' => Protocol::Can11Bit500,
204 '7' => Protocol::Can29Bit500,
205 '8' => Protocol::Can11Bit250,
206 '9' => Protocol::Can29Bit250,
207 _ => Protocol::Auto,
208 };
209 }
210 }
211
212 self.info = info.clone();
213 self.initialized = true;
214 Ok(info)
215 }
216
217 async fn request(&mut self, req: &ServiceRequest) -> Result<Vec<u8>, Obd2Error> {
218 match &req.target {
220 Target::Module(module_id) => {
221 debug!(module = %module_id, "targeting specific module");
222 }
223 Target::Broadcast => {
224 }
226 }
227
228 let cmd = if req.data.is_empty() {
230 format!("{:02X}", req.service_id)
231 } else {
232 let data_hex: Vec<String> = req.data.iter().map(|b| format!("{:02X}", b)).collect();
233 format!("{:02X}{}", req.service_id, data_hex.join(""))
234 };
235
236 let response = self.send_command(&cmd).await?;
237
238 Self::check_response_error(&response)?;
240
241 let skip = match req.service_id {
243 0x01 | 0x02 => 2, 0x03 | 0x04 | 0x07 | 0x0A => 1, 0x09 => 2, 0x22 | 0x21 => 3, _ => 1,
248 };
249
250 Self::parse_hex_response(&response, skip)
251 }
252
253 async fn supported_pids(&mut self) -> Result<HashSet<Pid>, Obd2Error> {
254 let mut all_supported = HashSet::new();
255
256 for base in [0x00u8, 0x20, 0x40, 0x60] {
258 let cmd = format!("01{:02X}", base);
259 match self.send_command(&cmd).await {
260 Ok(response) => {
261 if Self::check_response_error(&response).is_err() {
262 break; }
264 if let Ok(data) = Self::parse_hex_response(&response, 2) {
265 if data.len() >= 4 {
266 for pid_code in Self::parse_supported_pids(&data, base) {
267 all_supported.insert(Pid(pid_code));
268 }
269 }
270 }
271 }
272 Err(_) => break,
273 }
274 }
275
276 Ok(all_supported)
277 }
278
279 async fn battery_voltage(&mut self) -> Result<Option<f64>, Obd2Error> {
280 let response = self.send_command("ATRV").await?;
281 let cleaned = response
282 .replace(['V', 'v', '>', '\r', '\n'], "");
283 let cleaned = cleaned.trim().to_string();
284 match cleaned.parse::<f64>() {
285 Ok(v) => Ok(Some(v)),
286 Err(_) => Ok(None),
287 }
288 }
289
290 fn info(&self) -> &AdapterInfo {
291 &self.info
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use crate::transport::mock::MockTransport;
299
300 fn setup_init(transport: &mut MockTransport) {
301 transport.expect("ATZ", "ELM327 v2.1\r\r>");
302 transport.expect("STI", "?\r>");
303 transport.expect("ATE0", "OK\r>");
304 transport.expect("ATL0", "OK\r>");
305 transport.expect("ATH0", "OK\r>");
306 transport.expect("ATSP0", "OK\r>");
307 transport.expect("0100", "41 00 BE 3E B8 11\r>");
308 transport.expect("ATDPN", "A6\r>"); }
310
311 #[tokio::test]
312 async fn test_elm327_initialize() {
313 let mut transport = MockTransport::new();
314 setup_init(&mut transport);
315
316 let mut adapter = Elm327Adapter::new(Box::new(transport));
317 let info = adapter.initialize().await.unwrap();
318 assert_eq!(info.chipset, Chipset::Elm327Genuine);
319 }
320
321 #[tokio::test]
322 async fn test_elm327_read_pid() {
323 let mut transport = MockTransport::new();
324 setup_init(&mut transport);
325 transport.expect("010C", "41 0C 0A A0\r>");
326
327 let mut adapter = Elm327Adapter::new(Box::new(transport));
328 adapter.initialize().await.unwrap();
329
330 let req = ServiceRequest::read_pid(Pid::ENGINE_RPM);
331 let response = adapter.request(&req).await.unwrap();
332 assert_eq!(response, vec![0x0A, 0xA0]);
333 }
334
335 #[tokio::test]
336 async fn test_elm327_no_data() {
337 let mut transport = MockTransport::new();
338 setup_init(&mut transport);
339 transport.expect("015C", "NO DATA\r>");
340
341 let mut adapter = Elm327Adapter::new(Box::new(transport));
342 adapter.initialize().await.unwrap();
343
344 let req = ServiceRequest::read_pid(Pid::ENGINE_OIL_TEMP);
345 let result = adapter.request(&req).await;
346 assert!(matches!(result, Err(Obd2Error::NoData)));
347 }
348
349 #[tokio::test]
350 async fn test_elm327_parse_hex_response() {
351 let data = Elm327Adapter::parse_hex_response("41 0C 0A A0\r>", 2).unwrap();
352 assert_eq!(data, vec![0x0A, 0xA0]);
353 }
354
355 #[tokio::test]
356 async fn test_elm327_parse_supported_pids() {
357 let pids = Elm327Adapter::parse_supported_pids(&[0xBE, 0x3E, 0xB8, 0x11], 0x00);
359 assert!(pids.contains(&0x01)); assert!(pids.contains(&0x04)); assert!(pids.contains(&0x05)); assert!(pids.contains(&0x0C)); assert!(pids.contains(&0x0D)); }
365
366 #[tokio::test]
367 async fn test_elm327_read_dtcs() {
368 let mut transport = MockTransport::new();
369 setup_init(&mut transport);
370 transport.expect("03", "43 01 04 20 01 71\r>");
371
372 let mut adapter = Elm327Adapter::new(Box::new(transport));
373 adapter.initialize().await.unwrap();
374
375 let req = ServiceRequest::read_dtcs();
376 let response = adapter.request(&req).await.unwrap();
377 assert!(!response.is_empty());
379 }
380
381 #[tokio::test]
382 async fn test_elm327_battery_voltage() {
383 let mut transport = MockTransport::new();
384 setup_init(&mut transport);
385 transport.expect("ATRV", "14.4V\r>");
386
387 let mut adapter = Elm327Adapter::new(Box::new(transport));
388 adapter.initialize().await.unwrap();
389
390 let voltage = adapter.battery_voltage().await.unwrap();
391 assert_eq!(voltage, Some(14.4));
392 }
393
394 #[tokio::test]
395 async fn test_elm327_negative_response() {
396 let result = Elm327Adapter::check_response_error("7F 22 31\r>");
397 assert!(matches!(result, Err(Obd2Error::NegativeResponse { service: 0x22, .. })));
398 }
399}