matc/clusters/codec/
zone_management.rs

1//! Matter TLV encoders and decoders for Zone Management Cluster
2//! Cluster ID: 0x0550
3//!
4//! This file is automatically generated from ZoneManagement.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Enum definitions
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum ZoneEventStoppedReason {
16    /// Indicates that whatever triggered the Zone event has stopped being detected.
17    Actionstopped = 0,
18    /// Indicates that the max duration for detecting triggering activity has been reached.
19    Timeout = 1,
20}
21
22impl ZoneEventStoppedReason {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(ZoneEventStoppedReason::Actionstopped),
27            1 => Some(ZoneEventStoppedReason::Timeout),
28            _ => None,
29        }
30    }
31
32    /// Convert to u8 value
33    pub fn to_u8(self) -> u8 {
34        self as u8
35    }
36}
37
38impl From<ZoneEventStoppedReason> for u8 {
39    fn from(val: ZoneEventStoppedReason) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum ZoneEventTriggeredReason {
47    /// Zone event triggered because motion is detected
48    Motion = 0,
49}
50
51impl ZoneEventTriggeredReason {
52    /// Convert from u8 value
53    pub fn from_u8(value: u8) -> Option<Self> {
54        match value {
55            0 => Some(ZoneEventTriggeredReason::Motion),
56            _ => None,
57        }
58    }
59
60    /// Convert to u8 value
61    pub fn to_u8(self) -> u8 {
62        self as u8
63    }
64}
65
66impl From<ZoneEventTriggeredReason> for u8 {
67    fn from(val: ZoneEventTriggeredReason) -> Self {
68        val as u8
69    }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73#[repr(u8)]
74pub enum ZoneSource {
75    /// Indicates a Manufacturer defined Zone.
76    Mfg = 0,
77    /// Indicates a User defined Zone.
78    User = 1,
79}
80
81impl ZoneSource {
82    /// Convert from u8 value
83    pub fn from_u8(value: u8) -> Option<Self> {
84        match value {
85            0 => Some(ZoneSource::Mfg),
86            1 => Some(ZoneSource::User),
87            _ => None,
88        }
89    }
90
91    /// Convert to u8 value
92    pub fn to_u8(self) -> u8 {
93        self as u8
94    }
95}
96
97impl From<ZoneSource> for u8 {
98    fn from(val: ZoneSource) -> Self {
99        val as u8
100    }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
104#[repr(u8)]
105pub enum ZoneType {
106    /// Indicates a Two Dimensional Cartesian Zone
107    Twodcartzone = 0,
108}
109
110impl ZoneType {
111    /// Convert from u8 value
112    pub fn from_u8(value: u8) -> Option<Self> {
113        match value {
114            0 => Some(ZoneType::Twodcartzone),
115            _ => None,
116        }
117    }
118
119    /// Convert to u8 value
120    pub fn to_u8(self) -> u8 {
121        self as u8
122    }
123}
124
125impl From<ZoneType> for u8 {
126    fn from(val: ZoneType) -> Self {
127        val as u8
128    }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
132#[repr(u8)]
133pub enum ZoneUse {
134    /// Indicates Zone is intended to detect Motion
135    Motion = 0,
136    /// Indicates Zone is intended to protect privacy
137    Privacy = 1,
138    /// Indicates Zone provides a focus area
139    Focus = 2,
140}
141
142impl ZoneUse {
143    /// Convert from u8 value
144    pub fn from_u8(value: u8) -> Option<Self> {
145        match value {
146            0 => Some(ZoneUse::Motion),
147            1 => Some(ZoneUse::Privacy),
148            2 => Some(ZoneUse::Focus),
149            _ => None,
150        }
151    }
152
153    /// Convert to u8 value
154    pub fn to_u8(self) -> u8 {
155        self as u8
156    }
157}
158
159impl From<ZoneUse> for u8 {
160    fn from(val: ZoneUse) -> Self {
161        val as u8
162    }
163}
164
165// Struct definitions
166
167#[derive(Debug, serde::Serialize)]
168pub struct TwoDCartesianVertex {
169    pub x: Option<u16>,
170    pub y: Option<u16>,
171}
172
173#[derive(Debug, serde::Serialize)]
174pub struct TwoDCartesianZone {
175    pub name: Option<String>,
176    pub use_: Option<ZoneUse>,
177    pub vertices: Option<Vec<TwoDCartesianVertex>>,
178    pub color: Option<String>,
179}
180
181#[derive(Debug, serde::Serialize)]
182pub struct ZoneInformation {
183    pub zone_id: Option<u8>,
184    pub zone_type: Option<ZoneType>,
185    pub zone_source: Option<ZoneSource>,
186    pub two_d_cartesian_zone: Option<TwoDCartesianZone>,
187}
188
189#[derive(Debug, serde::Serialize)]
190pub struct ZoneTriggerControl {
191    pub zone_id: Option<u8>,
192    pub initial_duration: Option<u32>,
193    pub augmentation_duration: Option<u32>,
194    pub max_duration: Option<u32>,
195    pub blind_duration: Option<u32>,
196    pub sensitivity: Option<u8>,
197}
198
199// Command encoders
200
201/// Encode CreateTwoDCartesianZone command (0x00)
202pub fn encode_create_two_d_cartesian_zone(zone: TwoDCartesianZone) -> anyhow::Result<Vec<u8>> {
203            // Encode struct TwoDCartesianZoneStruct
204            let mut zone_fields = Vec::new();
205            if let Some(x) = zone.name { zone_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
206            if let Some(x) = zone.use_ { zone_fields.push((1, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
207            if let Some(listv) = zone.vertices {
208                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
209                    let mut nested_fields = Vec::new();
210                        if let Some(x) = inner.x { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
211                        if let Some(x) = inner.y { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
212                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
213                }).collect();
214                zone_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
215            }
216            if let Some(x) = zone.color { zone_fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
217    let tlv = tlv::TlvItemEnc {
218        tag: 0,
219        value: tlv::TlvItemValueEnc::StructInvisible(vec![
220        (0, tlv::TlvItemValueEnc::StructInvisible(zone_fields)).into(),
221        ]),
222    };
223    Ok(tlv.encode()?)
224}
225
226/// Encode UpdateTwoDCartesianZone command (0x02)
227pub fn encode_update_two_d_cartesian_zone(zone_id: u8, zone: TwoDCartesianZone) -> anyhow::Result<Vec<u8>> {
228            // Encode struct TwoDCartesianZoneStruct
229            let mut zone_fields = Vec::new();
230            if let Some(x) = zone.name { zone_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
231            if let Some(x) = zone.use_ { zone_fields.push((1, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
232            if let Some(listv) = zone.vertices {
233                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
234                    let mut nested_fields = Vec::new();
235                        if let Some(x) = inner.x { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
236                        if let Some(x) = inner.y { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
237                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
238                }).collect();
239                zone_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
240            }
241            if let Some(x) = zone.color { zone_fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
242    let tlv = tlv::TlvItemEnc {
243        tag: 0,
244        value: tlv::TlvItemValueEnc::StructInvisible(vec![
245        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
246        (1, tlv::TlvItemValueEnc::StructInvisible(zone_fields)).into(),
247        ]),
248    };
249    Ok(tlv.encode()?)
250}
251
252/// Encode RemoveZone command (0x03)
253pub fn encode_remove_zone(zone_id: u8) -> anyhow::Result<Vec<u8>> {
254    let tlv = tlv::TlvItemEnc {
255        tag: 0,
256        value: tlv::TlvItemValueEnc::StructInvisible(vec![
257        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
258        ]),
259    };
260    Ok(tlv.encode()?)
261}
262
263/// Encode CreateOrUpdateTrigger command (0x04)
264pub fn encode_create_or_update_trigger(trigger: ZoneTriggerControl) -> anyhow::Result<Vec<u8>> {
265            // Encode struct ZoneTriggerControlStruct
266            let mut trigger_fields = Vec::new();
267            // TODO: encoding for field zone_id (ZoneID) not implemented
268            if let Some(x) = trigger.initial_duration { trigger_fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
269            if let Some(x) = trigger.augmentation_duration { trigger_fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
270            if let Some(x) = trigger.max_duration { trigger_fields.push((3, tlv::TlvItemValueEnc::UInt32(x)).into()); }
271            if let Some(x) = trigger.blind_duration { trigger_fields.push((4, tlv::TlvItemValueEnc::UInt32(x)).into()); }
272            if let Some(x) = trigger.sensitivity { trigger_fields.push((5, tlv::TlvItemValueEnc::UInt8(x)).into()); }
273    let tlv = tlv::TlvItemEnc {
274        tag: 0,
275        value: tlv::TlvItemValueEnc::StructInvisible(vec![
276        (0, tlv::TlvItemValueEnc::StructInvisible(trigger_fields)).into(),
277        ]),
278    };
279    Ok(tlv.encode()?)
280}
281
282/// Encode RemoveTrigger command (0x05)
283pub fn encode_remove_trigger(zone_id: u8) -> anyhow::Result<Vec<u8>> {
284    let tlv = tlv::TlvItemEnc {
285        tag: 0,
286        value: tlv::TlvItemValueEnc::StructInvisible(vec![
287        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
288        ]),
289    };
290    Ok(tlv.encode()?)
291}
292
293// Attribute decoders
294
295/// Decode MaxUserDefinedZones attribute (0x0000)
296pub fn decode_max_user_defined_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
297    if let tlv::TlvItemValue::Int(v) = inp {
298        Ok(*v as u8)
299    } else {
300        Err(anyhow::anyhow!("Expected UInt8"))
301    }
302}
303
304/// Decode MaxZones attribute (0x0001)
305pub fn decode_max_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        Ok(*v as u8)
308    } else {
309        Err(anyhow::anyhow!("Expected UInt8"))
310    }
311}
312
313/// Decode Zones attribute (0x0002)
314pub fn decode_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ZoneInformation>> {
315    let mut res = Vec::new();
316    if let tlv::TlvItemValue::List(v) = inp {
317        for item in v {
318            res.push(ZoneInformation {
319                zone_id: item.get_int(&[0]).map(|v| v as u8),
320                zone_type: item.get_int(&[1]).and_then(|v| ZoneType::from_u8(v as u8)),
321                zone_source: item.get_int(&[2]).and_then(|v| ZoneSource::from_u8(v as u8)),
322                two_d_cartesian_zone: {
323                    if let Some(nested_tlv) = item.get(&[3]) {
324                        if let tlv::TlvItemValue::List(_) = nested_tlv {
325                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
326                            Some(TwoDCartesianZone {
327                name: nested_item.get_string_owned(&[0]),
328                use_: nested_item.get_int(&[1]).and_then(|v| ZoneUse::from_u8(v as u8)),
329                vertices: {
330                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[2]) {
331                        let mut items = Vec::new();
332                        for list_item in l {
333                            items.push(TwoDCartesianVertex {
334                x: list_item.get_int(&[0]).map(|v| v as u16),
335                y: list_item.get_int(&[1]).map(|v| v as u16),
336                            });
337                        }
338                        Some(items)
339                    } else {
340                        None
341                    }
342                },
343                color: nested_item.get_string_owned(&[3]),
344                            })
345                        } else {
346                            None
347                        }
348                    } else {
349                        None
350                    }
351                },
352            });
353        }
354    }
355    Ok(res)
356}
357
358/// Decode Triggers attribute (0x0003)
359pub fn decode_triggers(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ZoneTriggerControl>> {
360    let mut res = Vec::new();
361    if let tlv::TlvItemValue::List(v) = inp {
362        for item in v {
363            res.push(ZoneTriggerControl {
364                zone_id: item.get_int(&[0]).map(|v| v as u8),
365                initial_duration: item.get_int(&[1]).map(|v| v as u32),
366                augmentation_duration: item.get_int(&[2]).map(|v| v as u32),
367                max_duration: item.get_int(&[3]).map(|v| v as u32),
368                blind_duration: item.get_int(&[4]).map(|v| v as u32),
369                sensitivity: item.get_int(&[5]).map(|v| v as u8),
370            });
371        }
372    }
373    Ok(res)
374}
375
376/// Decode SensitivityMax attribute (0x0004)
377pub fn decode_sensitivity_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
378    if let tlv::TlvItemValue::Int(v) = inp {
379        Ok(*v as u8)
380    } else {
381        Err(anyhow::anyhow!("Expected UInt8"))
382    }
383}
384
385/// Decode Sensitivity attribute (0x0005)
386pub fn decode_sensitivity(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
387    if let tlv::TlvItemValue::Int(v) = inp {
388        Ok(*v as u8)
389    } else {
390        Err(anyhow::anyhow!("Expected UInt8"))
391    }
392}
393
394/// Decode TwoDCartesianMax attribute (0x0006)
395pub fn decode_two_d_cartesian_max(inp: &tlv::TlvItemValue) -> anyhow::Result<TwoDCartesianVertex> {
396    if let tlv::TlvItemValue::List(_fields) = inp {
397        // Struct with fields
398        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
399        Ok(TwoDCartesianVertex {
400                x: item.get_int(&[0]).map(|v| v as u16),
401                y: item.get_int(&[1]).map(|v| v as u16),
402        })
403    } else {
404        Err(anyhow::anyhow!("Expected struct fields"))
405    }
406}
407
408
409// JSON dispatcher function
410
411/// Decode attribute value and return as JSON string
412///
413/// # Parameters
414/// * `cluster_id` - The cluster identifier
415/// * `attribute_id` - The attribute identifier
416/// * `tlv_value` - The TLV value to decode
417///
418/// # Returns
419/// JSON string representation of the decoded value or error
420pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
421    // Verify this is the correct cluster
422    if cluster_id != 0x0550 {
423        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0550, got {}\"}}", cluster_id);
424    }
425
426    match attribute_id {
427        0x0000 => {
428            match decode_max_user_defined_zones(tlv_value) {
429                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
430                Err(e) => format!("{{\"error\": \"{}\"}}", e),
431            }
432        }
433        0x0001 => {
434            match decode_max_zones(tlv_value) {
435                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
436                Err(e) => format!("{{\"error\": \"{}\"}}", e),
437            }
438        }
439        0x0002 => {
440            match decode_zones(tlv_value) {
441                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
442                Err(e) => format!("{{\"error\": \"{}\"}}", e),
443            }
444        }
445        0x0003 => {
446            match decode_triggers(tlv_value) {
447                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
448                Err(e) => format!("{{\"error\": \"{}\"}}", e),
449            }
450        }
451        0x0004 => {
452            match decode_sensitivity_max(tlv_value) {
453                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
454                Err(e) => format!("{{\"error\": \"{}\"}}", e),
455            }
456        }
457        0x0005 => {
458            match decode_sensitivity(tlv_value) {
459                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
460                Err(e) => format!("{{\"error\": \"{}\"}}", e),
461            }
462        }
463        0x0006 => {
464            match decode_two_d_cartesian_max(tlv_value) {
465                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
466                Err(e) => format!("{{\"error\": \"{}\"}}", e),
467            }
468        }
469        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
470    }
471}
472
473/// Get list of all attributes supported by this cluster
474///
475/// # Returns
476/// Vector of tuples containing (attribute_id, attribute_name)
477pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
478    vec![
479        (0x0000, "MaxUserDefinedZones"),
480        (0x0001, "MaxZones"),
481        (0x0002, "Zones"),
482        (0x0003, "Triggers"),
483        (0x0004, "SensitivityMax"),
484        (0x0005, "Sensitivity"),
485        (0x0006, "TwoDCartesianMax"),
486    ]
487}
488
489#[derive(Debug, serde::Serialize)]
490pub struct CreateTwoDCartesianZoneResponse {
491    pub zone_id: Option<u8>,
492}
493
494// Command response decoders
495
496/// Decode CreateTwoDCartesianZoneResponse command response (01)
497pub fn decode_create_two_d_cartesian_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CreateTwoDCartesianZoneResponse> {
498    if let tlv::TlvItemValue::List(_fields) = inp {
499        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
500        Ok(CreateTwoDCartesianZoneResponse {
501                zone_id: item.get_int(&[0]).map(|v| v as u8),
502        })
503    } else {
504        Err(anyhow::anyhow!("Expected struct fields"))
505    }
506}
507
508#[derive(Debug, serde::Serialize)]
509pub struct ZoneTriggeredEvent {
510    pub zone: Option<u8>,
511    pub reason: Option<ZoneEventTriggeredReason>,
512}
513
514#[derive(Debug, serde::Serialize)]
515pub struct ZoneStoppedEvent {
516    pub zone: Option<u8>,
517    pub reason: Option<ZoneEventStoppedReason>,
518}
519
520// Event decoders
521
522/// Decode ZoneTriggered event (0x00, priority: info)
523pub fn decode_zone_triggered_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ZoneTriggeredEvent> {
524    if let tlv::TlvItemValue::List(_fields) = inp {
525        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
526        Ok(ZoneTriggeredEvent {
527                                zone: item.get_int(&[0]).map(|v| v as u8),
528                                reason: item.get_int(&[1]).and_then(|v| ZoneEventTriggeredReason::from_u8(v as u8)),
529        })
530    } else {
531        Err(anyhow::anyhow!("Expected struct fields"))
532    }
533}
534
535/// Decode ZoneStopped event (0x01, priority: info)
536pub fn decode_zone_stopped_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ZoneStoppedEvent> {
537    if let tlv::TlvItemValue::List(_fields) = inp {
538        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
539        Ok(ZoneStoppedEvent {
540                                zone: item.get_int(&[0]).map(|v| v as u8),
541                                reason: item.get_int(&[1]).and_then(|v| ZoneEventStoppedReason::from_u8(v as u8)),
542        })
543    } else {
544        Err(anyhow::anyhow!("Expected struct fields"))
545    }
546}
547