use crate::error::ModbusError;
use crate::function::Exception;
#[derive(Clone, Copy, Debug)]
pub struct Response<'a> {
pdu: &'a [u8],
}
impl<'a> Response<'a> {
pub fn new(pdu: &'a [u8]) -> Self {
Response { pdu }
}
pub fn function_code(&self) -> u8 {
self.pdu.first().copied().unwrap_or(0)
}
pub fn exception(&self) -> Option<Exception> {
if self.function_code() & 0x80 == 0 {
return None;
}
self.pdu.get(1).and_then(|&code| Exception::from_code(code))
}
fn payload(&self) -> Result<&'a [u8], ModbusError> {
if self.pdu.len() < 2 {
return Err(ModbusError::MalformedResponse);
}
let byte_count = usize::from(self.pdu[1]);
let data = &self.pdu[2..];
if data.len() != byte_count {
return Err(ModbusError::MalformedResponse);
}
Ok(data)
}
pub fn registers(&self) -> Result<Registers<'a>, ModbusError> {
let data = self.payload()?;
if data.len() % 2 != 0 {
return Err(ModbusError::MalformedResponse);
}
Ok(Registers { data })
}
pub fn coils(&self, count: u16) -> Result<Coils<'a>, ModbusError> {
let data = self.payload()?;
if data.len() != usize::from(count).div_ceil(8) {
return Err(ModbusError::MalformedResponse);
}
Ok(Coils {
data,
index: 0,
remaining: usize::from(count),
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct Registers<'a> {
data: &'a [u8],
}
impl Iterator for Registers<'_> {
type Item = u16;
fn next(&mut self) -> Option<u16> {
if self.data.len() < 2 {
return None;
}
let value = u16::from_be_bytes([self.data[0], self.data[1]]);
self.data = &self.data[2..];
Some(value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.data.len() / 2;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for Registers<'_> {}
#[derive(Clone, Copy, Debug)]
pub struct Coils<'a> {
data: &'a [u8],
index: usize,
remaining: usize,
}
impl Iterator for Coils<'_> {
type Item = bool;
fn next(&mut self) -> Option<bool> {
if self.remaining == 0 {
return None;
}
let bit = (self.data[self.index / 8] >> (self.index % 8)) & 1;
self.index += 1;
self.remaining -= 1;
Some(bit != 0)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for Coils<'_> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registers_decode_in_order() {
let pdu = [0x03, 0x06, 0x02, 0x2B, 0x00, 0x00, 0x00, 0x64];
let values: [u16; 3] = {
let mut it = Response::new(&pdu).registers().unwrap();
[it.next().unwrap(), it.next().unwrap(), it.next().unwrap()]
};
assert_eq!(values, [0x022B, 0x0000, 0x0064]);
}
#[test]
fn registers_report_an_exact_length() {
let pdu = [0x03, 0x06, 0x02, 0x2B, 0x00, 0x00, 0x00, 0x64];
assert_eq!(Response::new(&pdu).registers().unwrap().len(), 3);
}
#[test]
fn registers_reject_a_byte_count_mismatch() {
let pdu = [0x03, 0x06, 0x02, 0x2B];
assert_eq!(
Response::new(&pdu).registers().err(),
Some(ModbusError::MalformedResponse)
);
}
#[test]
fn coils_unpack_lsb_first_and_drop_padding() {
let pdu = [0x01, 0x01, 0x05];
let bits: [bool; 3] = {
let mut it = Response::new(&pdu).coils(3).unwrap();
[it.next().unwrap(), it.next().unwrap(), it.next().unwrap()]
};
assert_eq!(bits, [true, false, true]);
}
#[test]
fn coils_reject_a_count_that_does_not_match_the_byte_count() {
let pdu = [0x01, 0x01, 0x05];
assert_eq!(
Response::new(&pdu).coils(9).err(),
Some(ModbusError::MalformedResponse)
);
}
#[test]
fn an_exception_response_reads_as_an_exception() {
let pdu = [0x83, 0x02];
assert_eq!(
Response::new(&pdu).exception(),
Some(Exception::IllegalDataAddress)
);
}
#[test]
fn a_normal_response_has_no_exception() {
let pdu = [0x03, 0x02, 0x00, 0x64];
assert_eq!(Response::new(&pdu).exception(), None);
}
}