Skip to main content

mbus_core/function_codes/
public.rs

1//! # Modbus Public Function Codes and Sub-functions
2//!
3//! This module defines the standard function codes and sub-function codes used in the
4//! Modbus Application Protocol. It provides enums for:
5//!
6//! - **`FunctionCode`**: The primary operation identifier (e.g., Read Coils, Write Register).
7//! - **`DiagnosticSubFunction`**: Sub-codes for serial-line diagnostics (FC 0x08).
8//! - **`EncapsulatedInterfaceType`**: MEI types for tunneling other protocols (FC 0x2B).
9//!
10//! All types implement `TryFrom` for safe conversion from raw bytes and include
11//! documentation referencing the Modbus Application Protocol Specification V1.1b3.
12//!
13//! This module is `no_std` compatible and uses `repr` attributes to ensure
14//! memory layout matches the protocol's byte-level requirements.
15
16use crate::errors::{ExceptionCode, MbusError};
17
18/// Modbus Public Function Codes.
19///
20/// These are the standardized function codes defined in
21/// the Modbus Application Protocol Specification V1.1b3.
22///
23/// See:
24/// - Section 5.1 Public Function Code Definition
25/// - Section 6.x for individual function behaviors
26///
27/// Reference: Modbus Application Protocol Specification V1.1b3.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29#[repr(u8)]
30pub enum FunctionCode {
31    // ============================================================
32    // Bit Access (Single-bit data)
33    // ============================================================
34    /// 0x00 — Undefined
35    /// This value is not defined in the specification and can be used as a placeholder
36    /// for uninitialized or unknown function codes.
37    /// It is not a valid function code for actual Modbus transactions.
38    #[default]
39    Default = 0x00, // Placeholder for uninitialized or unknown function code
40
41    #[cfg(feature = "coils")]
42    /// 0x01 — Read Coils
43    ///
44    /// Reads the ON/OFF status of discrete output coils.
45    /// Section 6.1
46    ReadCoils = 0x01,
47
48    #[cfg(feature = "discrete-inputs")]
49    /// 0x02 — Read Discrete Inputs
50    ///
51    /// Reads the ON/OFF status of discrete input contacts.
52    /// Section 6.2
53    ReadDiscreteInputs = 0x02,
54
55    #[cfg(feature = "coils")]
56    /// 0x05 — Write Single Coil
57    ///
58    /// Forces a single coil to ON (0xFF00) or OFF (0x0000).
59    /// Section 6.5
60    WriteSingleCoil = 0x05,
61
62    #[cfg(feature = "coils")]
63    /// 0x0F — Write Multiple Coils
64    ///
65    /// Forces multiple coils to ON/OFF.
66    /// Section 6.11
67    WriteMultipleCoils = 0x0F,
68
69    // ============================================================
70    // 16-bit Register Access
71    // ============================================================
72    #[cfg(feature = "holding-registers")]
73    /// 0x03 — Read Holding Registers
74    ///
75    /// Reads one or more 16-bit holding registers.
76    /// Section 6.3
77    ReadHoldingRegisters = 0x03,
78
79    #[cfg(feature = "input-registers")]
80    /// 0x04 — Read Input Registers
81    ///
82    /// Reads one or more 16-bit input registers.
83    /// Section 6.4
84    ReadInputRegisters = 0x04,
85
86    /// 0x06 — Write Single Register
87    #[cfg(feature = "holding-registers")]
88    ///
89    /// Writes a single 16-bit holding register.
90    /// Section 6.6
91    WriteSingleRegister = 0x06,
92
93    #[cfg(feature = "holding-registers")]
94    /// 0x10 — Write Multiple Registers
95    ///
96    /// Writes multiple 16-bit holding registers.
97    /// Section 6.12
98    WriteMultipleRegisters = 0x10,
99
100    #[cfg(feature = "holding-registers")]
101    /// 0x16 — Mask Write Register
102    ///
103    /// Performs a bitwise mask write on a single register.
104    /// Section 6.16
105    MaskWriteRegister = 0x16,
106
107    #[cfg(feature = "holding-registers")]
108    /// 0x17 — Read/Write Multiple Registers
109    ///
110    /// Reads and writes multiple registers in a single transaction.
111    /// Section 6.17
112    ReadWriteMultipleRegisters = 0x17,
113
114    #[cfg(feature = "fifo")]
115    /// 0x18 — Read FIFO Queue
116    ///
117    /// Reads the contents of a FIFO queue.
118    /// Section 6.18
119    ReadFifoQueue = 0x18,
120
121    // ============================================================
122    // File Record Access
123    // ============================================================
124    #[cfg(feature = "file-record")]
125    /// 0x14 — Read File Record
126    ///
127    /// Reads structured file records.
128    /// Section 6.14
129    ReadFileRecord = 0x14,
130
131    /// 0x15 — Write File Record
132    #[cfg(feature = "file-record")]
133    ///
134    /// Writes structured file records.
135    /// Section 6.15
136    WriteFileRecord = 0x15,
137
138    // ============================================================
139    // Diagnostics & Device Information
140    // ============================================================
141    #[cfg(feature = "diagnostics")]
142    /// 0x07 — Read Exception Status (Serial Line Only)
143    ///
144    /// Returns 8-bit exception status.
145    /// Section 6.7
146    ReadExceptionStatus = 0x07,
147
148    #[cfg(feature = "diagnostics")]
149    /// 0x08 — Diagnostics (Serial Line Only)
150    ///
151    /// Provides diagnostic and loopback tests.
152    /// Requires sub-function codes.
153    /// Section 6.8
154    Diagnostics = 0x08,
155
156    #[cfg(feature = "diagnostics")]
157    /// 0x0B — Get Communication Event Counter (Serial Line Only)
158    ///
159    /// Returns communication event counter.
160    /// Section 6.9
161    GetCommEventCounter = 0x0B,
162
163    #[cfg(feature = "diagnostics")]
164    /// 0x0C — Get Communication Event Log (Serial Line Only)
165    ///
166    /// Returns communication event log.
167    /// Section 6.10
168    GetCommEventLog = 0x0C,
169
170    #[cfg(feature = "diagnostics")]
171    /// 0x11 — Report Server ID (Serial Line Only)
172    ///
173    /// Returns server identification.
174    /// Section 6.13
175    ReportServerId = 0x11,
176
177    #[cfg(feature = "diagnostics")]
178    /// 0x2B — Encapsulated Interface Transport
179    ///
180    /// Used for:
181    /// - CANopen General Reference (Sub-function 0x0D)
182    /// - Read Device Identification (Sub-function 0x0E)
183    ///
184    /// Section 6.19, 6.20, 6.21
185    EncapsulatedInterfaceTransport = 0x2B,
186
187    // ============================================================
188    // Exception Responses (0x80 bit set)
189    // ============================================================
190    /// 0x81 — Exception Response for Read Coils (0x01 | 0x80)
191    #[cfg(feature = "coils")]
192    ReadCoilsException = 0x81,
193
194    /// 0x82 — Exception Response for Read Discrete Inputs (0x02 | 0x80)
195    #[cfg(feature = "discrete-inputs")]
196    ReadDiscreteInputsException = 0x82,
197
198    /// 0x83 — Exception Response for Read Holding Registers (0x03 | 0x80)
199    #[cfg(feature = "holding-registers")]
200    ReadHoldingRegistersException = 0x83,
201
202    /// 0x84 — Exception Response for Read Input Registers (0x04 | 0x80)
203    #[cfg(feature = "input-registers")]
204    ReadInputRegistersException = 0x84,
205
206    /// 0x85 — Exception Response for Write Single Coil (0x05 | 0x80)
207    #[cfg(feature = "coils")]
208    WriteSingleCoilException = 0x85,
209
210    /// 0x86 — Exception Response for Write Single Register (0x06 | 0x80)
211    #[cfg(feature = "holding-registers")]
212    WriteSingleRegisterException = 0x86,
213
214    /// 0x87 — Exception Response for Read Exception Status (0x07 | 0x80)
215    #[cfg(feature = "diagnostics")]
216    ReadExceptionStatusException = 0x87,
217
218    /// 0x88 — Exception Response for Diagnostics (0x08 | 0x80)
219    #[cfg(feature = "diagnostics")]
220    DiagnosticsException = 0x88,
221
222    /// 0x8B — Exception Response for Get Communication Event Counter (0x0B | 0x80)
223    #[cfg(feature = "diagnostics")]
224    GetCommEventCounterException = 0x8B,
225
226    /// 0x8C — Exception Response for Get Communication Event Log (0x0C | 0x80)
227    #[cfg(feature = "diagnostics")]
228    GetCommEventLogException = 0x8C,
229
230    /// 0x8F — Exception Response for Write Multiple Coils (0x0F | 0x80)
231    #[cfg(feature = "coils")]
232    WriteMultipleCoilsException = 0x8F,
233
234    /// 0x90 — Exception Response for Write Multiple Registers (0x10 | 0x80)
235    #[cfg(feature = "holding-registers")]
236    WriteMultipleRegistersException = 0x90,
237
238    /// 0x91 — Exception Response for Report Server ID (0x11 | 0x80)
239    #[cfg(feature = "diagnostics")]
240    ReportServerIdException = 0x91,
241
242    /// 0x94 — Exception Response for Read File Record (0x14 | 0x80)
243    #[cfg(feature = "file-record")]
244    ReadFileRecordException = 0x94,
245
246    /// 0x95 — Exception Response for Write File Record (0x15 | 0x80)
247    #[cfg(feature = "file-record")]
248    WriteFileRecordException = 0x95,
249
250    /// 0x96 — Exception Response for Mask Write Register (0x16 | 0x80)
251    #[cfg(feature = "holding-registers")]
252    MaskWriteRegisterException = 0x96,
253
254    /// 0x97 — Exception Response for Read/Write Multiple Registers (0x17 | 0x80)
255    #[cfg(feature = "holding-registers")]
256    ReadWriteMultipleRegistersException = 0x97,
257
258    /// 0x98 — Exception Response for Read FIFO Queue (0x18 | 0x80)
259    #[cfg(feature = "fifo")]
260    ReadFifoQueueException = 0x98,
261
262    /// 0xAB — Exception Response for Encapsulated Interface Transport (0x2B | 0x80)
263    #[cfg(feature = "diagnostics")]
264    EncapsulatedInterfaceTransportException = 0xAB,
265}
266
267impl TryFrom<u8> for FunctionCode {
268    type Error = MbusError;
269
270    fn try_from(value: u8) -> Result<Self, Self::Error> {
271        #[allow(unused_imports)]
272        use FunctionCode::*;
273
274        match value {
275            #[cfg(feature = "coils")]
276            0x01 => Ok(ReadCoils),
277            #[cfg(feature = "discrete-inputs")]
278            0x02 => Ok(ReadDiscreteInputs),
279            #[cfg(feature = "holding-registers")]
280            0x03 => Ok(ReadHoldingRegisters),
281            #[cfg(feature = "input-registers")]
282            0x04 => Ok(ReadInputRegisters),
283            #[cfg(feature = "coils")]
284            0x05 => Ok(WriteSingleCoil),
285            #[cfg(feature = "holding-registers")]
286            0x06 => Ok(WriteSingleRegister),
287            #[cfg(feature = "diagnostics")]
288            0x07 => Ok(ReadExceptionStatus),
289            #[cfg(feature = "diagnostics")]
290            0x08 => Ok(Diagnostics),
291            #[cfg(feature = "diagnostics")]
292            0x0B => Ok(GetCommEventCounter),
293            #[cfg(feature = "diagnostics")]
294            0x0C => Ok(GetCommEventLog),
295            #[cfg(feature = "coils")]
296            0x0F => Ok(WriteMultipleCoils),
297            #[cfg(feature = "holding-registers")]
298            0x10 => Ok(WriteMultipleRegisters),
299            #[cfg(feature = "diagnostics")]
300            0x11 => Ok(ReportServerId),
301            #[cfg(feature = "file-record")]
302            0x14 => Ok(ReadFileRecord),
303            #[cfg(feature = "file-record")]
304            0x15 => Ok(WriteFileRecord),
305            #[cfg(feature = "holding-registers")]
306            0x16 => Ok(MaskWriteRegister),
307            #[cfg(feature = "holding-registers")]
308            0x17 => Ok(ReadWriteMultipleRegisters),
309            #[cfg(feature = "fifo")]
310            0x18 => Ok(ReadFifoQueue),
311            #[cfg(feature = "diagnostics")]
312            0x2B => Ok(EncapsulatedInterfaceTransport),
313            // Exception responses (0x80 bit set)
314            #[cfg(feature = "coils")]
315            0x81 => Ok(ReadCoilsException),
316            #[cfg(feature = "discrete-inputs")]
317            0x82 => Ok(ReadDiscreteInputsException),
318            #[cfg(feature = "holding-registers")]
319            0x83 => Ok(ReadHoldingRegistersException),
320            #[cfg(feature = "input-registers")]
321            0x84 => Ok(ReadInputRegistersException),
322            #[cfg(feature = "coils")]
323            0x85 => Ok(WriteSingleCoilException),
324            #[cfg(feature = "holding-registers")]
325            0x86 => Ok(WriteSingleRegisterException),
326            #[cfg(feature = "diagnostics")]
327            0x87 => Ok(ReadExceptionStatusException),
328            #[cfg(feature = "diagnostics")]
329            0x88 => Ok(DiagnosticsException),
330            #[cfg(feature = "diagnostics")]
331            0x8B => Ok(GetCommEventCounterException),
332            #[cfg(feature = "diagnostics")]
333            0x8C => Ok(GetCommEventLogException),
334            #[cfg(feature = "coils")]
335            0x8F => Ok(WriteMultipleCoilsException),
336            #[cfg(feature = "holding-registers")]
337            0x90 => Ok(WriteMultipleRegistersException),
338            #[cfg(feature = "diagnostics")]
339            0x91 => Ok(ReportServerIdException),
340            #[cfg(feature = "file-record")]
341            0x94 => Ok(ReadFileRecordException),
342            #[cfg(feature = "file-record")]
343            0x95 => Ok(WriteFileRecordException),
344            #[cfg(feature = "holding-registers")]
345            0x96 => Ok(MaskWriteRegisterException),
346            #[cfg(feature = "holding-registers")]
347            0x97 => Ok(ReadWriteMultipleRegistersException),
348            #[cfg(feature = "fifo")]
349            0x98 => Ok(ReadFifoQueueException),
350            #[cfg(feature = "diagnostics")]
351            0xAB => Ok(EncapsulatedInterfaceTransportException),
352            _ => Err(MbusError::UnsupportedFunction(value)),
353        }
354    }
355}
356
357impl FunctionCode {
358    /// Maps an application error to the corresponding Modbus exception code.
359    ///
360    /// This method determines the appropriate exception code to return based on the
361    /// error that occurred during request processing. For errors that don't map to
362    /// a specific exception code, `ServerDeviceFailure` is used as a default.
363    ///
364    /// # Arguments
365    /// * `error` - The error that occurred during processing
366    ///
367    /// # Returns
368    /// The Modbus exception code to send in the response
369    ///
370    /// # Example
371    /// ```ignore
372    /// let fc = FunctionCode::ReadHoldingRegisters;
373    /// let error = MbusError::InvalidAddress;
374    /// let exc_code = fc.exception_code_for_error(&error);
375    /// assert_eq!(exc_code, ExceptionCode::IllegalDataAddress);
376    /// ```
377    pub fn exception_code_for_error(&self, error: &MbusError) -> ExceptionCode {
378        match error {
379            // Protocol/address errors
380            MbusError::InvalidAddress | MbusError::InvalidOffset => {
381                ExceptionCode::IllegalDataAddress
382            }
383            // Data length and parsing errors — the data field itself is malformed
384            MbusError::InvalidDataLen
385            | MbusError::ParseError
386            | MbusError::BasicParseError
387            | MbusError::InvalidPduLength => ExceptionCode::IllegalDataAddress,
388            // Quantity/value errors
389            MbusError::InvalidQuantity
390            | MbusError::InvalidValue
391            | MbusError::InvalidByteCount
392            | MbusError::InvalidAndMask
393            | MbusError::InvalidOrMask
394            | MbusError::InvalidDeviceIdCode => ExceptionCode::IllegalDataValue,
395            // Function code errors — also includes illegal sub-function / MEI types
396            MbusError::InvalidFunctionCode
397            | MbusError::UnsupportedFunction(_)
398            | MbusError::ReservedSubFunction(_)
399            | MbusError::InvalidMeiType
400            | MbusError::BroadcastNotAllowed
401            | MbusError::InvalidBroadcastAddress => ExceptionCode::IllegalFunction,
402            // Default: all other errors map to server device failure
403            _ => ExceptionCode::ServerDeviceFailure,
404        }
405    }
406
407    /// Returns the exception function code variant (with 0x80 bit set) for this function code.
408    ///
409    /// Exception responses use function codes with the high bit (0x80) set to indicate
410    /// that an exception occurred. This method maps normal function codes to their
411    /// exception equivalents.
412    ///
413    /// # Returns
414    /// The exception function code variant, or `None` if this is not a valid function code
415    /// that can have exceptions.
416    ///
417    /// # Example
418    /// ```ignore
419    /// let fc = FunctionCode::ReadHoldingRegisters;
420    /// let exc_fc = fc.exception_response();
421    /// assert_eq!(exc_fc, Some(FunctionCode::ReadHoldingRegistersException));
422    /// ```
423    pub fn exception_response(&self) -> Option<FunctionCode> {
424        match self {
425            #[cfg(feature = "coils")]
426            FunctionCode::ReadCoils => Some(FunctionCode::ReadCoilsException),
427            #[cfg(feature = "discrete-inputs")]
428            FunctionCode::ReadDiscreteInputs => Some(FunctionCode::ReadDiscreteInputsException),
429            #[cfg(feature = "holding-registers")]
430            FunctionCode::ReadHoldingRegisters => Some(FunctionCode::ReadHoldingRegistersException),
431            #[cfg(feature = "input-registers")]
432            FunctionCode::ReadInputRegisters => Some(FunctionCode::ReadInputRegistersException),
433            #[cfg(feature = "coils")]
434            FunctionCode::WriteSingleCoil => Some(FunctionCode::WriteSingleCoilException),
435            #[cfg(feature = "holding-registers")]
436            FunctionCode::WriteSingleRegister => Some(FunctionCode::WriteSingleRegisterException),
437            #[cfg(feature = "diagnostics")]
438            FunctionCode::ReadExceptionStatus => Some(FunctionCode::ReadExceptionStatusException),
439            #[cfg(feature = "diagnostics")]
440            FunctionCode::Diagnostics => Some(FunctionCode::DiagnosticsException),
441            #[cfg(feature = "diagnostics")]
442            FunctionCode::GetCommEventCounter => Some(FunctionCode::GetCommEventCounterException),
443            #[cfg(feature = "diagnostics")]
444            FunctionCode::GetCommEventLog => Some(FunctionCode::GetCommEventLogException),
445            #[cfg(feature = "coils")]
446            FunctionCode::WriteMultipleCoils => Some(FunctionCode::WriteMultipleCoilsException),
447            #[cfg(feature = "holding-registers")]
448            FunctionCode::WriteMultipleRegisters => {
449                Some(FunctionCode::WriteMultipleRegistersException)
450            }
451            #[cfg(feature = "diagnostics")]
452            FunctionCode::ReportServerId => Some(FunctionCode::ReportServerIdException),
453            #[cfg(feature = "file-record")]
454            FunctionCode::ReadFileRecord => Some(FunctionCode::ReadFileRecordException),
455            #[cfg(feature = "file-record")]
456            FunctionCode::WriteFileRecord => Some(FunctionCode::WriteFileRecordException),
457            #[cfg(feature = "holding-registers")]
458            FunctionCode::MaskWriteRegister => Some(FunctionCode::MaskWriteRegisterException),
459            #[cfg(feature = "holding-registers")]
460            FunctionCode::ReadWriteMultipleRegisters => {
461                Some(FunctionCode::ReadWriteMultipleRegistersException)
462            }
463            #[cfg(feature = "fifo")]
464            FunctionCode::ReadFifoQueue => Some(FunctionCode::ReadFifoQueueException),
465            #[cfg(feature = "diagnostics")]
466            FunctionCode::EncapsulatedInterfaceTransport => {
467                Some(FunctionCode::EncapsulatedInterfaceTransportException)
468            }
469            // Already exception codes or default
470            _ => None,
471        }
472    }
473}
474
475/// Sub-function codes for Function Code 0x08 (Diagnostics).
476///
477/// Serial line only.
478/// See Modbus Application Protocol Specification V1.1b3, Section 6.8.
479///
480/// These values are 16-bit and encoded big-endian inside the PDU data field.
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482#[repr(u16)]
483pub enum DiagnosticSubFunction {
484    /// 0x0000 — Return Query Data (Loopback test)
485    ReturnQueryData = 0x0000,
486
487    /// 0x0001 — Restart Communications Option
488    RestartCommunicationsOption = 0x0001,
489
490    /// 0x0002 — Return Diagnostic Register
491    ReturnDiagnosticRegister = 0x0002,
492
493    /// 0x0003 — Change ASCII Input Delimiter
494    ChangeAsciiInputDelimiter = 0x0003,
495
496    /// 0x0004 — Force Listen Only Mode
497    ForceListenOnlyMode = 0x0004,
498
499    /// 0x000A — Clear Counters and Diagnostic Register
500    ClearCountersAndDiagnosticRegister = 0x000A,
501
502    /// 0x000B — Return Bus Message Count
503    ReturnBusMessageCount = 0x000B,
504
505    /// 0x000C — Return Bus Communication Error Count
506    ReturnBusCommunicationErrorCount = 0x000C,
507
508    /// 0x000D — Return Bus Exception Error Count
509    ReturnBusExceptionErrorCount = 0x000D,
510
511    /// 0x000E — Return Server Message Count
512    ReturnServerMessageCount = 0x000E,
513
514    /// 0x000F — Return Server No Response Count
515    ReturnServerNoResponseCount = 0x000F,
516
517    /// 0x0010 — Return Server NAK Count
518    ReturnServerNakCount = 0x0010,
519
520    /// 0x0011 — Return Server Busy Count
521    ReturnServerBusyCount = 0x0011,
522
523    /// 0x0012 — Return Bus Character Overrun Count
524    ReturnBusCharacterOverrunCount = 0x0012,
525
526    /// 0x0014 — Clear Overrun Counter and Flag
527    ClearOverrunCounterAndFlag = 0x0014,
528}
529
530impl DiagnosticSubFunction {
531    /// Converts the `DiagnosticSubFunction` enum variant into its 2-byte big-endian representation.
532    pub fn to_be_bytes(self) -> [u8; 2] {
533        (self as u16).to_be_bytes()
534    }
535}
536
537impl From<DiagnosticSubFunction> for u16 {
538    fn from(sub_func: DiagnosticSubFunction) -> Self {
539        sub_func as u16
540    }
541}
542
543impl TryFrom<u16> for DiagnosticSubFunction {
544    type Error = MbusError;
545
546    fn try_from(value: u16) -> Result<Self, Self::Error> {
547        use DiagnosticSubFunction::*;
548
549        match value {
550            0x0000 => Ok(ReturnQueryData),
551            0x0001 => Ok(RestartCommunicationsOption),
552            0x0002 => Ok(ReturnDiagnosticRegister),
553            0x0003 => Ok(ChangeAsciiInputDelimiter),
554            0x0004 => Ok(ForceListenOnlyMode),
555
556            // 0x0005–0x0009 Reserved
557            0x000A => Ok(ClearCountersAndDiagnosticRegister),
558            0x000B => Ok(ReturnBusMessageCount),
559            0x000C => Ok(ReturnBusCommunicationErrorCount),
560            0x000D => Ok(ReturnBusExceptionErrorCount),
561            0x000E => Ok(ReturnServerMessageCount),
562            0x000F => Ok(ReturnServerNoResponseCount),
563            0x0010 => Ok(ReturnServerNakCount),
564            0x0011 => Ok(ReturnServerBusyCount),
565            0x0012 => Ok(ReturnBusCharacterOverrunCount),
566
567            // 0x0013 Reserved
568            0x0014 => Ok(ClearOverrunCounterAndFlag),
569
570            // Everything else reserved per spec
571            _ => Err(MbusError::ReservedSubFunction(value)),
572        }
573    }
574}
575
576/// MEI (Modbus Encapsulated Interface) types
577/// for Function Code 0x2B.
578///
579/// See Section 6.19–6.21 of the specification.
580///
581/// Encoded as 1 byte following the function code.
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
583#[repr(u8)]
584pub enum EncapsulatedInterfaceType {
585    /// Placeholder default value used before a concrete MEI type is parsed.
586    /// This value should not appear in a valid decoded protocol frame.
587    #[default]
588    Err,
589    /// 0x0D — CANopen General Reference
590    CanopenGeneralReference = 0x0D,
591
592    /// 0x0E — Read Device Identification
593    ReadDeviceIdentification = 0x0E,
594}
595
596impl From<EncapsulatedInterfaceType> for u8 {
597    fn from(val: EncapsulatedInterfaceType) -> Self {
598        val as u8
599    }
600}
601
602impl TryFrom<u8> for EncapsulatedInterfaceType {
603    type Error = MbusError;
604
605    fn try_from(value: u8) -> Result<Self, Self::Error> {
606        match value {
607            0x0D => Ok(Self::CanopenGeneralReference),
608            0x0E => Ok(Self::ReadDeviceIdentification),
609            _ => Err(MbusError::ReservedSubFunction(value as u16)),
610        }
611    }
612}