use rusty_modbus_types::{Address, CoilValue, FunctionCode, Quantity};
use crate::error::{DecodeError, EncodeError};
use crate::request::Encode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteSingleCoilRequest {
pub address: Address,
pub value: CoilValue,
}
impl WriteSingleCoilRequest {
pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
DecodeError::check_exact_len(data, 4)?;
let address = Address(u16::from_be_bytes([data[0], data[1]]));
let raw_value = u16::from_be_bytes([data[2], data[3]]);
let value =
CoilValue::from_wire(raw_value).ok_or(DecodeError::InvalidCoilValue(raw_value))?;
Ok(Self { address, value })
}
}
impl Encode for WriteSingleCoilRequest {
fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
let len = self.encoded_len();
if buf.len() < len {
return Err(EncodeError::BufferTooSmall {
required: len,
available: buf.len(),
});
}
EncodeError::check_pdu_len(len)?;
buf[0] = FunctionCode::WriteSingleCoil.code();
buf[1..3].copy_from_slice(&self.address.0.to_be_bytes());
buf[3..5].copy_from_slice(&self.value.to_wire().to_be_bytes());
Ok(len)
}
fn encoded_len(&self) -> usize {
5
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteMultipleCoilsRequest<'buf> {
pub address: Address,
pub quantity: Quantity,
pub byte_count: u8,
pub coil_values: &'buf [u8],
}
impl<'buf> WriteMultipleCoilsRequest<'buf> {
const MAX_QUANTITY: u16 = 1968;
pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
if data.len() < 5 {
return Err(DecodeError::Truncated {
expected: 5,
actual: data.len(),
});
}
let address = Address(u16::from_be_bytes([data[0], data[1]]));
let quantity = u16::from_be_bytes([data[2], data[3]]);
if quantity == 0 || quantity > Self::MAX_QUANTITY {
return Err(DecodeError::QuantityOutOfRange { quantity });
}
let byte_count = data[4];
let expected_bytes = quantity.div_ceil(8);
if u16::from(byte_count) != expected_bytes {
return Err(DecodeError::ByteCountMismatch {
declared: usize::from(byte_count),
actual: expected_bytes as usize,
});
}
let remaining = data.len() - 5;
if byte_count as usize != remaining {
return Err(DecodeError::ByteCountMismatch {
declared: byte_count as usize,
actual: remaining,
});
}
let coil_values = &data[5..];
Ok(Self {
address,
quantity: Quantity(quantity),
byte_count,
coil_values,
})
}
}
impl Encode for WriteMultipleCoilsRequest<'_> {
fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
let len = self.encoded_len();
if buf.len() < len {
return Err(EncodeError::BufferTooSmall {
required: len,
available: buf.len(),
});
}
EncodeError::check_quantity(self.quantity.0, Self::MAX_QUANTITY)?;
let expected_bytes = usize::from(self.quantity.0.div_ceil(8));
EncodeError::check_byte_count(usize::from(self.byte_count), expected_bytes)?;
EncodeError::check_byte_count(expected_bytes, self.coil_values.len())?;
EncodeError::check_pdu_len(len)?;
buf[0] = FunctionCode::WriteMultipleCoils.code();
buf[1..3].copy_from_slice(&self.address.0.to_be_bytes());
buf[3..5].copy_from_slice(&self.quantity.0.to_be_bytes());
buf[5] = self.byte_count;
buf[6..6 + self.coil_values.len()].copy_from_slice(self.coil_values);
Ok(len)
}
fn encoded_len(&self) -> usize {
6 + self.coil_values.len()
}
}