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