use std::{borrow::Cow, fmt::Debug, io};
use async_trait::async_trait;
use crate::{frame::*, slave::*, Error, Result};
#[cfg(feature = "rtu")]
pub mod rtu;
#[cfg(feature = "tcp")]
pub mod tcp;
#[cfg(feature = "sync")]
pub mod sync;
#[async_trait]
pub trait Client: SlaveContext + Send + Debug {
async fn call(&mut self, request: Request<'_>) -> Result<Response>;
}
#[async_trait]
pub trait Reader: Client {
async fn read_coils(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>>;
async fn read_discrete_inputs(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>>;
async fn read_holding_registers(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Word>>;
async fn read_input_registers(&mut self, addr: Address, cnt: Quantity) -> Result<Vec<Word>>;
async fn read_write_multiple_registers(
&mut self,
read_addr: Address,
read_count: Quantity,
write_addr: Address,
write_data: &[Word],
) -> Result<Vec<Word>>;
}
#[async_trait]
pub trait Writer: Client {
async fn write_single_coil(&mut self, addr: Address, coil: Coil) -> Result<()>;
async fn write_single_register(&mut self, addr: Address, word: Word) -> Result<()>;
async fn write_multiple_coils(&mut self, addr: Address, coils: &'_ [Coil]) -> Result<()>;
async fn write_multiple_registers(&mut self, addr: Address, words: &[Word]) -> Result<()>;
async fn masked_write_register(
&mut self,
addr: Address,
and_mask: Word,
or_mask: Word,
) -> Result<()>;
}
#[derive(Debug)]
pub struct Context {
client: Box<dyn Client>,
}
impl Context {
pub async fn disconnect(&mut self) -> Result<()> {
let res = self.client.call(Request::Disconnect).await;
match res {
Ok(_) => unreachable!(),
Err(Error::Transport(err)) => match err.kind() {
io::ErrorKind::NotConnected | io::ErrorKind::BrokenPipe => Ok(Ok(())),
_ => Err(Error::Transport(err)),
},
Err(err) => Err(err),
}
}
}
impl From<Box<dyn Client>> for Context {
fn from(client: Box<dyn Client>) -> Self {
Self { client }
}
}
impl From<Context> for Box<dyn Client> {
fn from(val: Context) -> Self {
val.client
}
}
#[async_trait]
impl Client for Context {
async fn call(&mut self, request: Request<'_>) -> Result<Response> {
self.client.call(request).await
}
}
impl SlaveContext for Context {
fn set_slave(&mut self, slave: Slave) {
self.client.set_slave(slave);
}
}
#[async_trait]
impl Reader for Context {
async fn read_coils<'a>(&'a mut self, addr: Address, cnt: Quantity) -> Result<Vec<Coil>> {
self.client
.call(Request::ReadCoils(addr, cnt))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::ReadCoils(mut coils) => {
debug_assert!(coils.len() >= cnt.into());
coils.truncate(cnt.into());
coils
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn read_discrete_inputs<'a>(
&'a mut self,
addr: Address,
cnt: Quantity,
) -> Result<Vec<Coil>> {
self.client
.call(Request::ReadDiscreteInputs(addr, cnt))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::ReadDiscreteInputs(mut coils) => {
debug_assert!(coils.len() >= cnt.into());
coils.truncate(cnt.into());
coils
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn read_input_registers<'a>(
&'a mut self,
addr: Address,
cnt: Quantity,
) -> Result<Vec<Word>> {
self.client
.call(Request::ReadInputRegisters(addr, cnt))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::ReadInputRegisters(words) => {
debug_assert_eq!(words.len(), cnt.into());
words
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn read_holding_registers<'a>(
&'a mut self,
addr: Address,
cnt: Quantity,
) -> Result<Vec<Word>> {
self.client
.call(Request::ReadHoldingRegisters(addr, cnt))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::ReadHoldingRegisters(words) => {
debug_assert_eq!(words.len(), cnt.into());
words
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn read_write_multiple_registers<'a>(
&'a mut self,
read_addr: Address,
read_count: Quantity,
write_addr: Address,
write_data: &[Word],
) -> Result<Vec<Word>> {
self.client
.call(Request::ReadWriteMultipleRegisters(
read_addr,
read_count,
write_addr,
Cow::Borrowed(write_data),
))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::ReadWriteMultipleRegisters(words) => {
debug_assert_eq!(words.len(), read_count.into());
words
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
}
#[async_trait]
impl Writer for Context {
async fn write_single_coil<'a>(&'a mut self, addr: Address, coil: Coil) -> Result<()> {
self.client
.call(Request::WriteSingleCoil(addr, coil))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::WriteSingleCoil(rsp_addr, rsp_coil) => {
debug_assert_eq!(addr, rsp_addr);
debug_assert_eq!(coil, rsp_coil);
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn write_multiple_coils<'a>(&'a mut self, addr: Address, coils: &[Coil]) -> Result<()> {
let cnt = coils.len();
self.client
.call(Request::WriteMultipleCoils(addr, Cow::Borrowed(coils)))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::WriteMultipleCoils(rsp_addr, rsp_cnt) => {
debug_assert_eq!(addr, rsp_addr);
debug_assert_eq!(cnt, rsp_cnt.into());
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn write_single_register<'a>(&'a mut self, addr: Address, word: Word) -> Result<()> {
self.client
.call(Request::WriteSingleRegister(addr, word))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::WriteSingleRegister(rsp_addr, rsp_word) => {
debug_assert_eq!(addr, rsp_addr);
debug_assert_eq!(word, rsp_word);
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn write_multiple_registers<'a>(
&'a mut self,
addr: Address,
data: &[Word],
) -> Result<()> {
let cnt = data.len();
self.client
.call(Request::WriteMultipleRegisters(addr, Cow::Borrowed(data)))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::WriteMultipleRegisters(rsp_addr, rsp_cnt) => {
debug_assert_eq!(addr, rsp_addr);
debug_assert_eq!(cnt, rsp_cnt.into());
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
async fn masked_write_register<'a>(
&'a mut self,
addr: Address,
and_mask: Word,
or_mask: Word,
) -> Result<()> {
self.client
.call(Request::MaskWriteRegister(addr, and_mask, or_mask))
.await
.map(|result| {
result.map_err(Into::into).map(|response| match response {
Response::MaskWriteRegister(rsp_addr, rsp_and_mask, rsp_or_mask) => {
debug_assert_eq!(addr, rsp_addr);
debug_assert_eq!(and_mask, rsp_and_mask);
debug_assert_eq!(or_mask, rsp_or_mask);
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
}
}
#[cfg(test)]
mod tests {
use crate::Result;
use super::*;
use std::sync::Mutex;
#[derive(Default, Debug)]
pub(crate) struct ClientMock {
slave: Option<Slave>,
last_request: Mutex<Option<Request<'static>>>,
next_response: Option<Result<Response>>,
}
#[allow(dead_code)]
impl ClientMock {
pub(crate) fn slave(&self) -> Option<Slave> {
self.slave
}
pub(crate) fn last_request(&self) -> &Mutex<Option<Request<'static>>> {
&self.last_request
}
pub(crate) fn set_next_response(&mut self, next_response: Result<Response>) {
self.next_response = Some(next_response);
}
}
#[async_trait]
impl Client for ClientMock {
async fn call(&mut self, request: Request<'_>) -> Result<Response> {
*self.last_request.lock().unwrap() = Some(request.into_owned());
match self.next_response.take().unwrap() {
Ok(response) => Ok(response),
Err(Error::Transport(err)) => {
Err(io::Error::new(err.kind(), format!("{err}")).into())
}
Err(err) => Err(err),
}
}
}
impl SlaveContext for ClientMock {
fn set_slave(&mut self, slave: Slave) {
self.slave = Some(slave);
}
}
#[test]
fn read_some_coils() {
let response_coils = [true, false, false, true, false, true, false, true];
for num_coils in 1..8 {
let mut client = Box::<ClientMock>::default();
client.set_next_response(Ok(Ok(Response::ReadCoils(response_coils.to_vec()))));
let mut context = Context { client };
context.set_slave(Slave(1));
let coils = futures::executor::block_on(context.read_coils(1, num_coils))
.unwrap()
.unwrap();
assert_eq!(&response_coils[0..num_coils as usize], &coils[..]);
}
}
#[test]
fn read_some_discrete_inputs() {
let response_inputs = [true, false, false, true, false, true, false, true];
for num_inputs in 1..8 {
let mut client = Box::<ClientMock>::default();
client.set_next_response(Ok(Ok(Response::ReadDiscreteInputs(
response_inputs.to_vec(),
))));
let mut context = Context { client };
context.set_slave(Slave(1));
let inputs = futures::executor::block_on(context.read_discrete_inputs(1, num_inputs))
.unwrap()
.unwrap();
assert_eq!(&response_inputs[0..num_inputs as usize], &inputs[..]);
}
}
}