tokio_modbus/frame/
mod.rs

1// SPDX-FileCopyrightText: Copyright (c) 2017-2025 slowtec GmbH <post@slowtec.de>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4#[cfg(feature = "rtu")]
5pub(crate) mod rtu;
6
7#[cfg(feature = "tcp")]
8pub(crate) mod tcp;
9
10use std::{
11    borrow::Cow,
12    error,
13    fmt::{self, Display},
14};
15
16use crate::bytes::Bytes;
17
18/// MEI type code (`0x0E`) for Modbus "Read Device Identification" (function 0x2B).
19pub(crate) const MEI_TYPE_READ_DEVICE_IDENTIFICATION: u8 = 0x0E;
20
21/// A Modbus function code.
22///
23/// All function codes as defined by the protocol specification V1.1b3.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum FunctionCode {
26    /// 01 (0x01) Read Coils.
27    ReadCoils,
28
29    /// 02 (0x02) Read Discrete Inputs
30    ReadDiscreteInputs,
31
32    /// 03 (0x03) Read Holding Registers
33    ReadHoldingRegisters,
34
35    /// 04 (0x04) Read Input Registers
36    ReadInputRegisters,
37
38    /// 05 (0x05) Write Single Coil
39    WriteSingleCoil,
40
41    /// 06 (0x06) Write Single Register
42    WriteSingleRegister,
43
44    /// 07 (0x07) Read Exception Status (Serial Line only)
45    ReadExceptionStatus,
46
47    /// 08 (0x08) Diagnostics (Serial Line only)
48    Diagnostics,
49
50    /// 11 (0x0B) Get Comm Event Counter (Serial Line only)
51    GetCommEventCounter,
52
53    /// 12 (0x0C) Get Comm Event Log (Serial Line only)
54    GetCommEventLog,
55
56    /// 15 (0x0F) Write Multiple Coils
57    WriteMultipleCoils,
58
59    /// 16 (0x10) Write Multiple Registers
60    WriteMultipleRegisters,
61
62    /// 17 (0x11) Report Slave ID (Serial Line only)
63    ReportServerId,
64
65    /// 20 (0x14) Read File Record
66    ReadFileRecord,
67
68    /// 21 (0x15) Write File Record
69    WriteFileRecord,
70
71    /// 22 (0x16) Mask Write Register
72    MaskWriteRegister,
73
74    /// 23 (0x17) Read/Write Multiple Registers
75    ReadWriteMultipleRegisters,
76
77    /// 24 (0x18) Read FIFO Queue
78    ReadFifoQueue,
79
80    /// 43 (0x2B) Encapsulated Interface Transport
81    EncapsulatedInterfaceTransport,
82
83    /// Custom Modbus Function Code.
84    Custom(u8),
85}
86
87impl FunctionCode {
88    /// Create a new [`FunctionCode`] with `value`.
89    #[must_use]
90    pub const fn new(value: u8) -> Self {
91        match value {
92            0x01 => Self::ReadCoils,
93            0x02 => Self::ReadDiscreteInputs,
94            0x03 => Self::ReadHoldingRegisters,
95            0x04 => Self::ReadInputRegisters,
96            0x05 => Self::WriteSingleCoil,
97            0x06 => Self::WriteSingleRegister,
98            0x07 => Self::ReadExceptionStatus,
99            0x08 => Self::Diagnostics,
100            0x0B => Self::GetCommEventCounter,
101            0x0C => Self::GetCommEventLog,
102            0x0F => Self::WriteMultipleCoils,
103            0x10 => Self::WriteMultipleRegisters,
104            0x11 => Self::ReportServerId,
105            0x14 => Self::ReadFileRecord,
106            0x15 => Self::WriteFileRecord,
107            0x16 => Self::MaskWriteRegister,
108            0x17 => Self::ReadWriteMultipleRegisters,
109            0x18 => Self::ReadFifoQueue,
110            0x2B => Self::EncapsulatedInterfaceTransport,
111            code => Self::Custom(code),
112        }
113    }
114
115    /// Gets the [`u8`] value of the current [`FunctionCode`].
116    #[must_use]
117    pub const fn value(self) -> u8 {
118        match self {
119            Self::ReadCoils => 0x01,
120            Self::ReadDiscreteInputs => 0x02,
121            Self::ReadHoldingRegisters => 0x03,
122            Self::ReadInputRegisters => 0x04,
123            Self::WriteSingleCoil => 0x05,
124            Self::WriteSingleRegister => 0x06,
125            Self::ReadExceptionStatus => 0x07,
126            Self::Diagnostics => 0x08,
127            Self::GetCommEventCounter => 0x0B,
128            Self::GetCommEventLog => 0x0C,
129            Self::WriteMultipleCoils => 0x0F,
130            Self::WriteMultipleRegisters => 0x10,
131            Self::ReportServerId => 0x11,
132            Self::ReadFileRecord => 0x14,
133            Self::WriteFileRecord => 0x15,
134            Self::MaskWriteRegister => 0x16,
135            Self::ReadWriteMultipleRegisters => 0x17,
136            Self::ReadFifoQueue => 0x18,
137            Self::EncapsulatedInterfaceTransport => 0x2B,
138            Self::Custom(code) => code,
139        }
140    }
141}
142
143impl Display for FunctionCode {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        self.value().fmt(f)
146    }
147}
148
149/// A Modbus protocol address is represented by 16 bit from `0` to `65535`.
150///
151/// This *protocol address* uses 0-based indexing, while the *coil address* or
152/// *register address* is often specified as a number with 1-based indexing.
153/// Please consult the specification of your devices if 1-based coil/register
154/// addresses need to be converted to 0-based protocol addresses by subtracting 1.
155pub type Address = u16;
156
157/// A Coil represents a single bit.
158///
159/// - `true` is equivalent to `ON`, `1` and `0xFF00`.
160/// - `false` is equivalent to `OFF`, `0` and `0x0000`.
161pub(crate) type Coil = bool;
162
163/// Modbus uses 16 bit for its data items.
164///
165/// Transmitted using a big-endian representation.
166pub(crate) type Word = u16;
167
168/// Number of items to process.
169pub type Quantity = u16;
170
171/// A request represents a message from the client (master) to the server (slave).
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum Request<'a> {
174    /// A request to read multiple coils.
175    /// The first parameter is the address of the first coil to read.
176    /// The second parameter is the number of coils to read.
177    ReadCoils(Address, Quantity),
178
179    /// A request to read multiple discrete inputs
180    /// The first parameter is the address of the first discrete input to read.
181    /// The second parameter is the number of discrete inputs to read.
182    ReadDiscreteInputs(Address, Quantity),
183
184    /// A request to write a single coil.
185    /// The first parameter is the address of the coil.
186    /// The second parameter is the value to write to the coil.
187    WriteSingleCoil(Address, Coil),
188
189    /// A request to write multiple coils.
190    /// The first parameter is the address of the first coil to write.
191    /// The second parameter is the vector of values to write to the coils.
192    WriteMultipleCoils(Address, Cow<'a, [Coil]>),
193
194    /// A request to read multiple input registers.
195    /// The first parameter is the address of the first input register to read.
196    /// The second parameter is the number of input registers to read.
197    ReadInputRegisters(Address, Quantity),
198
199    /// A request to read multiple holding registers.
200    /// The first parameter is the address of the first holding register to read.
201    /// The second parameter is the number of holding registers to read.
202    ReadHoldingRegisters(Address, Quantity),
203
204    /// A request to write a single register.
205    /// The first parameter is the address of the register to read.
206    /// The second parameter is the value to write to the register.
207    WriteSingleRegister(Address, Word),
208
209    /// A request to write to multiple registers.
210    /// The first parameter is the address of the first register to write.
211    /// The second parameter is the vector of values to write to the registers.
212    WriteMultipleRegisters(Address, Cow<'a, [Word]>),
213
214    /// A request to report server ID (Serial Line only).
215    ReportServerId,
216
217    /// A request to set or clear individual bits of a holding register.
218    /// The first parameter is the address of the holding register.
219    /// The second parameter is the AND mask.
220    /// The third parameter is the OR mask.
221    MaskWriteRegister(Address, Word, Word),
222
223    /// A request to simultaneously read multiple registers and write multiple registers.
224    /// The first parameter is the address of the first register to read.
225    /// The second parameter is the number of registers to read.
226    /// The third parameter is the address of the first register to write.
227    /// The fourth parameter is the vector of values to write to the registers.
228    ReadWriteMultipleRegisters(Address, Quantity, Address, Cow<'a, [Word]>),
229
230    /// A request to read device identification.
231    /// The first parameter is the [`ReadCode`].
232    /// The second parameter is the object ID: the first object to return (stream access)
233    /// or the specific object to read (individual access).
234    ReadDeviceIdentification(ReadCode, ObjectId),
235
236    /// A raw Modbus request.
237    /// The first parameter is the Modbus function code.
238    /// The second parameter is the raw bytes of the request.
239    Custom(u8, Cow<'a, [u8]>),
240}
241
242impl Request<'_> {
243    /// Converts the request into an owned instance with `'static'` lifetime.
244    #[must_use]
245    pub fn into_owned(self) -> Request<'static> {
246        use Request::*;
247
248        match self {
249            ReadCoils(addr, qty) => ReadCoils(addr, qty),
250            ReadDiscreteInputs(addr, qty) => ReadDiscreteInputs(addr, qty),
251            WriteSingleCoil(addr, coil) => WriteSingleCoil(addr, coil),
252            WriteMultipleCoils(addr, coils) => {
253                WriteMultipleCoils(addr, Cow::Owned(coils.into_owned()))
254            }
255            ReadInputRegisters(addr, qty) => ReadInputRegisters(addr, qty),
256            ReadHoldingRegisters(addr, qty) => ReadHoldingRegisters(addr, qty),
257            WriteSingleRegister(addr, word) => WriteSingleRegister(addr, word),
258            WriteMultipleRegisters(addr, words) => {
259                WriteMultipleRegisters(addr, Cow::Owned(words.into_owned()))
260            }
261            ReportServerId => ReportServerId,
262            MaskWriteRegister(addr, and_mask, or_mask) => {
263                MaskWriteRegister(addr, and_mask, or_mask)
264            }
265            ReadWriteMultipleRegisters(addr, qty, write_addr, words) => {
266                ReadWriteMultipleRegisters(addr, qty, write_addr, Cow::Owned(words.into_owned()))
267            }
268            ReadDeviceIdentification(read_code, object_id) => {
269                ReadDeviceIdentification(read_code, object_id)
270            }
271
272            Custom(func, bytes) => Custom(func, Cow::Owned(bytes.into_owned())),
273        }
274    }
275
276    /// Get the [`FunctionCode`] of the [`Request`].
277    #[must_use]
278    pub const fn function_code(&self) -> FunctionCode {
279        use Request::*;
280
281        match self {
282            ReadCoils(_, _) => FunctionCode::ReadCoils,
283            ReadDiscreteInputs(_, _) => FunctionCode::ReadDiscreteInputs,
284
285            WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
286            WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
287
288            ReadInputRegisters(_, _) => FunctionCode::ReadInputRegisters,
289            ReadHoldingRegisters(_, _) => FunctionCode::ReadHoldingRegisters,
290
291            WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
292            WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
293
294            ReportServerId => FunctionCode::ReportServerId,
295
296            MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
297
298            ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters,
299
300            ReadDeviceIdentification(_, _) => FunctionCode::EncapsulatedInterfaceTransport,
301
302            Custom(code, _) => FunctionCode::Custom(*code),
303        }
304    }
305}
306
307/// A Modbus request with slave included
308#[cfg(feature = "server")]
309#[derive(Debug, Clone, PartialEq, Eq)]
310pub struct SlaveRequest<'a> {
311    /// Slave id from the request
312    pub slave: crate::slave::SlaveId,
313    /// A `Request` enum
314    pub request: Request<'a>,
315}
316
317#[cfg(feature = "server")]
318impl SlaveRequest<'_> {
319    /// Converts the request into an owned instance with `'static'` lifetime.
320    #[must_use]
321    pub fn into_owned(self) -> SlaveRequest<'static> {
322        let Self { slave, request } = self;
323        SlaveRequest {
324            slave,
325            request: request.into_owned(),
326        }
327    }
328}
329
330/// The data of a successful request.
331///
332/// ReadCoils/ReadDiscreteInputs: The length of the result Vec is always a
333/// multiple of 8. Only the values of the first bits/coils that have actually
334/// been requested are defined. The value of the remaining bits depend on the
335/// server implementation and those coils should be ignored.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub enum Response {
338    /// Response to a `ReadCoils` request
339    /// The parameter contains the coil values that have been read
340    /// See also the note above regarding the vector length
341    ReadCoils(Vec<Coil>),
342
343    /// Response to a `ReadDiscreteInputs` request
344    /// The parameter contains the discrete input values that have been read
345    /// See also the note above regarding the vector length
346    ReadDiscreteInputs(Vec<Coil>),
347
348    /// Response to a `WriteSingleCoil` request
349    /// The first parameter contains the address of the coil that has been written to
350    /// The second parameter contains the value that has been written to the coil the given address
351    WriteSingleCoil(Address, Coil),
352
353    /// Response to a `WriteMultipleCoils` request
354    /// The first parameter contains the address at the start of the range that has been written to
355    /// The second parameter contains the amount of values that have been written
356    WriteMultipleCoils(Address, Quantity),
357
358    /// Response to a `ReadInputRegisters` request
359    /// The parameter contains the register values that have been read
360    ReadInputRegisters(Vec<Word>),
361
362    /// Response to a `ReadHoldingRegisters` request
363    /// The parameter contains the register values that have been read
364    ReadHoldingRegisters(Vec<Word>),
365
366    /// Response to a `WriteSingleRegister` request
367    /// The first parameter contains the address of the register that has been written to
368    /// The second parameter contains the value that has been written to the register at the given address
369    WriteSingleRegister(Address, Word),
370
371    /// Response to a `WriteMultipleRegisters` request
372    /// The first parameter contains the address at the start of the register range that has been written to
373    /// The second parameter contains the amount of register that have been written
374    WriteMultipleRegisters(Address, Quantity),
375
376    /// Response to a `ReportServerId` request
377    /// The first parameter contains the server ID
378    /// The second parameter indicates whether the server is running
379    /// The third parameter contains additional data from the server
380    ReportServerId(u8, bool, Vec<u8>),
381
382    /// Response `MaskWriteRegister`
383    /// The first parameter is the address of the holding register.
384    /// The second parameter is the AND mask.
385    /// The third parameter is the OR mask.
386    MaskWriteRegister(Address, Word, Word),
387
388    /// Response to a `ReadWriteMultipleRegisters` request
389    /// The parameter contains the register values that have been read as part of the read instruction
390    ReadWriteMultipleRegisters(Vec<Word>),
391
392    /// Response to a `ReadDeviceIdentification` request
393    ReadDeviceIdentification(ReadDeviceIdentificationResponse),
394
395    /// Response to a raw Modbus request
396    /// The first parameter contains the returned Modbus function code
397    /// The second parameter contains the bytes read following the function code
398    Custom(u8, Bytes),
399}
400
401impl Response {
402    /// Get the [`FunctionCode`] of the [`Response`].
403    #[must_use]
404    pub const fn function_code(&self) -> FunctionCode {
405        use Response::*;
406
407        match self {
408            ReadCoils(_) => FunctionCode::ReadCoils,
409            ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs,
410
411            WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
412            WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
413
414            ReadInputRegisters(_) => FunctionCode::ReadInputRegisters,
415            ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters,
416
417            WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
418            WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
419
420            ReportServerId(_, _, _) => FunctionCode::ReportServerId,
421
422            MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
423
424            ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
425
426            ReadDeviceIdentification(_) => FunctionCode::EncapsulatedInterfaceTransport,
427
428            Custom(code, _) => FunctionCode::Custom(*code),
429        }
430    }
431}
432
433/// A server (slave) exception.
434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
435pub enum ExceptionCode {
436    /// 0x01
437    IllegalFunction,
438    /// 0x02
439    IllegalDataAddress,
440    /// 0x03
441    IllegalDataValue,
442    /// 0x04
443    ServerDeviceFailure,
444    /// 0x05
445    Acknowledge,
446    /// 0x06
447    ServerDeviceBusy,
448    /// 0x08
449    MemoryParityError,
450    /// 0x0A
451    GatewayPathUnavailable,
452    /// 0x0B
453    GatewayTargetDevice,
454    /// None of the above.
455    ///
456    /// Although encoding one of the predefined values as this is possible, it is not recommended.
457    /// Instead, prefer to use [`Self::new()`] to prevent such ambiguities.
458    Custom(u8),
459}
460
461impl From<ExceptionCode> for u8 {
462    fn from(from: ExceptionCode) -> Self {
463        use crate::frame::ExceptionCode::*;
464        match from {
465            IllegalFunction => 0x01,
466            IllegalDataAddress => 0x02,
467            IllegalDataValue => 0x03,
468            ServerDeviceFailure => 0x04,
469            Acknowledge => 0x05,
470            ServerDeviceBusy => 0x06,
471            MemoryParityError => 0x08,
472            GatewayPathUnavailable => 0x0A,
473            GatewayTargetDevice => 0x0B,
474            Custom(code) => code,
475        }
476    }
477}
478
479impl ExceptionCode {
480    /// Create a new [`ExceptionCode`] with `value`.
481    #[must_use]
482    pub const fn new(value: u8) -> Self {
483        use crate::frame::ExceptionCode::*;
484
485        match value {
486            0x01 => IllegalFunction,
487            0x02 => IllegalDataAddress,
488            0x03 => IllegalDataValue,
489            0x04 => ServerDeviceFailure,
490            0x05 => Acknowledge,
491            0x06 => ServerDeviceBusy,
492            0x08 => MemoryParityError,
493            0x0A => GatewayPathUnavailable,
494            0x0B => GatewayTargetDevice,
495            other => Custom(other),
496        }
497    }
498
499    pub(crate) fn description(&self) -> &str {
500        use crate::frame::ExceptionCode::*;
501
502        match *self {
503            IllegalFunction => "Illegal function",
504            IllegalDataAddress => "Illegal data address",
505            IllegalDataValue => "Illegal data value",
506            ServerDeviceFailure => "Server device failure",
507            Acknowledge => "Acknowledge",
508            ServerDeviceBusy => "Server device busy",
509            MemoryParityError => "Memory parity error",
510            GatewayPathUnavailable => "Gateway path unavailable",
511            GatewayTargetDevice => "Gateway target device failed to respond",
512            Custom(_) => "Custom",
513        }
514    }
515}
516
517/// Represents the Modbus read device identification access type.
518///
519/// Used to specify the type of information to retrieve from a device during a
520/// "Read Device Identification" Modbus function (0x2B / 0x0E).
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
522pub enum ReadCode {
523    /// Basic identification (stream access).
524    /// Corresponds to value `0x01`. Returns a minimal set of identification data.
525    Basic,
526    /// Regular identification (stream access).
527    /// Corresponds to value `0x02`. Returns additional identification beyond basic.
528    Regular,
529    /// Extended identification (stream access).
530    /// Corresponds to value `0x03`. Returns the most comprehensive set of device info.
531    Extended,
532    /// Specific identification (individual access).
533    /// Corresponds to value `0x04`. Used to retrieve a specific object by ID.
534    Specific,
535}
536
537impl ReadCode {
538    /// Attempts to convert a raw [`u8`] value to a [`ReadCode`].
539    ///
540    /// # Parameters
541    /// - `value`: The raw byte representing the read code.
542    ///
543    /// # Returns
544    /// - `Some(ReadCode)` if the value is valid.
545    /// - `None` otherwise.
546    #[must_use]
547    pub const fn try_from_value(value: u8) -> Option<Self> {
548        Some(match value {
549            0x01 => ReadCode::Basic,
550            0x02 => ReadCode::Regular,
551            0x03 => ReadCode::Extended,
552            0x04 => ReadCode::Specific,
553            _ => return None,
554        })
555    }
556
557    /// Returns the [`u8`] representation of the current [`ReadCode`] variant.
558    ///
559    /// # Returns
560    /// A byte corresponding to the Modbus function read code.
561    #[must_use]
562    pub const fn value(self) -> u8 {
563        match self {
564            ReadCode::Basic => 0x01,
565            ReadCode::Regular => 0x02,
566            ReadCode::Extended => 0x03,
567            ReadCode::Specific => 0x04,
568        }
569    }
570}
571
572/// Represents the conformity level of a Modbus device's identification support.
573///
574/// Indicates what types of identification objects a device supports,
575/// and whether access is limited to stream access or includes individual access.
576#[derive(Debug, Clone, Copy, PartialEq, Eq)]
577pub enum ConformityLevel {
578    /// Only basic identification objects via stream access (`0x01`).
579    BasicIdentificationStreamOnly,
580
581    /// Only regular identification objects via stream access (`0x02`).
582    RegularIdentificationStreamOnly,
583
584    /// Only extended identification objects via stream access (`0x03`).
585    ExtendedIdentificationStreamOnly,
586
587    /// Basic identification objects, with individual access supported (`0x81`).
588    BasicIdentification,
589
590    /// Regular identification objects, with individual access supported (`0x82`).
591    RegularIdentification,
592
593    /// Extended identification objects, with individual access supported (`0x83`).
594    ExtendedIdentification,
595}
596
597impl ConformityLevel {
598    /// Attempts to convert a raw [`u8`] to a [`ConformityLevel`].
599    ///
600    /// # Parameters
601    /// - `value`: The raw byte representing the device's conformity level.
602    ///
603    /// # Returns
604    /// - `Some(ConformityLevel)` if the value matches a known level.
605    /// - `None` for unrecognized values.
606    #[must_use]
607    pub const fn try_from_value(value: u8) -> Option<Self> {
608        Some(match value {
609            0x01 => ConformityLevel::BasicIdentificationStreamOnly,
610            0x02 => ConformityLevel::RegularIdentificationStreamOnly,
611            0x03 => ConformityLevel::ExtendedIdentificationStreamOnly,
612            0x81 => ConformityLevel::BasicIdentification,
613            0x82 => ConformityLevel::RegularIdentification,
614            0x83 => ConformityLevel::ExtendedIdentification,
615            _ => return None,
616        })
617    }
618
619    /// Returns the [`u8`] representation of the current [`ConformityLevel`] variant.
620    ///
621    /// # Returns
622    /// A byte that can be used in Modbus device identification responses.
623    #[must_use]
624    pub const fn value(self) -> u8 {
625        match self {
626            ConformityLevel::BasicIdentificationStreamOnly => 0x01,
627            ConformityLevel::RegularIdentificationStreamOnly => 0x02,
628            ConformityLevel::ExtendedIdentificationStreamOnly => 0x03,
629            ConformityLevel::BasicIdentification => 0x81,
630            ConformityLevel::RegularIdentification => 0x82,
631            ConformityLevel::ExtendedIdentification => 0x83,
632        }
633    }
634}
635
636/// Identifier of a single device ID object.
637///
638/// Each object represents a specific type of information (e.g., vendor name, product code).
639pub type ObjectId = u8;
640
641/// Indicates whether more identification objects follow in the response.
642///
643/// `true` means more data is available; `false` means this is the final part.
644pub type MoreFollows = bool;
645
646/// Specifies the ID of the next object to be requested in case of partial data.
647///
648/// Used when `MoreFollows` is `true`, should be 0 otherwise.
649pub type NextObjectId = u8;
650
651/// A vector of device identification objects ([`DeviceIdObject`]).
652///
653/// Each [`DeviceIdObject`] in the list represents a specific piece of identification
654/// information such as the vendor name, product code, or firmware version.
655///
656/// The object values are returned as raw bytes and can optionally be interpreted
657/// as UTF-8 strings using [`DeviceIdObject::value_as_str`].
658///
659/// This list is typically received in response to a Modbus "Read Device Identification"
660/// ([`Request::ReadDeviceIdentification`]) request.
661pub type DeviceIdObjects = Vec<DeviceIdObject>;
662
663/// Represents a single Modbus device identification object.
664///
665/// Each object consists of an ID and an associated binary value,
666/// typically representing device metadata such as vendor name,
667/// product code, or software version.
668///
669/// The value is stored as raw bytes and may be interpreted as a UTF-8 string.
670#[derive(Debug, Clone, PartialEq, Eq)]
671pub struct DeviceIdObject {
672    /// Object identifier (0x00 to 0xFF), as defined by the Modbus specification.
673    ///
674    /// Common IDs include:
675    /// - 0x00: `VendorName`
676    /// - 0x01: `ProductCode`
677    /// - 0x02: `MajorMinorRevision`
678    pub id: u8,
679
680    /// Raw byte value associated with this object.
681    ///
682    /// May contain UTF-8-encoded strings or other binary data.
683    pub value: Bytes,
684}
685
686impl DeviceIdObject {
687    /// Attempts to interpret the object's value as a UTF-8 string.
688    ///
689    /// This is useful when the identification object contains human-readable
690    /// data, such as a vendor name or software version.
691    ///
692    /// # Returns
693    /// - `Some(&str)` if the value is valid UTF-8.
694    /// - `None` if the value contains invalid UTF-8 bytes.
695    pub fn value_as_str(&self) -> Option<&str> {
696        std::str::from_utf8(&self.value).ok()
697    }
698}
699
700/// Response data returned from a Modbus "Read Device Identification" request (0x2B/0x0E).
701#[derive(Debug, Clone, PartialEq, Eq)]
702pub struct ReadDeviceIdentificationResponse {
703    /// Indicates the type of Read Device Identification request that was performed.
704    pub read_code: ReadCode,
705    /// Indicates the level of conformity supported by the device for identification.
706    pub conformity_level: ConformityLevel,
707    /// Signals whether additional identification objects are available.
708    pub more_follows: MoreFollows,
709    /// Specifies the next object ID to request if `more_follows` is true.
710    pub next_object_id: NextObjectId,
711    /// The list of device ID objects returned in this response.
712    pub device_id_objects: DeviceIdObjects,
713}
714
715/// A server (slave) exception response.
716#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717pub struct ExceptionResponse {
718    pub function: FunctionCode,
719    pub exception: ExceptionCode,
720}
721
722/// Represents a message from the client (slave) to the server (master).
723#[derive(Debug, Clone)]
724pub(crate) struct RequestPdu<'a>(pub(crate) Request<'a>);
725
726impl<'a> From<Request<'a>> for RequestPdu<'a> {
727    fn from(from: Request<'a>) -> Self {
728        RequestPdu(from)
729    }
730}
731
732impl<'a> From<RequestPdu<'a>> for Request<'a> {
733    fn from(from: RequestPdu<'a>) -> Self {
734        from.0
735    }
736}
737
738/// Represents a message from the server (slave) to the client (master).
739#[derive(Debug, Clone, PartialEq, Eq)]
740pub(crate) struct ResponsePdu(pub(crate) Result<Response, ExceptionResponse>);
741
742impl From<Response> for ResponsePdu {
743    fn from(from: Response) -> Self {
744        ResponsePdu(Ok(from))
745    }
746}
747
748impl From<ExceptionResponse> for ResponsePdu {
749    fn from(from: ExceptionResponse) -> Self {
750        ResponsePdu(Err(from))
751    }
752}
753
754#[cfg(any(
755    feature = "rtu-over-tcp-server",
756    feature = "rtu-server",
757    feature = "tcp-server"
758))]
759#[derive(Debug, Clone, PartialEq, Eq)]
760pub(crate) struct OptionalResponsePdu(pub(crate) Option<ResponsePdu>);
761
762#[cfg(any(
763    feature = "rtu-over-tcp-server",
764    feature = "rtu-server",
765    feature = "tcp-server"
766))]
767impl From<Result<Option<Response>, ExceptionResponse>> for OptionalResponsePdu {
768    fn from(from: Result<Option<Response>, ExceptionResponse>) -> Self {
769        match from {
770            Ok(None) => Self(None),
771            Ok(Some(response)) => Self(Some(response.into())),
772            Err(exception) => Self(Some(exception.into())),
773        }
774    }
775}
776
777impl From<ResponsePdu> for Result<Response, ExceptionResponse> {
778    fn from(from: ResponsePdu) -> Self {
779        from.0
780    }
781}
782
783impl fmt::Display for ExceptionCode {
784    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785        write!(f, "{}", self.description())
786    }
787}
788
789impl error::Error for ExceptionCode {
790    fn description(&self) -> &str {
791        self.description()
792    }
793}
794
795impl fmt::Display for ExceptionResponse {
796    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797        write!(f, "Modbus function {}: {}", self.function, self.exception)
798    }
799}
800
801impl error::Error for ExceptionResponse {
802    fn description(&self) -> &str {
803        self.exception.description()
804    }
805}
806
807#[cfg(test)]
808mod tests {
809    use super::*;
810
811    #[test]
812    fn new_function_code() {
813        assert_eq!(FunctionCode::ReadCoils, FunctionCode::new(0x01));
814        assert_eq!(FunctionCode::ReadDiscreteInputs, FunctionCode::new(0x02));
815
816        assert_eq!(FunctionCode::WriteSingleCoil, FunctionCode::new(0x05));
817        assert_eq!(FunctionCode::WriteSingleRegister, FunctionCode::new(0x06));
818
819        assert_eq!(FunctionCode::ReadHoldingRegisters, FunctionCode::new(0x03));
820        assert_eq!(FunctionCode::ReadInputRegisters, FunctionCode::new(0x04));
821
822        assert_eq!(FunctionCode::WriteMultipleCoils, FunctionCode::new(0x0F));
823        assert_eq!(
824            FunctionCode::WriteMultipleRegisters,
825            FunctionCode::new(0x10)
826        );
827
828        assert_eq!(FunctionCode::MaskWriteRegister, FunctionCode::new(0x16));
829
830        assert_eq!(
831            FunctionCode::ReadWriteMultipleRegisters,
832            FunctionCode::new(0x17)
833        );
834
835        assert_eq!(FunctionCode::Custom(70), FunctionCode::new(70));
836    }
837
838    #[test]
839    fn function_code_values() {
840        assert_eq!(FunctionCode::ReadCoils.value(), 0x01);
841        assert_eq!(FunctionCode::ReadDiscreteInputs.value(), 0x02);
842
843        assert_eq!(FunctionCode::WriteSingleCoil.value(), 0x05);
844        assert_eq!(FunctionCode::WriteSingleRegister.value(), 0x06);
845
846        assert_eq!(FunctionCode::ReadHoldingRegisters.value(), 0x03);
847        assert_eq!(FunctionCode::ReadInputRegisters.value(), 0x04);
848
849        assert_eq!(FunctionCode::WriteMultipleCoils.value(), 0x0F);
850        assert_eq!(FunctionCode::WriteMultipleRegisters.value(), 0x10);
851
852        assert_eq!(FunctionCode::MaskWriteRegister.value(), 0x16);
853
854        assert_eq!(FunctionCode::ReadWriteMultipleRegisters.value(), 0x17);
855
856        assert_eq!(FunctionCode::Custom(70).value(), 70);
857    }
858
859    #[test]
860    fn function_code_from_request() {
861        use Request::*;
862
863        assert_eq!(ReadCoils(0, 0).function_code(), FunctionCode::ReadCoils);
864        assert_eq!(
865            ReadDiscreteInputs(0, 0).function_code(),
866            FunctionCode::ReadDiscreteInputs
867        );
868
869        assert_eq!(
870            WriteSingleCoil(0, true).function_code(),
871            FunctionCode::WriteSingleCoil
872        );
873        assert_eq!(
874            WriteMultipleCoils(0, Cow::Borrowed(&[])).function_code(),
875            FunctionCode::WriteMultipleCoils
876        );
877
878        assert_eq!(
879            ReadInputRegisters(0, 0).function_code(),
880            FunctionCode::ReadInputRegisters
881        );
882        assert_eq!(
883            ReadHoldingRegisters(0, 0).function_code(),
884            FunctionCode::ReadHoldingRegisters
885        );
886
887        assert_eq!(
888            WriteSingleRegister(0, 0).function_code(),
889            FunctionCode::WriteSingleRegister
890        );
891        assert_eq!(
892            WriteMultipleRegisters(0, Cow::Borrowed(&[])).function_code(),
893            FunctionCode::WriteMultipleRegisters
894        );
895
896        assert_eq!(
897            MaskWriteRegister(0, 0, 0).function_code(),
898            FunctionCode::MaskWriteRegister
899        );
900
901        assert_eq!(
902            ReadWriteMultipleRegisters(0, 0, 0, Cow::Borrowed(&[])).function_code(),
903            FunctionCode::ReadWriteMultipleRegisters
904        );
905
906        assert_eq!(Custom(88, Cow::Borrowed(&[])).function_code().value(), 88);
907    }
908
909    #[test]
910    fn function_code_from_response() {
911        use Response::*;
912
913        assert_eq!(ReadCoils(vec![]).function_code(), FunctionCode::ReadCoils);
914        assert_eq!(
915            ReadDiscreteInputs(vec![]).function_code(),
916            FunctionCode::ReadDiscreteInputs
917        );
918
919        assert_eq!(
920            WriteSingleCoil(0x0, false).function_code(),
921            FunctionCode::WriteSingleCoil
922        );
923        assert_eq!(
924            WriteMultipleCoils(0x0, 0x0).function_code(),
925            FunctionCode::WriteMultipleCoils
926        );
927
928        assert_eq!(
929            ReadInputRegisters(vec![]).function_code(),
930            FunctionCode::ReadInputRegisters
931        );
932        assert_eq!(
933            ReadHoldingRegisters(vec![]).function_code(),
934            FunctionCode::ReadHoldingRegisters
935        );
936
937        assert_eq!(
938            WriteSingleRegister(0, 0).function_code(),
939            FunctionCode::WriteSingleRegister
940        );
941        assert_eq!(
942            WriteMultipleRegisters(0, 0).function_code(),
943            FunctionCode::WriteMultipleRegisters
944        );
945
946        assert_eq!(
947            MaskWriteRegister(0, 0, 0).function_code(),
948            FunctionCode::MaskWriteRegister
949        );
950
951        assert_eq!(
952            ReadWriteMultipleRegisters(vec![]).function_code(),
953            FunctionCode::ReadWriteMultipleRegisters
954        );
955
956        assert_eq!(
957            Custom(99, Bytes::from_static(&[])).function_code().value(),
958            99
959        );
960    }
961}