matc/clusters/codec/
window_covering.rs

1//! Matter TLV encoders and decoders for Window Covering Cluster
2//! Cluster ID: 0x0102
3//!
4//! This file is automatically generated from WindowCovering.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 EndProductType {
16    /// Simple Roller Shade
17    Rollershade = 0,
18    /// Roman Shade
19    Romanshade = 1,
20    /// Balloon Shade
21    Balloonshade = 2,
22    /// Woven Wood
23    Wovenwood = 3,
24    /// Pleated Shade
25    Pleatedshade = 4,
26    /// Cellular Shade
27    Cellularshade = 5,
28    /// Layered Shade
29    Layeredshade = 6,
30    /// Layered Shade 2D
31    Layeredshade2d = 7,
32    /// Sheer Shade
33    Sheershade = 8,
34    /// Tilt Only Interior Blind
35    Tiltonlyinteriorblind = 9,
36    /// Interior Blind
37    Interiorblind = 10,
38    /// Vertical Blind, Strip Curtain
39    Verticalblindstripcurtain = 11,
40    /// Interior Venetian Blind
41    Interiorvenetianblind = 12,
42    /// Exterior Venetian Blind
43    Exteriorvenetianblind = 13,
44    /// Lateral Left Curtain
45    Lateralleftcurtain = 14,
46    /// Lateral Right Curtain
47    Lateralrightcurtain = 15,
48    /// Central Curtain
49    Centralcurtain = 16,
50    /// Roller Shutter
51    Rollershutter = 17,
52    /// Exterior Vertical Screen
53    Exteriorverticalscreen = 18,
54    /// Awning Terrace (Patio)
55    Awningterracepatio = 19,
56    /// Awning Vertical Screen
57    Awningverticalscreen = 20,
58    /// Tilt Only Pergola
59    Tiltonlypergola = 21,
60    /// Swinging Shutter
61    Swingingshutter = 22,
62    /// Sliding Shutter
63    Slidingshutter = 23,
64    /// Unknown
65    Unknown = 255,
66}
67
68impl EndProductType {
69    /// Convert from u8 value
70    pub fn from_u8(value: u8) -> Option<Self> {
71        match value {
72            0 => Some(EndProductType::Rollershade),
73            1 => Some(EndProductType::Romanshade),
74            2 => Some(EndProductType::Balloonshade),
75            3 => Some(EndProductType::Wovenwood),
76            4 => Some(EndProductType::Pleatedshade),
77            5 => Some(EndProductType::Cellularshade),
78            6 => Some(EndProductType::Layeredshade),
79            7 => Some(EndProductType::Layeredshade2d),
80            8 => Some(EndProductType::Sheershade),
81            9 => Some(EndProductType::Tiltonlyinteriorblind),
82            10 => Some(EndProductType::Interiorblind),
83            11 => Some(EndProductType::Verticalblindstripcurtain),
84            12 => Some(EndProductType::Interiorvenetianblind),
85            13 => Some(EndProductType::Exteriorvenetianblind),
86            14 => Some(EndProductType::Lateralleftcurtain),
87            15 => Some(EndProductType::Lateralrightcurtain),
88            16 => Some(EndProductType::Centralcurtain),
89            17 => Some(EndProductType::Rollershutter),
90            18 => Some(EndProductType::Exteriorverticalscreen),
91            19 => Some(EndProductType::Awningterracepatio),
92            20 => Some(EndProductType::Awningverticalscreen),
93            21 => Some(EndProductType::Tiltonlypergola),
94            22 => Some(EndProductType::Swingingshutter),
95            23 => Some(EndProductType::Slidingshutter),
96            255 => Some(EndProductType::Unknown),
97            _ => None,
98        }
99    }
100
101    /// Convert to u8 value
102    pub fn to_u8(self) -> u8 {
103        self as u8
104    }
105}
106
107impl From<EndProductType> for u8 {
108    fn from(val: EndProductType) -> Self {
109        val as u8
110    }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
114#[repr(u8)]
115pub enum Type {
116    /// RollerShade
117    Rollershade = 0,
118    /// RollerShade - 2 Motor
119    Rollershade2motor = 1,
120    /// RollerShade - Exterior
121    Rollershadeexterior = 2,
122    /// RollerShade - Exterior - 2 Motor
123    Rollershadeexterior2motor = 3,
124    /// Drapery (curtain)
125    Drapery = 4,
126    /// Awning
127    Awning = 5,
128    /// Shutter
129    Shutter = 6,
130    /// Tilt Blind - Tilt Only
131    Tiltblindtiltonly = 7,
132    /// Tilt Blind - Lift & Tilt
133    Tiltblindliftandtilt = 8,
134    /// Projector Screen
135    Projectorscreen = 9,
136    /// Unknown
137    Unknown = 255,
138}
139
140impl Type {
141    /// Convert from u8 value
142    pub fn from_u8(value: u8) -> Option<Self> {
143        match value {
144            0 => Some(Type::Rollershade),
145            1 => Some(Type::Rollershade2motor),
146            2 => Some(Type::Rollershadeexterior),
147            3 => Some(Type::Rollershadeexterior2motor),
148            4 => Some(Type::Drapery),
149            5 => Some(Type::Awning),
150            6 => Some(Type::Shutter),
151            7 => Some(Type::Tiltblindtiltonly),
152            8 => Some(Type::Tiltblindliftandtilt),
153            9 => Some(Type::Projectorscreen),
154            255 => Some(Type::Unknown),
155            _ => None,
156        }
157    }
158
159    /// Convert to u8 value
160    pub fn to_u8(self) -> u8 {
161        self as u8
162    }
163}
164
165impl From<Type> for u8 {
166    fn from(val: Type) -> Self {
167        val as u8
168    }
169}
170
171// Bitmap definitions
172
173/// ConfigStatus bitmap type
174pub type ConfigStatus = u8;
175
176/// Constants for ConfigStatus
177pub mod configstatus {
178    /// Device is operational.
179    pub const OPERATIONAL: u8 = 0x01;
180    pub const ONLINE_RESERVED: u8 = 0x02;
181    /// The lift movement is reversed.
182    pub const LIFT_MOVEMENT_REVERSED: u8 = 0x04;
183    /// Supports the PositionAwareLift feature (PA_LF).
184    pub const LIFT_POSITION_AWARE: u8 = 0x08;
185    /// Supports the PositionAwareTilt feature (PA_TL).
186    pub const TILT_POSITION_AWARE: u8 = 0x10;
187    /// Uses an encoder for lift.
188    pub const LIFT_ENCODER_CONTROLLED: u8 = 0x20;
189    /// Uses an encoder for tilt.
190    pub const TILT_ENCODER_CONTROLLED: u8 = 0x40;
191}
192
193/// Mode bitmap type
194pub type Mode = u8;
195
196/// Constants for Mode
197pub mod mode {
198    /// Reverse the lift direction.
199    pub const MOTOR_DIRECTION_REVERSED: u8 = 0x01;
200    /// Perform a calibration.
201    pub const CALIBRATION_MODE: u8 = 0x02;
202    /// Freeze all motions for maintenance.
203    pub const MAINTENANCE_MODE: u8 = 0x04;
204    /// Control the LEDs feedback.
205    pub const LED_FEEDBACK: u8 = 0x08;
206}
207
208/// OperationalStatus bitmap type
209pub type OperationalStatus = u8;
210
211/// SafetyStatus bitmap type
212pub type SafetyStatus = u16;
213
214/// Constants for SafetyStatus
215pub mod safetystatus {
216    /// Movement commands are ignored (locked out). e.g. not granted authorization, outside some time/date range.
217    pub const REMOTE_LOCKOUT: u16 = 0x01;
218    /// Tampering detected on sensors or any other safety equipment. Ex: a device has been forcedly moved without its actuator(s).
219    pub const TAMPER_DETECTION: u16 = 0x02;
220    /// Communication failure to sensors or other safety equipment.
221    pub const FAILED_COMMUNICATION: u16 = 0x04;
222    /// Device has failed to reach the desired position. e.g. with position aware device, time expired before TargetPosition is reached.
223    pub const POSITION_FAILURE: u16 = 0x08;
224    /// Motor(s) and/or electric circuit thermal protection activated.
225    pub const THERMAL_PROTECTION: u16 = 0x10;
226    /// An obstacle is preventing actuator movement.
227    pub const OBSTACLE_DETECTED: u16 = 0x20;
228    /// Device has power related issue or limitation e.g. device is running w/ the help of a backup battery or power might not be fully available at the moment.
229    pub const POWER: u16 = 0x40;
230    /// Local safety sensor (not a direct obstacle) is preventing movements (e.g. Safety EU Standard EN60335).
231    pub const STOP_INPUT: u16 = 0x80;
232    /// Mechanical problem related to the motor(s) detected.
233    pub const MOTOR_JAMMED: u16 = 0x100;
234    /// PCB, fuse and other electrics problems.
235    pub const HARDWARE_FAILURE: u16 = 0x200;
236    /// Actuator is manually operated and is preventing actuator movement (e.g. actuator is disengaged/decoupled).
237    pub const MANUAL_OPERATION: u16 = 0x400;
238    /// Protection is activated.
239    pub const PROTECTION: u16 = 0x800;
240}
241
242// Command encoders
243
244/// Encode GoToLiftPercentage command (0x05)
245pub fn encode_go_to_lift_percentage(lift_percent100ths_value: u8) -> anyhow::Result<Vec<u8>> {
246    let tlv = tlv::TlvItemEnc {
247        tag: 0,
248        value: tlv::TlvItemValueEnc::StructInvisible(vec![
249        (0, tlv::TlvItemValueEnc::UInt8(lift_percent100ths_value)).into(),
250        ]),
251    };
252    Ok(tlv.encode()?)
253}
254
255/// Encode GoToTiltPercentage command (0x08)
256pub fn encode_go_to_tilt_percentage(tilt_percent100ths_value: u8) -> anyhow::Result<Vec<u8>> {
257    let tlv = tlv::TlvItemEnc {
258        tag: 0,
259        value: tlv::TlvItemValueEnc::StructInvisible(vec![
260        (0, tlv::TlvItemValueEnc::UInt8(tilt_percent100ths_value)).into(),
261        ]),
262    };
263    Ok(tlv.encode()?)
264}
265
266// Attribute decoders
267
268/// Decode Type attribute (0x0000)
269pub fn decode_type_(inp: &tlv::TlvItemValue) -> anyhow::Result<Type> {
270    if let tlv::TlvItemValue::Int(v) = inp {
271        Type::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
272    } else {
273        Err(anyhow::anyhow!("Expected Integer"))
274    }
275}
276
277/// Decode NumberOfActuationsLift attribute (0x0005)
278pub fn decode_number_of_actuations_lift(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
279    if let tlv::TlvItemValue::Int(v) = inp {
280        Ok(*v as u16)
281    } else {
282        Err(anyhow::anyhow!("Expected UInt16"))
283    }
284}
285
286/// Decode NumberOfActuationsTilt attribute (0x0006)
287pub fn decode_number_of_actuations_tilt(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
288    if let tlv::TlvItemValue::Int(v) = inp {
289        Ok(*v as u16)
290    } else {
291        Err(anyhow::anyhow!("Expected UInt16"))
292    }
293}
294
295/// Decode ConfigStatus attribute (0x0007)
296pub fn decode_config_status(inp: &tlv::TlvItemValue) -> anyhow::Result<ConfigStatus> {
297    if let tlv::TlvItemValue::Int(v) = inp {
298        Ok(*v as u8)
299    } else {
300        Err(anyhow::anyhow!("Expected Integer"))
301    }
302}
303
304/// Decode CurrentPositionLiftPercentage attribute (0x0008)
305pub fn decode_current_position_lift_percentage(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        Ok(Some(*v as u8))
308    } else {
309        Ok(None)
310    }
311}
312
313/// Decode CurrentPositionTiltPercentage attribute (0x0009)
314pub fn decode_current_position_tilt_percentage(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
315    if let tlv::TlvItemValue::Int(v) = inp {
316        Ok(Some(*v as u8))
317    } else {
318        Ok(None)
319    }
320}
321
322/// Decode OperationalStatus attribute (0x000A)
323pub fn decode_operational_status(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalStatus> {
324    if let tlv::TlvItemValue::Int(v) = inp {
325        Ok(*v as u8)
326    } else {
327        Err(anyhow::anyhow!("Expected Integer"))
328    }
329}
330
331/// Decode TargetPositionLiftPercent100ths attribute (0x000B)
332pub fn decode_target_position_lift_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
333    if let tlv::TlvItemValue::Int(v) = inp {
334        Ok(Some(*v as u8))
335    } else {
336        Ok(None)
337    }
338}
339
340/// Decode TargetPositionTiltPercent100ths attribute (0x000C)
341pub fn decode_target_position_tilt_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
342    if let tlv::TlvItemValue::Int(v) = inp {
343        Ok(Some(*v as u8))
344    } else {
345        Ok(None)
346    }
347}
348
349/// Decode EndProductType attribute (0x000D)
350pub fn decode_end_product_type(inp: &tlv::TlvItemValue) -> anyhow::Result<EndProductType> {
351    if let tlv::TlvItemValue::Int(v) = inp {
352        EndProductType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
353    } else {
354        Err(anyhow::anyhow!("Expected Integer"))
355    }
356}
357
358/// Decode CurrentPositionLiftPercent100ths attribute (0x000E)
359pub fn decode_current_position_lift_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
360    if let tlv::TlvItemValue::Int(v) = inp {
361        Ok(Some(*v as u8))
362    } else {
363        Ok(None)
364    }
365}
366
367/// Decode CurrentPositionTiltPercent100ths attribute (0x000F)
368pub fn decode_current_position_tilt_percent100ths(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
369    if let tlv::TlvItemValue::Int(v) = inp {
370        Ok(Some(*v as u8))
371    } else {
372        Ok(None)
373    }
374}
375
376/// Decode Mode attribute (0x0017)
377pub fn decode_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<Mode> {
378    if let tlv::TlvItemValue::Int(v) = inp {
379        Ok(*v as u8)
380    } else {
381        Err(anyhow::anyhow!("Expected Integer"))
382    }
383}
384
385/// Decode SafetyStatus attribute (0x001A)
386pub fn decode_safety_status(inp: &tlv::TlvItemValue) -> anyhow::Result<SafetyStatus> {
387    if let tlv::TlvItemValue::Int(v) = inp {
388        Ok(*v as u16)
389    } else {
390        Err(anyhow::anyhow!("Expected Integer"))
391    }
392}
393
394
395// JSON dispatcher function
396
397/// Decode attribute value and return as JSON string
398///
399/// # Parameters
400/// * `cluster_id` - The cluster identifier
401/// * `attribute_id` - The attribute identifier
402/// * `tlv_value` - The TLV value to decode
403///
404/// # Returns
405/// JSON string representation of the decoded value or error
406pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
407    // Verify this is the correct cluster
408    if cluster_id != 0x0102 {
409        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0102, got {}\"}}", cluster_id);
410    }
411
412    match attribute_id {
413        0x0000 => {
414            match decode_type_(tlv_value) {
415                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
416                Err(e) => format!("{{\"error\": \"{}\"}}", e),
417            }
418        }
419        0x0005 => {
420            match decode_number_of_actuations_lift(tlv_value) {
421                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
422                Err(e) => format!("{{\"error\": \"{}\"}}", e),
423            }
424        }
425        0x0006 => {
426            match decode_number_of_actuations_tilt(tlv_value) {
427                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
428                Err(e) => format!("{{\"error\": \"{}\"}}", e),
429            }
430        }
431        0x0007 => {
432            match decode_config_status(tlv_value) {
433                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
434                Err(e) => format!("{{\"error\": \"{}\"}}", e),
435            }
436        }
437        0x0008 => {
438            match decode_current_position_lift_percentage(tlv_value) {
439                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
440                Err(e) => format!("{{\"error\": \"{}\"}}", e),
441            }
442        }
443        0x0009 => {
444            match decode_current_position_tilt_percentage(tlv_value) {
445                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
446                Err(e) => format!("{{\"error\": \"{}\"}}", e),
447            }
448        }
449        0x000A => {
450            match decode_operational_status(tlv_value) {
451                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
452                Err(e) => format!("{{\"error\": \"{}\"}}", e),
453            }
454        }
455        0x000B => {
456            match decode_target_position_lift_percent100ths(tlv_value) {
457                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
458                Err(e) => format!("{{\"error\": \"{}\"}}", e),
459            }
460        }
461        0x000C => {
462            match decode_target_position_tilt_percent100ths(tlv_value) {
463                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
464                Err(e) => format!("{{\"error\": \"{}\"}}", e),
465            }
466        }
467        0x000D => {
468            match decode_end_product_type(tlv_value) {
469                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
470                Err(e) => format!("{{\"error\": \"{}\"}}", e),
471            }
472        }
473        0x000E => {
474            match decode_current_position_lift_percent100ths(tlv_value) {
475                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
476                Err(e) => format!("{{\"error\": \"{}\"}}", e),
477            }
478        }
479        0x000F => {
480            match decode_current_position_tilt_percent100ths(tlv_value) {
481                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
482                Err(e) => format!("{{\"error\": \"{}\"}}", e),
483            }
484        }
485        0x0017 => {
486            match decode_mode(tlv_value) {
487                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
488                Err(e) => format!("{{\"error\": \"{}\"}}", e),
489            }
490        }
491        0x001A => {
492            match decode_safety_status(tlv_value) {
493                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
494                Err(e) => format!("{{\"error\": \"{}\"}}", e),
495            }
496        }
497        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
498    }
499}
500
501/// Get list of all attributes supported by this cluster
502///
503/// # Returns
504/// Vector of tuples containing (attribute_id, attribute_name)
505pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
506    vec![
507        (0x0000, "Type"),
508        (0x0005, "NumberOfActuationsLift"),
509        (0x0006, "NumberOfActuationsTilt"),
510        (0x0007, "ConfigStatus"),
511        (0x0008, "CurrentPositionLiftPercentage"),
512        (0x0009, "CurrentPositionTiltPercentage"),
513        (0x000A, "OperationalStatus"),
514        (0x000B, "TargetPositionLiftPercent100ths"),
515        (0x000C, "TargetPositionTiltPercent100ths"),
516        (0x000D, "EndProductType"),
517        (0x000E, "CurrentPositionLiftPercent100ths"),
518        (0x000F, "CurrentPositionTiltPercent100ths"),
519        (0x0017, "Mode"),
520        (0x001A, "SafetyStatus"),
521    ]
522}
523