async_modbus/
response.rs

1//! Modbus response messages and validation against requests.
2//!
3//! Since the responses all implement [`zerocopy::FromBytes`], they can be read
4//! directly from a byte buffer. However, this also means that there is no
5//! validation of the response data, not even the CRC checksum.
6
7use crate::{ValidationError, request};
8
9use super::util::modbus_message;
10use zerocopy::{IntoBytes, big_endian};
11use zerocopy_derive::*;
12
13modbus_message! {
14    /// Write single holding register response
15    WriteHolding {
16        function_code: 0x06,
17        register: big_endian::U16,
18        value: big_endian::U16,
19    }
20}
21
22impl Response<request::WriteHolding> for WriteHolding {
23    type Data = ();
24
25    fn into_data(self, req: &request::WriteHolding) -> Result<(), ValidationError> {
26        self.validate_crc()?;
27
28        if self.address() == req.address()
29            && self.function() == req.function()
30            && self.register == req.register
31            && self.value == req.value
32        {
33            Ok(())
34        } else {
35            Err(ValidationError::UnexpectedResponse)
36        }
37    }
38}
39
40modbus_message! {
41    /// Read holding registers response
42    ReadHoldings<const N: usize> {
43        function_code: 0x03,
44        data_bytes: u8,
45        data: [big_endian::U16; N],
46    }
47}
48
49impl<const N: usize> Response<request::ReadHoldings> for ReadHoldings<N> {
50    type Data = [big_endian::U16; N];
51
52    fn into_data(self, req: &request::ReadHoldings) -> Result<Self::Data, ValidationError> {
53        self.validate_crc()?;
54
55        if self.address() == req.address()
56            && self.function() == req.function()
57            && self.data_bytes == 2 * req.n_registers.get() as u8
58        {
59            Ok(self.data)
60        } else {
61            Err(ValidationError::UnexpectedResponse)
62        }
63    }
64}
65
66modbus_message! {
67    /// Write multiple holding registers response
68    WriteHoldings {
69        function_code: 0x10,
70        starting_register: big_endian::U16,
71        n_registers: big_endian::U16,
72    }
73}
74
75impl<const N: usize> Response<request::WriteHoldings<N>> for WriteHoldings {
76    type Data = ();
77
78    fn into_data(self, req: &request::WriteHoldings<N>) -> Result<(), ValidationError> {
79        self.validate_crc()?;
80
81        if self.address() == req.address()
82            && self.function() == req.function()
83            && self.starting_register == req.starting_register
84            && self.n_registers == req.n_registers
85        {
86            Ok(())
87        } else {
88            Err(ValidationError::UnexpectedResponse)
89        }
90    }
91}
92
93modbus_message! {
94    /// Read input registers response
95    ReadInputs<const N: usize> {
96        function_code: 0x04,
97        data_bytes: u8,
98        data: [big_endian::U16; N],
99    }
100}
101
102impl<const N: usize> Response<request::ReadInputs> for ReadInputs<N> {
103    type Data = [big_endian::U16; N];
104
105    fn into_data(self, req: &request::ReadInputs) -> Result<Self::Data, ValidationError> {
106        self.validate_crc()?;
107
108        if self.address() == req.address()
109            && self.function() == req.function()
110            && self.data_bytes == 2 * req.n_registers.get() as u8
111        {
112            Ok(self.data)
113        } else {
114            Err(ValidationError::UnexpectedResponse)
115        }
116    }
117}
118
119/// Trait for Modbus response messages that can be validated against requests.
120pub trait Response<Request> {
121    /// The type of data extracted from the response.
122    type Data;
123
124    /// Validate the response against the given request.
125    fn into_data(self, request: &Request) -> Result<Self::Data, ValidationError>;
126}