use heapless::Vec;
use crate::{
app::CoilResponse,
services::ExpectedResponse,
services::coil::{Coils, MAX_COIL_BYTES},
services::{ClientCommon, ClientServices, coil},
};
use mbus_core::{
data_unit::common::{ModbusMessage, Pdu},
errors::MbusError,
function_codes::public::FunctionCode,
transport::Transport,
};
pub(super) struct ResponseParser;
impl ResponseParser {
pub(super) fn parse_write_multiple_coils_response(
pdu: &Pdu,
expected_address: u16,
expected_quantity: u16,
) -> Result<(), MbusError> {
if pdu.function_code() != FunctionCode::WriteMultipleCoils {
return Err(MbusError::ParseError);
}
let data_slice = pdu.data().as_slice();
if data_slice.len() != 4 {
return Err(MbusError::InvalidDataLen);
}
let response_address = u16::from_be_bytes([data_slice[0], data_slice[1]]);
let response_quantity = u16::from_be_bytes([data_slice[2], data_slice[3]]);
if response_address != expected_address {
return Err(MbusError::InvalidAddress); }
if response_quantity != expected_quantity {
return Err(MbusError::InvalidQuantity); }
Ok(())
}
pub(super) fn handle_coil_response(
pdu: &Pdu,
expected_quantity: u16,
from_address: u16,
) -> Result<Coils, MbusError> {
let coil_response = Self::parse_read_coils_response(pdu, expected_quantity)?;
let coils = Coils::new(from_address, expected_quantity)?
.with_values(&coil_response, expected_quantity)?;
Ok(coils)
}
pub(super) fn parse_read_coils_response(
pdu: &Pdu,
expected_quantity: u16,
) -> Result<Vec<u8, MAX_COIL_BYTES>, MbusError> {
if pdu.function_code() != FunctionCode::ReadCoils {
return Err(MbusError::InvalidFunctionCode);
}
let data_slice = pdu.data().as_slice();
if data_slice.is_empty() {
return Err(MbusError::InvalidDataLen);
}
let byte_count = data_slice[0] as usize;
if byte_count + 1 != data_slice.len() {
return Err(MbusError::InvalidByteCount);
}
let expected_byte_count = expected_quantity.div_ceil(8) as usize;
if byte_count != expected_byte_count {
return Err(MbusError::InvalidQuantity); }
let coils = Vec::from_slice(&data_slice[1..]).map_err(|_| MbusError::BufferLenMissmatch)?;
Ok(coils)
}
pub(super) fn parse_write_single_coil_response(
pdu: &Pdu,
expected_address: u16,
expected_value: bool,
) -> Result<(), MbusError> {
if pdu.function_code() != FunctionCode::WriteSingleCoil {
return Err(MbusError::InvalidFunctionCode);
}
let data_slice = pdu.data().as_slice();
if data_slice.len() != 4 {
return Err(MbusError::InvalidDataLen);
}
let response_address = u16::from_be_bytes([data_slice[0], data_slice[1]]);
let response_value = u16::from_be_bytes([data_slice[2], data_slice[3]]);
if response_address != expected_address {
return Err(MbusError::InvalidAddress); }
let expected_response_value = if expected_value { 0xFF00 } else { 0x0000 };
if response_value != expected_response_value {
return Err(MbusError::InvalidValue); }
Ok(())
}
}
impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
where
TRANSPORT: Transport,
APP: ClientCommon + CoilResponse,
{
pub(super) fn handle_read_coils_response(
&mut self,
ctx: &ExpectedResponse<TRANSPORT, APP, N>,
message: &ModbusMessage,
) {
let pdu = message.pdu();
let expected_quantity = ctx.operation_meta.quantity();
let from_address = ctx.operation_meta.address();
let transaction_id = ctx.txn_id;
let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
let coil_rsp = match coil::service::ServiceBuilder::handle_read_coil_rsp(
pdu,
expected_quantity,
from_address,
) {
Ok(coil_response) => coil_response,
Err(e) => {
self.app
.request_failed(transaction_id, unit_id_or_slave_addr, e);
return;
}
};
if ctx.operation_meta.is_single() {
let coil_value = match coil_rsp.value(from_address) {
Ok(v) => v,
Err(_) => return, };
self.app.read_single_coil_response(
transaction_id,
unit_id_or_slave_addr,
from_address,
coil_value,
);
} else {
self.app
.read_coils_response(transaction_id, unit_id_or_slave_addr, &coil_rsp);
}
}
pub(super) fn handle_write_single_coil_response(
&mut self,
ctx: &ExpectedResponse<TRANSPORT, APP, N>,
message: &ModbusMessage,
) {
let pdu = message.pdu();
let function_code = pdu.function_code();
let address = ctx.operation_meta.address();
let value = ctx.operation_meta.value() != 0;
let transaction_id = ctx.txn_id;
let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
if coil::service::ServiceBuilder::handle_write_single_coil_rsp(
function_code,
pdu,
address,
value,
)
.is_ok()
{
self.app.write_single_coil_response(
transaction_id,
message.unit_id_or_slave_addr(),
address,
value,
);
} else {
self.app
.request_failed(transaction_id, unit_id_or_slave_addr, MbusError::ParseError);
}
}
pub(super) fn handle_write_multiple_coils_response(
&mut self,
ctx: &ExpectedResponse<TRANSPORT, APP, N>,
message: &ModbusMessage,
) {
let function_code = message.pdu().function_code();
let pdu = message.pdu();
let txn_id = ctx.txn_id;
let unit_id = message.unit_id_or_slave_addr();
let address = ctx.operation_meta.address();
let quantity = ctx.operation_meta.quantity();
if coil::service::ServiceBuilder::handle_write_multiple_coils_rsp(
function_code,
pdu,
address,
quantity,
)
.is_ok()
{
self.app
.write_multiple_coils_response(txn_id, unit_id, address, quantity);
} else {
self.app
.request_failed(txn_id, unit_id, MbusError::ParseError);
}
}
}