modbus_rtu/
response.rs

1/// Represents the outcome of a Modbus RTU request, covering data reads, write
2/// acknowledgements, and protocol exceptions.
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum Response {
5    /// A collection of coil/discrete input states returned by the device.
6    Status(Box<[bool]>),
7
8    /// A collection of register values returned by the device.
9    Value(Box<[u16]>),
10
11    /// Confirmation that a write request completed successfully.
12    Success,
13
14    /// A Modbus application exception reported by the device.
15    Exception(crate::Exception),
16}
17
18impl Response {
19    /// Decodes a Modbus RTU response frame into a [`Response`] value.
20    ///
21    /// ---
22    /// # Arguments
23    /// - `request`: The originating request used to validate address, function,
24    ///   and quantity semantics.
25    /// - `bytes`: Raw response frame including slave id, function code, payload,
26    ///   and CRC.
27    ///
28    /// ---
29    /// # Errors
30    /// Returns [`ResponsePacketError`](crate::error::ResponsePacketError) when
31    /// the frame does not pass validation (CRC mismatch, unexpected responder,
32    /// malformed payload, etc.).
33    ///
34    /// ---
35    /// # Examples
36    /// ```rust
37    /// use modbus_rtu::{Function, Request, Response};
38    ///
39    /// let function = Function::ReadInputRegisters { starting_address: 0x0000, quantity: 2 };
40    /// let request = Request::new(0x01, &function, std::time::Duration::from_millis(100));
41    /// let frame = [0x01, 0x04, 0x04, 0x00, 0x10, 0x00, 0x20, 0xFB, 0x99];
42    ///
43    /// let response = Response::from_bytes(&request, &frame).unwrap();
44    /// match response {
45    ///     Response::Value(values) => assert_eq!(&values[..], &[0x0010, 0x0020]),
46    ///     _ => panic!("unexpected response variant"),
47    /// }
48    /// ```
49    ///
50    pub fn from_bytes(
51        request: &crate::Request,
52        bytes: &[u8],
53    ) -> Result<Self, crate::error::ResponsePacketError> {
54        // minimum length check
55        let len = bytes.len();
56        if len < 5 {
57            return Err(crate::error::ResponsePacketError::TooShort(len));
58        }
59
60        // crc check
61        crate::crc::validate(&bytes)?;
62
63        // exception check
64        let function_code = bytes[1];
65        if function_code & 0x80 != 0 {
66            let code = bytes[2];
67            return Ok(Self::Exception(crate::Exception::from_code(code)));
68        }
69
70        // modbus id check
71        if bytes[0] != request.modbus_id() {
72            return Err(crate::error::ResponsePacketError::UnexpectedResponder(
73                bytes[0],
74            ));
75        }
76
77        // function code check
78        let function_kind = match crate::FunctionKind::from_code(function_code) {
79            Some(kind) => kind,
80            None => return Err(crate::error::ResponsePacketError::InvalidFormat),
81        };
82        if function_kind != request.function().kind() {
83            return Err(crate::error::ResponsePacketError::InvalidFormat);
84        }
85
86        // trim
87        let packet = &bytes[2..(len - 2)];
88
89        // analyze
90        match function_kind {
91            crate::FunctionKind::ReadCoils | crate::FunctionKind::ReadDiscreteInputs => {
92                let byte_count = packet[0];
93                let quantity = match request.function() {
94                    crate::Function::ReadCoils { quantity, .. }
95                    | crate::Function::ReadDiscreteInputs { quantity, .. } => *quantity,
96                    _ => unreachable!(),
97                };
98                if byte_count < (quantity as u8 + 7) / 8 {
99                    return Err(crate::error::ResponsePacketError::InvalidFormat);
100                }
101                if packet.len() < byte_count as usize + 1 {
102                    return Err(crate::error::ResponsePacketError::InvalidFormat);
103                }
104                let mut list: Vec<bool> = Vec::with_capacity(quantity as usize);
105                for (i, byte) in packet[1..].iter().enumerate() {
106                    for j in 0..8_usize {
107                        if (i * 8) + j >= quantity as usize {
108                            break;
109                        }
110                        let value = byte & (0b1 << j) != 0;
111                        list.push(value);
112                    }
113                }
114                Ok(Self::Status(list.into_boxed_slice()))
115            }
116            crate::FunctionKind::ReadHoldingRegisters | crate::FunctionKind::ReadInputRegisters => {
117                let byte_count = packet[0];
118                let quantity = match request.function() {
119                    crate::Function::ReadHoldingRegisters { quantity, .. }
120                    | crate::Function::ReadInputRegisters { quantity, .. } => *quantity,
121                    _ => unreachable!(),
122                };
123                if byte_count < quantity as u8 * 2 {
124                    return Err(crate::error::ResponsePacketError::InvalidFormat);
125                }
126                if packet.len() < byte_count as usize + 1 {
127                    return Err(crate::error::ResponsePacketError::InvalidFormat);
128                }
129                let mut list: Vec<u16> = Vec::with_capacity(quantity as usize * 2);
130                for i in 0..(quantity as usize) {
131                    let hi = packet[1 + (i * 2)];
132                    let lo = packet[2 + (i * 2)];
133                    let value = u16::from_be_bytes([hi, lo]);
134                    list.push(value);
135                }
136                Ok(Self::Value(list.into_boxed_slice()))
137            }
138            crate::FunctionKind::WriteSingleCoil | crate::FunctionKind::WriteSingleRegister => {
139                if packet.len() != 4 {
140                    return Err(crate::error::ResponsePacketError::InvalidFormat);
141                }
142                let (req_address, req_value) = match request.function() {
143                    crate::Function::WriteSingleCoil { address, value } => {
144                        (*address, if *value == true { 0xFF00 } else { 0x0000 })
145                    }
146                    crate::Function::WriteSingleRegister { address, value } => (*address, *value),
147                    _ => unreachable!(),
148                };
149                let res_address = u16::from_be_bytes([packet[0], packet[1]]);
150                let res_value = u16::from_be_bytes([packet[2], packet[3]]);
151                if req_address != res_address || req_value != res_value {
152                    return Err(crate::error::ResponsePacketError::InvalidFormat);
153                }
154                Ok(Self::Success)
155            }
156            crate::FunctionKind::WriteMultipleCoils
157            | crate::FunctionKind::WriteMultipleRegisters => {
158                if packet.len() != 4 {
159                    return Err(crate::error::ResponsePacketError::InvalidFormat);
160                }
161                let (req_address, req_quantity) = match request.function() {
162                    crate::Function::WriteMultipleCoils {
163                        starting_address,
164                        value,
165                    } => (*starting_address, value.len() as u16),
166                    crate::Function::WriteMultipleRegisters {
167                        starting_address,
168                        value,
169                    } => (*starting_address, value.len() as u16),
170                    _ => unreachable!(),
171                };
172                let res_address = u16::from_be_bytes([packet[0], packet[1]]);
173                let res_quantity = u16::from_be_bytes([packet[2], packet[3]]);
174                if req_address != res_address || req_quantity != res_quantity {
175                    return Err(crate::error::ResponsePacketError::InvalidFormat);
176                }
177                Ok(Self::Success)
178            }
179        }
180    }
181
182    /// Returns `true` when the response indicates that the request succeeded.
183    ///
184    /// The method treats the Modbus `Acknowledge (0x05)` exception as success
185    /// because it signals that the device accepted the request and will complete
186    /// it asynchronously.
187    ///
188    /// ---
189    /// # Examples
190    /// ```rust
191    /// use modbus_rtu::{Exception, Response};
192    ///
193    /// assert!(Response::Success.is_success());
194    /// assert!(Response::Exception(Exception::Acknowledge).is_success());
195    /// assert!(!Response::Exception(Exception::IllegalFunction).is_success());
196    /// ```
197    ///
198    pub fn is_success(&self) -> bool {
199        match self {
200            Response::Status(_) | Response::Value(_) | Response::Success => true,
201            Response::Exception(exception) => *exception == crate::Exception::Acknowledge,
202        }
203    }
204}
205
206impl std::fmt::Display for Response {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        write!(
209            f,
210            "{}",
211            match self {
212                Response::Status(items) => format!("{:?}", items),
213                Response::Value(items) => format!("{:?}", items),
214                Response::Success => "Success".to_string(),
215                Response::Exception(exception) => exception.to_string(),
216            }
217        )
218    }
219}