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}