Skip to main content

rs_modbus/
error.rs

1use thiserror::Error;
2
3#[repr(u8)]
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ErrorCode {
6    IllegalFunction = 0x01,
7    IllegalDataAddress = 0x02,
8    IllegalDataValue = 0x03,
9    ServerDeviceFailure = 0x04,
10    Acknowledge = 0x05,
11    ServerDeviceBusy = 0x06,
12    MemoryParityError = 0x08,
13    GatewayPathUnavailable = 0x0a,
14    GatewayTargetDeviceFailedToRespond = 0x0b,
15}
16
17#[derive(Error, Debug, Clone)]
18pub enum ModbusError {
19    #[error("CRC check failed")]
20    CrcCheckFailed,
21    #[error("LRC check failed")]
22    LrcCheckFailed,
23    #[error("Insufficient data length")]
24    InsufficientData,
25    #[error("Invalid response")]
26    InvalidResponse,
27    #[error("Invalid data")]
28    InvalidData,
29    #[error("Invalid hex character")]
30    InvalidHex,
31    #[error("Inter-character timeout (t1.5) exceeded")]
32    T1_5Exceeded,
33    #[error("Incomplete frame at t3.5")]
34    IncompleteFrame,
35    #[error("Timeout")]
36    Timeout,
37    #[error("Port is not open")]
38    PortNotOpen,
39    #[error("Port is already open")]
40    PortAlreadyOpen,
41    #[error("Port is destroyed")]
42    PortDestroyed,
43    #[error("MODBUS_ERROR_CODE_{0}")]
44    ModbusErrorCode(u8),
45    #[error("Not supported")]
46    NotSupported,
47    #[error("Illegal function")]
48    IllegalFunction,
49    #[error("Illegal data address")]
50    IllegalDataAddress,
51    #[error("Illegal data value")]
52    IllegalDataValue,
53    #[error("Server device failure")]
54    ServerDeviceFailure,
55    #[error("Connection error: {0}")]
56    ConnectionError(String),
57    #[error("Invalid state: {0}")]
58    InvalidState(String),
59    #[error("IO error: {0}")]
60    Io(std::sync::Arc<std::io::Error>),
61}
62
63impl From<std::io::Error> for ModbusError {
64    fn from(e: std::io::Error) -> Self {
65        ModbusError::Io(std::sync::Arc::new(e))
66    }
67}
68
69impl TryFrom<u8> for ErrorCode {
70    type Error = ();
71
72    fn try_from(value: u8) -> Result<Self, Self::Error> {
73        match value {
74            0x01 => Ok(ErrorCode::IllegalFunction),
75            0x02 => Ok(ErrorCode::IllegalDataAddress),
76            0x03 => Ok(ErrorCode::IllegalDataValue),
77            0x04 => Ok(ErrorCode::ServerDeviceFailure),
78            0x05 => Ok(ErrorCode::Acknowledge),
79            0x06 => Ok(ErrorCode::ServerDeviceBusy),
80            0x08 => Ok(ErrorCode::MemoryParityError),
81            0x0a => Ok(ErrorCode::GatewayPathUnavailable),
82            0x0b => Ok(ErrorCode::GatewayTargetDeviceFailedToRespond),
83            _ => Err(()),
84        }
85    }
86}
87
88pub fn get_error_by_code(code: ErrorCode) -> ModbusError {
89    match code {
90        ErrorCode::IllegalFunction => ModbusError::IllegalFunction,
91        ErrorCode::IllegalDataAddress => ModbusError::IllegalDataAddress,
92        ErrorCode::IllegalDataValue => ModbusError::IllegalDataValue,
93        ErrorCode::ServerDeviceFailure => ModbusError::ServerDeviceFailure,
94        _ => ModbusError::ModbusErrorCode(code as u8),
95    }
96}
97
98pub fn get_code_by_error(err: &ModbusError) -> ErrorCode {
99    match err {
100        ModbusError::IllegalFunction => ErrorCode::IllegalFunction,
101        ModbusError::IllegalDataAddress => ErrorCode::IllegalDataAddress,
102        ModbusError::IllegalDataValue => ErrorCode::IllegalDataValue,
103        ModbusError::ServerDeviceFailure => ErrorCode::ServerDeviceFailure,
104        ModbusError::ModbusErrorCode(code) => {
105            ErrorCode::try_from(*code).unwrap_or(ErrorCode::ServerDeviceFailure)
106        }
107        _ => ErrorCode::ServerDeviceFailure,
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_error_code_values() {
117        assert_eq!(ErrorCode::IllegalFunction as u8, 0x01);
118        assert_eq!(ErrorCode::IllegalDataAddress as u8, 0x02);
119        assert_eq!(ErrorCode::IllegalDataValue as u8, 0x03);
120        assert_eq!(ErrorCode::ServerDeviceFailure as u8, 0x04);
121        assert_eq!(ErrorCode::Acknowledge as u8, 0x05);
122        assert_eq!(ErrorCode::ServerDeviceBusy as u8, 0x06);
123        assert_eq!(ErrorCode::MemoryParityError as u8, 0x08);
124        assert_eq!(ErrorCode::GatewayPathUnavailable as u8, 0x0a);
125        assert_eq!(ErrorCode::GatewayTargetDeviceFailedToRespond as u8, 0x0b);
126    }
127
128    #[test]
129    fn test_get_error_by_code() {
130        let err = get_error_by_code(ErrorCode::IllegalFunction);
131        assert!(matches!(err, ModbusError::IllegalFunction));
132    }
133
134    #[test]
135    fn test_get_code_by_error_roundtrip() {
136        for code in [
137            ErrorCode::IllegalFunction,
138            ErrorCode::IllegalDataAddress,
139            ErrorCode::IllegalDataValue,
140            ErrorCode::ServerDeviceFailure,
141            ErrorCode::Acknowledge,
142            ErrorCode::ServerDeviceBusy,
143            ErrorCode::MemoryParityError,
144            ErrorCode::GatewayPathUnavailable,
145            ErrorCode::GatewayTargetDeviceFailedToRespond,
146        ] {
147            let err = get_error_by_code(code);
148            assert_eq!(get_code_by_error(&err), code);
149        }
150    }
151
152    #[test]
153    fn test_get_code_by_error_non_modbus() {
154        let err = ModbusError::Timeout;
155        assert_eq!(get_code_by_error(&err), ErrorCode::ServerDeviceFailure);
156    }
157
158    #[test]
159    fn test_get_code_by_error_unknown() {
160        let err = ModbusError::ModbusErrorCode(0x99);
161        assert_eq!(get_code_by_error(&err), ErrorCode::ServerDeviceFailure);
162    }
163}