1use crate::error::{ModbusError, ModbusResult};
7use bytes::{BufMut, BytesMut};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum FunctionCode {
13 ReadCoils = 0x01,
15 ReadDiscreteInputs = 0x02,
17 ReadHoldingRegisters = 0x03,
19 ReadInputRegisters = 0x04,
21 WriteSingleCoil = 0x05,
23 WriteSingleRegister = 0x06,
25 WriteMultipleCoils = 0x0F,
27 WriteMultipleRegisters = 0x10,
29}
30
31impl FunctionCode {
32 pub fn from_u8(code: u8) -> ModbusResult<Self> {
34 match code {
35 0x01 => Ok(Self::ReadCoils),
36 0x02 => Ok(Self::ReadDiscreteInputs),
37 0x03 => Ok(Self::ReadHoldingRegisters),
38 0x04 => Ok(Self::ReadInputRegisters),
39 0x05 => Ok(Self::WriteSingleCoil),
40 0x06 => Ok(Self::WriteSingleRegister),
41 0x0F => Ok(Self::WriteMultipleCoils),
42 0x10 => Ok(Self::WriteMultipleRegisters),
43 _ => Err(ModbusError::InvalidFunctionCode(code)),
44 }
45 }
46
47 pub fn as_u8(self) -> u8 {
49 self as u8
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct ModbusTcpFrame {
56 pub transaction_id: u16,
58
59 pub protocol_id: u16,
61
62 pub unit_id: u8,
64
65 pub function_code: FunctionCode,
67
68 pub data: Vec<u8>,
70}
71
72impl ModbusTcpFrame {
73 pub fn read_holding_registers(
75 transaction_id: u16,
76 unit_id: u8,
77 start_addr: u16,
78 count: u16,
79 ) -> Self {
80 let mut data = BytesMut::with_capacity(4);
81 data.put_u16(start_addr);
82 data.put_u16(count);
83
84 Self {
85 transaction_id,
86 protocol_id: 0,
87 unit_id,
88 function_code: FunctionCode::ReadHoldingRegisters,
89 data: data.to_vec(),
90 }
91 }
92
93 pub fn write_single_register(transaction_id: u16, unit_id: u8, addr: u16, value: u16) -> Self {
95 let mut data = BytesMut::with_capacity(4);
96 data.put_u16(addr);
97 data.put_u16(value);
98
99 Self {
100 transaction_id,
101 protocol_id: 0,
102 unit_id,
103 function_code: FunctionCode::WriteSingleRegister,
104 data: data.to_vec(),
105 }
106 }
107
108 pub fn to_bytes(&self) -> Vec<u8> {
110 let length = 1 + 1 + self.data.len(); let mut bytes = BytesMut::with_capacity(7 + self.data.len());
112
113 bytes.put_u16(self.transaction_id);
115 bytes.put_u16(self.protocol_id);
116 bytes.put_u16(length as u16);
117 bytes.put_u8(self.unit_id);
118
119 bytes.put_u8(self.function_code.as_u8());
121 bytes.put(self.data.as_slice());
122
123 bytes.to_vec()
124 }
125
126 pub fn from_bytes(bytes: &[u8]) -> ModbusResult<Self> {
128 if bytes.len() < 8 {
129 return Err(ModbusError::Io(std::io::Error::new(
130 std::io::ErrorKind::UnexpectedEof,
131 "Frame too short",
132 )));
133 }
134
135 let transaction_id = u16::from_be_bytes([bytes[0], bytes[1]]);
136 let protocol_id = u16::from_be_bytes([bytes[2], bytes[3]]);
137 let _length = u16::from_be_bytes([bytes[4], bytes[5]]);
138 let unit_id = bytes[6];
139 let function_code_byte = bytes[7];
140
141 if function_code_byte & 0x80 != 0 {
143 let original_function = function_code_byte & 0x7F;
144 let exception_code = if bytes.len() > 8 { bytes[8] } else { 0 };
145 return Err(ModbusError::ModbusException {
146 code: exception_code,
147 function: original_function,
148 });
149 }
150
151 let function_code = FunctionCode::from_u8(function_code_byte)?;
152 let data = bytes[8..].to_vec();
153
154 Ok(Self {
155 transaction_id,
156 protocol_id,
157 unit_id,
158 function_code,
159 data,
160 })
161 }
162
163 pub fn extract_registers(&self) -> ModbusResult<Vec<u16>> {
165 if self.data.is_empty() {
166 return Ok(Vec::new());
167 }
168
169 let byte_count = self.data[0] as usize;
170 if self.data.len() < 1 + byte_count {
171 return Err(ModbusError::Io(std::io::Error::new(
172 std::io::ErrorKind::UnexpectedEof,
173 "Incomplete register data",
174 )));
175 }
176
177 let mut registers = Vec::with_capacity(byte_count / 2);
178 let mut pos = 1;
179
180 while pos + 1 < self.data.len() && pos < 1 + byte_count {
181 let value = u16::from_be_bytes([self.data[pos], self.data[pos + 1]]);
182 registers.push(value);
183 pos += 2;
184 }
185
186 Ok(registers)
187 }
188}
189
190#[derive(Debug, Clone)]
192pub enum ModbusFrame {
193 Tcp(ModbusTcpFrame),
195 #[cfg(feature = "rtu")]
197 Rtu(ModbusRtuFrame),
198}
199
200#[cfg(feature = "rtu")]
204#[derive(Debug, Clone)]
205pub struct ModbusRtuFrame {
206 pub unit_id: u8,
208 pub function_code: FunctionCode,
210 pub data: Vec<u8>,
212 pub crc: u16,
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_function_code_conversion() {
222 assert_eq!(
223 FunctionCode::from_u8(0x03).unwrap(),
224 FunctionCode::ReadHoldingRegisters
225 );
226 assert_eq!(FunctionCode::ReadHoldingRegisters.as_u8(), 0x03);
227 }
228
229 #[test]
230 fn test_invalid_function_code() {
231 assert!(FunctionCode::from_u8(0xFF).is_err());
232 }
233
234 #[test]
235 fn test_build_read_holding_registers() {
236 let frame = ModbusTcpFrame::read_holding_registers(1, 1, 0, 10);
237 assert_eq!(frame.transaction_id, 1);
238 assert_eq!(frame.unit_id, 1);
239 assert_eq!(frame.function_code, FunctionCode::ReadHoldingRegisters);
240 assert_eq!(frame.data.len(), 4); }
242
243 #[test]
244 fn test_serialize_frame() {
245 let frame = ModbusTcpFrame::read_holding_registers(1, 1, 0, 10);
246 let bytes = frame.to_bytes();
247
248 assert_eq!(bytes.len(), 12);
251
252 assert_eq!(u16::from_be_bytes([bytes[0], bytes[1]]), 1);
254
255 assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0);
257
258 assert_eq!(bytes[7], 0x03);
260 }
261
262 #[test]
263 fn test_parse_response() {
264 let response = vec![
266 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x03, 0x0A, 0x00, 0x64, 0x00, 0xC8, 0x01, 0x2C, 0x01, 0x90, 0x01, 0xF4, ];
278
279 let frame = ModbusTcpFrame::from_bytes(&response).unwrap();
280 assert_eq!(frame.transaction_id, 1);
281 assert_eq!(frame.function_code, FunctionCode::ReadHoldingRegisters);
282
283 let registers = frame.extract_registers().unwrap();
284 assert_eq!(registers, vec![100, 200, 300, 400, 500]);
285 }
286
287 #[test]
288 fn test_exception_response() {
289 let response = vec![
291 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
298
299 let result = ModbusTcpFrame::from_bytes(&response);
300 assert!(result.is_err());
301
302 if let Err(ModbusError::ModbusException { code, function }) = result {
303 assert_eq!(code, 0x02);
304 assert_eq!(function, 0x03);
305 } else {
306 panic!("Expected ModbusException error");
307 }
308 }
309}