Skip to main content

rustmod_core/pdu/
function_code.rs

1use crate::DecodeError;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4#[cfg_attr(feature = "defmt", derive(defmt::Format))]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum FunctionCode {
7    ReadCoils,
8    ReadDiscreteInputs,
9    ReadHoldingRegisters,
10    ReadInputRegisters,
11    WriteSingleCoil,
12    WriteSingleRegister,
13    WriteMultipleCoils,
14    WriteMultipleRegisters,
15    MaskWriteRegister,
16    ReadWriteMultipleRegisters,
17    Custom(u8),
18}
19
20impl FunctionCode {
21    pub const fn as_u8(self) -> u8 {
22        match self {
23            Self::ReadCoils => 0x01,
24            Self::ReadDiscreteInputs => 0x02,
25            Self::ReadHoldingRegisters => 0x03,
26            Self::ReadInputRegisters => 0x04,
27            Self::WriteSingleCoil => 0x05,
28            Self::WriteSingleRegister => 0x06,
29            Self::WriteMultipleCoils => 0x0F,
30            Self::WriteMultipleRegisters => 0x10,
31            Self::MaskWriteRegister => 0x16,
32            Self::ReadWriteMultipleRegisters => 0x17,
33            Self::Custom(code) => code,
34        }
35    }
36
37    pub fn from_u8(value: u8) -> Result<Self, DecodeError> {
38        if Self::is_exception(value) {
39            return Err(DecodeError::InvalidFunctionCode);
40        }
41        match value {
42            0x01 => Ok(Self::ReadCoils),
43            0x02 => Ok(Self::ReadDiscreteInputs),
44            0x03 => Ok(Self::ReadHoldingRegisters),
45            0x04 => Ok(Self::ReadInputRegisters),
46            0x05 => Ok(Self::WriteSingleCoil),
47            0x06 => Ok(Self::WriteSingleRegister),
48            0x0F => Ok(Self::WriteMultipleCoils),
49            0x10 => Ok(Self::WriteMultipleRegisters),
50            0x16 => Ok(Self::MaskWriteRegister),
51            0x17 => Ok(Self::ReadWriteMultipleRegisters),
52            _ => Ok(Self::Custom(value)),
53        }
54    }
55
56    pub const fn is_exception(value: u8) -> bool {
57        (value & 0x80) != 0
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::FunctionCode;
64    use crate::DecodeError;
65
66    #[test]
67    fn parses_known_codes() {
68        assert_eq!(FunctionCode::from_u8(0x03).unwrap(), FunctionCode::ReadHoldingRegisters);
69        assert_eq!(FunctionCode::from_u8(0x10).unwrap(), FunctionCode::WriteMultipleRegisters);
70        assert_eq!(FunctionCode::from_u8(0x16).unwrap(), FunctionCode::MaskWriteRegister);
71        assert_eq!(
72            FunctionCode::from_u8(0x17).unwrap(),
73            FunctionCode::ReadWriteMultipleRegisters
74        );
75    }
76
77    #[test]
78    fn preserves_custom_codes() {
79        assert_eq!(FunctionCode::from_u8(0x41).unwrap(), FunctionCode::Custom(0x41));
80    }
81
82    #[test]
83    fn rejects_exception_bit_codes() {
84        assert_eq!(FunctionCode::from_u8(0x83).unwrap_err(), DecodeError::InvalidFunctionCode);
85    }
86
87    #[test]
88    fn exception_bit_is_detected() {
89        assert!(FunctionCode::is_exception(0x83));
90        assert!(!FunctionCode::is_exception(0x03));
91    }
92}