matc/clusters/codec/
closure_dimension.rs

1//! Matter TLV encoders and decoders for Closure Dimension Cluster
2//! Cluster ID: 0x0105
3//!
4//! This file is automatically generated from ClosureDimension.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 ClosureUnit {
16    /// Millimeter used as unit
17    Millimeter = 0,
18    /// Degree used as unit
19    Degree = 1,
20}
21
22impl ClosureUnit {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(ClosureUnit::Millimeter),
27            1 => Some(ClosureUnit::Degree),
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<ClosureUnit> for u8 {
39    fn from(val: ClosureUnit) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum ModulationType {
47    /// Orientation of the slats
48    Slatsorientation = 0,
49    /// Aperture of the slats
50    Slatsopenwork = 1,
51    /// Alignment of blind stripes (Zebra)
52    Stripesalignment = 2,
53    /// Opacity of a surface
54    Opacity = 3,
55    /// Ventilation control
56    Ventilation = 4,
57}
58
59impl ModulationType {
60    /// Convert from u8 value
61    pub fn from_u8(value: u8) -> Option<Self> {
62        match value {
63            0 => Some(ModulationType::Slatsorientation),
64            1 => Some(ModulationType::Slatsopenwork),
65            2 => Some(ModulationType::Stripesalignment),
66            3 => Some(ModulationType::Opacity),
67            4 => Some(ModulationType::Ventilation),
68            _ => None,
69        }
70    }
71
72    /// Convert to u8 value
73    pub fn to_u8(self) -> u8 {
74        self as u8
75    }
76}
77
78impl From<ModulationType> for u8 {
79    fn from(val: ModulationType) -> Self {
80        val as u8
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
85#[repr(u8)]
86pub enum Overflow {
87    /// No overflow
88    Nooverflow = 0,
89    /// Inside overflow
90    Inside = 1,
91    /// Outside overflow
92    Outside = 2,
93    /// Top inside overflow
94    Topinside = 3,
95    /// Top outside overflow
96    Topoutside = 4,
97    /// Bottom inside overflow
98    Bottominside = 5,
99    /// Bottom outside overflow
100    Bottomoutside = 6,
101    /// Left inside overflow
102    Leftinside = 7,
103    /// Left outside overflow
104    Leftoutside = 8,
105    /// Right inside overflow
106    Rightinside = 9,
107    /// Right outside overflow
108    Rightoutside = 10,
109}
110
111impl Overflow {
112    /// Convert from u8 value
113    pub fn from_u8(value: u8) -> Option<Self> {
114        match value {
115            0 => Some(Overflow::Nooverflow),
116            1 => Some(Overflow::Inside),
117            2 => Some(Overflow::Outside),
118            3 => Some(Overflow::Topinside),
119            4 => Some(Overflow::Topoutside),
120            5 => Some(Overflow::Bottominside),
121            6 => Some(Overflow::Bottomoutside),
122            7 => Some(Overflow::Leftinside),
123            8 => Some(Overflow::Leftoutside),
124            9 => Some(Overflow::Rightinside),
125            10 => Some(Overflow::Rightoutside),
126            _ => None,
127        }
128    }
129
130    /// Convert to u8 value
131    pub fn to_u8(self) -> u8 {
132        self as u8
133    }
134}
135
136impl From<Overflow> for u8 {
137    fn from(val: Overflow) -> Self {
138        val as u8
139    }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
143#[repr(u8)]
144pub enum RotationAxis {
145    /// The panel rotates around a vertical axis located on the left side of the panel
146    Left = 0,
147    /// The panel rotates around a vertical axis located in the center of the panel
148    Centeredvertical = 1,
149    /// The panels rotates around vertical axes located on the left and right sides of the panel
150    Leftandright = 2,
151    /// The panel rotates around a vertical axis located on the right side of the panel
152    Right = 3,
153    /// The panel rotates around a horizontal axis located on the top of the panel
154    Top = 4,
155    /// The panel rotates around a horizontal axis located in the center of the panel
156    Centeredhorizontal = 5,
157    /// The panels rotates around horizontal axes located on the top and bottom of the panel
158    Topandbottom = 6,
159    /// The panel rotates around a horizontal axis located on the bottom of the panel
160    Bottom = 7,
161    /// The barrier tilts around an axis located at the left end of the barrier
162    Leftbarrier = 8,
163    /// The dual barriers tilt around axes located at each side of the composite barrier
164    Leftandrightbarriers = 9,
165    /// The barrier tilts around an axis located at the right end of the barrier
166    Rightbarrier = 10,
167}
168
169impl RotationAxis {
170    /// Convert from u8 value
171    pub fn from_u8(value: u8) -> Option<Self> {
172        match value {
173            0 => Some(RotationAxis::Left),
174            1 => Some(RotationAxis::Centeredvertical),
175            2 => Some(RotationAxis::Leftandright),
176            3 => Some(RotationAxis::Right),
177            4 => Some(RotationAxis::Top),
178            5 => Some(RotationAxis::Centeredhorizontal),
179            6 => Some(RotationAxis::Topandbottom),
180            7 => Some(RotationAxis::Bottom),
181            8 => Some(RotationAxis::Leftbarrier),
182            9 => Some(RotationAxis::Leftandrightbarriers),
183            10 => Some(RotationAxis::Rightbarrier),
184            _ => None,
185        }
186    }
187
188    /// Convert to u8 value
189    pub fn to_u8(self) -> u8 {
190        self as u8
191    }
192}
193
194impl From<RotationAxis> for u8 {
195    fn from(val: RotationAxis) -> Self {
196        val as u8
197    }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
201#[repr(u8)]
202pub enum StepDirection {
203    /// Decrease towards 0.00%
204    Decrease = 0,
205    /// Increase towards 100.00%
206    Increase = 1,
207}
208
209impl StepDirection {
210    /// Convert from u8 value
211    pub fn from_u8(value: u8) -> Option<Self> {
212        match value {
213            0 => Some(StepDirection::Decrease),
214            1 => Some(StepDirection::Increase),
215            _ => None,
216        }
217    }
218
219    /// Convert to u8 value
220    pub fn to_u8(self) -> u8 {
221        self as u8
222    }
223}
224
225impl From<StepDirection> for u8 {
226    fn from(val: StepDirection) -> Self {
227        val as u8
228    }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
232#[repr(u8)]
233pub enum TranslationDirection {
234    /// Downward translation
235    Downward = 0,
236    /// Upward translation
237    Upward = 1,
238    /// Vertical mask translation
239    Verticalmask = 2,
240    /// Vertical symmetry translation
241    Verticalsymmetry = 3,
242    /// Leftward translation
243    Leftward = 4,
244    /// Rightward translation
245    Rightward = 5,
246    /// Horizontal mask translation
247    Horizontalmask = 6,
248    /// Horizontal symmetry translation
249    Horizontalsymmetry = 7,
250    /// Forward translation
251    Forward = 8,
252    /// Backward translation
253    Backward = 9,
254    /// Depth mask translation
255    Depthmask = 10,
256    /// Depth symmetry translation
257    Depthsymmetry = 11,
258}
259
260impl TranslationDirection {
261    /// Convert from u8 value
262    pub fn from_u8(value: u8) -> Option<Self> {
263        match value {
264            0 => Some(TranslationDirection::Downward),
265            1 => Some(TranslationDirection::Upward),
266            2 => Some(TranslationDirection::Verticalmask),
267            3 => Some(TranslationDirection::Verticalsymmetry),
268            4 => Some(TranslationDirection::Leftward),
269            5 => Some(TranslationDirection::Rightward),
270            6 => Some(TranslationDirection::Horizontalmask),
271            7 => Some(TranslationDirection::Horizontalsymmetry),
272            8 => Some(TranslationDirection::Forward),
273            9 => Some(TranslationDirection::Backward),
274            10 => Some(TranslationDirection::Depthmask),
275            11 => Some(TranslationDirection::Depthsymmetry),
276            _ => None,
277        }
278    }
279
280    /// Convert to u8 value
281    pub fn to_u8(self) -> u8 {
282        self as u8
283    }
284}
285
286impl From<TranslationDirection> for u8 {
287    fn from(val: TranslationDirection) -> Self {
288        val as u8
289    }
290}
291
292// Bitmap definitions
293
294/// LatchControlModes bitmap type
295pub type LatchControlModes = u8;
296
297/// Constants for LatchControlModes
298pub mod latchcontrolmodes {
299    /// Remote latching capability
300    pub const REMOTE_LATCHING: u8 = 0x01;
301    /// Remote unlatching capability
302    pub const REMOTE_UNLATCHING: u8 = 0x02;
303}
304
305// Struct definitions
306
307#[derive(Debug, serde::Serialize)]
308pub struct DimensionState {
309    pub position: Option<u8>,
310    pub latch: Option<bool>,
311    pub speed: Option<u8>,
312}
313
314#[derive(Debug, serde::Serialize)]
315pub struct RangePercent {
316    pub min: Option<u8>,
317    pub max: Option<u8>,
318}
319
320#[derive(Debug, serde::Serialize)]
321pub struct UnitRange {
322    pub min: Option<i16>,
323    pub max: Option<i16>,
324}
325
326// Command encoders
327
328/// Encode SetTarget command (0x00)
329pub fn encode_set_target(position: u8, latch: bool, speed: u8) -> anyhow::Result<Vec<u8>> {
330    let tlv = tlv::TlvItemEnc {
331        tag: 0,
332        value: tlv::TlvItemValueEnc::StructInvisible(vec![
333        (0, tlv::TlvItemValueEnc::UInt8(position)).into(),
334        (1, tlv::TlvItemValueEnc::Bool(latch)).into(),
335        (2, tlv::TlvItemValueEnc::UInt8(speed)).into(),
336        ]),
337    };
338    Ok(tlv.encode()?)
339}
340
341/// Encode Step command (0x01)
342pub fn encode_step(direction: StepDirection, number_of_steps: u16, speed: u8) -> anyhow::Result<Vec<u8>> {
343    let tlv = tlv::TlvItemEnc {
344        tag: 0,
345        value: tlv::TlvItemValueEnc::StructInvisible(vec![
346        (0, tlv::TlvItemValueEnc::UInt8(direction.to_u8())).into(),
347        (1, tlv::TlvItemValueEnc::UInt16(number_of_steps)).into(),
348        (2, tlv::TlvItemValueEnc::UInt8(speed)).into(),
349        ]),
350    };
351    Ok(tlv.encode()?)
352}
353
354// Attribute decoders
355
356/// Decode CurrentState attribute (0x0000)
357pub fn decode_current_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<DimensionState>> {
358    if let tlv::TlvItemValue::List(_fields) = inp {
359        // Struct with fields
360        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
361        Ok(Some(DimensionState {
362                position: item.get_int(&[0]).map(|v| v as u8),
363                latch: item.get_bool(&[1]),
364                speed: item.get_int(&[2]).map(|v| v as u8),
365        }))
366    //} else if let tlv::TlvItemValue::Null = inp {
367    //    // Null value for nullable struct
368    //    Ok(None)
369    } else {
370    Ok(None)
371    //    Err(anyhow::anyhow!("Expected struct fields or null"))
372    }
373}
374
375/// Decode TargetState attribute (0x0001)
376pub fn decode_target_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<DimensionState>> {
377    if let tlv::TlvItemValue::List(_fields) = inp {
378        // Struct with fields
379        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
380        Ok(Some(DimensionState {
381                position: item.get_int(&[0]).map(|v| v as u8),
382                latch: item.get_bool(&[1]),
383                speed: item.get_int(&[2]).map(|v| v as u8),
384        }))
385    //} else if let tlv::TlvItemValue::Null = inp {
386    //    // Null value for nullable struct
387    //    Ok(None)
388    } else {
389    Ok(None)
390    //    Err(anyhow::anyhow!("Expected struct fields or null"))
391    }
392}
393
394/// Decode Resolution attribute (0x0002)
395pub fn decode_resolution(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
396    if let tlv::TlvItemValue::Int(v) = inp {
397        Ok(*v as u8)
398    } else {
399        Err(anyhow::anyhow!("Expected UInt8"))
400    }
401}
402
403/// Decode StepValue attribute (0x0003)
404pub fn decode_step_value(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
405    if let tlv::TlvItemValue::Int(v) = inp {
406        Ok(*v as u8)
407    } else {
408        Err(anyhow::anyhow!("Expected UInt8"))
409    }
410}
411
412/// Decode Unit attribute (0x0004)
413pub fn decode_unit(inp: &tlv::TlvItemValue) -> anyhow::Result<ClosureUnit> {
414    if let tlv::TlvItemValue::Int(v) = inp {
415        ClosureUnit::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
416    } else {
417        Err(anyhow::anyhow!("Expected Integer"))
418    }
419}
420
421/// Decode UnitRange attribute (0x0005)
422pub fn decode_unit_range(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<UnitRange>> {
423    if let tlv::TlvItemValue::List(_fields) = inp {
424        // Struct with fields
425        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
426        Ok(Some(UnitRange {
427                min: item.get_int(&[0]).map(|v| v as i16),
428                max: item.get_int(&[1]).map(|v| v as i16),
429        }))
430    //} else if let tlv::TlvItemValue::Null = inp {
431    //    // Null value for nullable struct
432    //    Ok(None)
433    } else {
434    Ok(None)
435    //    Err(anyhow::anyhow!("Expected struct fields or null"))
436    }
437}
438
439/// Decode LimitRange attribute (0x0006)
440pub fn decode_limit_range(inp: &tlv::TlvItemValue) -> anyhow::Result<RangePercent> {
441    if let tlv::TlvItemValue::List(_fields) = inp {
442        // Struct with fields
443        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
444        Ok(RangePercent {
445                min: item.get_int(&[0]).map(|v| v as u8),
446                max: item.get_int(&[1]).map(|v| v as u8),
447        })
448    } else {
449        Err(anyhow::anyhow!("Expected struct fields"))
450    }
451}
452
453/// Decode TranslationDirection attribute (0x0007)
454pub fn decode_translation_direction(inp: &tlv::TlvItemValue) -> anyhow::Result<TranslationDirection> {
455    if let tlv::TlvItemValue::Int(v) = inp {
456        TranslationDirection::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
457    } else {
458        Err(anyhow::anyhow!("Expected Integer"))
459    }
460}
461
462/// Decode RotationAxis attribute (0x0008)
463pub fn decode_rotation_axis(inp: &tlv::TlvItemValue) -> anyhow::Result<RotationAxis> {
464    if let tlv::TlvItemValue::Int(v) = inp {
465        RotationAxis::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
466    } else {
467        Err(anyhow::anyhow!("Expected Integer"))
468    }
469}
470
471/// Decode Overflow attribute (0x0009)
472pub fn decode_overflow(inp: &tlv::TlvItemValue) -> anyhow::Result<Overflow> {
473    if let tlv::TlvItemValue::Int(v) = inp {
474        Overflow::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
475    } else {
476        Err(anyhow::anyhow!("Expected Integer"))
477    }
478}
479
480/// Decode ModulationType attribute (0x000A)
481pub fn decode_modulation_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ModulationType> {
482    if let tlv::TlvItemValue::Int(v) = inp {
483        ModulationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
484    } else {
485        Err(anyhow::anyhow!("Expected Integer"))
486    }
487}
488
489/// Decode LatchControlModes attribute (0x000B)
490pub fn decode_latch_control_modes(inp: &tlv::TlvItemValue) -> anyhow::Result<LatchControlModes> {
491    if let tlv::TlvItemValue::Int(v) = inp {
492        Ok(*v as u8)
493    } else {
494        Err(anyhow::anyhow!("Expected Integer"))
495    }
496}
497
498
499// JSON dispatcher function
500
501/// Decode attribute value and return as JSON string
502///
503/// # Parameters
504/// * `cluster_id` - The cluster identifier
505/// * `attribute_id` - The attribute identifier
506/// * `tlv_value` - The TLV value to decode
507///
508/// # Returns
509/// JSON string representation of the decoded value or error
510pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
511    // Verify this is the correct cluster
512    if cluster_id != 0x0105 {
513        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0105, got {}\"}}", cluster_id);
514    }
515
516    match attribute_id {
517        0x0000 => {
518            match decode_current_state(tlv_value) {
519                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
520                Err(e) => format!("{{\"error\": \"{}\"}}", e),
521            }
522        }
523        0x0001 => {
524            match decode_target_state(tlv_value) {
525                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
526                Err(e) => format!("{{\"error\": \"{}\"}}", e),
527            }
528        }
529        0x0002 => {
530            match decode_resolution(tlv_value) {
531                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
532                Err(e) => format!("{{\"error\": \"{}\"}}", e),
533            }
534        }
535        0x0003 => {
536            match decode_step_value(tlv_value) {
537                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
538                Err(e) => format!("{{\"error\": \"{}\"}}", e),
539            }
540        }
541        0x0004 => {
542            match decode_unit(tlv_value) {
543                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
544                Err(e) => format!("{{\"error\": \"{}\"}}", e),
545            }
546        }
547        0x0005 => {
548            match decode_unit_range(tlv_value) {
549                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
550                Err(e) => format!("{{\"error\": \"{}\"}}", e),
551            }
552        }
553        0x0006 => {
554            match decode_limit_range(tlv_value) {
555                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
556                Err(e) => format!("{{\"error\": \"{}\"}}", e),
557            }
558        }
559        0x0007 => {
560            match decode_translation_direction(tlv_value) {
561                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
562                Err(e) => format!("{{\"error\": \"{}\"}}", e),
563            }
564        }
565        0x0008 => {
566            match decode_rotation_axis(tlv_value) {
567                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
568                Err(e) => format!("{{\"error\": \"{}\"}}", e),
569            }
570        }
571        0x0009 => {
572            match decode_overflow(tlv_value) {
573                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
574                Err(e) => format!("{{\"error\": \"{}\"}}", e),
575            }
576        }
577        0x000A => {
578            match decode_modulation_type(tlv_value) {
579                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
580                Err(e) => format!("{{\"error\": \"{}\"}}", e),
581            }
582        }
583        0x000B => {
584            match decode_latch_control_modes(tlv_value) {
585                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
586                Err(e) => format!("{{\"error\": \"{}\"}}", e),
587            }
588        }
589        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
590    }
591}
592
593/// Get list of all attributes supported by this cluster
594///
595/// # Returns
596/// Vector of tuples containing (attribute_id, attribute_name)
597pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
598    vec![
599        (0x0000, "CurrentState"),
600        (0x0001, "TargetState"),
601        (0x0002, "Resolution"),
602        (0x0003, "StepValue"),
603        (0x0004, "Unit"),
604        (0x0005, "UnitRange"),
605        (0x0006, "LimitRange"),
606        (0x0007, "TranslationDirection"),
607        (0x0008, "RotationAxis"),
608        (0x0009, "Overflow"),
609        (0x000A, "ModulationType"),
610        (0x000B, "LatchControlModes"),
611    ]
612}
613