zigbee_cluster_library/
frame.rs

1//! General ZCL Frame
2#![allow(missing_docs)]
3#![allow(clippy::panic)]
4
5use byte::ctx;
6use byte::BytesExt;
7use byte::TryRead;
8use heapless::Vec;
9use zigbee::internal::macros::impl_byte;
10
11use crate::common::data_types::ZclDataType;
12use crate::header::ZclHeader;
13use crate::payload::ZclFramePayload;
14
15impl_byte! {
16    /// ZCL Frame
17    ///
18    /// See Section 2.4.1
19    #[derive(Debug)]
20    pub struct ZclFrame<'a> {
21        pub header: ZclHeader,
22        #[ctx = &header]
23        #[ctx_write = ()]
24        pub payload: ZclFramePayload<'a>,
25    }
26}
27
28#[derive(Debug)]
29pub enum GeneralCommand<'a> {
30    ReadAttributesCommand(Vec<ReadAttribute, 16>),
31    ReportAttributesCommand(Vec<AttributeReport<'a>, 16>),
32    // ...
33}
34
35impl_byte! {
36    #[derive(Debug,PartialEq)]
37    pub struct ReadAttribute {
38        pub attribute_id: u16,
39    }
40}
41
42#[derive(Debug, PartialEq)]
43pub struct AttributeReport<'a> {
44    pub attribute_id: u16,
45    pub data_type: ZclDataType<'a>,
46}
47
48impl<'a> TryRead<'a, ()> for AttributeReport<'a> {
49    fn try_read(bytes: &'a [u8], _: ()) -> byte::Result<(Self, usize)> {
50        let offset = &mut 0;
51        let attribute_id: u16 = bytes.read_with(offset, ctx::LE)?;
52        let data_type: u8 = bytes.read_with(offset, ctx::LE)?;
53        let data: ZclDataType = bytes.read_with(offset, data_type)?;
54
55        let report = Self {
56            attribute_id,
57            data_type: data,
58        };
59
60        Ok((report, *offset))
61    }
62}
63
64#[allow(missing_docs)]
65pub struct ClusterSpecificCommand<'a> {
66    /// ZCL Header
67    pub header: ZclHeader,
68    /// ZCL Payload
69    pub payload: &'a [u8],
70}
71
72#[cfg(test)]
73mod tests {
74    use byte::TryRead;
75
76    use super::*;
77    use crate::common::data_types::SignedN;
78
79    #[test]
80    fn parse_attribute_report_payload() {
81        // given
82        let input: &[u8] = &[
83            0x00, 0x00, // identifier
84            0x29, 0xab, 0x03,
85        ];
86
87        // when
88        let (report, _) = AttributeReport::try_read(input, ())
89            .expect("Failed to read AttributeReport payload in test");
90
91        // then
92        assert_eq!(report.attribute_id, 0u16);
93        assert_eq!(
94            report.data_type,
95            ZclDataType::SignedInt(SignedN::Int16(939))
96        );
97    }
98
99    #[allow(clippy::panic)]
100    #[test]
101    fn zcl_general_command() {
102        // given
103        let input: &[u8] = &[
104            0x18, // frame control
105            0x01, // sequence number
106            0x0A, // command identifier
107            0x00, 0x00, 0x29, 0x3f, 0x0a, // payload
108        ];
109
110        // when
111        let (frame, _) = ZclFrame::try_read(input, ()).expect("Failed to read ZclFrame");
112
113        // then
114        let _expected = &[0x00, 0x00, 0x29, 0x3f, 0x0a];
115        assert!(matches!(frame.payload, ZclFramePayload::GeneralCommand(_)));
116
117        if let ZclFramePayload::GeneralCommand(cmd) = frame.payload {
118            if let GeneralCommand::ReportAttributesCommand(report) = cmd {
119                assert_eq!(report.len(), 1);
120                let attribute_report = report.first().expect("Expected ONE report in test");
121                assert_eq!(attribute_report.attribute_id, 0u16);
122                assert_eq!(
123                    attribute_report.data_type,
124                    ZclDataType::SignedInt(SignedN::Int16(2623))
125                );
126            } else {
127                panic!("Report Attributes Command expected!");
128            }
129        } else {
130            panic!("GeneralCommand expected!");
131        }
132    }
133
134    #[allow(clippy::panic)]
135    #[test]
136    fn cluster_specific_command() {
137        // given
138        let input: &[u8] = &[
139            0x19, // frame control
140            0x01, // sequence number
141            0x01, // command identifier
142            0x00, 0x00, 0x29, 0x3f, 0x0a, // payload
143        ];
144
145        // when
146        let (frame, _) = ZclFrame::try_read(input, ()).expect("Failed to read ZclFrame");
147
148        // then
149        let expected = &[0x00, 0x00, 0x29, 0x3f, 0x0a];
150        assert!(matches!(
151            frame.payload,
152            ZclFramePayload::ClusterSpecificCommand(_)
153        ));
154        if let ZclFramePayload::ClusterSpecificCommand(cmd) = frame.payload {
155            assert_eq!(cmd, expected);
156        } else {
157            panic!("ClusterSpecificCommand expected!");
158        }
159    }
160}