rustmod_core/pdu/
function_code.rs1use crate::DecodeError;
2
3#[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 ReadCoils,
11 ReadDiscreteInputs,
13 ReadHoldingRegisters,
15 ReadInputRegisters,
17 WriteSingleCoil,
19 WriteSingleRegister,
21 WriteMultipleCoils,
23 WriteMultipleRegisters,
25 MaskWriteRegister,
27 ReadWriteMultipleRegisters,
29 ReadExceptionStatus,
31 Diagnostics,
33 ReadFifoQueue,
35 Custom(u8),
37}
38
39impl FunctionCode {
40 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 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 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}