1use std::fmt;
6
7pub const ERR_OK: &str = "OK";
10pub const ERR_TIMEOUT: &str = "TIMEOUT";
11pub const ERR_CONNECTION: &str = "CONNECTION ERROR";
12pub const ERR_PROTOCOL: &str = "PROTOCOL ERROR";
13pub const ERR_EXCEPTION: &str = "MODBUS EXCEPTION";
14pub const ERR_SERIAL: &str = "PORT ERROR";
15pub const ERR_OTHER: &str = "ERROR";
16
17pub const TIMEOUT_PREFIX: &str = "TIMEOUT:";
20pub const CONNECTION_PREFIX: &str = "CONNECTION ERROR:";
21pub const PROTOCOL_PREFIX: &str = "PROTOCOL ERROR:";
22pub const PORT_PREFIX: &str = "PORT ERROR:";
23
24pub const EXN_ILLEGAL_FUNCTION: &str = "Illegal Function";
27pub const EXN_ILLEGAL_DATA_ADDRESS: &str = "Illegal Data Address";
28pub const EXN_ILLEGAL_DATA_VALUE: &str = "Illegal Data Value";
29pub const EXN_SERVER_DEVICE_FAILURE: &str = "Server Device Failure";
30pub const EXN_ACKNOWLEDGE: &str = "Acknowledge";
31pub const EXN_SERVER_DEVICE_BUSY: &str = "Server Device Busy";
32pub const EXN_NEGATIVE_ACKNOWLEDGE: &str = "Negative Acknowledge";
33pub const EXN_MEMORY_PARITY_ERROR: &str = "Memory Parity Error";
34pub const EXN_GATEWAY_PATH_UNAVAILABLE: &str = "Gateway Path Unavailable";
35pub const EXN_GATEWAY_TARGET_FAILED: &str = "Gateway Target Device Failed to Respond";
36pub const EXN_UNKNOWN: &str = "Unknown Exception";
37
38pub const SEND_TIMEOUT: &str = "send timed out";
42pub const RECV_TIMEOUT: &str = "recv timed out";
44
45pub const TCP_SEND_TIMEOUT: &str = "TCP send timed out";
46pub const TCP_RECV_TIMEOUT: &str = "TCP recv timed out";
47pub const TCP_SEND_ERROR: &str = "TCP send:";
48pub const TCP_RECV_ERROR: &str = "TCP recv:";
49pub const CONN_CLOSED: &str = "connection closed";
50pub const TCP_EMPTY_RESP: &str = "empty TCP response PDU";
51
52pub const RTU_SEND_TIMEOUT: &str = "RTU send timed out";
53pub const RTU_RECV_TIMEOUT: &str = "RTU recv timed out";
54pub const RTU_SEND_ERROR: &str = "RTU send:";
55pub const RTU_RECV_ERROR: &str = "RTU recv:";
56pub const RTU_EMPTY_RESP: &str = "empty RTU response";
57
58pub const ASCII_SEND_TIMEOUT: &str = "ASCII send timed out";
59pub const ASCII_RECV_TIMEOUT: &str = "ASCII recv timed out";
60pub const ASCII_SEND_ERROR: &str = "ASCII send:";
61pub const ASCII_RECV_ERROR: &str = "ASCII recv:";
62pub const ASCII_EMPTY_RESP: &str = "empty ASCII response";
63
64pub const PDU_ENCODE_ERROR: &str = "PDU encode:";
65pub const PDU_DECODE_ERROR: &str = "PDU decode:";
66pub const SLAVE_ID_MISMATCH: &str = "slave ID mismatch: expected";
67
68#[derive(Clone, Debug, PartialEq)]
94#[non_exhaustive]
95pub enum ModbusError {
96 NoError,
98 Timeout(String),
99 Connection(String),
100 Protocol(String),
101 Exception {
102 function: u8,
103 code: u8,
104 },
105 Serial(String),
106 Other(String),
107}
108
109fn exception_name(code: u8) -> &'static str {
111 match code {
112 1 => EXN_ILLEGAL_FUNCTION,
113 2 => EXN_ILLEGAL_DATA_ADDRESS,
114 3 => EXN_ILLEGAL_DATA_VALUE,
115 4 => EXN_SERVER_DEVICE_FAILURE,
116 5 => EXN_ACKNOWLEDGE,
117 6 => EXN_SERVER_DEVICE_BUSY,
118 7 => EXN_NEGATIVE_ACKNOWLEDGE,
119 8 => EXN_MEMORY_PARITY_ERROR,
120 10 => EXN_GATEWAY_PATH_UNAVAILABLE,
121 11 => EXN_GATEWAY_TARGET_FAILED,
122 _ => EXN_UNKNOWN,
123 }
124}
125
126impl fmt::Display for ModbusError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 ModbusError::Exception { function: _, code } => {
132 write!(f, "{} (code={})", exception_name(*code), code)
133 }
134 ModbusError::Other(m) => write!(f, "{m}"),
135 _ => write!(f, "{}", self.label()),
136 }
137 }
138}
139
140impl ModbusError {
141 pub fn detail(&self) -> String {
144 match self {
145 ModbusError::NoError => "OK".to_string(),
146 ModbusError::Timeout(m) => format!("{TIMEOUT_PREFIX} {m}"),
147 ModbusError::Connection(m) => format!("{CONNECTION_PREFIX} {m}"),
148 ModbusError::Protocol(m) => format!("{PROTOCOL_PREFIX} {m}"),
149 ModbusError::Exception { function, code } => {
150 let ex_name = exception_name(*code);
151 format!("{ex_name} (FC={function}, code={code})")
152 }
153 ModbusError::Serial(m) => format!("{PORT_PREFIX} {m}"),
154 ModbusError::Other(m) => m.clone(),
155 }
156 }
157
158 pub fn label(&self) -> &'static str {
160 match self {
161 ModbusError::NoError => ERR_OK,
162 ModbusError::Timeout(_) => ERR_TIMEOUT,
163 ModbusError::Connection(_) => ERR_CONNECTION,
164 ModbusError::Protocol(_) => ERR_PROTOCOL,
165 ModbusError::Exception { .. } => ERR_EXCEPTION,
166 ModbusError::Serial(_) => ERR_SERIAL,
167 ModbusError::Other(_) => ERR_OTHER,
168 }
169 }
170
171 pub const fn no_error() -> Self {
173 ModbusError::NoError
174 }
175 pub fn timeout(msg: impl Into<String>) -> Self {
177 ModbusError::Timeout(msg.into())
178 }
179 pub fn connection(msg: impl Into<String>) -> Self {
181 ModbusError::Connection(msg.into())
182 }
183 pub fn protocol(msg: impl Into<String>) -> Self {
185 ModbusError::Protocol(msg.into())
186 }
187 pub fn exception(function: u8, code: u8) -> Self {
189 ModbusError::Exception { function, code }
190 }
191 pub fn serial(msg: impl Into<String>) -> Self {
193 ModbusError::Serial(msg.into())
194 }
195 pub fn other(msg: impl Into<String>) -> Self {
197 ModbusError::Other(msg.into())
198 }
199}
200
201impl From<String> for ModbusError {
202 fn from(s: String) -> Self {
203 ModbusError::Other(s)
204 }
205}
206
207impl From<std::io::Error> for ModbusError {
208 fn from(e: std::io::Error) -> Self {
209 use std::io::ErrorKind;
210 match e.kind() {
211 ErrorKind::TimedOut | ErrorKind::WouldBlock => ModbusError::timeout(e.to_string()),
212 ErrorKind::ConnectionRefused
213 | ErrorKind::ConnectionReset
214 | ErrorKind::ConnectionAborted
215 | ErrorKind::BrokenPipe
216 | ErrorKind::NotConnected => ModbusError::connection(e.to_string()),
217 _ => ModbusError::Other(e.to_string()),
218 }
219 }
220}
221
222impl std::error::Error for ModbusError {}