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    /// 0x0A: Gateway Path Unavailable - Specialized use in gateways.
32    GatewayPathUnavailable = 0x0A,
33    /// 0x0B: Gateway Target Device Failed to Respond - Specialized use in gateways.
34    GatewayTargetDeviceFailedToRespond = 0x0B,
35}
36
37impl From<ExceptionCode> for u8 {
38    fn from(code: ExceptionCode) -> Self {
39        code as u8
40    }
41}
42
43/// Represents a Modbus error.
44#[derive(Debug, PartialEq, Eq, Clone, Copy)]
45pub enum MbusError {
46    /// An error occurred while parsing the Modbus ADU.
47    ParseError,
48    /// This is used for receieved frame is fundamentally malformed
49    BasicParseError,
50    /// The transaction timed out waiting for a response.
51    Timeout,
52    /// The server responded with a Modbus exception code.
53    ModbusException(u8),
54    /// An I/O error occurred during TCP communication.
55    IoError,
56    /// An unexpected error occurred.
57    Unexpected,
58    /// The connection was lost during an active transaction.
59    ConnectionLost,
60    /// The function code is not supported
61    UnsupportedFunction(u8),
62    /// The sub-function code is not available
63    ReservedSubFunction(u16),
64    /// The PDU length is invalid
65    InvalidPduLength,
66    /// The ADU length is invalid
67    InvalidAduLength,
68    /// Connection failed
69    ConnectionFailed,
70    /// Connection closed
71    ConnectionClosed,
72    /// The data was too large for the buffer
73    BufferTooSmall,
74    /// Buffer length is not matching
75    BufferLenMissmatch,
76    /// Failed to send data
77    SendFailed,
78    /// Invalid address
79    InvalidAddress,
80    /// Invalid offset
81    InvalidOffset,
82    /// Too many requests in flight, expected responses buffer is full
83    TooManyRequests,
84    /// Invalid function code
85    InvalidFunctionCode,
86    /// No retries left for the transaction
87    NoRetriesLeft,
88    /// Too many sub-requests in a PDU, Max allowed is 35
89    TooManyFileReadSubRequests,
90    /// File read PDU overflow, total length of file read sub-requests exceeds maximum allowed bytes per PDU
91    FileReadPduOverflow,
92    /// An unexpected response was received that does not match the expected response type for the transaction.
93    UnexpectedResponse,
94    /// The transport is invalid for the requested operation
95    InvalidTransport,
96    /// Invalid slave address
97    InvalidSlaveAddress,
98    /// Checksum error
99    ChecksumError,
100    /// Invalid configuration
101    InvalidConfiguration,
102    /// Invalid number of expected responses.
103    ///
104    /// For Modbus Serial transports, only one request may be in flight at a time,
105    /// so the expected-response queue size must be exactly `1`.
106    InvalidNumOfExpectedRsps,
107    /// Invalid data length
108    InvalidDataLen,
109    /// Invalid Quantity
110    InvalidQuantity,
111    /// Invalid Value
112    InvalidValue,
113    /// Invalid Masking value
114    InvalidAndMask,
115    /// Invalid Masking value
116    InvalidOrMask,
117    /// Invalid byte count
118    InvalidByteCount,
119    /// Invalid device identification
120    InvalidDeviceIdentification,
121    /// Invalid device id code
122    InvalidDeviceIdCode,
123    /// Invalid MEI type
124    InvalidMeiType,
125    /// Invalid broadcast address (0): Broadcast must be created explicitly.
126    /// Use `UnitIdOrSlaveAddr::new_broadcast_address()` to signal broadcast intent.
127    InvalidBroadcastAddress,
128    /// Broadcast not allowed.
129    ///
130    /// Note: This variant name contains a historical typo and is kept for
131    /// compatibility with existing code.
132    BroadcastNotAllowed,
133    /// Transport detected a protocol-level framing or timing violation.
134    ///
135    /// For serial transports this typically indicates an inter-character gap
136    /// exceeding t1.5 character times within a frame.  The server must discard
137    /// any partially accumulated frame data and resume listening for new frames.
138    ///
139    /// This is **not** a connection-level error — the bus remains usable.
140    FramingError,
141}
142
143impl MbusError {
144    /// Returns the canonical "broadcast not allowed" error.
145    ///
146    /// This helper exists to provide a correctly spelled API path while
147    /// preserving the legacy enum variant name for compatibility.
148    pub const fn broadcast_not_allowed() -> Self {
149        Self::BroadcastNotAllowed
150    }
151}
152
153#[cfg(all(feature = "defmt-format", target_os = "none"))]
154impl defmt::Format for MbusError {
155    fn format(&self, f: defmt::Formatter) {
156        match self {
157            MbusError::ParseError => defmt::write!(
158                f,
159                "Parse error: An error occurred while parsing the Modbus ADU"
160            ),
161            MbusError::BasicParseError => defmt::write!(
162                f,
163                "Basic parse error: The received frame is fundamentally malformed"
164            ),
165            MbusError::Timeout => defmt::write!(
166                f,
167                "Timeout: The transaction timed out waiting for a response"
168            ),
169            MbusError::ModbusException(code) => defmt::write!(
170                f,
171                "Modbus exception: The server responded with exception code 0x{:02X}",
172                code
173            ),
174            MbusError::IoError => defmt::write!(
175                f,
176                "I/O error: An I/O error occurred during TCP communication"
177            ),
178            MbusError::Unexpected => {
179                defmt::write!(f, "Unexpected error: An unexpected error occurred")
180            }
181            MbusError::ConnectionLost => defmt::write!(
182                f,
183                "Connection lost: The connection was lost during an active transaction"
184            ),
185            MbusError::UnsupportedFunction(code) => defmt::write!(
186                f,
187                "Unsupported function: Function code 0x{:02X} is not supported",
188                code
189            ),
190            MbusError::ReservedSubFunction(code) => defmt::write!(
191                f,
192                "Reserved sub-function: Sub-function code 0x{:04X} is not available",
193                code
194            ),
195            MbusError::InvalidPduLength => {
196                defmt::write!(f, "Invalid PDU length: The PDU length is invalid")
197            }
198            MbusError::InvalidAduLength => {
199                defmt::write!(f, "Invalid ADU length: The ADU length is invalid")
200            }
201            MbusError::ConnectionFailed => defmt::write!(f, "Connection failed"),
202            MbusError::ConnectionClosed => defmt::write!(f, "Connection closed"),
203            MbusError::BufferTooSmall => {
204                defmt::write!(f, "Buffer too small: The data was too large for the buffer")
205            }
206            MbusError::BufferLenMissmatch => {
207                defmt::write!(f, "Buffer length mismatch: Buffer length is not matching")
208            }
209            MbusError::SendFailed => defmt::write!(f, "Send failed: Failed to send data"),
210            MbusError::InvalidAddress => defmt::write!(f, "Invalid address"),
211            MbusError::TooManyRequests => {
212                defmt::write!(f, "Too many requests: Expected responses buffer is full")
213            }
214            MbusError::InvalidFunctionCode => defmt::write!(f, "Invalid function code"),
215            MbusError::NoRetriesLeft => defmt::write!(f, "No retries left for the transaction"),
216            MbusError::TooManyFileReadSubRequests => defmt::write!(
217                f,
218                "Too many sub-requests: Maximum of 35 sub-requests per PDU allowed"
219            ),
220            MbusError::FileReadPduOverflow => defmt::write!(
221                f,
222                "File read PDU overflow: Total length of file read sub-requests exceeds maximum allowed bytes per PDU"
223            ),
224            MbusError::UnexpectedResponse => defmt::write!(
225                f,
226                "Unexpected response: An unexpected response was received"
227            ),
228            MbusError::InvalidTransport => defmt::write!(
229                f,
230                "Invalid transport: The transport is invalid for the requested operation"
231            ),
232            MbusError::InvalidSlaveAddress => defmt::write!(
233                f,
234                "Invalid slave address: The provided slave address is invalid"
235            ),
236            MbusError::ChecksumError => defmt::write!(
237                f,
238                "Checksum error: The received frame has an invalid checksum"
239            ),
240            MbusError::InvalidConfiguration => defmt::write!(
241                f,
242                "Invalid configuration: The provided configuration is invalid"
243            ),
244            MbusError::InvalidNumOfExpectedRsps => defmt::write!(
245                f,
246                "Invalid number of expected responses: for serial transports the queue size N must be exactly 1"
247            ),
248            MbusError::InvalidDataLen => defmt::write!(
249                f,
250                "Invalid data length: The provided data length is invalid"
251            ),
252            MbusError::InvalidQuantity => {
253                defmt::write!(f, "Invalid quantity: The provided quantity is invalid")
254            }
255            MbusError::InvalidValue => {
256                defmt::write!(f, "Invalid value: The provided value is invalid")
257            }
258            MbusError::InvalidAndMask => {
259                defmt::write!(f, "Invalid AND mask: The provided AND mask is invalid")
260            }
261            MbusError::InvalidOrMask => {
262                defmt::write!(f, "Invalid OR mask: The provided OR mask is invalid")
263            }
264            MbusError::InvalidByteCount => {
265                defmt::write!(f, "Invalid byte count: The provided byte count is invalid")
266            }
267            MbusError::InvalidDeviceIdentification => defmt::write!(
268                f,
269                "Invalid device identification: The provided device identification is invalid"
270            ),
271            MbusError::InvalidDeviceIdCode => defmt::write!(
272                f,
273                "Invalid device ID code: The provided device ID code is invalid"
274            ),
275            MbusError::InvalidMeiType => {
276                defmt::write!(f, "Invalid MEI type: The provided MEI type is invalid")
277            }
278            MbusError::InvalidBroadcastAddress => defmt::write!(
279                f,
280                "Invalid broadcast address: The provided broadcast address (0) is invalid. Must use UnitIdOrSlaveAddr::new_broadcast_address() instead."
281            ),
282            MbusError::BroadcastNotAllowed => {
283                defmt::write!(f, "Broadcast not allowed: Broadcast not allowed")
284            }
285            MbusError::InvalidOffset => {
286                defmt::write!(f, "Invalid offset: The provided offset is invalid")
287            }
288            MbusError::FramingError => {
289                defmt::write!(
290                    f,
291                    "Framing error: Transport detected a protocol timing violation"
292                )
293            }
294        }
295    }
296}
297
298#[cfg(feature = "error-trait")]
299impl core::fmt::Display for MbusError {
300    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
301        match self {
302            MbusError::ParseError => write!(f, "Parse error"),
303            MbusError::BasicParseError => write!(f, "Basic parse error"),
304            MbusError::Timeout => write!(f, "Timeout"),
305            MbusError::ModbusException(code) => write!(f, "Modbus exception 0x{code:02X}"),
306            MbusError::IoError => write!(f, "I/O error"),
307            MbusError::Unexpected => write!(f, "Unexpected error"),
308            MbusError::ConnectionLost => write!(f, "Connection lost"),
309            MbusError::UnsupportedFunction(code) => write!(f, "Unsupported function 0x{code:02X}"),
310            MbusError::ReservedSubFunction(code) => write!(f, "Reserved sub-function 0x{code:04X}"),
311            MbusError::InvalidPduLength => write!(f, "Invalid PDU length"),
312            MbusError::InvalidAduLength => write!(f, "Invalid ADU length"),
313            MbusError::ConnectionFailed => write!(f, "Connection failed"),
314            MbusError::ConnectionClosed => write!(f, "Connection closed"),
315            MbusError::BufferTooSmall => write!(f, "Buffer too small"),
316            MbusError::BufferLenMissmatch => write!(f, "Buffer length mismatch"),
317            MbusError::SendFailed => write!(f, "Send failed"),
318            MbusError::InvalidAddress => write!(f, "Invalid address"),
319            MbusError::InvalidOffset => write!(f, "Invalid offset"),
320            MbusError::TooManyRequests => write!(f, "Too many requests"),
321            MbusError::InvalidFunctionCode => write!(f, "Invalid function code"),
322            MbusError::NoRetriesLeft => write!(f, "No retries left"),
323            MbusError::TooManyFileReadSubRequests => write!(f, "Too many file read sub-requests"),
324            MbusError::FileReadPduOverflow => write!(f, "File read PDU overflow"),
325            MbusError::UnexpectedResponse => write!(f, "Unexpected response"),
326            MbusError::InvalidTransport => write!(f, "Invalid transport"),
327            MbusError::InvalidSlaveAddress => write!(f, "Invalid slave address"),
328            MbusError::ChecksumError => write!(f, "Checksum error"),
329            MbusError::InvalidConfiguration => write!(f, "Invalid configuration"),
330            MbusError::InvalidNumOfExpectedRsps => {
331                write!(f, "Invalid number of expected responses")
332            }
333            MbusError::InvalidDataLen => write!(f, "Invalid data length"),
334            MbusError::InvalidQuantity => write!(f, "Invalid quantity"),
335            MbusError::InvalidValue => write!(f, "Invalid value"),
336            MbusError::InvalidAndMask => write!(f, "Invalid AND mask"),
337            MbusError::InvalidOrMask => write!(f, "Invalid OR mask"),
338            MbusError::InvalidByteCount => write!(f, "Invalid byte count"),
339            MbusError::InvalidDeviceIdentification => write!(f, "Invalid device identification"),
340            MbusError::InvalidDeviceIdCode => write!(f, "Invalid device ID code"),
341            MbusError::InvalidMeiType => write!(f, "Invalid MEI type"),
342            MbusError::InvalidBroadcastAddress => write!(f, "Invalid broadcast address"),
343            MbusError::BroadcastNotAllowed => write!(f, "Broadcast not allowed"),
344            MbusError::FramingError => write!(f, "Framing error"),
345        }
346    }
347}
348
349#[cfg(feature = "error-trait")]
350impl core::error::Error for MbusError {}