From e0beeb9c661e3684dab0d2a2ff1c914143c0c144 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sun, 13 Oct 2024 14:22:25 +0200 Subject: [PATCH] Rename types and functions --- examples/rtu-client.rs | 17 +++--- src/client/rtu.rs | 16 ++---- src/prelude.rs | 2 +- src/service/rtu.rs | 119 ++++++++++++++++++++++++++--------------- 4 files changed, 89 insertions(+), 65 deletions(-) diff --git a/examples/rtu-client.rs b/examples/rtu-client.rs index 550012b..98cad14 100644 --- a/examples/rtu-client.rs +++ b/examples/rtu-client.rs @@ -21,28 +21,27 @@ async fn main() -> Result<(), Box> { let builder = tokio_serial::new(SERIAL_PATH, BAUD_RATE); let transport = SerialStream::open(&builder).unwrap(); - let mut connection = rtu::ClientConnection::new(transport); + let mut client = rtu::Client::new(transport); - println!("Reading sensor values (request/response"); + println!("Reading sensor values (request/response using the low-level API"); let request = Request::ReadHoldingRegisters(SENSOR_ADDRESS, SENSOR_QUANTITY); - let request_context = connection.send_request(request, SERVER).await?; - let Response::ReadHoldingRegisters(values) = - connection.recv_response(request_context).await?? + let request_context = client.send_request(request, SERVER).await?; + let Response::ReadHoldingRegisters(values) = client.recv_response(request_context).await?? else { // The response variant will always match its corresponding request variant if successful. unreachable!(); }; println!("Sensor responded with: {values:?}"); - println!("Reading sensor values (call"); - let mut context = rtu::client_context(connection, SERVER); - let values = context + println!("Reading sensor values (call) using the high-level API"); + let mut client_context = client::Context::from(rtu::ClientContext::new(client, SERVER).boxed()); + let values = client_context .read_holding_registers(SENSOR_ADDRESS, SENSOR_QUANTITY) .await??; println!("Sensor responded with: {values:?}"); println!("Disconnecting"); - context.disconnect().await?; + client_context.disconnect().await?; Ok(()) } diff --git a/src/client/rtu.rs b/src/client/rtu.rs index 0e9e078..5d04396 100644 --- a/src/client/rtu.rs +++ b/src/client/rtu.rs @@ -5,7 +5,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; -use crate::prelude::rtu::ClientConnection; +use crate::service::rtu::{Client, ClientContext}; use super::*; @@ -23,17 +23,9 @@ pub fn attach_slave(transport: T, slave: Slave) -> Context where T: AsyncRead + AsyncWrite + Debug + Unpin + Send + 'static, { - let connection = ClientConnection::new(transport); - client_context(connection, slave) -} - -/// Creates a client/server connection. -pub fn client_context(connection: ClientConnection, server: Slave) -> Context -where - T: AsyncRead + AsyncWrite + Debug + Unpin + Send + 'static, -{ - let client = crate::service::rtu::Client::new(connection, server); + let client = Client::new(transport); + let context = ClientContext::new(client, slave); Context { - client: Box::new(client), + client: Box::new(context), } } diff --git a/src/prelude.rs b/src/prelude.rs index f769509..268196d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::client; pub mod rtu { pub use crate::client::rtu::*; pub use crate::frame::rtu::RequestContext; - pub use crate::service::rtu::ClientConnection; + pub use crate::service::rtu::{Client, ClientContext}; } #[allow(missing_docs)] diff --git a/src/service/rtu.rs b/src/service/rtu.rs index cae2288..0993400 100644 --- a/src/service/rtu.rs +++ b/src/service/rtu.rs @@ -16,13 +16,13 @@ use crate::{ use super::disconnect; -/// Modbus RTU client +/// _Modbus_ RTU client. #[derive(Debug)] -pub struct ClientConnection { +pub struct Client { framed: Framed, } -impl ClientConnection +impl Client where T: AsyncRead + AsyncWrite + Unpin, { @@ -72,68 +72,93 @@ where res_adu.try_into_response(request_context) } -} -fn request_adu<'a, R>(req: R, server: Slave) -> RequestAdu<'a> -where - R: Into>, -{ - let hdr = Header { - slave_id: server.into(), - }; - let pdu = req.into(); - RequestAdu { hdr, pdu } + pub async fn call<'a>(&mut self, request: Request<'a>, server: Slave) -> Result { + let request_context = self.send_request(request, server).await?; + self.recv_response(request_context).await + } } -/// Modbus RTU client +/// _Modbus_ RTU client with (server) context and connection state. +/// +/// Client that invokes methods (request/response) on a single or many (broadcast) server(s). +/// +/// The server can be switched between method calls. #[derive(Debug)] -pub(crate) struct Client { - connection: Option>, - slave_id: SlaveId, +pub struct ClientContext { + client: Option>, + server: Slave, } -impl Client -where - T: AsyncRead + AsyncWrite + Unpin, -{ - pub(crate) fn new(connection: ClientConnection, slave: Slave) -> Self { - let slave_id = slave.into(); +impl ClientContext { + pub fn new(client: Client, server: Slave) -> Self { Self { - connection: Some(connection), - slave_id, + client: Some(client), + server, } } - async fn disconnect(&mut self) -> io::Result<()> { - let Some(connection) = self.connection.take() else { + #[must_use] + pub const fn is_connected(&self) -> bool { + self.client.is_some() + } + + #[must_use] + pub const fn server(&self) -> Slave { + self.server + } + + pub fn set_server(&mut self, server: Slave) { + self.server = server; + } +} + +impl ClientContext +where + T: AsyncWrite + Unpin, +{ + pub async fn disconnect(&mut self) -> io::Result<()> { + let Some(client) = self.client.take() else { // Already disconnected. return Ok(()); }; - disconnect(connection.framed).await + disconnect(client.framed).await } +} - async fn call(&mut self, request: Request<'_>) -> Result { +impl ClientContext +where + T: AsyncRead + AsyncWrite + Unpin, +{ + pub async fn call(&mut self, request: Request<'_>) -> Result { log::debug!("Call {:?}", request); - let Some(connection) = &mut self.connection else { + let Some(client) = &mut self.client else { return Err(io::Error::new(io::ErrorKind::NotConnected, "disconnected").into()); }; - let request_context = connection - .send_request(request, Slave(self.slave_id)) - .await?; - connection.recv_response(request_context).await + client.call(request, self.server).await + } +} + +impl ClientContext +where + T: AsyncRead + AsyncWrite + Unpin + fmt::Debug + Send + 'static, +{ + #[must_use] + pub fn boxed(self) -> Box { + Box::new(self) } } -impl SlaveContext for Client { +impl SlaveContext for ClientContext { fn set_slave(&mut self, slave: Slave) { - self.slave_id = slave.into(); + self.set_server(slave); } } #[async_trait::async_trait] -impl crate::client::Client for Client +impl crate::client::Client for ClientContext where T: fmt::Debug + AsyncRead + AsyncWrite + Send + Unpin, { @@ -146,6 +171,17 @@ where } } +fn request_adu<'a, R>(req: R, server: Slave) -> RequestAdu<'a> +where + R: Into>, +{ + let hdr = Header { + slave_id: server.into(), + }; + let pdu = req.into(); + RequestAdu { hdr, pdu } +} + #[cfg(test)] mod tests { use core::{ @@ -190,12 +226,9 @@ mod tests { #[tokio::test] async fn handle_broken_pipe() { let transport = MockTransport; - let connection = ClientConnection::new(transport); - let mut client = - crate::service::rtu::Client::new(connection, crate::service::rtu::Slave::broadcast()); - let res = client - .call(crate::service::rtu::Request::ReadCoils(0x00, 5)) - .await; + let client = Client::new(transport); + let mut context = ClientContext::new(client, Slave::broadcast()); + let res = context.call(Request::ReadCoils(0x00, 5)).await; assert!(res.is_err()); let err = res.err().unwrap(); assert!(