Skip to content

Commit

Permalink
rtu: Send request and receive response one after another
Browse files Browse the repository at this point in the history
  • Loading branch information
uklotzde committed Oct 12, 2024
1 parent f8dca34 commit 92b4030
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 119 deletions.
13 changes: 9 additions & 4 deletions examples/rtu-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let builder = tokio_serial::new(tty_path, 19200);
let port = SerialStream::open(&builder).unwrap();

let mut ctx = rtu::attach_slave(port, slave);
let mut conn = rtu::ClientConnection::new(port);
println!("Reading a sensor value");
let rsp = ctx.read_holding_registers(0x082B, 2).await??;
println!("Sensor value is: {rsp:?}");
let request = Request::ReadHoldingRegisters(0x082B, 2);
let request_context = conn.send_request(request, slave).await?;
let Response::ReadHoldingRegisters(value) = conn.recv_response(request_context).await?? else {
// The response variant will always match its corresponding request variant if successful.
unreachable!();
};
println!("Sensor value is: {value:?}");

println!("Disconnecting");
ctx.disconnect().await?;
conn.disconnect().await?;

Ok(())
}
20 changes: 19 additions & 1 deletion src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::bytes::Bytes;
/// A Modbus function code.
///
/// All function codes as defined by the protocol specification V1.1b3.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FunctionCode {
/// 01 (0x01) Read Coils.
ReadCoils,
Expand Down Expand Up @@ -586,6 +586,24 @@ impl error::Error for ExceptionResponse {
}
}

/// Check that `req_hdr` is the same `Header` as `rsp_hdr`.
///
/// # Errors
///
/// If the 2 headers are different, an error message with the details will be returned.
#[cfg(any(feature = "rtu", feature = "tcp"))]
pub(crate) fn verify_response_header<H: Eq + std::fmt::Debug>(
req_hdr: &H,
rsp_hdr: &H,
) -> Result<(), String> {
if req_hdr != rsp_hdr {
return Err(format!(
"expected/request = {req_hdr:?}, actual/response = {rsp_hdr:?}"
));
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
96 changes: 94 additions & 2 deletions src/frame/rtu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@

use super::*;

use crate::slave::SlaveId;
use crate::{ProtocolError, Result, SlaveId};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RequestContext {
function_code: FunctionCode,
header: Header,
}

impl RequestContext {
#[must_use]
pub const fn function_code(&self) -> FunctionCode {
self.function_code
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Header {
pub(crate) slave_id: SlaveId,
}
Expand All @@ -16,12 +29,60 @@ pub struct RequestAdu<'a> {
pub(crate) pdu: RequestPdu<'a>,
}

impl RequestAdu<'_> {
pub(crate) fn context(&self) -> RequestContext {
RequestContext {
function_code: self.pdu.0.function_code(),
header: self.hdr,
}
}
}

#[derive(Debug, Clone)]
pub(crate) struct ResponseAdu {
pub(crate) hdr: Header,
pub(crate) pdu: ResponsePdu,
}

impl ResponseAdu {
pub(crate) fn try_into_response(self, request_context: RequestContext) -> Result<Response> {
let RequestContext {
function_code: req_function_code,
header: req_hdr,
} = request_context;

let ResponseAdu {
hdr: rsp_hdr,
pdu: rsp_pdu,
} = self;
let ResponsePdu(result) = rsp_pdu;

if let Err(message) = verify_response_header(&req_hdr, &rsp_hdr) {
return Err(ProtocolError::HeaderMismatch { message, result }.into());
}

// Match function codes of request and response.
let rsp_function_code = match &result {
Ok(response) => response.function_code(),
Err(ExceptionResponse { function, .. }) => *function,
};
if req_function_code != rsp_function_code {
return Err(ProtocolError::FunctionCodeMismatch {
request: req_function_code,
result,
}
.into());
}

Ok(result.map_err(
|ExceptionResponse {
function: _,
exception,
}| exception,
))
}
}

impl<'a> From<RequestAdu<'a>> for Request<'a> {
fn from(from: RequestAdu<'a>) -> Self {
from.pdu.into()
Expand All @@ -37,3 +98,34 @@ impl<'a> From<RequestAdu<'a>> for SlaveRequest<'a> {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn validate_same_headers() {
// Given
let req_hdr = Header { slave_id: 0 };
let rsp_hdr = Header { slave_id: 0 };

// When
let result = verify_response_header(&req_hdr, &rsp_hdr);

// Then
assert!(result.is_ok());
}

#[test]
fn invalid_validate_not_same_slave_id() {
// Given
let req_hdr = Header { slave_id: 0 };
let rsp_hdr = Header { slave_id: 5 };

// When
let result = verify_response_header(&req_hdr, &rsp_hdr);

// Then
assert!(result.is_err());
}
}
2 changes: 2 additions & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub use crate::client;
#[cfg(feature = "rtu")]
pub mod rtu {
pub use crate::client::rtu::*;
pub use crate::frame::rtu::RequestContext;
pub use crate::service::rtu::ClientConnection;
}

#[allow(missing_docs)]
Expand Down
15 changes: 0 additions & 15 deletions src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,3 @@ pub(crate) mod rtu;

#[cfg(feature = "tcp")]
pub(crate) mod tcp;

/// Check that `req_hdr` is the same `Header` as `rsp_hdr`.
///
/// # Errors
///
/// If the 2 headers are different, an error message with the details will be returned.
#[cfg(any(feature = "rtu", feature = "tcp"))]
fn verify_response_header<H: Eq + std::fmt::Debug>(req_hdr: &H, rsp_hdr: &H) -> Result<(), String> {
if req_hdr != rsp_hdr {
return Err(format!(
"expected/request = {req_hdr:?}, actual/response = {rsp_hdr:?}"
));
}
Ok(())
}
Loading

0 comments on commit 92b4030

Please sign in to comment.