Skip to main content

obd2_core/adapter/
detect.rs

1//! Chipset detection and capability probing.
2
3use super::{AdapterInfo, Chipset, Capabilities};
4use crate::vehicle::Protocol;
5
6impl AdapterInfo {
7    /// Detect adapter chipset and capabilities from initialization responses.
8    ///
9    /// # Arguments
10    /// * `atz_response` — Response to ATZ (reset) command
11    /// * `sti_response` — Response to STI command (None if not probed or no response)
12    pub fn detect(atz_response: &str, sti_response: Option<&str>) -> Self {
13        let (chipset, firmware) = if let Some(sti) = sti_response {
14            if sti.contains("STN") {
15                (Chipset::Stn, sti.trim().to_string())
16            } else {
17                detect_elm_version(atz_response)
18            }
19        } else {
20            detect_elm_version(atz_response)
21        };
22
23        let capabilities = match chipset {
24            Chipset::Stn => Capabilities {
25                can_clear_dtcs: true,
26                dual_can: true,
27                enhanced_diag: true,
28                battery_voltage: true,
29                adaptive_timing: true,
30            },
31            Chipset::Elm327Genuine => Capabilities {
32                can_clear_dtcs: true,
33                dual_can: false,
34                enhanced_diag: true,
35                battery_voltage: true,
36                adaptive_timing: true,
37            },
38            Chipset::Elm327Clone => Capabilities {
39                can_clear_dtcs: false,
40                dual_can: false,
41                enhanced_diag: false,
42                battery_voltage: true,
43                adaptive_timing: false,
44            },
45            Chipset::Unknown => Capabilities::default(),
46        };
47
48        Self {
49            chipset,
50            firmware,
51            protocol: Protocol::Auto,
52            capabilities,
53        }
54    }
55}
56
57fn detect_elm_version(response: &str) -> (Chipset, String) {
58    let response = response.trim();
59    if let Some(ver_start) = response.find("ELM327") {
60        let firmware = response[ver_start..].trim().to_string();
61        // Extract version number
62        if let Some(v_pos) = firmware.find('v').or_else(|| firmware.find('V')) {
63            let ver_str = &firmware[v_pos + 1..];
64            let version: f32 = ver_str
65                .chars()
66                .take_while(|c| c.is_ascii_digit() || *c == '.')
67                .collect::<String>()
68                .parse()
69                .unwrap_or(0.0);
70
71            if version >= 2.0 {
72                (Chipset::Elm327Genuine, firmware)
73            } else {
74                (Chipset::Elm327Clone, firmware)
75            }
76        } else {
77            (Chipset::Elm327Clone, firmware)
78        }
79    } else {
80        (Chipset::Unknown, response.to_string())
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_detect_elm327_clone() {
90        let info = AdapterInfo::detect("ELM327 v1.5", None);
91        assert_eq!(info.chipset, Chipset::Elm327Clone);
92        assert!(!info.capabilities.can_clear_dtcs);
93        assert!(!info.capabilities.dual_can);
94    }
95
96    #[test]
97    fn test_detect_elm327_genuine() {
98        let info = AdapterInfo::detect("ELM327 v2.1", None);
99        assert_eq!(info.chipset, Chipset::Elm327Genuine);
100        assert!(info.capabilities.can_clear_dtcs);
101        assert!(info.capabilities.enhanced_diag);
102    }
103
104    #[test]
105    fn test_detect_stn() {
106        let info = AdapterInfo::detect("ELM327 v1.5", Some("STN2120"));
107        assert_eq!(info.chipset, Chipset::Stn);
108        assert!(info.capabilities.dual_can);
109        assert!(info.capabilities.enhanced_diag);
110    }
111
112    #[test]
113    fn test_detect_unknown() {
114        let info = AdapterInfo::detect("UNKNOWN DEVICE", None);
115        assert_eq!(info.chipset, Chipset::Unknown);
116    }
117
118    #[test]
119    fn test_detect_elm_with_garbage() {
120        let info = AdapterInfo::detect("\r\nELM327 v2.2\r\n>", None);
121        assert_eq!(info.chipset, Chipset::Elm327Genuine);
122    }
123
124    #[test]
125    fn test_capabilities_default() {
126        let caps = Capabilities::default();
127        assert!(!caps.can_clear_dtcs);
128        assert!(!caps.dual_can);
129    }
130
131    #[test]
132    fn test_stn_has_all_capabilities() {
133        let info = AdapterInfo::detect("ELM327 v1.5", Some("STN1110 v4.2"));
134        assert!(info.capabilities.can_clear_dtcs);
135        assert!(info.capabilities.dual_can);
136        assert!(info.capabilities.enhanced_diag);
137        assert!(info.capabilities.battery_voltage);
138        assert!(info.capabilities.adaptive_timing);
139    }
140}