1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum NegativeResponse {
7 GeneralReject,
9 ServiceNotSupported,
11 SubFunctionNotSupported,
13 IncorrectMessageLength,
15 ResponseTooLong,
17 ConditionsNotCorrect,
19 RequestOutOfRange,
21 SecurityAccessDenied,
23 InvalidKey,
25 ExceededAttempts,
27 TimeDelayNotExpired,
29 GeneralProgrammingFailure,
31 ResponsePending,
33}
34
35impl NegativeResponse {
36 pub fn from_byte(b: u8) -> Option<Self> {
37 match b {
38 0x10 => Some(Self::GeneralReject),
39 0x11 => Some(Self::ServiceNotSupported),
40 0x12 => Some(Self::SubFunctionNotSupported),
41 0x13 => Some(Self::IncorrectMessageLength),
42 0x14 => Some(Self::ResponseTooLong),
43 0x22 => Some(Self::ConditionsNotCorrect),
44 0x31 => Some(Self::RequestOutOfRange),
45 0x33 => Some(Self::SecurityAccessDenied),
46 0x35 => Some(Self::InvalidKey),
47 0x36 => Some(Self::ExceededAttempts),
48 0x37 => Some(Self::TimeDelayNotExpired),
49 0x72 => Some(Self::GeneralProgrammingFailure),
50 0x78 => Some(Self::ResponsePending),
51 _ => None,
52 }
53 }
54
55 pub fn code(&self) -> u8 {
56 match self {
57 Self::GeneralReject => 0x10,
58 Self::ServiceNotSupported => 0x11,
59 Self::SubFunctionNotSupported => 0x12,
60 Self::IncorrectMessageLength => 0x13,
61 Self::ResponseTooLong => 0x14,
62 Self::ConditionsNotCorrect => 0x22,
63 Self::RequestOutOfRange => 0x31,
64 Self::SecurityAccessDenied => 0x33,
65 Self::InvalidKey => 0x35,
66 Self::ExceededAttempts => 0x36,
67 Self::TimeDelayNotExpired => 0x37,
68 Self::GeneralProgrammingFailure => 0x72,
69 Self::ResponsePending => 0x78,
70 }
71 }
72}
73
74impl std::fmt::Display for NegativeResponse {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(f, "{:?} (0x{:02X})", self, self.code())
77 }
78}
79
80#[derive(Debug, thiserror::Error)]
82#[non_exhaustive]
83pub enum Obd2Error {
84 #[error("transport error: {0}")]
85 Transport(String),
86
87 #[error("adapter error: {0}")]
88 Adapter(String),
89
90 #[error("adapter is busy (stop polling first)")]
91 AdapterBusy,
92
93 #[error("timeout waiting for response")]
94 Timeout,
95
96 #[error("no data (vehicle did not respond)")]
97 NoData,
98
99 #[error("PID {pid:#04x} not supported by this vehicle")]
100 UnsupportedPid { pid: u8 },
101
102 #[error("module '{0}' not found in vehicle spec")]
103 ModuleNotFound(String),
104
105 #[error("negative response: {nrc} for service {service:#04x}")]
106 NegativeResponse { service: u8, nrc: NegativeResponse },
107
108 #[error("security access required (call enter_diagnostic_session + security_access first)")]
109 SecurityRequired,
110
111 #[error("no vehicle spec matched — load a spec or call identify_vehicle()")]
112 NoSpec,
113
114 #[error("bus '{0}' not available on this vehicle")]
115 BusNotAvailable(String),
116
117 #[error("spec parse error: {0}")]
118 SpecParse(String),
119
120 #[error("parse error: {0}")]
121 ParseError(String),
122
123 #[error("IO error: {0}")]
124 Io(#[from] std::io::Error),
125
126 #[error(transparent)]
127 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_error_display_timeout() {
136 let err = Obd2Error::Timeout;
137 assert_eq!(err.to_string(), "timeout waiting for response");
138 }
139
140 #[test]
141 fn test_error_display_nrc() {
142 let err = Obd2Error::NegativeResponse {
143 service: 0x22,
144 nrc: NegativeResponse::RequestOutOfRange,
145 };
146 let s = err.to_string();
147 assert!(s.contains("RequestOutOfRange"));
148 assert!(s.contains("0x22"));
149 }
150
151 #[test]
152 fn test_io_error_conversion() {
153 let io_err = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "gone");
154 let obd_err: Obd2Error = io_err.into();
155 assert!(matches!(obd_err, Obd2Error::Io(_)));
156 }
157
158 #[test]
159 fn test_nrc_from_byte() {
160 assert_eq!(
161 NegativeResponse::from_byte(0x31),
162 Some(NegativeResponse::RequestOutOfRange)
163 );
164 assert_eq!(
165 NegativeResponse::from_byte(0x78),
166 Some(NegativeResponse::ResponsePending)
167 );
168 assert_eq!(NegativeResponse::from_byte(0xFF), None);
169 }
170
171 #[test]
172 fn test_nrc_code() {
173 assert_eq!(NegativeResponse::GeneralReject.code(), 0x10);
174 assert_eq!(NegativeResponse::ResponsePending.code(), 0x78);
175 }
176
177 #[test]
178 fn test_nrc_display() {
179 let nrc = NegativeResponse::SecurityAccessDenied;
180 let s = format!("{}", nrc);
181 assert!(s.contains("SecurityAccessDenied"));
182 assert!(s.contains("0x33"));
183 }
184
185 #[test]
186 fn test_error_is_send_sync() {
187 fn assert_send<T: Send>() {}
188 fn assert_sync<T: Sync>() {}
189 assert_send::<Obd2Error>();
191 }
192}