pamoja_modbus/
response.rs1use crate::error::ModbusError;
4use crate::function::Exception;
5
6#[derive(Clone, Copy, Debug)]
24pub struct Response<'a> {
25 pdu: &'a [u8],
26}
27
28impl<'a> Response<'a> {
29 pub fn new(pdu: &'a [u8]) -> Self {
39 Response { pdu }
40 }
41
42 pub fn function_code(&self) -> u8 {
48 self.pdu.first().copied().unwrap_or(0)
49 }
50
51 pub fn exception(&self) -> Option<Exception> {
58 if self.function_code() & 0x80 == 0 {
59 return None;
60 }
61 self.pdu.get(1).and_then(|&code| Exception::from_code(code))
62 }
63
64 fn payload(&self) -> Result<&'a [u8], ModbusError> {
67 if self.pdu.len() < 2 {
68 return Err(ModbusError::MalformedResponse);
69 }
70 let byte_count = usize::from(self.pdu[1]);
71 let data = &self.pdu[2..];
72 if data.len() != byte_count {
73 return Err(ModbusError::MalformedResponse);
74 }
75 Ok(data)
76 }
77
78 pub fn registers(&self) -> Result<Registers<'a>, ModbusError> {
89 let data = self.payload()?;
90 if data.len() % 2 != 0 {
91 return Err(ModbusError::MalformedResponse);
92 }
93 Ok(Registers { data })
94 }
95
96 pub fn coils(&self, count: u16) -> Result<Coils<'a>, ModbusError> {
114 let data = self.payload()?;
115 if data.len() != usize::from(count).div_ceil(8) {
116 return Err(ModbusError::MalformedResponse);
117 }
118 Ok(Coils {
119 data,
120 index: 0,
121 remaining: usize::from(count),
122 })
123 }
124}
125
126#[derive(Clone, Copy, Debug)]
128pub struct Registers<'a> {
129 data: &'a [u8],
130}
131
132impl Iterator for Registers<'_> {
133 type Item = u16;
134
135 fn next(&mut self) -> Option<u16> {
136 if self.data.len() < 2 {
137 return None;
138 }
139 let value = u16::from_be_bytes([self.data[0], self.data[1]]);
140 self.data = &self.data[2..];
141 Some(value)
142 }
143
144 fn size_hint(&self) -> (usize, Option<usize>) {
145 let remaining = self.data.len() / 2;
146 (remaining, Some(remaining))
147 }
148}
149
150impl ExactSizeIterator for Registers<'_> {}
151
152#[derive(Clone, Copy, Debug)]
154pub struct Coils<'a> {
155 data: &'a [u8],
156 index: usize,
157 remaining: usize,
158}
159
160impl Iterator for Coils<'_> {
161 type Item = bool;
162
163 fn next(&mut self) -> Option<bool> {
164 if self.remaining == 0 {
165 return None;
166 }
167 let bit = (self.data[self.index / 8] >> (self.index % 8)) & 1;
168 self.index += 1;
169 self.remaining -= 1;
170 Some(bit != 0)
171 }
172
173 fn size_hint(&self) -> (usize, Option<usize>) {
174 (self.remaining, Some(self.remaining))
175 }
176}
177
178impl ExactSizeIterator for Coils<'_> {}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn registers_decode_in_order() {
186 let pdu = [0x03, 0x06, 0x02, 0x2B, 0x00, 0x00, 0x00, 0x64];
187 let values: [u16; 3] = {
188 let mut it = Response::new(&pdu).registers().unwrap();
189 [it.next().unwrap(), it.next().unwrap(), it.next().unwrap()]
190 };
191 assert_eq!(values, [0x022B, 0x0000, 0x0064]);
192 }
193
194 #[test]
195 fn registers_report_an_exact_length() {
196 let pdu = [0x03, 0x06, 0x02, 0x2B, 0x00, 0x00, 0x00, 0x64];
197 assert_eq!(Response::new(&pdu).registers().unwrap().len(), 3);
198 }
199
200 #[test]
201 fn registers_reject_a_byte_count_mismatch() {
202 let pdu = [0x03, 0x06, 0x02, 0x2B];
204 assert_eq!(
205 Response::new(&pdu).registers().err(),
206 Some(ModbusError::MalformedResponse)
207 );
208 }
209
210 #[test]
211 fn coils_unpack_lsb_first_and_drop_padding() {
212 let pdu = [0x01, 0x01, 0x05];
214 let bits: [bool; 3] = {
215 let mut it = Response::new(&pdu).coils(3).unwrap();
216 [it.next().unwrap(), it.next().unwrap(), it.next().unwrap()]
217 };
218 assert_eq!(bits, [true, false, true]);
219 }
220
221 #[test]
222 fn coils_reject_a_count_that_does_not_match_the_byte_count() {
223 let pdu = [0x01, 0x01, 0x05];
224 assert_eq!(
226 Response::new(&pdu).coils(9).err(),
227 Some(ModbusError::MalformedResponse)
228 );
229 }
230
231 #[test]
232 fn an_exception_response_reads_as_an_exception() {
233 let pdu = [0x83, 0x02];
234 assert_eq!(
235 Response::new(&pdu).exception(),
236 Some(Exception::IllegalDataAddress)
237 );
238 }
239
240 #[test]
241 fn a_normal_response_has_no_exception() {
242 let pdu = [0x03, 0x02, 0x00, 0x64];
243 assert_eq!(Response::new(&pdu).exception(), None);
244 }
245}