Skip to main content

mbus_core/errors/
mod.rs

1//! Modbus Error Module
2//!
3//! This module defines the centralized error handling for the Modbus stack.
4//! It provides the [`MbusError`] enum, which covers a wide range of error conditions
5//! including protocol-specific exceptions, parsing failures, transport-layer issues,
6//! and buffer management errors.
7//!
8//! The error types are designed to be compatible with `no_std` environments while
9//! providing descriptive error messages through the `Display` trait implementation.
10//!
11//! Modbus Specification Reference: V1.1b3, Section 7 (MODBUS Exception Responses).
12
13#[cfg(all(feature = "defmt-format", target_os = "none"))]
14use defmt;
15
16/// Modbus exception codes as defined in the Modbus Application Protocol Specification V1.1b3.
17///
18/// These codes are used in exception responses (function code | 0x80) to indicate
19/// the type of error that occurred when processing a request.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u8)]
22pub enum ExceptionCode {
23    /// 0x01: Illegal Function - The function code is not supported by the server.
24    IllegalFunction = 0x01,
25    /// 0x02: Illegal Data Address - The addressed register does not exist.
26    IllegalDataAddress = 0x02,
27    /// 0x03: Illegal Data Value - The quantity of items to read/write is invalid.
28    IllegalDataValue = 0x03,
29    /// 0x04: Server Device Failure - Unrecoverable device failure.
30    ServerDeviceFailure = 0x04,
31}
32
33impl From<ExceptionCode> for u8 {
34    fn from(code: ExceptionCode) -> Self {
35        code as u8
36    }
37}
38
39/// Represents a Modbus error.
40#[derive(Debug, PartialEq, Eq, Clone, Copy)]
41pub enum MbusError {
42    /// An error occurred while parsing the Modbus ADU.
43    ParseError,
44    /// This is used for receieved frame is fundamentally malformed
45    BasicParseError,
46    /// The transaction timed out waiting for a response.
47    Timeout,
48    /// The server responded with a Modbus exception code.
49    ModbusException(u8),
50    /// An I/O error occurred during TCP communication.
51    IoError,
52    /// An unexpected error occurred.
53    Unexpected,
54    /// The connection was lost during an active transaction.
55    ConnectionLost,
56    /// The function code is not supported
57    UnsupportedFunction(u8),
58    /// The sub-function code is not available
59    ReservedSubFunction(u16),
60    /// The PDU length is invalid
61    InvalidPduLength,
62    /// The ADU length is invalid
63    InvalidAduLength,
64    /// Connection failed
65    ConnectionFailed,
66    /// Connection closed
67    ConnectionClosed,
68    /// The data was too large for the buffer
69    BufferTooSmall,
70    /// Buffer length is not matching
71    BufferLenMissmatch,
72    /// Failed to send data
73    SendFailed,
74    /// Invalid address
75    InvalidAddress,
76    /// Invalid offset
77    InvalidOffset,
78    /// Too many requests in flight, expected responses buffer is full
79    TooManyRequests,
80    /// Invalid function code
81    InvalidFunctionCode,
82    /// No retries left for the transaction
83    NoRetriesLeft,
84    /// Too many sub-requests in a PDU, Max allowed is 35
85    TooManyFileReadSubRequests,
86    /// File read PDU overflow, total length of file read sub-requests exceeds maximum allowed bytes per PDU
87    FileReadPduOverflow,
88    /// An unexpected response was received that does not match the expected response type for the transaction.
89    UnexpectedResponse,
90    /// The transport is invalid for the requested operation
91    InvalidTransport,
92    /// Invalid slave address
93    InvalidSlaveAddress,
94    /// Checksum error
95    ChecksumError,
96    /// Invalid configuration
97    InvalidConfiguration,
98    /// Invalid number of expected responses.
99    ///
100    /// For Modbus Serial transports, only one request may be in flight at a time,
101    /// so the expected-response queue size must be exactly `1`.
102    InvalidNumOfExpectedRsps,
103    /// Invalid data length
104    InvalidDataLen,
105    /// Invalid Quantity
106    InvalidQuantity,
107    /// Invalid Value
108    InvalidValue,
109    /// Invalid Masking value
110    InvalidAndMask,
111    /// Invalid Masking value
112    InvalidOrMask,
113    /// Invalid byte count
114    InvalidByteCount,
115    /// Invalid device identification
116    InvalidDeviceIdentification,
117    /// Invalid device id code
118    InvalidDeviceIdCode,
119    /// Invalid MEI type
120    InvalidMeiType,
121    /// Invalid broadcast address (0): Broadcast must be created explicitly.
122    /// Use `UnitIdOrSlaveAddr::new_broadcast_address()` to signal broadcast intent.
123    InvalidBroadcastAddress,
124    /// Broadcast not allowed.
125    ///
126    /// Note: This variant name contains a historical typo and is kept for
127    /// compatibility with existing code.
128    BroadcastNotAllowed,
129}
130
131impl MbusError {
132    /// Returns the canonical "broadcast not allowed" error.
133    ///
134    /// This helper exists to provide a correctly spelled API path while
135    /// preserving the legacy enum variant name for compatibility.
136    pub const fn broadcast_not_allowed() -> Self {
137        Self::BroadcastNotAllowed
138    }
139}
140
141#[cfg(all(feature = "defmt-format", target_os = "none"))]
142impl defmt::Format for MbusError {
143    fn format(&self, f: defmt::Formatter) {
144        match self {
145            MbusError::ParseError => defmt::write!(
146                f,
147                "Parse error: An error occurred while parsing the Modbus ADU"
148            ),
149            MbusError::BasicParseError => defmt::write!(
150                f,
151                "Basic parse error: The received frame is fundamentally malformed"
152            ),
153            MbusError::Timeout => defmt::write!(
154                f,
155                "Timeout: The transaction timed out waiting for a response"
156            ),
157            MbusError::ModbusException(code) => defmt::write!(
158                f,
159                "Modbus exception: The server responded with exception code 0x{:02X}",
160                code
161            ),
162            MbusError::IoError => defmt::write!(
163                f,
164                "I/O error: An I/O error occurred during TCP communication"
165            ),
166            MbusError::Unexpected => {
167                defmt::write!(f, "Unexpected error: An unexpected error occurred")
168            }
169            MbusError::ConnectionLost => defmt::write!(
170                f,
171                "Connection lost: The connection was lost during an active transaction"
172            ),
173            MbusError::UnsupportedFunction(code) => defmt::write!(
174                f,
175                "Unsupported function: Function code 0x{:02X} is not supported",
176                code
177            ),
178            MbusError::ReservedSubFunction(code) => defmt::write!(
179                f,
180                "Reserved sub-function: Sub-function code 0x{:04X} is not available",
181                code
182            ),
183            MbusError::InvalidPduLength => {
184                defmt::write!(f, "Invalid PDU length: The PDU length is invalid")
185            }
186            MbusError::InvalidAduLength => {
187                defmt::write!(f, "Invalid ADU length: The ADU length is invalid")
188            }
189            MbusError::ConnectionFailed => defmt::write!(f, "Connection failed"),
190            MbusError::ConnectionClosed => defmt::write!(f, "Connection closed"),
191            MbusError::BufferTooSmall => {
192                defmt::write!(f, "Buffer too small: The data was too large for the buffer")
193            }
194            MbusError::BufferLenMissmatch => {
195                defmt::write!(f, "Buffer length mismatch: Buffer length is not matching")
196            }
197            MbusError::SendFailed => defmt::write!(f, "Send failed: Failed to send data"),
198            MbusError::InvalidAddress => defmt::write!(f, "Invalid address"),
199            MbusError::TooManyRequests => {
200                defmt::write!(f, "Too many requests: Expected responses buffer is full")
201            }
202            MbusError::InvalidFunctionCode => defmt::write!(f, "Invalid function code"),
203            MbusError::NoRetriesLeft => defmt::write!(f, "No retries left for the transaction"),
204            MbusError::TooManyFileReadSubRequests => defmt::write!(
205                f,
206                "Too many sub-requests: Maximum of 35 sub-requests per PDU allowed"
207            ),
208            MbusError::FileReadPduOverflow => defmt::write!(
209                f,
210                "File read PDU overflow: Total length of file read sub-requests exceeds maximum allowed bytes per PDU"
211            ),
212            MbusError::UnexpectedResponse => defmt::write!(
213                f,
214                "Unexpected response: An unexpected response was received"
215            ),
216            MbusError::InvalidTransport => defmt::write!(
217                f,
218                "Invalid transport: The transport is invalid for the requested operation"
219            ),
220            MbusError::InvalidSlaveAddress => defmt::write!(
221                f,
222                "Invalid slave address: The provided slave address is invalid"
223            ),
224            MbusError::ChecksumError => defmt::write!(
225                f,
226                "Checksum error: The received frame has an invalid checksum"
227            ),
228            MbusError::InvalidConfiguration => defmt::write!(
229                f,
230                "Invalid configuration: The provided configuration is invalid"
231            ),
232            MbusError::InvalidNumOfExpectedRsps => defmt::write!(
233                f,
234                "Invalid number of expected responses: for serial transports the queue size N must be exactly 1"
235            ),
236            MbusError::InvalidDataLen => defmt::write!(
237                f,
238                "Invalid data length: The provided data length is invalid"
239            ),
240            MbusError::InvalidQuantity => {
241                defmt::write!(f, "Invalid quantity: The provided quantity is invalid")
242            }
243            MbusError::InvalidValue => {
244                defmt::write!(f, "Invalid value: The provided value is invalid")
245            }
246            MbusError::InvalidAndMask => {
247                defmt::write!(f, "Invalid AND mask: The provided AND mask is invalid")
248            }
249            MbusError::InvalidOrMask => {
250                defmt::write!(f, "Invalid OR mask: The provided OR mask is invalid")
251            }
252            MbusError::InvalidByteCount => {
253                defmt::write!(f, "Invalid byte count: The provided byte count is invalid")
254            }
255            MbusError::InvalidDeviceIdentification => defmt::write!(
256                f,
257                "Invalid device identification: The provided device identification is invalid"
258            ),
259            MbusError::InvalidDeviceIdCode => defmt::write!(
260                f,
261                "Invalid device ID code: The provided device ID code is invalid"
262            ),
263            MbusError::InvalidMeiType => {
264                defmt::write!(f, "Invalid MEI type: The provided MEI type is invalid")
265            }
266            MbusError::InvalidBroadcastAddress => defmt::write!(
267                f,
268                "Invalid broadcast address: The provided broadcast address (0) is invalid. Must use UnitIdOrSlaveAddr::new_broadcast_address() instead."
269            ),
270            MbusError::BroadcastNotAllowed => {
271                defmt::write!(f, "Broadcast not allowed: Broadcast not allowed")
272            }
273            MbusError::InvalidOffset => {
274                defmt::write!(f, "Invalid offset: The provided offset is invalid")
275            }
276        }
277    }
278}
279
280#[cfg(feature = "error-trait")]
281impl core::fmt::Display for MbusError {
282    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
283        match self {
284            MbusError::ParseError => write!(f, "Parse error"),
285            MbusError::BasicParseError => write!(f, "Basic parse error"),
286            MbusError::Timeout => write!(f, "Timeout"),
287            MbusError::ModbusException(code) => write!(f, "Modbus exception 0x{code:02X}"),
288            MbusError::IoError => write!(f, "I/O error"),
289            MbusError::Unexpected => write!(f, "Unexpected error"),
290            MbusError::ConnectionLost => write!(f, "Connection lost"),
291            MbusError::UnsupportedFunction(code) => write!(f, "Unsupported function 0x{code:02X}"),
292            MbusError::ReservedSubFunction(code) => write!(f, "Reserved sub-function 0x{code:04X}"),
293            MbusError::InvalidPduLength => write!(f, "Invalid PDU length"),
294            MbusError::InvalidAduLength => write!(f, "Invalid ADU length"),
295            MbusError::ConnectionFailed => write!(f, "Connection failed"),
296            MbusError::ConnectionClosed => write!(f, "Connection closed"),
297            MbusError::BufferTooSmall => write!(f, "Buffer too small"),
298            MbusError::BufferLenMissmatch => write!(f, "Buffer length mismatch"),
299            MbusError::SendFailed => write!(f, "Send failed"),
300            MbusError::InvalidAddress => write!(f, "Invalid address"),
301            MbusError::InvalidOffset => write!(f, "Invalid offset"),
302            MbusError::TooManyRequests => write!(f, "Too many requests"),
303            MbusError::InvalidFunctionCode => write!(f, "Invalid function code"),
304            MbusError::NoRetriesLeft => write!(f, "No retries left"),
305            MbusError::TooManyFileReadSubRequests => write!(f, "Too many file read sub-requests"),
306            MbusError::FileReadPduOverflow => write!(f, "File read PDU overflow"),
307            MbusError::UnexpectedResponse => write!(f, "Unexpected response"),
308            MbusError::InvalidTransport => write!(f, "Invalid transport"),
309            MbusError::InvalidSlaveAddress => write!(f, "Invalid slave address"),
310            MbusError::ChecksumError => write!(f, "Checksum error"),
311            MbusError::InvalidConfiguration => write!(f, "Invalid configuration"),
312            MbusError::InvalidNumOfExpectedRsps => {
313                write!(f, "Invalid number of expected responses")
314            }
315            MbusError::InvalidDataLen => write!(f, "Invalid data length"),
316            MbusError::InvalidQuantity => write!(f, "Invalid quantity"),
317            MbusError::InvalidValue => write!(f, "Invalid value"),
318            MbusError::InvalidAndMask => write!(f, "Invalid AND mask"),
319            MbusError::InvalidOrMask => write!(f, "Invalid OR mask"),
320            MbusError::InvalidByteCount => write!(f, "Invalid byte count"),
321            MbusError::InvalidDeviceIdentification => write!(f, "Invalid device identification"),
322            MbusError::InvalidDeviceIdCode => write!(f, "Invalid device ID code"),
323            MbusError::InvalidMeiType => write!(f, "Invalid MEI type"),
324            MbusError::InvalidBroadcastAddress => write!(f, "Invalid broadcast address"),
325            MbusError::BroadcastNotAllowed => write!(f, "Broadcast not allowed"),
326        }
327    }
328}
329
330#[cfg(feature = "error-trait")]
331impl core::error::Error for MbusError {}