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, little_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> ReadHoldings<N> {
50    /// Create a new ReadHoldings response.
51    ///
52    /// # Panics
53    ///
54    /// Panics if the number of registers `N` is greater than 127.
55    #[inline]
56    pub fn new(addr: u8, data: [big_endian::U16; N]) -> Self {
57        Self::new_inner(addr, 2 * N as u8, data)
58    }
59
60    /// Create a new ReadHoldings response in place.
61    #[inline]
62    pub fn new_with(addr: u8, f: impl FnOnce(&mut [big_endian::U16; N])) -> Self {
63        Self::new_with_inner(addr, |m| f(&mut m.data))
64    }
65
66    pub fn data_mut(&mut self) -> &mut [big_endian::U16; N] {
67        &mut self.data
68    }
69}
70
71pub struct ReadHoldingsBuilder<const N: usize>(ReadHoldings<N>);
72
73impl<const N: usize> Default for ReadHoldingsBuilder<N> {
74    fn default() -> Self {
75        Self::new(0)
76    }
77}
78
79impl<const N: usize> ReadHoldingsBuilder<N> {
80    pub const fn new(addr: u8) -> Self {
81        Self(ReadHoldings {
82            addr,
83            function: ReadHoldings::<N>::FUNCTION,
84            data_bytes: 2 * N as u8,
85            data: [big_endian::U16::ZERO; N],
86            crc: little_endian::U16::ZERO,
87        })
88    }
89
90    pub fn data_mut(&mut self) -> &mut [big_endian::U16; N] {
91        &mut self.0.data
92    }
93
94    pub fn finish_ref(&mut self) -> &mut ReadHoldings<N> {
95        self.0.update_crc();
96        &mut self.0
97    }
98
99    pub fn finish(mut self) -> ReadHoldings<N> {
100        self.0.update_crc();
101        self.0
102    }
103}
104
105impl<const N: usize> Response<request::ReadHoldings> for ReadHoldings<N> {
106    type Data = [big_endian::U16; N];
107
108    fn into_data(self, req: &request::ReadHoldings) -> Result<Self::Data, ValidationError> {
109        self.validate_crc()?;
110
111        if self.address() == req.address()
112            && self.function() == req.function()
113            && self.data_bytes == 2 * req.n_registers.get() as u8
114        {
115            Ok(self.data)
116        } else {
117            Err(ValidationError::UnexpectedResponse)
118        }
119    }
120}
121
122modbus_message! {
123    /// Write multiple holding registers response
124    WriteHoldings {
125        function_code: 0x10,
126        starting_register: big_endian::U16,
127        n_registers: big_endian::U16,
128    }
129}
130
131impl<const N: usize> Response<request::WriteHoldings<N>> for WriteHoldings {
132    type Data = ();
133
134    fn into_data(self, req: &request::WriteHoldings<N>) -> Result<(), ValidationError> {
135        self.validate_crc()?;
136
137        if self.address() == req.address()
138            && self.function() == req.function()
139            && self.starting_register == req.starting_register
140            && self.n_registers == req.n_registers
141        {
142            Ok(())
143        } else {
144            Err(ValidationError::UnexpectedResponse)
145        }
146    }
147}
148
149modbus_message! {
150    /// Read input registers response
151    ReadInputs<const N: usize> {
152        function_code: 0x04,
153        data_bytes: u8,
154        data: [big_endian::U16; N],
155    }
156}
157
158impl<const N: usize> Response<request::ReadInputs> for ReadInputs<N> {
159    type Data = [big_endian::U16; N];
160
161    fn into_data(self, req: &request::ReadInputs) -> Result<Self::Data, ValidationError> {
162        self.validate_crc()?;
163
164        if self.address() == req.address()
165            && self.function() == req.function()
166            && self.data_bytes == 2 * req.n_registers.get() as u8
167        {
168            Ok(self.data)
169        } else {
170            Err(ValidationError::UnexpectedResponse)
171        }
172    }
173}
174
175/// Trait for Modbus response messages that can be validated against requests.
176pub trait Response<Request> {
177    /// The type of data extracted from the response.
178    type Data;
179
180    /// Validate the response against a given request and extract the data on
181    /// success.
182    fn into_data(self, request: &Request) -> Result<Self::Data, ValidationError>;
183}