zigbee_cluster_library/
frame.rs1#![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 #[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 }
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 pub header: ZclHeader,
68 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 let input: &[u8] = &[
83 0x00, 0x00, 0x29, 0xab, 0x03,
85 ];
86
87 let (report, _) = AttributeReport::try_read(input, ())
89 .expect("Failed to read AttributeReport payload in test");
90
91 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 let input: &[u8] = &[
104 0x18, 0x01, 0x0A, 0x00, 0x00, 0x29, 0x3f, 0x0a, ];
109
110 let (frame, _) = ZclFrame::try_read(input, ()).expect("Failed to read ZclFrame");
112
113 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 let input: &[u8] = &[
139 0x19, 0x01, 0x01, 0x00, 0x00, 0x29, 0x3f, 0x0a, ];
144
145 let (frame, _) = ZclFrame::try_read(input, ()).expect("Failed to read ZclFrame");
147
148 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}