1use crate::transport::ModbusError;
11
12pub(crate) const FN_READ_HOLDING: u8 = 0x03;
15pub(crate) const FN_WRITE_SINGLE: u8 = 0x06;
16pub(crate) const FN_WRITE_MULTIPLE: u8 = 0x10;
17
18pub const EXCEPTION_BIT: u8 = 0x80;
22
23pub const MAX_ADU: usize = 256;
25
26pub const MAX_READ_REGS: usize = 125;
29
30pub const MAX_WRITE_REGS: usize = 123;
33
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub enum FrameError {
40 InvalidLength(usize),
42 BufferTooSmall { needed: usize, actual: usize },
44}
45
46impl core::fmt::Display for FrameError {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 match self {
49 Self::InvalidLength(n) => write!(f, "invalid register count {n}"),
50 Self::BufferTooSmall { needed, actual } => {
51 write!(f, "buffer too small (need {needed}, have {actual})")
52 }
53 }
54 }
55}
56
57impl core::error::Error for FrameError {}
58
59pub fn crc16_modbus(data: &[u8]) -> u16 {
63 let mut crc: u16 = 0xFFFF;
64 for &b in data {
65 crc ^= b as u16;
66 for _ in 0..8 {
67 if crc & 1 != 0 {
68 crc = (crc >> 1) ^ 0xA001;
69 } else {
70 crc >>= 1;
71 }
72 }
73 }
74 crc
75}
76
77fn append_crc(buf: &mut [u8], len: usize) {
78 let crc = crc16_modbus(&buf[..len]);
79 buf[len] = crc as u8;
80 buf[len + 1] = (crc >> 8) as u8;
81}
82
83pub fn build_read_request(slave: u8, addr: u16, count: u16) -> [u8; 8] {
87 let mut req = [0u8; 8];
88 req[0] = slave;
89 req[1] = FN_READ_HOLDING;
90 req[2..4].copy_from_slice(&addr.to_be_bytes());
91 req[4..6].copy_from_slice(&count.to_be_bytes());
92 append_crc(&mut req, 6);
93 req
94}
95
96pub fn build_write_single_request(slave: u8, addr: u16, value: u16) -> [u8; 8] {
98 let mut req = [0u8; 8];
99 req[0] = slave;
100 req[1] = FN_WRITE_SINGLE;
101 req[2..4].copy_from_slice(&addr.to_be_bytes());
102 req[4..6].copy_from_slice(&value.to_be_bytes());
103 append_crc(&mut req, 6);
104 req
105}
106
107pub fn build_write_multiple_request(
111 slave: u8,
112 addr: u16,
113 values: &[u16],
114 out: &mut [u8],
115) -> Result<usize, FrameError> {
116 if values.is_empty() || values.len() > MAX_WRITE_REGS {
117 return Err(FrameError::InvalidLength(values.len()));
118 }
119 let bc = 2 * values.len();
120 let len = 7 + bc + 2;
121 if out.len() < len {
122 return Err(FrameError::BufferTooSmall {
123 needed: len,
124 actual: out.len(),
125 });
126 }
127 out[0] = slave;
128 out[1] = FN_WRITE_MULTIPLE;
129 out[2..4].copy_from_slice(&addr.to_be_bytes());
130 out[4..6].copy_from_slice(&(values.len() as u16).to_be_bytes());
131 out[6] = bc as u8;
132 for (i, v) in values.iter().enumerate() {
133 out[7 + 2 * i..9 + 2 * i].copy_from_slice(&v.to_be_bytes());
134 }
135 append_crc(out, 7 + bc);
136 Ok(len)
137}
138
139fn check_crc(resp: &[u8], len: usize) -> Result<(), ModbusError> {
142 if resp.len() < len {
143 return Err(ModbusError::ShortResponse(resp.len()));
144 }
145 let got = u16::from_le_bytes([resp[len - 2], resp[len - 1]]);
146 let calc = crc16_modbus(&resp[..len - 2]);
147 if got == calc {
148 Ok(())
149 } else {
150 Err(ModbusError::BadCrc)
151 }
152}
153
154fn check_exception(resp: &[u8], slave: u8) -> Result<(), ModbusError> {
155 if resp.len() < 5 {
156 return Err(ModbusError::ShortResponse(resp.len()));
157 }
158 if resp[0] != slave {
159 return Err(ModbusError::BadSlave(resp[0]));
160 }
161 if resp[1] & EXCEPTION_BIT != 0 {
162 check_crc(resp, 5)?;
163 return Err(ModbusError::Exception(resp[2]));
164 }
165 Ok(())
166}
167
168pub fn parse_read_response(resp: &[u8], slave: u8, out: &mut [u16]) -> Result<(), ModbusError> {
171 check_exception(resp, slave)?;
172 let count = out.len();
173 let expected_len = 5 + 2 * count;
174 if resp[1] != FN_READ_HOLDING || resp[2] as usize != 2 * count {
175 return Err(ModbusError::BadHeader);
176 }
177 check_crc(resp, expected_len)?;
178 for (i, slot) in out.iter_mut().enumerate() {
179 *slot = u16::from_be_bytes([resp[3 + 2 * i], resp[4 + 2 * i]]);
180 }
181 Ok(())
182}
183
184pub fn parse_write_single_response(resp: &[u8], req: &[u8; 8]) -> Result<(), ModbusError> {
187 check_exception(resp, req[0])?;
188 check_crc(resp, 8)?;
189 if resp.len() < 8 || resp[..8] != req[..] {
190 return Err(ModbusError::BadHeader);
191 }
192 Ok(())
193}
194
195pub fn parse_write_multiple_response(
198 resp: &[u8],
199 slave: u8,
200 addr: u16,
201 qty: u16,
202) -> Result<(), ModbusError> {
203 check_exception(resp, slave)?;
204 check_crc(resp, 8)?;
205 if resp[1] != FN_WRITE_MULTIPLE
206 || u16::from_be_bytes([resp[2], resp[3]]) != addr
207 || u16::from_be_bytes([resp[4], resp[5]]) != qty
208 {
209 return Err(ModbusError::BadHeader);
210 }
211 Ok(())
212}
213
214#[cfg(test)]
215mod tests;