Skip to main content

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}