Skip to main content

rusty_modbus_codec/response/
bit_read.rs

1//! Response types for bit-read function codes (FC 01, 02).
2
3use crate::error::{DecodeError, EncodeError};
4use crate::request::Encode;
5use rusty_modbus_types::FunctionCode;
6
7/// Response to a Read Coils request (FC 0x01).
8///
9/// Contains bit-packed coil status data in LSB-first order.
10#[derive(Debug)]
11pub struct ReadCoilsResponse<'buf> {
12    /// Number of data bytes that follow.
13    pub byte_count: u8,
14    /// Bit-packed coil status, LSB-first within each byte.
15    pub coil_status: &'buf [u8],
16}
17
18impl<'buf> ReadCoilsResponse<'buf> {
19    /// Returns the state of the coil at the given zero-based index.
20    ///
21    /// # Panics
22    ///
23    /// Panics if `index` is out of range for the coil status data.
24    #[must_use]
25    pub fn coil(&self, index: usize) -> bool {
26        let byte_idx = index / 8;
27        let bit_idx = index % 8;
28        (self.coil_status[byte_idx] >> bit_idx) & 1 == 1
29    }
30
31    /// Decode from the data bytes following the function code.
32    ///
33    /// # Errors
34    ///
35    /// Returns `DecodeError::Truncated` if `data` is too short.
36    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
37    /// does not match the remaining data length.
38    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
39        if data.is_empty() {
40            return Err(DecodeError::Truncated {
41                expected: 1,
42                actual: 0,
43            });
44        }
45        let byte_count = data[0];
46        let coil_status = &data[1..];
47        if coil_status.len() != usize::from(byte_count) {
48            return Err(DecodeError::ByteCountMismatch {
49                declared: usize::from(byte_count),
50                actual: coil_status.len(),
51            });
52        }
53        Ok(Self {
54            byte_count,
55            coil_status,
56        })
57    }
58}
59
60impl Encode for ReadCoilsResponse<'_> {
61    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
62        let len = self.encoded_len();
63        if buf.len() < len {
64            return Err(EncodeError::BufferTooSmall {
65                required: len,
66                available: buf.len(),
67            });
68        }
69        EncodeError::check_byte_count(usize::from(self.byte_count), self.coil_status.len())?;
70        EncodeError::check_pdu_len(len)?;
71        buf[0] = FunctionCode::ReadCoils.code();
72        buf[1] = self.byte_count;
73        buf[2..2 + usize::from(self.byte_count)].copy_from_slice(self.coil_status);
74        Ok(len)
75    }
76
77    fn encoded_len(&self) -> usize {
78        1 + 1 + usize::from(self.byte_count)
79    }
80}
81
82/// Response to a Read Discrete Inputs request (FC 0x02).
83///
84/// Contains bit-packed discrete input status data in LSB-first order.
85#[derive(Debug)]
86pub struct ReadDiscreteInputsResponse<'buf> {
87    /// Number of data bytes that follow.
88    pub byte_count: u8,
89    /// Bit-packed input status, LSB-first within each byte.
90    pub coil_status: &'buf [u8],
91}
92
93impl<'buf> ReadDiscreteInputsResponse<'buf> {
94    /// Returns the state of the discrete input at the given zero-based index.
95    ///
96    /// # Panics
97    ///
98    /// Panics if `index` is out of range for the input status data.
99    #[must_use]
100    pub fn coil(&self, index: usize) -> bool {
101        let byte_idx = index / 8;
102        let bit_idx = index % 8;
103        (self.coil_status[byte_idx] >> bit_idx) & 1 == 1
104    }
105
106    /// Decode from the data bytes following the function code.
107    ///
108    /// # Errors
109    ///
110    /// Returns `DecodeError::Truncated` if `data` is too short.
111    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
112    /// does not match the remaining data length.
113    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
114        if data.is_empty() {
115            return Err(DecodeError::Truncated {
116                expected: 1,
117                actual: 0,
118            });
119        }
120        let byte_count = data[0];
121        let coil_status = &data[1..];
122        if coil_status.len() != usize::from(byte_count) {
123            return Err(DecodeError::ByteCountMismatch {
124                declared: usize::from(byte_count),
125                actual: coil_status.len(),
126            });
127        }
128        Ok(Self {
129            byte_count,
130            coil_status,
131        })
132    }
133}
134
135impl Encode for ReadDiscreteInputsResponse<'_> {
136    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
137        let len = self.encoded_len();
138        if buf.len() < len {
139            return Err(EncodeError::BufferTooSmall {
140                required: len,
141                available: buf.len(),
142            });
143        }
144        EncodeError::check_byte_count(usize::from(self.byte_count), self.coil_status.len())?;
145        EncodeError::check_pdu_len(len)?;
146        buf[0] = FunctionCode::ReadDiscreteInputs.code();
147        buf[1] = self.byte_count;
148        buf[2..2 + usize::from(self.byte_count)].copy_from_slice(self.coil_status);
149        Ok(len)
150    }
151
152    fn encoded_len(&self) -> usize {
153        1 + 1 + usize::from(self.byte_count)
154    }
155}