use std::fmt;
pub const ERR_OK: &str = "OK";
pub const ERR_TIMEOUT: &str = "TIMEOUT";
pub const ERR_CONNECTION: &str = "CONNECTION ERROR";
pub const ERR_PROTOCOL: &str = "PROTOCOL ERROR";
pub const ERR_EXCEPTION: &str = "MODBUS EXCEPTION";
pub const ERR_SERIAL: &str = "PORT ERROR";
pub const ERR_OTHER: &str = "ERROR";
pub const TIMEOUT_PREFIX: &str = "TIMEOUT:";
pub const CONNECTION_PREFIX: &str = "CONNECTION ERROR:";
pub const PROTOCOL_PREFIX: &str = "PROTOCOL ERROR:";
pub const PORT_PREFIX: &str = "PORT ERROR:";
pub const EXN_ILLEGAL_FUNCTION: &str = "Illegal Function";
pub const EXN_ILLEGAL_DATA_ADDRESS: &str = "Illegal Data Address";
pub const EXN_ILLEGAL_DATA_VALUE: &str = "Illegal Data Value";
pub const EXN_SERVER_DEVICE_FAILURE: &str = "Server Device Failure";
pub const EXN_ACKNOWLEDGE: &str = "Acknowledge";
pub const EXN_SERVER_DEVICE_BUSY: &str = "Server Device Busy";
pub const EXN_NEGATIVE_ACKNOWLEDGE: &str = "Negative Acknowledge";
pub const EXN_MEMORY_PARITY_ERROR: &str = "Memory Parity Error";
pub const EXN_GATEWAY_PATH_UNAVAILABLE: &str = "Gateway Path Unavailable";
pub const EXN_GATEWAY_TARGET_FAILED: &str = "Gateway Target Device Failed to Respond";
pub const EXN_UNKNOWN: &str = "Unknown Exception";
pub const SEND_TIMEOUT: &str = "send timed out";
pub const RECV_TIMEOUT: &str = "recv timed out";
pub const TCP_SEND_TIMEOUT: &str = "TCP send timed out";
pub const TCP_RECV_TIMEOUT: &str = "TCP recv timed out";
pub const TCP_SEND_ERROR: &str = "TCP send:";
pub const TCP_RECV_ERROR: &str = "TCP recv:";
pub const CONN_CLOSED: &str = "connection closed";
pub const TCP_EMPTY_RESP: &str = "empty TCP response PDU";
pub const RTU_SEND_TIMEOUT: &str = "RTU send timed out";
pub const RTU_RECV_TIMEOUT: &str = "RTU recv timed out";
pub const RTU_SEND_ERROR: &str = "RTU send:";
pub const RTU_RECV_ERROR: &str = "RTU recv:";
pub const RTU_EMPTY_RESP: &str = "empty RTU response";
pub const ASCII_SEND_TIMEOUT: &str = "ASCII send timed out";
pub const ASCII_RECV_TIMEOUT: &str = "ASCII recv timed out";
pub const ASCII_SEND_ERROR: &str = "ASCII send:";
pub const ASCII_RECV_ERROR: &str = "ASCII recv:";
pub const ASCII_EMPTY_RESP: &str = "empty ASCII response";
pub const PDU_ENCODE_ERROR: &str = "PDU encode:";
pub const PDU_DECODE_ERROR: &str = "PDU decode:";
pub const SLAVE_ID_MISMATCH: &str = "slave ID mismatch: expected";
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ModbusError {
NoError,
Timeout(String),
Connection(String),
Protocol(String),
Exception {
function: u8,
code: u8,
},
Serial(String),
Other(String),
}
fn exception_name(code: u8) -> &'static str {
match code {
1 => EXN_ILLEGAL_FUNCTION,
2 => EXN_ILLEGAL_DATA_ADDRESS,
3 => EXN_ILLEGAL_DATA_VALUE,
4 => EXN_SERVER_DEVICE_FAILURE,
5 => EXN_ACKNOWLEDGE,
6 => EXN_SERVER_DEVICE_BUSY,
7 => EXN_NEGATIVE_ACKNOWLEDGE,
8 => EXN_MEMORY_PARITY_ERROR,
10 => EXN_GATEWAY_PATH_UNAVAILABLE,
11 => EXN_GATEWAY_TARGET_FAILED,
_ => EXN_UNKNOWN,
}
}
impl fmt::Display for ModbusError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModbusError::Exception { function: _, code } => {
write!(f, "{} (code={})", exception_name(*code), code)
}
ModbusError::Other(m) => write!(f, "{m}"),
_ => write!(f, "{}", self.label()),
}
}
}
impl ModbusError {
pub fn detail(&self) -> String {
match self {
ModbusError::NoError => "OK".to_string(),
ModbusError::Timeout(m) => format!("{TIMEOUT_PREFIX} {m}"),
ModbusError::Connection(m) => format!("{CONNECTION_PREFIX} {m}"),
ModbusError::Protocol(m) => format!("{PROTOCOL_PREFIX} {m}"),
ModbusError::Exception { function, code } => {
let ex_name = exception_name(*code);
format!("{ex_name} (FC={function}, code={code})")
}
ModbusError::Serial(m) => format!("{PORT_PREFIX} {m}"),
ModbusError::Other(m) => m.clone(),
}
}
pub fn label(&self) -> &'static str {
match self {
ModbusError::NoError => ERR_OK,
ModbusError::Timeout(_) => ERR_TIMEOUT,
ModbusError::Connection(_) => ERR_CONNECTION,
ModbusError::Protocol(_) => ERR_PROTOCOL,
ModbusError::Exception { .. } => ERR_EXCEPTION,
ModbusError::Serial(_) => ERR_SERIAL,
ModbusError::Other(_) => ERR_OTHER,
}
}
pub const fn no_error() -> Self {
ModbusError::NoError
}
pub fn timeout(msg: impl Into<String>) -> Self {
ModbusError::Timeout(msg.into())
}
pub fn connection(msg: impl Into<String>) -> Self {
ModbusError::Connection(msg.into())
}
pub fn protocol(msg: impl Into<String>) -> Self {
ModbusError::Protocol(msg.into())
}
pub fn exception(function: u8, code: u8) -> Self {
ModbusError::Exception { function, code }
}
pub fn serial(msg: impl Into<String>) -> Self {
ModbusError::Serial(msg.into())
}
pub fn other(msg: impl Into<String>) -> Self {
ModbusError::Other(msg.into())
}
}
impl From<String> for ModbusError {
fn from(s: String) -> Self {
ModbusError::Other(s)
}
}
impl From<std::io::Error> for ModbusError {
fn from(e: std::io::Error) -> Self {
use std::io::ErrorKind;
match e.kind() {
ErrorKind::TimedOut | ErrorKind::WouldBlock => ModbusError::timeout(e.to_string()),
ErrorKind::ConnectionRefused
| ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe
| ErrorKind::NotConnected => ModbusError::connection(e.to_string()),
_ => ModbusError::Other(e.to_string()),
}
}
}
impl std::error::Error for ModbusError {}