modbus_rtu/
function.rs

1/// ## Function
2///
3/// Represents a Modbus RTU function request along with the data required to
4/// encode it into a protocol-compliant frame.
5///
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum Function {
8    /// Read Coils `(0x01)`
9    ReadCoils {
10        starting_address: u16,
11        quantity: u16,
12    },
13
14    /// Read Discrete Inputs `(0x02)`
15    ReadDiscreteInputs {
16        starting_address: u16,
17        quantity: u16,
18    },
19
20    /// Read Holding Registers `(0x03)`
21    ReadHoldingRegisters {
22        starting_address: u16,
23        quantity: u16,
24    },
25
26    /// Read Input Registers `(0x04)`
27    ReadInputRegisters {
28        starting_address: u16,
29        quantity: u16,
30    },
31
32    /// Write Single Coil `(0x05)`
33    WriteSingleCoil { address: u16, value: bool },
34
35    /// Write Single Register `(0x06)`
36    WriteSingleRegister { address: u16, value: u16 },
37
38    /// Write Multiple Coils `(0x0F)`
39    WriteMultipleCoils {
40        starting_address: u16,
41        value: Box<[bool]>,
42    },
43
44    /// Write Multiple Registers `(0x10)`
45    WriteMultipleRegisters {
46        starting_address: u16,
47        value: Box<[u16]>,
48    },
49}
50
51impl Function {
52    /// Returns the [`FunctionKind`] associated with this request.
53    ///
54    /// ---
55    /// # Examples
56    /// ```rust
57    /// use modbus_rtu::{Function, FunctionKind};
58    ///
59    /// let function = Function::ReadCoils { starting_address: 0, quantity: 2 };
60    /// assert_eq!(function.kind(), FunctionKind::ReadCoils);
61    /// ```
62    ///
63    pub const fn kind(&self) -> crate::FunctionKind {
64        use crate::FunctionKind;
65        match self {
66            Function::ReadCoils { .. } => FunctionKind::ReadCoils,
67            Function::ReadDiscreteInputs { .. } => FunctionKind::ReadDiscreteInputs,
68            Function::ReadHoldingRegisters { .. } => FunctionKind::ReadHoldingRegisters,
69            Function::ReadInputRegisters { .. } => FunctionKind::ReadInputRegisters,
70            Function::WriteSingleCoil { .. } => FunctionKind::WriteSingleCoil,
71            Function::WriteSingleRegister { .. } => FunctionKind::WriteSingleRegister,
72            Function::WriteMultipleCoils { .. } => FunctionKind::WriteMultipleCoils,
73            Function::WriteMultipleRegisters { .. } => FunctionKind::WriteMultipleRegisters,
74        }
75    }
76
77    /// Returns the Modbus RTU function code for this request.
78    ///
79    /// ---
80    /// # Examples
81    /// ```rust
82    /// use modbus_rtu::Function;
83    ///
84    /// let function = Function::WriteSingleRegister { address: 0x10, value: 0x1234 };
85    /// assert_eq!(function.as_code(), 0x06);
86    /// ```
87    ///
88    pub const fn as_code(&self) -> u8 {
89        self.kind().as_code()
90    }
91
92    /// Serializes this function into a Modbus RTU payload (function code + data).
93    ///
94    /// Returns [`FunctionError`](crate::error::FunctionError) when the generated
95    /// payload would exceed the 256-byte packet limit imposed by the Modbus RTU
96    /// specification.
97    ///
98    /// # Examples
99    /// ```ignore
100    /// use modbus_rtu::Function;
101    ///
102    /// let function = Function::WriteSingleCoil { address: 0x0025, value: true };
103    /// let bytes = function.to_bytes().unwrap();
104    /// assert_eq!(&bytes[..], &[0x05, 0x00, 0x25, 0xFF, 0x00]);
105    /// ```
106    ///
107    pub(crate) fn to_bytes(&self) -> Result<Box<[u8]>, crate::error::RequestPacketError> {
108        let mut buf: Vec<u8> = Vec::with_capacity(5);
109        buf.push(self.kind().as_code());
110        match self {
111            Function::ReadCoils {
112                starting_address,
113                quantity,
114            }
115            | Function::ReadDiscreteInputs {
116                starting_address,
117                quantity,
118            } => {
119                #[cfg(not(feature = "unlimited_packet_size"))]
120                {
121                    if *quantity > 2008 {
122                        return Err(crate::error::RequestPacketError::ResponseWillTooBig);
123                    }
124                }
125                buf.extend_from_slice(&starting_address.to_be_bytes());
126                buf.extend_from_slice(&quantity.to_be_bytes());
127            }
128            Function::ReadHoldingRegisters {
129                starting_address,
130                quantity,
131            }
132            | Function::ReadInputRegisters {
133                starting_address,
134                quantity,
135            } => {
136                #[cfg(not(feature = "unlimited_packet_size"))]
137                {
138                    if *quantity > 125 {
139                        return Err(crate::error::RequestPacketError::ResponseWillTooBig);
140                    }
141                }
142                buf.extend_from_slice(&starting_address.to_be_bytes());
143                buf.extend_from_slice(&quantity.to_be_bytes());
144            }
145            Function::WriteSingleCoil { address, value } => {
146                buf.extend_from_slice(&address.to_be_bytes());
147                buf.push(if *value == true { 0xFF } else { 0x00 });
148                buf.push(0x00);
149            }
150            Function::WriteSingleRegister { address, value } => {
151                buf.extend_from_slice(&address.to_be_bytes());
152                buf.extend_from_slice(&value.to_be_bytes());
153            }
154            Function::WriteMultipleCoils {
155                starting_address,
156                value,
157            } => {
158                let quantity = value.len() as u16;
159                #[cfg(not(feature = "unlimited_packet_size"))]
160                {
161                    if quantity > 1976 {
162                        return Err(crate::error::RequestPacketError::RequestTooBig);
163                    }
164                }
165                let byte_count = ((quantity + 7) / 8) as u8;
166                buf.extend_from_slice(&starting_address.to_be_bytes());
167                buf.extend_from_slice(&quantity.to_be_bytes());
168                buf.push(byte_count);
169                let mut chunks = value.chunks(8);
170                while let Some(chunk) = chunks.next() {
171                    let mut byte: u8 = 0x00;
172                    for (i, value) in chunk.iter().enumerate() {
173                        if *value == true {
174                            byte |= 0b1 << i;
175                        } else {
176                            byte &= !(0b1 << i);
177                        }
178                    }
179                    buf.push(byte);
180                }
181            }
182            Function::WriteMultipleRegisters {
183                starting_address,
184                value,
185            } => {
186                let quantity = value.len() as u16;
187                #[cfg(not(feature = "unlimited_packet_size"))]
188                {
189                    if quantity > 123 {
190                        return Err(crate::error::RequestPacketError::RequestTooBig);
191                    }
192                }
193                let byte_count = (quantity * 2) as u8;
194                buf.extend_from_slice(&starting_address.to_be_bytes());
195                buf.extend_from_slice(&quantity.to_be_bytes());
196                buf.push(byte_count);
197                for each in value {
198                    buf.extend_from_slice(&each.to_be_bytes());
199                }
200            }
201        }
202        Ok(buf.into_boxed_slice())
203    }
204
205    /// Returns the minimum expected response length for this function.
206    ///
207    /// This helps callers pre-allocate receive buffers before the Modbus frame
208    /// arrives.
209    ///
210    /// ---
211    /// # Examples
212    /// ```rust
213    /// use modbus_rtu::Function;
214    ///
215    /// let func = Function::ReadHoldingRegisters { starting_address: 0, quantity: 2 };
216    /// assert_eq!(func.expected_len(), 5 + (2 * 2));
217    /// ```
218    /// 
219    pub const fn expected_len(&self) -> usize {
220        match self {
221            Function::ReadCoils { quantity, .. } |
222            Function::ReadDiscreteInputs { quantity, .. } => 5 + ((*quantity as usize + 7) / 8),
223            Function::ReadHoldingRegisters { quantity, .. } |
224            Function::ReadInputRegisters { quantity, .. } => 5 + (*quantity as usize * 2),
225            Function::WriteSingleCoil { .. } |
226            Function::WriteSingleRegister { .. } |
227            Function::WriteMultipleCoils { .. } |
228            Function::WriteMultipleRegisters { .. } => 8,
229        }
230    }
231}