Skip to main content

rusty_modbus_codec/request/
device_id.rs

1//! Read Device Identification request (FC 0x2B, MEI type 0x0E, Spec V1.1b3 §6.21).
2
3use rusty_modbus_types::{DeviceIdCode, FunctionCode, MeiType};
4
5use crate::error::{DecodeError, EncodeError};
6use crate::request::Encode;
7
8/// FC 0x2B / MEI 0x0E — Read Device Identification request.
9///
10/// Spec V1.1b3 §6.21. Wire format: FC(1) + MEI(1) + DevIdCode(1) + ObjectId(1).
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ReadDeviceIdentificationRequest {
13    /// Access code: basic (0x01), regular (0x02), extended (0x03), or individual (0x04).
14    pub device_id_code: DeviceIdCode,
15    /// First object ID to read (0x00 for stream access from the beginning).
16    pub object_id: u8,
17}
18
19impl ReadDeviceIdentificationRequest {
20    /// Decode from the data bytes following the function code.
21    ///
22    /// The `data` slice starts at the MEI type byte (FC byte already consumed).
23    ///
24    /// # Errors
25    ///
26    /// Returns `DecodeError::Truncated` if data is too short.
27    /// Returns `DecodeError::LengthMismatch` if data has extra bytes.
28    /// Returns `DecodeError::UnknownMeiType` if MEI type is not 0x0E.
29    /// Returns `DecodeError::InvalidDeviceIdCode` if the access code is unrecognized.
30    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
31        DecodeError::check_exact_len(data, 3)?;
32        if data[0] != MeiType::ReadDeviceIdentification.code() {
33            return Err(DecodeError::UnknownMeiType(data[0]));
34        }
35        let device_id_code =
36            DeviceIdCode::from_raw(data[1]).ok_or(DecodeError::InvalidDeviceIdCode(data[1]))?;
37        let object_id = data[2];
38        Ok(Self {
39            device_id_code,
40            object_id,
41        })
42    }
43}
44
45impl Encode for ReadDeviceIdentificationRequest {
46    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
47        let len = self.encoded_len();
48        if buf.len() < len {
49            return Err(EncodeError::BufferTooSmall {
50                required: len,
51                available: buf.len(),
52            });
53        }
54        EncodeError::check_pdu_len(len)?;
55        buf[0] = FunctionCode::EncapsulatedInterfaceTransport.code();
56        buf[1] = MeiType::ReadDeviceIdentification.code();
57        buf[2] = self.device_id_code.code();
58        buf[3] = self.object_id;
59        Ok(len)
60    }
61
62    fn encoded_len(&self) -> usize {
63        4 // FC + MEI + DevIdCode + ObjId
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn encode_basic_stream_request() {
73        let req = ReadDeviceIdentificationRequest {
74            device_id_code: DeviceIdCode::BasicStream,
75            object_id: 0x00,
76        };
77        let mut buf = [0u8; 5];
78        let len = req.encode_into(&mut buf).unwrap();
79        assert_eq!(&buf[..len], &[0x2B, 0x0E, 0x01, 0x00]);
80    }
81
82    #[test]
83    fn decode_basic_stream_request() {
84        let data = [0x0E, 0x01, 0x00];
85        let req = ReadDeviceIdentificationRequest::decode(&data).unwrap();
86        assert_eq!(req.device_id_code, DeviceIdCode::BasicStream);
87        assert_eq!(req.object_id, 0x00);
88    }
89
90    #[test]
91    fn decode_individual_request() {
92        let data = [0x0E, 0x04, 0x03];
93        let req = ReadDeviceIdentificationRequest::decode(&data).unwrap();
94        assert_eq!(req.device_id_code, DeviceIdCode::Individual);
95        assert_eq!(req.object_id, 0x03);
96    }
97
98    #[test]
99    fn decode_wrong_mei_type() {
100        let data = [0x0D, 0x01, 0x00];
101        assert!(matches!(
102            ReadDeviceIdentificationRequest::decode(&data),
103            Err(DecodeError::UnknownMeiType(0x0D))
104        ));
105    }
106
107    #[test]
108    fn decode_invalid_device_id_code() {
109        let data = [0x0E, 0xFF, 0x00];
110        assert!(matches!(
111            ReadDeviceIdentificationRequest::decode(&data),
112            Err(DecodeError::InvalidDeviceIdCode(0xFF))
113        ));
114    }
115
116    #[test]
117    fn decode_truncated() {
118        let data = [0x0E, 0x01];
119        assert!(matches!(
120            ReadDeviceIdentificationRequest::decode(&data),
121            Err(DecodeError::Truncated { .. })
122        ));
123    }
124}