matc/clusters/codec/
fan_control.rs

1//! Matter TLV encoders and decoders for Fan Control Cluster
2//! Cluster ID: 0x0202
3//!
4//! This file is automatically generated from FanControl.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 AirflowDirection {
16    /// Airflow is in the forward direction
17    Forward = 0,
18    /// Airflow is in the reverse direction
19    Reverse = 1,
20}
21
22impl AirflowDirection {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(AirflowDirection::Forward),
27            1 => Some(AirflowDirection::Reverse),
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<AirflowDirection> for u8 {
39    fn from(val: AirflowDirection) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum FanMode {
47    /// Fan is off
48    Off = 0,
49    /// Fan using low speed
50    Low = 1,
51    /// Fan using medium speed
52    Medium = 2,
53    /// Fan using high speed
54    High = 3,
55    On = 4,
56    /// Fan is using auto mode
57    Auto = 5,
58    /// Fan is using smart mode
59    Smart = 6,
60}
61
62impl FanMode {
63    /// Convert from u8 value
64    pub fn from_u8(value: u8) -> Option<Self> {
65        match value {
66            0 => Some(FanMode::Off),
67            1 => Some(FanMode::Low),
68            2 => Some(FanMode::Medium),
69            3 => Some(FanMode::High),
70            4 => Some(FanMode::On),
71            5 => Some(FanMode::Auto),
72            6 => Some(FanMode::Smart),
73            _ => None,
74        }
75    }
76
77    /// Convert to u8 value
78    pub fn to_u8(self) -> u8 {
79        self as u8
80    }
81}
82
83impl From<FanMode> for u8 {
84    fn from(val: FanMode) -> Self {
85        val as u8
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[repr(u8)]
91pub enum FanModeSequence {
92    /// Fan is capable of off, low, medium and high modes
93    Offlowmedhigh = 0,
94    /// Fan is capable of off, low and high modes
95    Offlowhigh = 1,
96    /// Fan is capable of off, low, medium, high and auto modes
97    Offlowmedhighauto = 2,
98    /// Fan is capable of off, low, high and auto modes
99    Offlowhighauto = 3,
100    /// Fan is capable of off, high and auto modes
101    Offhighauto = 4,
102    /// Fan is capable of off and high modes
103    Offhigh = 5,
104}
105
106impl FanModeSequence {
107    /// Convert from u8 value
108    pub fn from_u8(value: u8) -> Option<Self> {
109        match value {
110            0 => Some(FanModeSequence::Offlowmedhigh),
111            1 => Some(FanModeSequence::Offlowhigh),
112            2 => Some(FanModeSequence::Offlowmedhighauto),
113            3 => Some(FanModeSequence::Offlowhighauto),
114            4 => Some(FanModeSequence::Offhighauto),
115            5 => Some(FanModeSequence::Offhigh),
116            _ => None,
117        }
118    }
119
120    /// Convert to u8 value
121    pub fn to_u8(self) -> u8 {
122        self as u8
123    }
124}
125
126impl From<FanModeSequence> for u8 {
127    fn from(val: FanModeSequence) -> Self {
128        val as u8
129    }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
133#[repr(u8)]
134pub enum StepDirection {
135    /// Step moves in increasing direction
136    Increase = 0,
137    /// Step moves in decreasing direction
138    Decrease = 1,
139}
140
141impl StepDirection {
142    /// Convert from u8 value
143    pub fn from_u8(value: u8) -> Option<Self> {
144        match value {
145            0 => Some(StepDirection::Increase),
146            1 => Some(StepDirection::Decrease),
147            _ => None,
148        }
149    }
150
151    /// Convert to u8 value
152    pub fn to_u8(self) -> u8 {
153        self as u8
154    }
155}
156
157impl From<StepDirection> for u8 {
158    fn from(val: StepDirection) -> Self {
159        val as u8
160    }
161}
162
163// Bitmap definitions
164
165/// Rock bitmap type
166pub type Rock = u8;
167
168/// Constants for Rock
169pub mod rock {
170    /// Indicate rock left to right
171    pub const ROCK_LEFT_RIGHT: u8 = 0x01;
172    /// Indicate rock up and down
173    pub const ROCK_UP_DOWN: u8 = 0x02;
174    /// Indicate rock around
175    pub const ROCK_ROUND: u8 = 0x04;
176}
177
178/// Wind bitmap type
179pub type Wind = u8;
180
181/// Constants for Wind
182pub mod wind {
183    /// Indicate sleep wind
184    pub const SLEEP_WIND: u8 = 0x01;
185    /// Indicate natural wind
186    pub const NATURAL_WIND: u8 = 0x02;
187}
188
189// Command encoders
190
191/// Encode Step command (0x00)
192pub fn encode_step(direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<Vec<u8>> {
193    let tlv = tlv::TlvItemEnc {
194        tag: 0,
195        value: tlv::TlvItemValueEnc::StructInvisible(vec![
196        (0, tlv::TlvItemValueEnc::UInt8(direction.to_u8())).into(),
197        (1, tlv::TlvItemValueEnc::Bool(wrap)).into(),
198        (2, tlv::TlvItemValueEnc::Bool(lowest_off)).into(),
199        ]),
200    };
201    Ok(tlv.encode()?)
202}
203
204// Attribute decoders
205
206/// Decode FanMode attribute (0x0000)
207pub fn decode_fan_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<FanMode> {
208    if let tlv::TlvItemValue::Int(v) = inp {
209        FanMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
210    } else {
211        Err(anyhow::anyhow!("Expected Integer"))
212    }
213}
214
215/// Decode FanModeSequence attribute (0x0001)
216pub fn decode_fan_mode_sequence(inp: &tlv::TlvItemValue) -> anyhow::Result<FanModeSequence> {
217    if let tlv::TlvItemValue::Int(v) = inp {
218        FanModeSequence::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
219    } else {
220        Err(anyhow::anyhow!("Expected Integer"))
221    }
222}
223
224/// Decode PercentSetting attribute (0x0002)
225pub fn decode_percent_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
226    if let tlv::TlvItemValue::Int(v) = inp {
227        Ok(Some(*v as u8))
228    } else {
229        Ok(None)
230    }
231}
232
233/// Decode PercentCurrent attribute (0x0003)
234pub fn decode_percent_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
235    if let tlv::TlvItemValue::Int(v) = inp {
236        Ok(*v as u8)
237    } else {
238        Err(anyhow::anyhow!("Expected UInt8"))
239    }
240}
241
242/// Decode SpeedMax attribute (0x0004)
243pub fn decode_speed_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
244    if let tlv::TlvItemValue::Int(v) = inp {
245        Ok(*v as u8)
246    } else {
247        Err(anyhow::anyhow!("Expected UInt8"))
248    }
249}
250
251/// Decode SpeedSetting attribute (0x0005)
252pub fn decode_speed_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
253    if let tlv::TlvItemValue::Int(v) = inp {
254        Ok(Some(*v as u8))
255    } else {
256        Ok(None)
257    }
258}
259
260/// Decode SpeedCurrent attribute (0x0006)
261pub fn decode_speed_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
262    if let tlv::TlvItemValue::Int(v) = inp {
263        Ok(*v as u8)
264    } else {
265        Err(anyhow::anyhow!("Expected UInt8"))
266    }
267}
268
269/// Decode RockSupport attribute (0x0007)
270pub fn decode_rock_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
271    if let tlv::TlvItemValue::Int(v) = inp {
272        Ok(*v as u8)
273    } else {
274        Err(anyhow::anyhow!("Expected Integer"))
275    }
276}
277
278/// Decode RockSetting attribute (0x0008)
279pub fn decode_rock_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
280    if let tlv::TlvItemValue::Int(v) = inp {
281        Ok(*v as u8)
282    } else {
283        Err(anyhow::anyhow!("Expected Integer"))
284    }
285}
286
287/// Decode WindSupport attribute (0x0009)
288pub fn decode_wind_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
289    if let tlv::TlvItemValue::Int(v) = inp {
290        Ok(*v as u8)
291    } else {
292        Err(anyhow::anyhow!("Expected Integer"))
293    }
294}
295
296/// Decode WindSetting attribute (0x000A)
297pub fn decode_wind_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
298    if let tlv::TlvItemValue::Int(v) = inp {
299        Ok(*v as u8)
300    } else {
301        Err(anyhow::anyhow!("Expected Integer"))
302    }
303}
304
305/// Decode AirflowDirection attribute (0x000B)
306pub fn decode_airflow_direction(inp: &tlv::TlvItemValue) -> anyhow::Result<AirflowDirection> {
307    if let tlv::TlvItemValue::Int(v) = inp {
308        AirflowDirection::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
309    } else {
310        Err(anyhow::anyhow!("Expected Integer"))
311    }
312}
313
314
315// JSON dispatcher function
316
317/// Decode attribute value and return as JSON string
318///
319/// # Parameters
320/// * `cluster_id` - The cluster identifier
321/// * `attribute_id` - The attribute identifier
322/// * `tlv_value` - The TLV value to decode
323///
324/// # Returns
325/// JSON string representation of the decoded value or error
326pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
327    // Verify this is the correct cluster
328    if cluster_id != 0x0202 {
329        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0202, got {}\"}}", cluster_id);
330    }
331
332    match attribute_id {
333        0x0000 => {
334            match decode_fan_mode(tlv_value) {
335                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
336                Err(e) => format!("{{\"error\": \"{}\"}}", e),
337            }
338        }
339        0x0001 => {
340            match decode_fan_mode_sequence(tlv_value) {
341                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
342                Err(e) => format!("{{\"error\": \"{}\"}}", e),
343            }
344        }
345        0x0002 => {
346            match decode_percent_setting(tlv_value) {
347                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
348                Err(e) => format!("{{\"error\": \"{}\"}}", e),
349            }
350        }
351        0x0003 => {
352            match decode_percent_current(tlv_value) {
353                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
354                Err(e) => format!("{{\"error\": \"{}\"}}", e),
355            }
356        }
357        0x0004 => {
358            match decode_speed_max(tlv_value) {
359                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
360                Err(e) => format!("{{\"error\": \"{}\"}}", e),
361            }
362        }
363        0x0005 => {
364            match decode_speed_setting(tlv_value) {
365                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
366                Err(e) => format!("{{\"error\": \"{}\"}}", e),
367            }
368        }
369        0x0006 => {
370            match decode_speed_current(tlv_value) {
371                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
372                Err(e) => format!("{{\"error\": \"{}\"}}", e),
373            }
374        }
375        0x0007 => {
376            match decode_rock_support(tlv_value) {
377                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
378                Err(e) => format!("{{\"error\": \"{}\"}}", e),
379            }
380        }
381        0x0008 => {
382            match decode_rock_setting(tlv_value) {
383                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
384                Err(e) => format!("{{\"error\": \"{}\"}}", e),
385            }
386        }
387        0x0009 => {
388            match decode_wind_support(tlv_value) {
389                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
390                Err(e) => format!("{{\"error\": \"{}\"}}", e),
391            }
392        }
393        0x000A => {
394            match decode_wind_setting(tlv_value) {
395                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
396                Err(e) => format!("{{\"error\": \"{}\"}}", e),
397            }
398        }
399        0x000B => {
400            match decode_airflow_direction(tlv_value) {
401                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
402                Err(e) => format!("{{\"error\": \"{}\"}}", e),
403            }
404        }
405        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
406    }
407}
408
409/// Get list of all attributes supported by this cluster
410///
411/// # Returns
412/// Vector of tuples containing (attribute_id, attribute_name)
413pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
414    vec![
415        (0x0000, "FanMode"),
416        (0x0001, "FanModeSequence"),
417        (0x0002, "PercentSetting"),
418        (0x0003, "PercentCurrent"),
419        (0x0004, "SpeedMax"),
420        (0x0005, "SpeedSetting"),
421        (0x0006, "SpeedCurrent"),
422        (0x0007, "RockSupport"),
423        (0x0008, "RockSetting"),
424        (0x0009, "WindSupport"),
425        (0x000A, "WindSetting"),
426        (0x000B, "AirflowDirection"),
427    ]
428}
429