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/// A Modbus function code.
19///
20/// All function codes as defined by the protocol specification V1.1b3.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum FunctionCode {
23    /// 01 (0x01) Read Coils.
24    ReadCoils,
25
26    /// 02 (0x02) Read Discrete Inputs
27    ReadDiscreteInputs,
28
29    /// 03 (0x03) Read Holding Registers
30    ReadHoldingRegisters,
31
32    /// 04 (0x04) Read Input Registers
33    ReadInputRegisters,
34
35    /// 05 (0x05) Write Single Coil
36    WriteSingleCoil,
37
38    /// 06 (0x06) Write Single Register
39    WriteSingleRegister,
40
41    /// 07 (0x07) Read Exception Status (Serial Line only)
42    ReadExceptionStatus,
43
44    /// 08 (0x08) Diagnostics (Serial Line only)
45    Diagnostics,
46
47    /// 11 (0x0B) Get Comm Event Counter (Serial Line only)
48    GetCommEventCounter,
49
50    /// 12 (0x0C) Get Comm Event Log (Serial Line only)
51    GetCommEventLog,
52
53    /// 15 (0x0F) Write Multiple Coils
54    WriteMultipleCoils,
55
56    /// 16 (0x10) Write Multiple Registers
57    WriteMultipleRegisters,
58
59    /// 17 (0x11) Report Slave ID (Serial Line only)
60    ReportServerId,
61
62    /// 20 (0x14) Read File Record
63    ReadFileRecord,
64
65    /// 21 (0x15) Write File Record
66    WriteFileRecord,
67
68    /// 22 (0x16) Mask Write Register
69    MaskWriteRegister,
70
71    /// 23 (0x17) Read/Write Multiple Registers
72    ReadWriteMultipleRegisters,
73
74    /// 24 (0x18) Read FIFO Queue
75    ReadFifoQueue,
76
77    /// 43 (0x2B) Encapsulated Interface Transport
78    EncapsulatedInterfaceTransport,
79
80    /// Custom Modbus Function Code.
81    Custom(u8),
82}
83
84impl FunctionCode {
85    /// Create a new [`FunctionCode`] with `value`.
86    #[must_use]
87    pub const fn new(value: u8) -> Self {
88        match value {
89            0x01 => Self::ReadCoils,
90            0x02 => Self::ReadDiscreteInputs,
91            0x03 => Self::ReadHoldingRegisters,
92            0x04 => Self::ReadInputRegisters,
93            0x05 => Self::WriteSingleCoil,
94            0x06 => Self::WriteSingleRegister,
95            0x07 => Self::ReadExceptionStatus,
96            0x08 => Self::Diagnostics,
97            0x0B => Self::GetCommEventCounter,
98            0x0C => Self::GetCommEventLog,
99            0x0F => Self::WriteMultipleCoils,
100            0x10 => Self::WriteMultipleRegisters,
101            0x11 => Self::ReportServerId,
102            0x14 => Self::ReadFileRecord,
103            0x15 => Self::WriteFileRecord,
104            0x16 => Self::MaskWriteRegister,
105            0x17 => Self::ReadWriteMultipleRegisters,
106            0x18 => Self::ReadFifoQueue,
107            0x2B => Self::EncapsulatedInterfaceTransport,
108            code => Self::Custom(code),
109        }
110    }
111
112    /// Gets the [`u8`] value of the current [`FunctionCode`].
113    #[must_use]
114    pub const fn value(self) -> u8 {
115        match self {
116            Self::ReadCoils => 0x01,
117            Self::ReadDiscreteInputs => 0x02,
118            Self::ReadHoldingRegisters => 0x03,
119            Self::ReadInputRegisters => 0x04,
120            Self::WriteSingleCoil => 0x05,
121            Self::WriteSingleRegister => 0x06,
122            Self::ReadExceptionStatus => 0x07,
123            Self::Diagnostics => 0x08,
124            Self::GetCommEventCounter => 0x0B,
125            Self::GetCommEventLog => 0x0C,
126            Self::WriteMultipleCoils => 0x0F,
127            Self::WriteMultipleRegisters => 0x10,
128            Self::ReportServerId => 0x11,
129            Self::ReadFileRecord => 0x14,
130            Self::WriteFileRecord => 0x15,
131            Self::MaskWriteRegister => 0x16,
132            Self::ReadWriteMultipleRegisters => 0x17,
133            Self::ReadFifoQueue => 0x18,
134            Self::EncapsulatedInterfaceTransport => 0x2B,
135            Self::Custom(code) => code,
136        }
137    }
138}
139
140impl Display for FunctionCode {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        self.value().fmt(f)
143    }
144}
145
146/// A Modbus protocol address is represented by 16 bit from `0` to `65535`.
147///
148/// This *protocol address* uses 0-based indexing, while the *coil address* or
149/// *register address* is often specified as a number with 1-based indexing.
150/// Please consult the specification of your devices if 1-based coil/register
151/// addresses need to be converted to 0-based protocol addresses by subtracting 1.
152pub type Address = u16;
153
154/// A Coil represents a single bit.
155///
156/// - `true` is equivalent to `ON`, `1` and `0xFF00`.
157/// - `false` is equivalent to `OFF`, `0` and `0x0000`.
158pub(crate) type Coil = bool;
159
160/// Modbus uses 16 bit for its data items.
161///
162/// Transmitted using a big-endian representation.
163pub(crate) type Word = u16;
164
165/// Number of items to process.
166pub type Quantity = u16;
167
168/// A request represents a message from the client (master) to the server (slave).
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum Request<'a> {
171    /// A request to read multiple coils.
172    /// The first parameter is the address of the first coil to read.
173    /// The second parameter is the number of coils to read.
174    ReadCoils(Address, Quantity),
175
176    /// A request to read multiple discrete inputs
177    /// The first parameter is the address of the first discrete input to read.
178    /// The second parameter is the number of discrete inputs to read.
179    ReadDiscreteInputs(Address, Quantity),
180
181    /// A request to write a single coil.
182    /// The first parameter is the address of the coil.
183    /// The second parameter is the value to write to the coil.
184    WriteSingleCoil(Address, Coil),
185
186    /// A request to write multiple coils.
187    /// The first parameter is the address of the first coil to write.
188    /// The second parameter is the vector of values to write to the coils.
189    WriteMultipleCoils(Address, Cow<'a, [Coil]>),
190
191    /// A request to read multiple input registers.
192    /// The first parameter is the address of the first input register to read.
193    /// The second parameter is the number of input registers to read.
194    ReadInputRegisters(Address, Quantity),
195
196    /// A request to read multiple holding registers.
197    /// The first parameter is the address of the first holding register to read.
198    /// The second parameter is the number of holding registers to read.
199    ReadHoldingRegisters(Address, Quantity),
200
201    /// A request to write a single register.
202    /// The first parameter is the address of the register to read.
203    /// The second parameter is the value to write to the register.
204    WriteSingleRegister(Address, Word),
205
206    /// A request to write to multiple registers.
207    /// The first parameter is the address of the first register to write.
208    /// The second parameter is the vector of values to write to the registers.
209    WriteMultipleRegisters(Address, Cow<'a, [Word]>),
210
211    /// A request to report server ID (Serial Line only).
212    ReportServerId,
213
214    /// A request to set or clear individual bits of a holding register.
215    /// The first parameter is the address of the holding register.
216    /// The second parameter is the AND mask.
217    /// The third parameter is the OR mask.
218    MaskWriteRegister(Address, Word, Word),
219
220    /// A request to simultaneously read multiple registers and write multiple registers.
221    /// The first parameter is the address of the first register to read.
222    /// The second parameter is the number of registers to read.
223    /// The third parameter is the address of the first register to write.
224    /// The fourth parameter is the vector of values to write to the registers.
225    ReadWriteMultipleRegisters(Address, Quantity, Address, Cow<'a, [Word]>),
226
227    /// A raw Modbus request.
228    /// The first parameter is the Modbus function code.
229    /// The second parameter is the raw bytes of the request.
230    Custom(u8, Cow<'a, [u8]>),
231}
232
233impl Request<'_> {
234    /// Converts the request into an owned instance with `'static'` lifetime.
235    #[must_use]
236    pub fn into_owned(self) -> Request<'static> {
237        use Request::*;
238
239        match self {
240            ReadCoils(addr, qty) => ReadCoils(addr, qty),
241            ReadDiscreteInputs(addr, qty) => ReadDiscreteInputs(addr, qty),
242            WriteSingleCoil(addr, coil) => WriteSingleCoil(addr, coil),
243            WriteMultipleCoils(addr, coils) => {
244                WriteMultipleCoils(addr, Cow::Owned(coils.into_owned()))
245            }
246            ReadInputRegisters(addr, qty) => ReadInputRegisters(addr, qty),
247            ReadHoldingRegisters(addr, qty) => ReadHoldingRegisters(addr, qty),
248            WriteSingleRegister(addr, word) => WriteSingleRegister(addr, word),
249            WriteMultipleRegisters(addr, words) => {
250                WriteMultipleRegisters(addr, Cow::Owned(words.into_owned()))
251            }
252            ReportServerId => ReportServerId,
253            MaskWriteRegister(addr, and_mask, or_mask) => {
254                MaskWriteRegister(addr, and_mask, or_mask)
255            }
256            ReadWriteMultipleRegisters(addr, qty, write_addr, words) => {
257                ReadWriteMultipleRegisters(addr, qty, write_addr, Cow::Owned(words.into_owned()))
258            }
259            Custom(func, bytes) => Custom(func, Cow::Owned(bytes.into_owned())),
260        }
261    }
262
263    /// Get the [`FunctionCode`] of the [`Request`].
264    #[must_use]
265    pub const fn function_code(&self) -> FunctionCode {
266        use Request::*;
267
268        match self {
269            ReadCoils(_, _) => FunctionCode::ReadCoils,
270            ReadDiscreteInputs(_, _) => FunctionCode::ReadDiscreteInputs,
271
272            WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
273            WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
274
275            ReadInputRegisters(_, _) => FunctionCode::ReadInputRegisters,
276            ReadHoldingRegisters(_, _) => FunctionCode::ReadHoldingRegisters,
277
278            WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
279            WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
280
281            ReportServerId => FunctionCode::ReportServerId,
282
283            MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
284
285            ReadWriteMultipleRegisters(_, _, _, _) => FunctionCode::ReadWriteMultipleRegisters,
286
287            Custom(code, _) => FunctionCode::Custom(*code),
288        }
289    }
290}
291
292/// A Modbus request with slave included
293#[cfg(feature = "server")]
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct SlaveRequest<'a> {
296    /// Slave id from the request
297    pub slave: crate::slave::SlaveId,
298    /// A `Request` enum
299    pub request: Request<'a>,
300}
301
302#[cfg(feature = "server")]
303impl SlaveRequest<'_> {
304    /// Converts the request into an owned instance with `'static'` lifetime.
305    #[must_use]
306    pub fn into_owned(self) -> SlaveRequest<'static> {
307        let Self { slave, request } = self;
308        SlaveRequest {
309            slave,
310            request: request.into_owned(),
311        }
312    }
313}
314
315/// The data of a successful request.
316///
317/// ReadCoils/ReadDiscreteInputs: The length of the result Vec is always a
318/// multiple of 8. Only the values of the first bits/coils that have actually
319/// been requested are defined. The value of the remaining bits depend on the
320/// server implementation and those coils should be ignored.
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub enum Response {
323    /// Response to a `ReadCoils` request
324    /// The parameter contains the coil values that have been read
325    /// See also the note above regarding the vector length
326    ReadCoils(Vec<Coil>),
327
328    /// Response to a `ReadDiscreteInputs` request
329    /// The parameter contains the discrete input values that have been read
330    /// See also the note above regarding the vector length
331    ReadDiscreteInputs(Vec<Coil>),
332
333    /// Response to a `WriteSingleCoil` request
334    /// The first parameter contains the address of the coil that has been written to
335    /// The second parameter contains the value that has been written to the coil the given address
336    WriteSingleCoil(Address, Coil),
337
338    /// Response to a `WriteMultipleCoils` request
339    /// The first parameter contains the address at the start of the range that has been written to
340    /// The second parameter contains the amount of values that have been written
341    WriteMultipleCoils(Address, Quantity),
342
343    /// Response to a `ReadInputRegisters` request
344    /// The parameter contains the register values that have been read
345    ReadInputRegisters(Vec<Word>),
346
347    /// Response to a `ReadHoldingRegisters` request
348    /// The parameter contains the register values that have been read
349    ReadHoldingRegisters(Vec<Word>),
350
351    /// Response to a `WriteSingleRegister` request
352    /// The first parameter contains the address of the register that has been written to
353    /// The second parameter contains the value that has been written to the register at the given address
354    WriteSingleRegister(Address, Word),
355
356    /// Response to a `WriteMultipleRegisters` request
357    /// The first parameter contains the address at the start of the register range that has been written to
358    /// The second parameter contains the amount of register that have been written
359    WriteMultipleRegisters(Address, Quantity),
360
361    /// Response to a `ReportServerId` request
362    /// The first parameter contains the server ID
363    /// The second parameter indicates whether the server is running
364    /// The third parameter contains additional data from the server
365    ReportServerId(u8, bool, Vec<u8>),
366
367    /// Response `MaskWriteRegister`
368    /// The first parameter is the address of the holding register.
369    /// The second parameter is the AND mask.
370    /// The third parameter is the OR mask.
371    MaskWriteRegister(Address, Word, Word),
372
373    /// Response to a `ReadWriteMultipleRegisters` request
374    /// The parameter contains the register values that have been read as part of the read instruction
375    ReadWriteMultipleRegisters(Vec<Word>),
376
377    /// Response to a raw Modbus request
378    /// The first parameter contains the returned Modbus function code
379    /// The second parameter contains the bytes read following the function code
380    Custom(u8, Bytes),
381}
382
383impl Response {
384    /// Get the [`FunctionCode`] of the [`Response`].
385    #[must_use]
386    pub const fn function_code(&self) -> FunctionCode {
387        use Response::*;
388
389        match self {
390            ReadCoils(_) => FunctionCode::ReadCoils,
391            ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs,
392
393            WriteSingleCoil(_, _) => FunctionCode::WriteSingleCoil,
394            WriteMultipleCoils(_, _) => FunctionCode::WriteMultipleCoils,
395
396            ReadInputRegisters(_) => FunctionCode::ReadInputRegisters,
397            ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters,
398
399            WriteSingleRegister(_, _) => FunctionCode::WriteSingleRegister,
400            WriteMultipleRegisters(_, _) => FunctionCode::WriteMultipleRegisters,
401
402            ReportServerId(_, _, _) => FunctionCode::ReportServerId,
403
404            MaskWriteRegister(_, _, _) => FunctionCode::MaskWriteRegister,
405
406            ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
407
408            Custom(code, _) => FunctionCode::Custom(*code),
409        }
410    }
411}
412
413/// A server (slave) exception.
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
415pub enum ExceptionCode {
416    /// 0x01
417    IllegalFunction,
418    /// 0x02
419    IllegalDataAddress,
420    /// 0x03
421    IllegalDataValue,
422    /// 0x04
423    ServerDeviceFailure,
424    /// 0x05
425    Acknowledge,
426    /// 0x06
427    ServerDeviceBusy,
428    /// 0x08
429    MemoryParityError,
430    /// 0x0A
431    GatewayPathUnavailable,
432    /// 0x0B
433    GatewayTargetDevice,
434    /// None of the above.
435    ///
436    /// Although encoding one of the predefined values as this is possible, it is not recommended.
437    /// Instead, prefer to use [`Self::new()`] to prevent such ambiguities.
438    Custom(u8),
439}
440
441impl From<ExceptionCode> for u8 {
442    fn from(from: ExceptionCode) -> Self {
443        use crate::frame::ExceptionCode::*;
444        match from {
445            IllegalFunction => 0x01,
446            IllegalDataAddress => 0x02,
447            IllegalDataValue => 0x03,
448            ServerDeviceFailure => 0x04,
449            Acknowledge => 0x05,
450            ServerDeviceBusy => 0x06,
451            MemoryParityError => 0x08,
452            GatewayPathUnavailable => 0x0A,
453            GatewayTargetDevice => 0x0B,
454            Custom(code) => code,
455        }
456    }
457}
458
459impl ExceptionCode {
460    /// Create a new [`ExceptionCode`] with `value`.
461    #[must_use]
462    pub const fn new(value: u8) -> Self {
463        use crate::frame::ExceptionCode::*;
464
465        match value {
466            0x01 => IllegalFunction,
467            0x02 => IllegalDataAddress,
468            0x03 => IllegalDataValue,
469            0x04 => ServerDeviceFailure,
470            0x05 => Acknowledge,
471            0x06 => ServerDeviceBusy,
472            0x08 => MemoryParityError,
473            0x0A => GatewayPathUnavailable,
474            0x0B => GatewayTargetDevice,
475            other => Custom(other),
476        }
477    }
478
479    pub(crate) fn description(&self) -> &str {
480        use crate::frame::ExceptionCode::*;
481
482        match *self {
483            IllegalFunction => "Illegal function",
484            IllegalDataAddress => "Illegal data address",
485            IllegalDataValue => "Illegal data value",
486            ServerDeviceFailure => "Server device failure",
487            Acknowledge => "Acknowledge",
488            ServerDeviceBusy => "Server device busy",
489            MemoryParityError => "Memory parity error",
490            GatewayPathUnavailable => "Gateway path unavailable",
491            GatewayTargetDevice => "Gateway target device failed to respond",
492            Custom(_) => "Custom",
493        }
494    }
495}
496
497/// A server (slave) exception response.
498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
499pub struct ExceptionResponse {
500    pub function: FunctionCode,
501    pub exception: ExceptionCode,
502}
503
504/// Represents a message from the client (slave) to the server (master).
505#[derive(Debug, Clone)]
506pub(crate) struct RequestPdu<'a>(pub(crate) Request<'a>);
507
508impl<'a> From<Request<'a>> for RequestPdu<'a> {
509    fn from(from: Request<'a>) -> Self {
510        RequestPdu(from)
511    }
512}
513
514impl<'a> From<RequestPdu<'a>> for Request<'a> {
515    fn from(from: RequestPdu<'a>) -> Self {
516        from.0
517    }
518}
519
520/// Represents a message from the server (slave) to the client (master).
521#[derive(Debug, Clone, PartialEq, Eq)]
522pub(crate) struct ResponsePdu(pub(crate) Result<Response, ExceptionResponse>);
523
524impl From<Response> for ResponsePdu {
525    fn from(from: Response) -> Self {
526        ResponsePdu(Ok(from))
527    }
528}
529
530impl From<ExceptionResponse> for ResponsePdu {
531    fn from(from: ExceptionResponse) -> Self {
532        ResponsePdu(Err(from))
533    }
534}
535
536#[cfg(any(
537    feature = "rtu-over-tcp-server",
538    feature = "rtu-server",
539    feature = "tcp-server"
540))]
541#[derive(Debug, Clone, PartialEq, Eq)]
542pub(crate) struct OptionalResponsePdu(pub(crate) Option<ResponsePdu>);
543
544#[cfg(any(
545    feature = "rtu-over-tcp-server",
546    feature = "rtu-server",
547    feature = "tcp-server"
548))]
549impl From<Result<Option<Response>, ExceptionResponse>> for OptionalResponsePdu {
550    fn from(from: Result<Option<Response>, ExceptionResponse>) -> Self {
551        match from {
552            Ok(None) => Self(None),
553            Ok(Some(response)) => Self(Some(response.into())),
554            Err(exception) => Self(Some(exception.into())),
555        }
556    }
557}
558
559impl From<ResponsePdu> for Result<Response, ExceptionResponse> {
560    fn from(from: ResponsePdu) -> Self {
561        from.0
562    }
563}
564
565impl fmt::Display for ExceptionCode {
566    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567        write!(f, "{}", self.description())
568    }
569}
570
571impl error::Error for ExceptionCode {
572    fn description(&self) -> &str {
573        self.description()
574    }
575}
576
577impl fmt::Display for ExceptionResponse {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        write!(f, "Modbus function {}: {}", self.function, self.exception)
580    }
581}
582
583impl error::Error for ExceptionResponse {
584    fn description(&self) -> &str {
585        self.exception.description()
586    }
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    #[test]
594    fn new_function_code() {
595        assert_eq!(FunctionCode::ReadCoils, FunctionCode::new(0x01));
596        assert_eq!(FunctionCode::ReadDiscreteInputs, FunctionCode::new(0x02));
597
598        assert_eq!(FunctionCode::WriteSingleCoil, FunctionCode::new(0x05));
599        assert_eq!(FunctionCode::WriteSingleRegister, FunctionCode::new(0x06));
600
601        assert_eq!(FunctionCode::ReadHoldingRegisters, FunctionCode::new(0x03));
602        assert_eq!(FunctionCode::ReadInputRegisters, FunctionCode::new(0x04));
603
604        assert_eq!(FunctionCode::WriteMultipleCoils, FunctionCode::new(0x0F));
605        assert_eq!(
606            FunctionCode::WriteMultipleRegisters,
607            FunctionCode::new(0x10)
608        );
609
610        assert_eq!(FunctionCode::MaskWriteRegister, FunctionCode::new(0x16));
611
612        assert_eq!(
613            FunctionCode::ReadWriteMultipleRegisters,
614            FunctionCode::new(0x17)
615        );
616
617        assert_eq!(FunctionCode::Custom(70), FunctionCode::new(70));
618    }
619
620    #[test]
621    fn function_code_values() {
622        assert_eq!(FunctionCode::ReadCoils.value(), 0x01);
623        assert_eq!(FunctionCode::ReadDiscreteInputs.value(), 0x02);
624
625        assert_eq!(FunctionCode::WriteSingleCoil.value(), 0x05);
626        assert_eq!(FunctionCode::WriteSingleRegister.value(), 0x06);
627
628        assert_eq!(FunctionCode::ReadHoldingRegisters.value(), 0x03);
629        assert_eq!(FunctionCode::ReadInputRegisters.value(), 0x04);
630
631        assert_eq!(FunctionCode::WriteMultipleCoils.value(), 0x0F);
632        assert_eq!(FunctionCode::WriteMultipleRegisters.value(), 0x10);
633
634        assert_eq!(FunctionCode::MaskWriteRegister.value(), 0x16);
635
636        assert_eq!(FunctionCode::ReadWriteMultipleRegisters.value(), 0x17);
637
638        assert_eq!(FunctionCode::Custom(70).value(), 70);
639    }
640
641    #[test]
642    fn function_code_from_request() {
643        use Request::*;
644
645        assert_eq!(ReadCoils(0, 0).function_code(), FunctionCode::ReadCoils);
646        assert_eq!(
647            ReadDiscreteInputs(0, 0).function_code(),
648            FunctionCode::ReadDiscreteInputs
649        );
650
651        assert_eq!(
652            WriteSingleCoil(0, true).function_code(),
653            FunctionCode::WriteSingleCoil
654        );
655        assert_eq!(
656            WriteMultipleCoils(0, Cow::Borrowed(&[])).function_code(),
657            FunctionCode::WriteMultipleCoils
658        );
659
660        assert_eq!(
661            ReadInputRegisters(0, 0).function_code(),
662            FunctionCode::ReadInputRegisters
663        );
664        assert_eq!(
665            ReadHoldingRegisters(0, 0).function_code(),
666            FunctionCode::ReadHoldingRegisters
667        );
668
669        assert_eq!(
670            WriteSingleRegister(0, 0).function_code(),
671            FunctionCode::WriteSingleRegister
672        );
673        assert_eq!(
674            WriteMultipleRegisters(0, Cow::Borrowed(&[])).function_code(),
675            FunctionCode::WriteMultipleRegisters
676        );
677
678        assert_eq!(
679            MaskWriteRegister(0, 0, 0).function_code(),
680            FunctionCode::MaskWriteRegister
681        );
682
683        assert_eq!(
684            ReadWriteMultipleRegisters(0, 0, 0, Cow::Borrowed(&[])).function_code(),
685            FunctionCode::ReadWriteMultipleRegisters
686        );
687
688        assert_eq!(Custom(88, Cow::Borrowed(&[])).function_code().value(), 88);
689    }
690
691    #[test]
692    fn function_code_from_response() {
693        use Response::*;
694
695        assert_eq!(ReadCoils(vec![]).function_code(), FunctionCode::ReadCoils);
696        assert_eq!(
697            ReadDiscreteInputs(vec![]).function_code(),
698            FunctionCode::ReadDiscreteInputs
699        );
700
701        assert_eq!(
702            WriteSingleCoil(0x0, false).function_code(),
703            FunctionCode::WriteSingleCoil
704        );
705        assert_eq!(
706            WriteMultipleCoils(0x0, 0x0).function_code(),
707            FunctionCode::WriteMultipleCoils
708        );
709
710        assert_eq!(
711            ReadInputRegisters(vec![]).function_code(),
712            FunctionCode::ReadInputRegisters
713        );
714        assert_eq!(
715            ReadHoldingRegisters(vec![]).function_code(),
716            FunctionCode::ReadHoldingRegisters
717        );
718
719        assert_eq!(
720            WriteSingleRegister(0, 0).function_code(),
721            FunctionCode::WriteSingleRegister
722        );
723        assert_eq!(
724            WriteMultipleRegisters(0, 0).function_code(),
725            FunctionCode::WriteMultipleRegisters
726        );
727
728        assert_eq!(
729            MaskWriteRegister(0, 0, 0).function_code(),
730            FunctionCode::MaskWriteRegister
731        );
732
733        assert_eq!(
734            ReadWriteMultipleRegisters(vec![]).function_code(),
735            FunctionCode::ReadWriteMultipleRegisters
736        );
737
738        assert_eq!(
739            Custom(99, Bytes::from_static(&[])).function_code().value(),
740            99
741        );
742    }
743}