use async_trait::async_trait;
pub use crate::error::ModbusError;
use crate::frame::{Request, Response};
#[inline]
fn unexpected_response(rsp: Response) -> ModbusError {
match rsp {
Response::Exception(fc, ex) => ModbusError::exception(fc, u8::from(ex)),
other => ModbusError::protocol(format!("unexpected response: {other:?}")),
}
}
#[async_trait]
pub trait ModbusClient: Send + Sync {
async fn call(&self, slave: u8, request: Request<'_>) -> Result<Response, ModbusError>;
async fn read_coils(
&self,
slave: u8,
addr: u16,
quantity: u16,
) -> Result<Vec<bool>, ModbusError> {
match self.call(slave, Request::ReadCoils(addr, quantity)).await? {
Response::ReadCoils(bits) => Ok(bits),
other => Err(unexpected_response(other)),
}
}
async fn read_discrete_inputs(
&self,
slave: u8,
addr: u16,
quantity: u16,
) -> Result<Vec<bool>, ModbusError> {
match self
.call(slave, Request::ReadDiscreteInputs(addr, quantity))
.await?
{
Response::ReadDiscreteInputs(bits) => Ok(bits),
other => Err(unexpected_response(other)),
}
}
async fn read_holding_registers(
&self,
slave: u8,
addr: u16,
quantity: u16,
) -> Result<Vec<u16>, ModbusError> {
match self
.call(slave, Request::ReadHoldingRegisters(addr, quantity))
.await?
{
Response::ReadHoldingRegisters(regs) => Ok(regs),
other => Err(unexpected_response(other)),
}
}
async fn read_input_registers(
&self,
slave: u8,
addr: u16,
quantity: u16,
) -> Result<Vec<u16>, ModbusError> {
match self
.call(slave, Request::ReadInputRegisters(addr, quantity))
.await?
{
Response::ReadInputRegisters(regs) => Ok(regs),
other => Err(unexpected_response(other)),
}
}
async fn write_single_coil(
&self,
slave: u8,
addr: u16,
value: bool,
) -> Result<(), ModbusError> {
match self
.call(slave, Request::WriteSingleCoil(addr, value))
.await?
{
Response::WriteSingleCoil(a, v) if a == addr && v == value => Ok(()),
other => Err(unexpected_response(other)),
}
}
async fn write_single_register(
&self,
slave: u8,
addr: u16,
value: u16,
) -> Result<(), ModbusError> {
match self
.call(slave, Request::WriteSingleRegister(addr, value))
.await?
{
Response::WriteSingleRegister(a, v) if a == addr && v == value => Ok(()),
other => Err(unexpected_response(other)),
}
}
async fn write_multiple_coils(
&self,
slave: u8,
addr: u16,
values: &[bool],
) -> Result<(), ModbusError> {
let req = Request::WriteMultipleCoils(addr, std::borrow::Cow::Borrowed(values));
match self.call(slave, req).await? {
Response::WriteMultipleCoils(a, n) if a == addr && n as usize == values.len() => Ok(()),
other => Err(unexpected_response(other)),
}
}
async fn write_multiple_registers(
&self,
slave: u8,
addr: u16,
values: &[u16],
) -> Result<(), ModbusError> {
let req = Request::WriteMultipleRegisters(addr, std::borrow::Cow::Borrowed(values));
match self.call(slave, req).await? {
Response::WriteMultipleRegisters(a, n) if a == addr && n as usize == values.len() => {
Ok(())
}
other => Err(unexpected_response(other)),
}
}
async fn mask_write_register(
&self,
slave: u8,
addr: u16,
and_mask: u16,
or_mask: u16,
) -> Result<(), ModbusError> {
match self
.call(slave, Request::MaskWriteRegister(addr, and_mask, or_mask))
.await?
{
Response::MaskWriteRegister(a, and, or)
if a == addr && and == and_mask && or == or_mask =>
{
Ok(())
}
other => Err(unexpected_response(other)),
}
}
async fn diagnostic(
&self,
slave: u8,
sub_function: u16,
data: u16,
) -> Result<(u16, u16), ModbusError> {
match self
.call(slave, Request::Diagnostic(sub_function, data))
.await?
{
Response::Diagnostic(sf, d) => Ok((sf, d)),
other => Err(unexpected_response(other)),
}
}
async fn read_write_multiple_registers(
&self,
slave: u8,
read_addr: u16,
read_qty: u16,
write_addr: u16,
values: &[u16],
) -> Result<Vec<u16>, ModbusError> {
let req = Request::ReadWriteMultipleRegisters(
read_addr,
read_qty,
write_addr,
std::borrow::Cow::Borrowed(values),
);
match self.call(slave, req).await? {
Response::ReadWriteMultipleRegisters(regs) => Ok(regs),
other => Err(unexpected_response(other)),
}
}
}