async_modbus/pdu.rs
1//! PDUs in Modbus are everything in between the unit ID and CRC.
2
3use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes, Unaligned};
4
5/// Modbus request PDUs. Wrap them in a [`Frame`](crate::Frame) to give them a
6/// unit ID and CRC.
7///
8/// ```
9/// # use async_modbus::{Frame, pdu::request::WriteHolding};
10/// # use hex_literal::hex;
11/// use async_modbus::zerocopy::IntoBytes;
12///
13/// let pdu = WriteHolding::new().with_register(0x10BC).with_value(12345);
14/// let frame = Frame::new(0x01, pdu);
15/// assert_eq!(frame.as_bytes(), hex!("01 06 10 BC 30 39 98 FC"));
16/// ```
17#[allow(unused)]
18pub mod request {
19 include!(concat!(env!("OUT_DIR"), "/pdu_req.rs"));
20
21 #[cfg(test)]
22 mod tests {
23 use hex_literal::hex;
24 use zerocopy::IntoBytes;
25
26 use crate::frame::FrameBuilder;
27
28 use super::*;
29
30 // #[test]
31 // fn test_write_holding_register() {
32 // let msg = WriteHolding::new(0x01, 0x1001, 0x03E8);
33 // assert_eq!(msg.as_bytes(), hex!("01 06 10 01 03 E8 DC 74"),);
34 // }
35
36 #[test]
37 fn test_read_holding_registers() {
38 let frame = FrameBuilder::with_pdu(
39 0x01,
40 ReadHoldings::new()
41 .with_n_registers(0x03E8)
42 .with_starting_register(0x1001),
43 )
44 .build();
45
46 assert_eq!(frame.as_bytes(), hex!("01 03 10 01 03 E8 10 74"),);
47 }
48 }
49}
50
51/// Response PDUs!
52pub mod response {
53 include!(concat!(env!("OUT_DIR"), "/pdu_res.rs"));
54}
55
56/// A Protocol Data Unit (PDU) contains the function code and data.
57pub trait Pdu: Unaligned + Immutable + IntoBytes + TryFromBytes + KnownLayout {
58 /// The function code of the PDU.
59 const FUNCTION_CODE: u8;
60
61 /// Default value for the PDU. Might be all zeros, but not always:
62 ///
63 /// For instance, [`response::ReadHoldings`] is always initialized with a
64 /// default byte count of `2 * N`.
65 const DEFAULT: Self;
66}
67
68/// The [`Response`] trait is used to validate a response against a request,
69/// essentially to ensure that the response corresponds to the request.
70///
71/// It also provides a method to convert the response into its data payload.
72pub trait Response<Request>: Pdu {
73 /// The data payload of the response.
74 type Data;
75
76 /// Validates the response against the request.
77 fn matches_request(&self, request: &Request) -> bool;
78
79 /// Converts the response into its data payload.
80 fn into_data(self) -> Self::Data;
81}
82
83/// Error indicating a CRC validation failure.
84#[derive(Debug, Clone, Copy, thiserror::Error)]
85#[cfg_attr(feature = "defmt", derive(defmt::Format))]
86#[error("CRC validation failed")]
87pub struct CrcError;
88
89/// Errors that can occur when validating a Modbus response.
90#[derive(Debug, thiserror::Error)]
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
92pub enum ValidationError {
93 /// CRC validation failed.
94 #[error(transparent)]
95 Crc(#[from] CrcError),
96 /// The response did not match the request.
97 #[error("unexpected response")]
98 UnexpectedResponse,
99}