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
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 AirflowDirection {
18    /// Airflow is in the forward direction
19    Forward = 0,
20    /// Airflow is in the reverse direction
21    Reverse = 1,
22}
23
24impl AirflowDirection {
25    /// Convert from u8 value
26    pub fn from_u8(value: u8) -> Option<Self> {
27        match value {
28            0 => Some(AirflowDirection::Forward),
29            1 => Some(AirflowDirection::Reverse),
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<AirflowDirection> for u8 {
41    fn from(val: AirflowDirection) -> Self {
42        val as u8
43    }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum FanMode {
49    /// Fan is off
50    Off = 0,
51    /// Fan using low speed
52    Low = 1,
53    /// Fan using medium speed
54    Medium = 2,
55    /// Fan using high speed
56    High = 3,
57    On = 4,
58    /// Fan is using auto mode
59    Auto = 5,
60    /// Fan is using smart mode
61    Smart = 6,
62}
63
64impl FanMode {
65    /// Convert from u8 value
66    pub fn from_u8(value: u8) -> Option<Self> {
67        match value {
68            0 => Some(FanMode::Off),
69            1 => Some(FanMode::Low),
70            2 => Some(FanMode::Medium),
71            3 => Some(FanMode::High),
72            4 => Some(FanMode::On),
73            5 => Some(FanMode::Auto),
74            6 => Some(FanMode::Smart),
75            _ => None,
76        }
77    }
78
79    /// Convert to u8 value
80    pub fn to_u8(self) -> u8 {
81        self as u8
82    }
83}
84
85impl From<FanMode> for u8 {
86    fn from(val: FanMode) -> Self {
87        val as u8
88    }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
92#[repr(u8)]
93pub enum FanModeSequence {
94    /// Fan is capable of off, low, medium and high modes
95    Offlowmedhigh = 0,
96    /// Fan is capable of off, low and high modes
97    Offlowhigh = 1,
98    /// Fan is capable of off, low, medium, high and auto modes
99    Offlowmedhighauto = 2,
100    /// Fan is capable of off, low, high and auto modes
101    Offlowhighauto = 3,
102    /// Fan is capable of off, high and auto modes
103    Offhighauto = 4,
104    /// Fan is capable of off and high modes
105    Offhigh = 5,
106}
107
108impl FanModeSequence {
109    /// Convert from u8 value
110    pub fn from_u8(value: u8) -> Option<Self> {
111        match value {
112            0 => Some(FanModeSequence::Offlowmedhigh),
113            1 => Some(FanModeSequence::Offlowhigh),
114            2 => Some(FanModeSequence::Offlowmedhighauto),
115            3 => Some(FanModeSequence::Offlowhighauto),
116            4 => Some(FanModeSequence::Offhighauto),
117            5 => Some(FanModeSequence::Offhigh),
118            _ => None,
119        }
120    }
121
122    /// Convert to u8 value
123    pub fn to_u8(self) -> u8 {
124        self as u8
125    }
126}
127
128impl From<FanModeSequence> for u8 {
129    fn from(val: FanModeSequence) -> Self {
130        val as u8
131    }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
135#[repr(u8)]
136pub enum StepDirection {
137    /// Step moves in increasing direction
138    Increase = 0,
139    /// Step moves in decreasing direction
140    Decrease = 1,
141}
142
143impl StepDirection {
144    /// Convert from u8 value
145    pub fn from_u8(value: u8) -> Option<Self> {
146        match value {
147            0 => Some(StepDirection::Increase),
148            1 => Some(StepDirection::Decrease),
149            _ => None,
150        }
151    }
152
153    /// Convert to u8 value
154    pub fn to_u8(self) -> u8 {
155        self as u8
156    }
157}
158
159impl From<StepDirection> for u8 {
160    fn from(val: StepDirection) -> Self {
161        val as u8
162    }
163}
164
165// Bitmap definitions
166
167/// Rock bitmap type
168pub type Rock = u8;
169
170/// Constants for Rock
171pub mod rock {
172    /// Indicate rock left to right
173    pub const ROCK_LEFT_RIGHT: u8 = 0x01;
174    /// Indicate rock up and down
175    pub const ROCK_UP_DOWN: u8 = 0x02;
176    /// Indicate rock around
177    pub const ROCK_ROUND: u8 = 0x04;
178}
179
180/// Wind bitmap type
181pub type Wind = u8;
182
183/// Constants for Wind
184pub mod wind {
185    /// Indicate sleep wind
186    pub const SLEEP_WIND: u8 = 0x01;
187    /// Indicate natural wind
188    pub const NATURAL_WIND: u8 = 0x02;
189}
190
191// Command encoders
192
193/// Encode Step command (0x00)
194pub fn encode_step(direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<Vec<u8>> {
195    let tlv = tlv::TlvItemEnc {
196        tag: 0,
197        value: tlv::TlvItemValueEnc::StructInvisible(vec![
198        (0, tlv::TlvItemValueEnc::UInt8(direction.to_u8())).into(),
199        (1, tlv::TlvItemValueEnc::Bool(wrap)).into(),
200        (2, tlv::TlvItemValueEnc::Bool(lowest_off)).into(),
201        ]),
202    };
203    Ok(tlv.encode()?)
204}
205
206// Attribute decoders
207
208/// Decode FanMode attribute (0x0000)
209pub fn decode_fan_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<FanMode> {
210    if let tlv::TlvItemValue::Int(v) = inp {
211        FanMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
212    } else {
213        Err(anyhow::anyhow!("Expected Integer"))
214    }
215}
216
217/// Decode FanModeSequence attribute (0x0001)
218pub fn decode_fan_mode_sequence(inp: &tlv::TlvItemValue) -> anyhow::Result<FanModeSequence> {
219    if let tlv::TlvItemValue::Int(v) = inp {
220        FanModeSequence::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
221    } else {
222        Err(anyhow::anyhow!("Expected Integer"))
223    }
224}
225
226/// Decode PercentSetting attribute (0x0002)
227pub fn decode_percent_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
228    if let tlv::TlvItemValue::Int(v) = inp {
229        Ok(Some(*v as u8))
230    } else {
231        Ok(None)
232    }
233}
234
235/// Decode PercentCurrent attribute (0x0003)
236pub fn decode_percent_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
237    if let tlv::TlvItemValue::Int(v) = inp {
238        Ok(*v as u8)
239    } else {
240        Err(anyhow::anyhow!("Expected UInt8"))
241    }
242}
243
244/// Decode SpeedMax attribute (0x0004)
245pub fn decode_speed_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
246    if let tlv::TlvItemValue::Int(v) = inp {
247        Ok(*v as u8)
248    } else {
249        Err(anyhow::anyhow!("Expected UInt8"))
250    }
251}
252
253/// Decode SpeedSetting attribute (0x0005)
254pub fn decode_speed_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
255    if let tlv::TlvItemValue::Int(v) = inp {
256        Ok(Some(*v as u8))
257    } else {
258        Ok(None)
259    }
260}
261
262/// Decode SpeedCurrent attribute (0x0006)
263pub fn decode_speed_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
264    if let tlv::TlvItemValue::Int(v) = inp {
265        Ok(*v as u8)
266    } else {
267        Err(anyhow::anyhow!("Expected UInt8"))
268    }
269}
270
271/// Decode RockSupport attribute (0x0007)
272pub fn decode_rock_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
273    if let tlv::TlvItemValue::Int(v) = inp {
274        Ok(*v as u8)
275    } else {
276        Err(anyhow::anyhow!("Expected Integer"))
277    }
278}
279
280/// Decode RockSetting attribute (0x0008)
281pub fn decode_rock_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Rock> {
282    if let tlv::TlvItemValue::Int(v) = inp {
283        Ok(*v as u8)
284    } else {
285        Err(anyhow::anyhow!("Expected Integer"))
286    }
287}
288
289/// Decode WindSupport attribute (0x0009)
290pub fn decode_wind_support(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
291    if let tlv::TlvItemValue::Int(v) = inp {
292        Ok(*v as u8)
293    } else {
294        Err(anyhow::anyhow!("Expected Integer"))
295    }
296}
297
298/// Decode WindSetting attribute (0x000A)
299pub fn decode_wind_setting(inp: &tlv::TlvItemValue) -> anyhow::Result<Wind> {
300    if let tlv::TlvItemValue::Int(v) = inp {
301        Ok(*v as u8)
302    } else {
303        Err(anyhow::anyhow!("Expected Integer"))
304    }
305}
306
307/// Decode AirflowDirection attribute (0x000B)
308pub fn decode_airflow_direction(inp: &tlv::TlvItemValue) -> anyhow::Result<AirflowDirection> {
309    if let tlv::TlvItemValue::Int(v) = inp {
310        AirflowDirection::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
311    } else {
312        Err(anyhow::anyhow!("Expected Integer"))
313    }
314}
315
316
317// JSON dispatcher function
318
319/// Decode attribute value and return as JSON string
320///
321/// # Parameters
322/// * `cluster_id` - The cluster identifier
323/// * `attribute_id` - The attribute identifier
324/// * `tlv_value` - The TLV value to decode
325///
326/// # Returns
327/// JSON string representation of the decoded value or error
328pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
329    // Verify this is the correct cluster
330    if cluster_id != 0x0202 {
331        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0202, got {}\"}}", cluster_id);
332    }
333
334    match attribute_id {
335        0x0000 => {
336            match decode_fan_mode(tlv_value) {
337                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
338                Err(e) => format!("{{\"error\": \"{}\"}}", e),
339            }
340        }
341        0x0001 => {
342            match decode_fan_mode_sequence(tlv_value) {
343                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
344                Err(e) => format!("{{\"error\": \"{}\"}}", e),
345            }
346        }
347        0x0002 => {
348            match decode_percent_setting(tlv_value) {
349                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
350                Err(e) => format!("{{\"error\": \"{}\"}}", e),
351            }
352        }
353        0x0003 => {
354            match decode_percent_current(tlv_value) {
355                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
356                Err(e) => format!("{{\"error\": \"{}\"}}", e),
357            }
358        }
359        0x0004 => {
360            match decode_speed_max(tlv_value) {
361                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
362                Err(e) => format!("{{\"error\": \"{}\"}}", e),
363            }
364        }
365        0x0005 => {
366            match decode_speed_setting(tlv_value) {
367                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
368                Err(e) => format!("{{\"error\": \"{}\"}}", e),
369            }
370        }
371        0x0006 => {
372            match decode_speed_current(tlv_value) {
373                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
374                Err(e) => format!("{{\"error\": \"{}\"}}", e),
375            }
376        }
377        0x0007 => {
378            match decode_rock_support(tlv_value) {
379                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
380                Err(e) => format!("{{\"error\": \"{}\"}}", e),
381            }
382        }
383        0x0008 => {
384            match decode_rock_setting(tlv_value) {
385                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
386                Err(e) => format!("{{\"error\": \"{}\"}}", e),
387            }
388        }
389        0x0009 => {
390            match decode_wind_support(tlv_value) {
391                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
392                Err(e) => format!("{{\"error\": \"{}\"}}", e),
393            }
394        }
395        0x000A => {
396            match decode_wind_setting(tlv_value) {
397                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
398                Err(e) => format!("{{\"error\": \"{}\"}}", e),
399            }
400        }
401        0x000B => {
402            match decode_airflow_direction(tlv_value) {
403                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
404                Err(e) => format!("{{\"error\": \"{}\"}}", e),
405            }
406        }
407        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
408    }
409}
410
411/// Get list of all attributes supported by this cluster
412///
413/// # Returns
414/// Vector of tuples containing (attribute_id, attribute_name)
415pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
416    vec![
417        (0x0000, "FanMode"),
418        (0x0001, "FanModeSequence"),
419        (0x0002, "PercentSetting"),
420        (0x0003, "PercentCurrent"),
421        (0x0004, "SpeedMax"),
422        (0x0005, "SpeedSetting"),
423        (0x0006, "SpeedCurrent"),
424        (0x0007, "RockSupport"),
425        (0x0008, "RockSetting"),
426        (0x0009, "WindSupport"),
427        (0x000A, "WindSetting"),
428        (0x000B, "AirflowDirection"),
429    ]
430}
431
432// Command listing
433
434pub fn get_command_list() -> Vec<(u32, &'static str)> {
435    vec![
436        (0x00, "Step"),
437    ]
438}
439
440pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
441    match cmd_id {
442        0x00 => Some("Step"),
443        _ => None,
444    }
445}
446
447pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
448    match cmd_id {
449        0x00 => Some(vec![
450            crate::clusters::codec::CommandField { tag: 0, name: "direction", kind: crate::clusters::codec::FieldKind::Enum { name: "StepDirection", variants: &[(0, "Increase"), (1, "Decrease")] }, optional: false, nullable: false },
451            crate::clusters::codec::CommandField { tag: 1, name: "wrap", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
452            crate::clusters::codec::CommandField { tag: 2, name: "lowest_off", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
453        ]),
454        _ => None,
455    }
456}
457
458pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
459    match cmd_id {
460        0x00 => {
461        let direction = {
462            let n = crate::clusters::codec::json_util::get_u64(args, "direction")?;
463            StepDirection::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid StepDirection: {}", n))?
464        };
465        let wrap = crate::clusters::codec::json_util::get_bool(args, "wrap")?;
466        let lowest_off = crate::clusters::codec::json_util::get_bool(args, "lowest_off")?;
467        encode_step(direction, wrap, lowest_off)
468        }
469        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
470    }
471}
472
473// Typed facade (invokes + reads)
474
475/// Invoke `Step` command on cluster `Fan Control`.
476pub async fn step(conn: &crate::controller::Connection, endpoint: u16, direction: StepDirection, wrap: bool, lowest_off: bool) -> anyhow::Result<()> {
477    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_CMD_ID_STEP, &encode_step(direction, wrap, lowest_off)?).await?;
478    Ok(())
479}
480
481/// Read `FanMode` attribute from cluster `Fan Control`.
482pub async fn read_fan_mode(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<FanMode> {
483    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_FANMODE).await?;
484    decode_fan_mode(&tlv)
485}
486
487/// Read `FanModeSequence` attribute from cluster `Fan Control`.
488pub async fn read_fan_mode_sequence(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<FanModeSequence> {
489    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_FANMODESEQUENCE).await?;
490    decode_fan_mode_sequence(&tlv)
491}
492
493/// Read `PercentSetting` attribute from cluster `Fan Control`.
494pub async fn read_percent_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
495    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_PERCENTSETTING).await?;
496    decode_percent_setting(&tlv)
497}
498
499/// Read `PercentCurrent` attribute from cluster `Fan Control`.
500pub async fn read_percent_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
501    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_PERCENTCURRENT).await?;
502    decode_percent_current(&tlv)
503}
504
505/// Read `SpeedMax` attribute from cluster `Fan Control`.
506pub async fn read_speed_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
507    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDMAX).await?;
508    decode_speed_max(&tlv)
509}
510
511/// Read `SpeedSetting` attribute from cluster `Fan Control`.
512pub async fn read_speed_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
513    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDSETTING).await?;
514    decode_speed_setting(&tlv)
515}
516
517/// Read `SpeedCurrent` attribute from cluster `Fan Control`.
518pub async fn read_speed_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
519    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_SPEEDCURRENT).await?;
520    decode_speed_current(&tlv)
521}
522
523/// Read `RockSupport` attribute from cluster `Fan Control`.
524pub async fn read_rock_support(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Rock> {
525    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_ROCKSUPPORT).await?;
526    decode_rock_support(&tlv)
527}
528
529/// Read `RockSetting` attribute from cluster `Fan Control`.
530pub async fn read_rock_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Rock> {
531    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_ROCKSETTING).await?;
532    decode_rock_setting(&tlv)
533}
534
535/// Read `WindSupport` attribute from cluster `Fan Control`.
536pub async fn read_wind_support(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Wind> {
537    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_WINDSUPPORT).await?;
538    decode_wind_support(&tlv)
539}
540
541/// Read `WindSetting` attribute from cluster `Fan Control`.
542pub async fn read_wind_setting(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Wind> {
543    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_WINDSETTING).await?;
544    decode_wind_setting(&tlv)
545}
546
547/// Read `AirflowDirection` attribute from cluster `Fan Control`.
548pub async fn read_airflow_direction(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AirflowDirection> {
549    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_FAN_CONTROL, crate::clusters::defs::CLUSTER_FAN_CONTROL_ATTR_ID_AIRFLOWDIRECTION).await?;
550    decode_airflow_direction(&tlv)
551}
552