Skip to main content

rustmod_core/pdu/
function_code.rs

1use crate::DecodeError;
2
3/// Modbus function codes identifying the type of request or response.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[non_exhaustive]
8pub enum FunctionCode {
9    /// FC01 — Read Coils.
10    ReadCoils,
11    /// FC02 — Read Discrete Inputs.
12    ReadDiscreteInputs,
13    /// FC03 — Read Holding Registers.
14    ReadHoldingRegisters,
15    /// FC04 — Read Input Registers.
16    ReadInputRegisters,
17    /// FC05 — Write Single Coil.
18    WriteSingleCoil,
19    /// FC06 — Write Single Register.
20    WriteSingleRegister,
21    /// FC15 (0x0F) — Write Multiple Coils.
22    WriteMultipleCoils,
23    /// FC16 (0x10) — Write Multiple Registers.
24    WriteMultipleRegisters,
25    /// FC22 (0x16) — Mask Write Register.
26    MaskWriteRegister,
27    /// FC23 (0x17) — Read/Write Multiple Registers.
28    ReadWriteMultipleRegisters,
29    /// FC07 — Read Exception Status.
30    ReadExceptionStatus,
31    /// FC08 — Diagnostics.
32    Diagnostics,
33    /// FC24 (0x18) — Read FIFO Queue.
34    ReadFifoQueue,
35    /// A function code not recognised by this library.
36    Custom(u8),
37}
38
39impl FunctionCode {
40    /// Return the wire byte value for this function code.
41    pub const fn as_u8(self) -> u8 {
42        match self {
43            Self::ReadCoils => 0x01,
44            Self::ReadDiscreteInputs => 0x02,
45            Self::ReadHoldingRegisters => 0x03,
46            Self::ReadInputRegisters => 0x04,
47            Self::WriteSingleCoil => 0x05,
48            Self::WriteSingleRegister => 0x06,
49            Self::WriteMultipleCoils => 0x0F,
50            Self::WriteMultipleRegisters => 0x10,
51            Self::MaskWriteRegister => 0x16,
52            Self::ReadWriteMultipleRegisters => 0x17,
53            Self::ReadExceptionStatus => 0x07,
54            Self::Diagnostics => 0x08,
55            Self::ReadFifoQueue => 0x18,
56            Self::Custom(code) => code,
57        }
58    }
59
60    /// Parse a function code from its wire byte value.
61    pub fn from_u8(value: u8) -> Result<Self, DecodeError> {
62        if value == 0 || Self::is_exception(value) {
63            return Err(DecodeError::InvalidFunctionCode);
64        }
65        match value {
66            0x01 => Ok(Self::ReadCoils),
67            0x02 => Ok(Self::ReadDiscreteInputs),
68            0x03 => Ok(Self::ReadHoldingRegisters),
69            0x04 => Ok(Self::ReadInputRegisters),
70            0x05 => Ok(Self::WriteSingleCoil),
71            0x06 => Ok(Self::WriteSingleRegister),
72            0x0F => Ok(Self::WriteMultipleCoils),
73            0x10 => Ok(Self::WriteMultipleRegisters),
74            0x16 => Ok(Self::MaskWriteRegister),
75            0x17 => Ok(Self::ReadWriteMultipleRegisters),
76            0x07 => Ok(Self::ReadExceptionStatus),
77            0x08 => Ok(Self::Diagnostics),
78            0x18 => Ok(Self::ReadFifoQueue),
79            _ => Ok(Self::Custom(value)),
80        }
81    }
82
83    /// Returns `true` if the high bit (0x80) is set, indicating an exception response.
84    pub const fn is_exception(value: u8) -> bool {
85        (value & 0x80) != 0
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::FunctionCode;
92    use crate::DecodeError;
93
94    #[test]
95    fn parses_known_codes() {
96        assert_eq!(FunctionCode::from_u8(0x03).unwrap(), FunctionCode::ReadHoldingRegisters);
97        assert_eq!(FunctionCode::from_u8(0x10).unwrap(), FunctionCode::WriteMultipleRegisters);
98        assert_eq!(FunctionCode::from_u8(0x16).unwrap(), FunctionCode::MaskWriteRegister);
99        assert_eq!(
100            FunctionCode::from_u8(0x17).unwrap(),
101            FunctionCode::ReadWriteMultipleRegisters
102        );
103    }
104
105    #[test]
106    fn preserves_custom_codes() {
107        assert_eq!(FunctionCode::from_u8(0x41).unwrap(), FunctionCode::Custom(0x41));
108    }
109
110    #[test]
111    fn rejects_zero_function_code() {
112        assert_eq!(FunctionCode::from_u8(0x00).unwrap_err(), DecodeError::InvalidFunctionCode);
113    }
114
115    #[test]
116    fn rejects_exception_bit_codes() {
117        assert_eq!(FunctionCode::from_u8(0x83).unwrap_err(), DecodeError::InvalidFunctionCode);
118    }
119
120    #[test]
121    fn exception_bit_is_detected() {
122        assert!(FunctionCode::is_exception(0x83));
123        assert!(!FunctionCode::is_exception(0x03));
124    }
125}