matc/clusters/codec/
closure_control.rs

1//! Matter TLV encoders and decoders for Closure Control Cluster
2//! Cluster ID: 0x0104
3//!
4//! This file is automatically generated from ClosureControl.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 ClosureError {
16    /// An obstacle is blocking the closure movement
17    Physicallyblocked = 0,
18    /// The closure is unsafe to move, as determined by a sensor (e.g. photoelectric sensor) before attempting movement
19    Blockedbysensor = 1,
20    /// A warning raised by the closure that indicates an over-temperature, e.g. due to excessive drive or stall current
21    Temperaturelimited = 2,
22    /// Some malfunctions that are not easily recoverable are detected, or urgent servicing is needed
23    Maintenancerequired = 3,
24    /// An internal element is prohibiting motion, e.g. an integrated door within a bigger garage door is open and prevents motion
25    Internalinterference = 4,
26}
27
28impl ClosureError {
29    /// Convert from u8 value
30    pub fn from_u8(value: u8) -> Option<Self> {
31        match value {
32            0 => Some(ClosureError::Physicallyblocked),
33            1 => Some(ClosureError::Blockedbysensor),
34            2 => Some(ClosureError::Temperaturelimited),
35            3 => Some(ClosureError::Maintenancerequired),
36            4 => Some(ClosureError::Internalinterference),
37            _ => None,
38        }
39    }
40
41    /// Convert to u8 value
42    pub fn to_u8(self) -> u8 {
43        self as u8
44    }
45}
46
47impl From<ClosureError> for u8 {
48    fn from(val: ClosureError) -> Self {
49        val as u8
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54#[repr(u8)]
55pub enum CurrentPosition {
56    /// Fully closed state
57    Fullyclosed = 0,
58    /// Fully opened state
59    Fullyopened = 1,
60    /// Partially opened state (closure is not fully opened or fully closed)
61    Partiallyopened = 2,
62    /// Closure is in the Pedestrian position
63    Openedforpedestrian = 3,
64    /// Closure is in the Ventilation position
65    Openedforventilation = 4,
66    /// Closure is in its "Signature position"
67    Openedatsignature = 5,
68}
69
70impl CurrentPosition {
71    /// Convert from u8 value
72    pub fn from_u8(value: u8) -> Option<Self> {
73        match value {
74            0 => Some(CurrentPosition::Fullyclosed),
75            1 => Some(CurrentPosition::Fullyopened),
76            2 => Some(CurrentPosition::Partiallyopened),
77            3 => Some(CurrentPosition::Openedforpedestrian),
78            4 => Some(CurrentPosition::Openedforventilation),
79            5 => Some(CurrentPosition::Openedatsignature),
80            _ => None,
81        }
82    }
83
84    /// Convert to u8 value
85    pub fn to_u8(self) -> u8 {
86        self as u8
87    }
88}
89
90impl From<CurrentPosition> for u8 {
91    fn from(val: CurrentPosition) -> Self {
92        val as u8
93    }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
97#[repr(u8)]
98pub enum MainState {
99    /// Closure is stopped
100    Stopped = 0,
101    /// Closure is actively moving
102    Moving = 1,
103    /// Closure is waiting before a motion (e.g. pre-heat, pre-check)
104    Waitingformotion = 2,
105    /// Closure is in an error state
106    Error = 3,
107    /// Closure is currently calibrating its Opened and Closed limits to determine effective physical range
108    Calibrating = 4,
109    /// Some protective measures are activated to prevent damage to the closure. Commands MAY be rejected.
110    Protected = 5,
111    /// Closure has a disengaged element preventing any actuator movements
112    Disengaged = 6,
113    /// Movement commands are ignored since the closure is not operational and requires further setup and/or calibration
114    Setuprequired = 7,
115}
116
117impl MainState {
118    /// Convert from u8 value
119    pub fn from_u8(value: u8) -> Option<Self> {
120        match value {
121            0 => Some(MainState::Stopped),
122            1 => Some(MainState::Moving),
123            2 => Some(MainState::Waitingformotion),
124            3 => Some(MainState::Error),
125            4 => Some(MainState::Calibrating),
126            5 => Some(MainState::Protected),
127            6 => Some(MainState::Disengaged),
128            7 => Some(MainState::Setuprequired),
129            _ => None,
130        }
131    }
132
133    /// Convert to u8 value
134    pub fn to_u8(self) -> u8 {
135        self as u8
136    }
137}
138
139impl From<MainState> for u8 {
140    fn from(val: MainState) -> Self {
141        val as u8
142    }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
146#[repr(u8)]
147pub enum TargetPosition {
148    /// Move to a fully closed state
149    Movetofullyclosed = 0,
150    /// Move to a fully open state
151    Movetofullyopen = 1,
152    /// Move to the Pedestrian position
153    Movetopedestrianposition = 2,
154    /// Move to the Ventilation position
155    Movetoventilationposition = 3,
156    /// Move to the Signature position
157    Movetosignatureposition = 4,
158}
159
160impl TargetPosition {
161    /// Convert from u8 value
162    pub fn from_u8(value: u8) -> Option<Self> {
163        match value {
164            0 => Some(TargetPosition::Movetofullyclosed),
165            1 => Some(TargetPosition::Movetofullyopen),
166            2 => Some(TargetPosition::Movetopedestrianposition),
167            3 => Some(TargetPosition::Movetoventilationposition),
168            4 => Some(TargetPosition::Movetosignatureposition),
169            _ => None,
170        }
171    }
172
173    /// Convert to u8 value
174    pub fn to_u8(self) -> u8 {
175        self as u8
176    }
177}
178
179impl From<TargetPosition> for u8 {
180    fn from(val: TargetPosition) -> Self {
181        val as u8
182    }
183}
184
185// Bitmap definitions
186
187/// LatchControlModes bitmap type
188pub type LatchControlModes = u8;
189
190/// Constants for LatchControlModes
191pub mod latchcontrolmodes {
192    /// Remote latching capability
193    pub const REMOTE_LATCHING: u8 = 0x01;
194    /// Remote unlatching capability
195    pub const REMOTE_UNLATCHING: u8 = 0x02;
196}
197
198// Struct definitions
199
200#[derive(Debug, serde::Serialize)]
201pub struct OverallCurrentState {
202    pub position: Option<CurrentPosition>,
203    pub latch: Option<bool>,
204    pub speed: Option<u8>,
205    pub secure_state: Option<bool>,
206}
207
208#[derive(Debug, serde::Serialize)]
209pub struct OverallTargetState {
210    pub position: Option<TargetPosition>,
211    pub latch: Option<bool>,
212    pub speed: Option<u8>,
213}
214
215// Command encoders
216
217/// Encode MoveTo command (0x01)
218pub fn encode_move_to(position: TargetPosition, latch: bool, speed: u8) -> anyhow::Result<Vec<u8>> {
219    let tlv = tlv::TlvItemEnc {
220        tag: 0,
221        value: tlv::TlvItemValueEnc::StructInvisible(vec![
222        (0, tlv::TlvItemValueEnc::UInt8(position.to_u8())).into(),
223        (1, tlv::TlvItemValueEnc::Bool(latch)).into(),
224        (2, tlv::TlvItemValueEnc::UInt8(speed)).into(),
225        ]),
226    };
227    Ok(tlv.encode()?)
228}
229
230// Attribute decoders
231
232/// Decode CountdownTime attribute (0x0000)
233pub fn decode_countdown_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
234    if let tlv::TlvItemValue::Int(v) = inp {
235        Ok(Some(*v as u32))
236    } else {
237        Ok(None)
238    }
239}
240
241/// Decode MainState attribute (0x0001)
242pub fn decode_main_state(inp: &tlv::TlvItemValue) -> anyhow::Result<MainState> {
243    if let tlv::TlvItemValue::Int(v) = inp {
244        MainState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
245    } else {
246        Err(anyhow::anyhow!("Expected Integer"))
247    }
248}
249
250/// Decode CurrentErrorList attribute (0x0002)
251pub fn decode_current_error_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ClosureError>> {
252    let mut res = Vec::new();
253    if let tlv::TlvItemValue::List(v) = inp {
254        for item in v {
255            if let tlv::TlvItemValue::Int(i) = &item.value {
256                if let Some(enum_val) = ClosureError::from_u8(*i as u8) {
257                    res.push(enum_val);
258                }
259            }
260        }
261    }
262    Ok(res)
263}
264
265/// Decode OverallCurrentState attribute (0x0003)
266pub fn decode_overall_current_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<OverallCurrentState>> {
267    if let tlv::TlvItemValue::List(_fields) = inp {
268        // Struct with fields
269        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
270        Ok(Some(OverallCurrentState {
271                position: item.get_int(&[0]).and_then(|v| CurrentPosition::from_u8(v as u8)),
272                latch: item.get_bool(&[1]),
273                speed: item.get_int(&[2]).map(|v| v as u8),
274                secure_state: item.get_bool(&[3]),
275        }))
276    //} else if let tlv::TlvItemValue::Null = inp {
277    //    // Null value for nullable struct
278    //    Ok(None)
279    } else {
280    Ok(None)
281    //    Err(anyhow::anyhow!("Expected struct fields or null"))
282    }
283}
284
285/// Decode OverallTargetState attribute (0x0004)
286pub fn decode_overall_target_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<OverallTargetState>> {
287    if let tlv::TlvItemValue::List(_fields) = inp {
288        // Struct with fields
289        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
290        Ok(Some(OverallTargetState {
291                position: item.get_int(&[0]).and_then(|v| TargetPosition::from_u8(v as u8)),
292                latch: item.get_bool(&[1]),
293                speed: item.get_int(&[2]).map(|v| v as u8),
294        }))
295    //} else if let tlv::TlvItemValue::Null = inp {
296    //    // Null value for nullable struct
297    //    Ok(None)
298    } else {
299    Ok(None)
300    //    Err(anyhow::anyhow!("Expected struct fields or null"))
301    }
302}
303
304/// Decode LatchControlModes attribute (0x0005)
305pub fn decode_latch_control_modes(inp: &tlv::TlvItemValue) -> anyhow::Result<LatchControlModes> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        Ok(*v as u8)
308    } else {
309        Err(anyhow::anyhow!("Expected Integer"))
310    }
311}
312
313
314// JSON dispatcher function
315
316/// Decode attribute value and return as JSON string
317///
318/// # Parameters
319/// * `cluster_id` - The cluster identifier
320/// * `attribute_id` - The attribute identifier
321/// * `tlv_value` - The TLV value to decode
322///
323/// # Returns
324/// JSON string representation of the decoded value or error
325pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
326    // Verify this is the correct cluster
327    if cluster_id != 0x0104 {
328        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0104, got {}\"}}", cluster_id);
329    }
330
331    match attribute_id {
332        0x0000 => {
333            match decode_countdown_time(tlv_value) {
334                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
335                Err(e) => format!("{{\"error\": \"{}\"}}", e),
336            }
337        }
338        0x0001 => {
339            match decode_main_state(tlv_value) {
340                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
341                Err(e) => format!("{{\"error\": \"{}\"}}", e),
342            }
343        }
344        0x0002 => {
345            match decode_current_error_list(tlv_value) {
346                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
347                Err(e) => format!("{{\"error\": \"{}\"}}", e),
348            }
349        }
350        0x0003 => {
351            match decode_overall_current_state(tlv_value) {
352                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
353                Err(e) => format!("{{\"error\": \"{}\"}}", e),
354            }
355        }
356        0x0004 => {
357            match decode_overall_target_state(tlv_value) {
358                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
359                Err(e) => format!("{{\"error\": \"{}\"}}", e),
360            }
361        }
362        0x0005 => {
363            match decode_latch_control_modes(tlv_value) {
364                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
365                Err(e) => format!("{{\"error\": \"{}\"}}", e),
366            }
367        }
368        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
369    }
370}
371
372/// Get list of all attributes supported by this cluster
373///
374/// # Returns
375/// Vector of tuples containing (attribute_id, attribute_name)
376pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
377    vec![
378        (0x0000, "CountdownTime"),
379        (0x0001, "MainState"),
380        (0x0002, "CurrentErrorList"),
381        (0x0003, "OverallCurrentState"),
382        (0x0004, "OverallTargetState"),
383        (0x0005, "LatchControlModes"),
384    ]
385}
386
387#[derive(Debug, serde::Serialize)]
388pub struct OperationalErrorEvent {
389    pub error_state: Option<Vec<ClosureError>>,
390}
391
392#[derive(Debug, serde::Serialize)]
393pub struct EngageStateChangedEvent {
394    pub engage_value: Option<bool>,
395}
396
397#[derive(Debug, serde::Serialize)]
398pub struct SecureStateChangedEvent {
399    pub secure_value: Option<bool>,
400}
401
402// Event decoders
403
404/// Decode OperationalError event (0x00, priority: critical)
405pub fn decode_operational_error_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalErrorEvent> {
406    if let tlv::TlvItemValue::List(_fields) = inp {
407        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
408        Ok(OperationalErrorEvent {
409                                error_state: {
410                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
411                        let items: Vec<ClosureError> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { ClosureError::from_u8(*v as u8) } else { None } }).collect();
412                        Some(items)
413                    } else {
414                        None
415                    }
416                },
417        })
418    } else {
419        Err(anyhow::anyhow!("Expected struct fields"))
420    }
421}
422
423/// Decode EngageStateChanged event (0x02, priority: info)
424pub fn decode_engage_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EngageStateChangedEvent> {
425    if let tlv::TlvItemValue::List(_fields) = inp {
426        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
427        Ok(EngageStateChangedEvent {
428                                engage_value: item.get_bool(&[0]),
429        })
430    } else {
431        Err(anyhow::anyhow!("Expected struct fields"))
432    }
433}
434
435/// Decode SecureStateChanged event (0x03, priority: info)
436pub fn decode_secure_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<SecureStateChangedEvent> {
437    if let tlv::TlvItemValue::List(_fields) = inp {
438        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
439        Ok(SecureStateChangedEvent {
440                                secure_value: item.get_bool(&[0]),
441        })
442    } else {
443        Err(anyhow::anyhow!("Expected struct fields"))
444    }
445}
446