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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum ZoneEventStoppedReason {
18    /// Indicates that whatever triggered the Zone event has stopped being detected.
19    Actionstopped = 0,
20    /// Indicates that the max duration for detecting triggering activity has been reached.
21    Timeout = 1,
22}
23
24impl ZoneEventStoppedReason {
25    /// Convert from u8 value
26    pub fn from_u8(value: u8) -> Option<Self> {
27        match value {
28            0 => Some(ZoneEventStoppedReason::Actionstopped),
29            1 => Some(ZoneEventStoppedReason::Timeout),
30            _ => None,
31        }
32    }
33
34    /// Convert to u8 value
35    pub fn to_u8(self) -> u8 {
36        self as u8
37    }
38}
39
40impl From<ZoneEventStoppedReason> for u8 {
41    fn from(val: ZoneEventStoppedReason) -> Self {
42        val as u8
43    }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum ZoneEventTriggeredReason {
49    /// Zone event triggered because motion is detected
50    Motion = 0,
51}
52
53impl ZoneEventTriggeredReason {
54    /// Convert from u8 value
55    pub fn from_u8(value: u8) -> Option<Self> {
56        match value {
57            0 => Some(ZoneEventTriggeredReason::Motion),
58            _ => None,
59        }
60    }
61
62    /// Convert to u8 value
63    pub fn to_u8(self) -> u8 {
64        self as u8
65    }
66}
67
68impl From<ZoneEventTriggeredReason> for u8 {
69    fn from(val: ZoneEventTriggeredReason) -> Self {
70        val as u8
71    }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
75#[repr(u8)]
76pub enum ZoneSource {
77    /// Indicates a Manufacturer defined Zone.
78    Mfg = 0,
79    /// Indicates a User defined Zone.
80    User = 1,
81}
82
83impl ZoneSource {
84    /// Convert from u8 value
85    pub fn from_u8(value: u8) -> Option<Self> {
86        match value {
87            0 => Some(ZoneSource::Mfg),
88            1 => Some(ZoneSource::User),
89            _ => None,
90        }
91    }
92
93    /// Convert to u8 value
94    pub fn to_u8(self) -> u8 {
95        self as u8
96    }
97}
98
99impl From<ZoneSource> for u8 {
100    fn from(val: ZoneSource) -> Self {
101        val as u8
102    }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
106#[repr(u8)]
107pub enum ZoneType {
108    /// Indicates a Two Dimensional Cartesian Zone
109    Twodcartzone = 0,
110}
111
112impl ZoneType {
113    /// Convert from u8 value
114    pub fn from_u8(value: u8) -> Option<Self> {
115        match value {
116            0 => Some(ZoneType::Twodcartzone),
117            _ => None,
118        }
119    }
120
121    /// Convert to u8 value
122    pub fn to_u8(self) -> u8 {
123        self as u8
124    }
125}
126
127impl From<ZoneType> for u8 {
128    fn from(val: ZoneType) -> Self {
129        val as u8
130    }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
134#[repr(u8)]
135pub enum ZoneUse {
136    /// Indicates Zone is intended to detect Motion
137    Motion = 0,
138    /// Indicates Zone is intended to protect privacy
139    Privacy = 1,
140    /// Indicates Zone provides a focus area
141    Focus = 2,
142}
143
144impl ZoneUse {
145    /// Convert from u8 value
146    pub fn from_u8(value: u8) -> Option<Self> {
147        match value {
148            0 => Some(ZoneUse::Motion),
149            1 => Some(ZoneUse::Privacy),
150            2 => Some(ZoneUse::Focus),
151            _ => None,
152        }
153    }
154
155    /// Convert to u8 value
156    pub fn to_u8(self) -> u8 {
157        self as u8
158    }
159}
160
161impl From<ZoneUse> for u8 {
162    fn from(val: ZoneUse) -> Self {
163        val as u8
164    }
165}
166
167// Struct definitions
168
169#[derive(Debug, serde::Serialize)]
170pub struct TwoDCartesianVertex {
171    pub x: Option<u16>,
172    pub y: Option<u16>,
173}
174
175#[derive(Debug, serde::Serialize)]
176pub struct TwoDCartesianZone {
177    pub name: Option<String>,
178    pub use_: Option<ZoneUse>,
179    pub vertices: Option<Vec<TwoDCartesianVertex>>,
180    pub color: Option<String>,
181}
182
183#[derive(Debug, serde::Serialize)]
184pub struct ZoneInformation {
185    pub zone_id: Option<u8>,
186    pub zone_type: Option<ZoneType>,
187    pub zone_source: Option<ZoneSource>,
188    pub two_d_cartesian_zone: Option<TwoDCartesianZone>,
189}
190
191#[derive(Debug, serde::Serialize)]
192pub struct ZoneTriggerControl {
193    pub zone_id: Option<u8>,
194    pub initial_duration: Option<u32>,
195    pub augmentation_duration: Option<u32>,
196    pub max_duration: Option<u32>,
197    pub blind_duration: Option<u32>,
198    pub sensitivity: Option<u8>,
199}
200
201// Command encoders
202
203/// Encode CreateTwoDCartesianZone command (0x00)
204pub fn encode_create_two_d_cartesian_zone(zone: TwoDCartesianZone) -> anyhow::Result<Vec<u8>> {
205            // Encode struct TwoDCartesianZoneStruct
206            let mut zone_fields = Vec::new();
207            if let Some(x) = zone.name { zone_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
208            if let Some(x) = zone.use_ { zone_fields.push((1, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
209            if let Some(listv) = zone.vertices {
210                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
211                    let mut nested_fields = Vec::new();
212                        if let Some(x) = inner.x { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
213                        if let Some(x) = inner.y { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
214                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
215                }).collect();
216                zone_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
217            }
218            if let Some(x) = zone.color { zone_fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
219    let tlv = tlv::TlvItemEnc {
220        tag: 0,
221        value: tlv::TlvItemValueEnc::StructInvisible(vec![
222        (0, tlv::TlvItemValueEnc::StructInvisible(zone_fields)).into(),
223        ]),
224    };
225    Ok(tlv.encode()?)
226}
227
228/// Encode UpdateTwoDCartesianZone command (0x02)
229pub fn encode_update_two_d_cartesian_zone(zone_id: u8, zone: TwoDCartesianZone) -> anyhow::Result<Vec<u8>> {
230            // Encode struct TwoDCartesianZoneStruct
231            let mut zone_fields = Vec::new();
232            if let Some(x) = zone.name { zone_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
233            if let Some(x) = zone.use_ { zone_fields.push((1, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
234            if let Some(listv) = zone.vertices {
235                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
236                    let mut nested_fields = Vec::new();
237                        if let Some(x) = inner.x { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
238                        if let Some(x) = inner.y { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
239                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
240                }).collect();
241                zone_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
242            }
243            if let Some(x) = zone.color { zone_fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
244    let tlv = tlv::TlvItemEnc {
245        tag: 0,
246        value: tlv::TlvItemValueEnc::StructInvisible(vec![
247        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
248        (1, tlv::TlvItemValueEnc::StructInvisible(zone_fields)).into(),
249        ]),
250    };
251    Ok(tlv.encode()?)
252}
253
254/// Encode RemoveZone command (0x03)
255pub fn encode_remove_zone(zone_id: u8) -> anyhow::Result<Vec<u8>> {
256    let tlv = tlv::TlvItemEnc {
257        tag: 0,
258        value: tlv::TlvItemValueEnc::StructInvisible(vec![
259        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
260        ]),
261    };
262    Ok(tlv.encode()?)
263}
264
265/// Encode CreateOrUpdateTrigger command (0x04)
266pub fn encode_create_or_update_trigger(trigger: ZoneTriggerControl) -> anyhow::Result<Vec<u8>> {
267            // Encode struct ZoneTriggerControlStruct
268            let mut trigger_fields = Vec::new();
269            // TODO: encoding for field zone_id (ZoneID) not implemented
270            if let Some(x) = trigger.initial_duration { trigger_fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
271            if let Some(x) = trigger.augmentation_duration { trigger_fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
272            if let Some(x) = trigger.max_duration { trigger_fields.push((3, tlv::TlvItemValueEnc::UInt32(x)).into()); }
273            if let Some(x) = trigger.blind_duration { trigger_fields.push((4, tlv::TlvItemValueEnc::UInt32(x)).into()); }
274            if let Some(x) = trigger.sensitivity { trigger_fields.push((5, tlv::TlvItemValueEnc::UInt8(x)).into()); }
275    let tlv = tlv::TlvItemEnc {
276        tag: 0,
277        value: tlv::TlvItemValueEnc::StructInvisible(vec![
278        (0, tlv::TlvItemValueEnc::StructInvisible(trigger_fields)).into(),
279        ]),
280    };
281    Ok(tlv.encode()?)
282}
283
284/// Encode RemoveTrigger command (0x05)
285pub fn encode_remove_trigger(zone_id: u8) -> anyhow::Result<Vec<u8>> {
286    let tlv = tlv::TlvItemEnc {
287        tag: 0,
288        value: tlv::TlvItemValueEnc::StructInvisible(vec![
289        (0, tlv::TlvItemValueEnc::UInt8(zone_id)).into(),
290        ]),
291    };
292    Ok(tlv.encode()?)
293}
294
295// Attribute decoders
296
297/// Decode MaxUserDefinedZones attribute (0x0000)
298pub fn decode_max_user_defined_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
299    if let tlv::TlvItemValue::Int(v) = inp {
300        Ok(*v as u8)
301    } else {
302        Err(anyhow::anyhow!("Expected UInt8"))
303    }
304}
305
306/// Decode MaxZones attribute (0x0001)
307pub fn decode_max_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
308    if let tlv::TlvItemValue::Int(v) = inp {
309        Ok(*v as u8)
310    } else {
311        Err(anyhow::anyhow!("Expected UInt8"))
312    }
313}
314
315/// Decode Zones attribute (0x0002)
316pub fn decode_zones(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ZoneInformation>> {
317    let mut res = Vec::new();
318    if let tlv::TlvItemValue::List(v) = inp {
319        for item in v {
320            res.push(ZoneInformation {
321                zone_id: item.get_int(&[0]).map(|v| v as u8),
322                zone_type: item.get_int(&[1]).and_then(|v| ZoneType::from_u8(v as u8)),
323                zone_source: item.get_int(&[2]).and_then(|v| ZoneSource::from_u8(v as u8)),
324                two_d_cartesian_zone: {
325                    if let Some(nested_tlv) = item.get(&[3]) {
326                        if let tlv::TlvItemValue::List(_) = nested_tlv {
327                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
328                            Some(TwoDCartesianZone {
329                name: nested_item.get_string_owned(&[0]),
330                use_: nested_item.get_int(&[1]).and_then(|v| ZoneUse::from_u8(v as u8)),
331                vertices: {
332                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[2]) {
333                        let mut items = Vec::new();
334                        for list_item in l {
335                            items.push(TwoDCartesianVertex {
336                x: list_item.get_int(&[0]).map(|v| v as u16),
337                y: list_item.get_int(&[1]).map(|v| v as u16),
338                            });
339                        }
340                        Some(items)
341                    } else {
342                        None
343                    }
344                },
345                color: nested_item.get_string_owned(&[3]),
346                            })
347                        } else {
348                            None
349                        }
350                    } else {
351                        None
352                    }
353                },
354            });
355        }
356    }
357    Ok(res)
358}
359
360/// Decode Triggers attribute (0x0003)
361pub fn decode_triggers(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ZoneTriggerControl>> {
362    let mut res = Vec::new();
363    if let tlv::TlvItemValue::List(v) = inp {
364        for item in v {
365            res.push(ZoneTriggerControl {
366                zone_id: item.get_int(&[0]).map(|v| v as u8),
367                initial_duration: item.get_int(&[1]).map(|v| v as u32),
368                augmentation_duration: item.get_int(&[2]).map(|v| v as u32),
369                max_duration: item.get_int(&[3]).map(|v| v as u32),
370                blind_duration: item.get_int(&[4]).map(|v| v as u32),
371                sensitivity: item.get_int(&[5]).map(|v| v as u8),
372            });
373        }
374    }
375    Ok(res)
376}
377
378/// Decode SensitivityMax attribute (0x0004)
379pub fn decode_sensitivity_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
380    if let tlv::TlvItemValue::Int(v) = inp {
381        Ok(*v as u8)
382    } else {
383        Err(anyhow::anyhow!("Expected UInt8"))
384    }
385}
386
387/// Decode Sensitivity attribute (0x0005)
388pub fn decode_sensitivity(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
389    if let tlv::TlvItemValue::Int(v) = inp {
390        Ok(*v as u8)
391    } else {
392        Err(anyhow::anyhow!("Expected UInt8"))
393    }
394}
395
396/// Decode TwoDCartesianMax attribute (0x0006)
397pub fn decode_two_d_cartesian_max(inp: &tlv::TlvItemValue) -> anyhow::Result<TwoDCartesianVertex> {
398    if let tlv::TlvItemValue::List(_fields) = inp {
399        // Struct with fields
400        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
401        Ok(TwoDCartesianVertex {
402                x: item.get_int(&[0]).map(|v| v as u16),
403                y: item.get_int(&[1]).map(|v| v as u16),
404        })
405    } else {
406        Err(anyhow::anyhow!("Expected struct fields"))
407    }
408}
409
410
411// JSON dispatcher function
412
413/// Decode attribute value and return as JSON string
414///
415/// # Parameters
416/// * `cluster_id` - The cluster identifier
417/// * `attribute_id` - The attribute identifier
418/// * `tlv_value` - The TLV value to decode
419///
420/// # Returns
421/// JSON string representation of the decoded value or error
422pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
423    // Verify this is the correct cluster
424    if cluster_id != 0x0550 {
425        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0550, got {}\"}}", cluster_id);
426    }
427
428    match attribute_id {
429        0x0000 => {
430            match decode_max_user_defined_zones(tlv_value) {
431                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
432                Err(e) => format!("{{\"error\": \"{}\"}}", e),
433            }
434        }
435        0x0001 => {
436            match decode_max_zones(tlv_value) {
437                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
438                Err(e) => format!("{{\"error\": \"{}\"}}", e),
439            }
440        }
441        0x0002 => {
442            match decode_zones(tlv_value) {
443                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
444                Err(e) => format!("{{\"error\": \"{}\"}}", e),
445            }
446        }
447        0x0003 => {
448            match decode_triggers(tlv_value) {
449                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
450                Err(e) => format!("{{\"error\": \"{}\"}}", e),
451            }
452        }
453        0x0004 => {
454            match decode_sensitivity_max(tlv_value) {
455                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
456                Err(e) => format!("{{\"error\": \"{}\"}}", e),
457            }
458        }
459        0x0005 => {
460            match decode_sensitivity(tlv_value) {
461                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
462                Err(e) => format!("{{\"error\": \"{}\"}}", e),
463            }
464        }
465        0x0006 => {
466            match decode_two_d_cartesian_max(tlv_value) {
467                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
468                Err(e) => format!("{{\"error\": \"{}\"}}", e),
469            }
470        }
471        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
472    }
473}
474
475/// Get list of all attributes supported by this cluster
476///
477/// # Returns
478/// Vector of tuples containing (attribute_id, attribute_name)
479pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
480    vec![
481        (0x0000, "MaxUserDefinedZones"),
482        (0x0001, "MaxZones"),
483        (0x0002, "Zones"),
484        (0x0003, "Triggers"),
485        (0x0004, "SensitivityMax"),
486        (0x0005, "Sensitivity"),
487        (0x0006, "TwoDCartesianMax"),
488    ]
489}
490
491// Command listing
492
493pub fn get_command_list() -> Vec<(u32, &'static str)> {
494    vec![
495        (0x00, "CreateTwoDCartesianZone"),
496        (0x02, "UpdateTwoDCartesianZone"),
497        (0x03, "RemoveZone"),
498        (0x04, "CreateOrUpdateTrigger"),
499        (0x05, "RemoveTrigger"),
500    ]
501}
502
503pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
504    match cmd_id {
505        0x00 => Some("CreateTwoDCartesianZone"),
506        0x02 => Some("UpdateTwoDCartesianZone"),
507        0x03 => Some("RemoveZone"),
508        0x04 => Some("CreateOrUpdateTrigger"),
509        0x05 => Some("RemoveTrigger"),
510        _ => None,
511    }
512}
513
514pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
515    match cmd_id {
516        0x00 => Some(vec![
517            crate::clusters::codec::CommandField { tag: 0, name: "zone", kind: crate::clusters::codec::FieldKind::Struct { name: "TwoDCartesianZoneStruct" }, optional: false, nullable: false },
518        ]),
519        0x02 => Some(vec![
520            crate::clusters::codec::CommandField { tag: 0, name: "zone_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
521            crate::clusters::codec::CommandField { tag: 1, name: "zone", kind: crate::clusters::codec::FieldKind::Struct { name: "TwoDCartesianZoneStruct" }, optional: false, nullable: false },
522        ]),
523        0x03 => Some(vec![
524            crate::clusters::codec::CommandField { tag: 0, name: "zone_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
525        ]),
526        0x04 => Some(vec![
527            crate::clusters::codec::CommandField { tag: 0, name: "trigger", kind: crate::clusters::codec::FieldKind::Struct { name: "ZoneTriggerControlStruct" }, optional: false, nullable: false },
528        ]),
529        0x05 => Some(vec![
530            crate::clusters::codec::CommandField { tag: 0, name: "zone_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
531        ]),
532        _ => None,
533    }
534}
535
536pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
537    match cmd_id {
538        0x00 => Err(anyhow::anyhow!("command \"CreateTwoDCartesianZone\" has complex args: use raw mode")),
539        0x02 => Err(anyhow::anyhow!("command \"UpdateTwoDCartesianZone\" has complex args: use raw mode")),
540        0x03 => {
541        let zone_id = crate::clusters::codec::json_util::get_u8(args, "zone_id")?;
542        encode_remove_zone(zone_id)
543        }
544        0x04 => Err(anyhow::anyhow!("command \"CreateOrUpdateTrigger\" has complex args: use raw mode")),
545        0x05 => {
546        let zone_id = crate::clusters::codec::json_util::get_u8(args, "zone_id")?;
547        encode_remove_trigger(zone_id)
548        }
549        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
550    }
551}
552
553#[derive(Debug, serde::Serialize)]
554pub struct CreateTwoDCartesianZoneResponse {
555    pub zone_id: Option<u8>,
556}
557
558// Command response decoders
559
560/// Decode CreateTwoDCartesianZoneResponse command response (01)
561pub fn decode_create_two_d_cartesian_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CreateTwoDCartesianZoneResponse> {
562    if let tlv::TlvItemValue::List(_fields) = inp {
563        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
564        Ok(CreateTwoDCartesianZoneResponse {
565                zone_id: item.get_int(&[0]).map(|v| v as u8),
566        })
567    } else {
568        Err(anyhow::anyhow!("Expected struct fields"))
569    }
570}
571
572// Typed facade (invokes + reads)
573
574/// Invoke `CreateTwoDCartesianZone` command on cluster `Zone Management`.
575pub async fn create_two_d_cartesian_zone(conn: &crate::controller::Connection, endpoint: u16, zone: TwoDCartesianZone) -> anyhow::Result<CreateTwoDCartesianZoneResponse> {
576    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_CMD_ID_CREATETWODCARTESIANZONE, &encode_create_two_d_cartesian_zone(zone)?).await?;
577    decode_create_two_d_cartesian_zone_response(&tlv)
578}
579
580/// Invoke `UpdateTwoDCartesianZone` command on cluster `Zone Management`.
581pub async fn update_two_d_cartesian_zone(conn: &crate::controller::Connection, endpoint: u16, zone_id: u8, zone: TwoDCartesianZone) -> anyhow::Result<()> {
582    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_CMD_ID_UPDATETWODCARTESIANZONE, &encode_update_two_d_cartesian_zone(zone_id, zone)?).await?;
583    Ok(())
584}
585
586/// Invoke `RemoveZone` command on cluster `Zone Management`.
587pub async fn remove_zone(conn: &crate::controller::Connection, endpoint: u16, zone_id: u8) -> anyhow::Result<()> {
588    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_CMD_ID_REMOVEZONE, &encode_remove_zone(zone_id)?).await?;
589    Ok(())
590}
591
592/// Invoke `CreateOrUpdateTrigger` command on cluster `Zone Management`.
593pub async fn create_or_update_trigger(conn: &crate::controller::Connection, endpoint: u16, trigger: ZoneTriggerControl) -> anyhow::Result<()> {
594    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_CMD_ID_CREATEORUPDATETRIGGER, &encode_create_or_update_trigger(trigger)?).await?;
595    Ok(())
596}
597
598/// Invoke `RemoveTrigger` command on cluster `Zone Management`.
599pub async fn remove_trigger(conn: &crate::controller::Connection, endpoint: u16, zone_id: u8) -> anyhow::Result<()> {
600    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_CMD_ID_REMOVETRIGGER, &encode_remove_trigger(zone_id)?).await?;
601    Ok(())
602}
603
604/// Read `MaxUserDefinedZones` attribute from cluster `Zone Management`.
605pub async fn read_max_user_defined_zones(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
606    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_MAXUSERDEFINEDZONES).await?;
607    decode_max_user_defined_zones(&tlv)
608}
609
610/// Read `MaxZones` attribute from cluster `Zone Management`.
611pub async fn read_max_zones(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
612    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_MAXZONES).await?;
613    decode_max_zones(&tlv)
614}
615
616/// Read `Zones` attribute from cluster `Zone Management`.
617pub async fn read_zones(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ZoneInformation>> {
618    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_ZONES).await?;
619    decode_zones(&tlv)
620}
621
622/// Read `Triggers` attribute from cluster `Zone Management`.
623pub async fn read_triggers(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ZoneTriggerControl>> {
624    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_TRIGGERS).await?;
625    decode_triggers(&tlv)
626}
627
628/// Read `SensitivityMax` attribute from cluster `Zone Management`.
629pub async fn read_sensitivity_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
630    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_SENSITIVITYMAX).await?;
631    decode_sensitivity_max(&tlv)
632}
633
634/// Read `Sensitivity` attribute from cluster `Zone Management`.
635pub async fn read_sensitivity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
636    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_SENSITIVITY).await?;
637    decode_sensitivity(&tlv)
638}
639
640/// Read `TwoDCartesianMax` attribute from cluster `Zone Management`.
641pub async fn read_two_d_cartesian_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TwoDCartesianVertex> {
642    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ZONE_MANAGEMENT, crate::clusters::defs::CLUSTER_ZONE_MANAGEMENT_ATTR_ID_TWODCARTESIANMAX).await?;
643    decode_two_d_cartesian_max(&tlv)
644}
645
646#[derive(Debug, serde::Serialize)]
647pub struct ZoneTriggeredEvent {
648    pub zone: Option<u8>,
649    pub reason: Option<ZoneEventTriggeredReason>,
650}
651
652#[derive(Debug, serde::Serialize)]
653pub struct ZoneStoppedEvent {
654    pub zone: Option<u8>,
655    pub reason: Option<ZoneEventStoppedReason>,
656}
657
658// Event decoders
659
660/// Decode ZoneTriggered event (0x00, priority: info)
661pub fn decode_zone_triggered_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ZoneTriggeredEvent> {
662    if let tlv::TlvItemValue::List(_fields) = inp {
663        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
664        Ok(ZoneTriggeredEvent {
665                                zone: item.get_int(&[0]).map(|v| v as u8),
666                                reason: item.get_int(&[1]).and_then(|v| ZoneEventTriggeredReason::from_u8(v as u8)),
667        })
668    } else {
669        Err(anyhow::anyhow!("Expected struct fields"))
670    }
671}
672
673/// Decode ZoneStopped event (0x01, priority: info)
674pub fn decode_zone_stopped_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ZoneStoppedEvent> {
675    if let tlv::TlvItemValue::List(_fields) = inp {
676        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
677        Ok(ZoneStoppedEvent {
678                                zone: item.get_int(&[0]).map(|v| v as u8),
679                                reason: item.get_int(&[1]).and_then(|v| ZoneEventStoppedReason::from_u8(v as u8)),
680        })
681    } else {
682        Err(anyhow::anyhow!("Expected struct fields"))
683    }
684}
685