matc/clusters/codec/
on_off.rs

1//! Matter TLV encoders and decoders for On/Off Cluster
2//! Cluster ID: 0x0006
3//!
4//! This file is automatically generated from OnOff.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 DelayedAllOffEffectVariant {
18    /// Fade to off in 0.8 seconds
19    Delayedofffastfade = 0,
20    /// No fade
21    Nofade = 1,
22    /// 50% dim down in 0.8 seconds then fade to off in 12 seconds
23    Delayedoffslowfade = 2,
24}
25
26impl DelayedAllOffEffectVariant {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(DelayedAllOffEffectVariant::Delayedofffastfade),
31            1 => Some(DelayedAllOffEffectVariant::Nofade),
32            2 => Some(DelayedAllOffEffectVariant::Delayedoffslowfade),
33            _ => None,
34        }
35    }
36
37    /// Convert to u8 value
38    pub fn to_u8(self) -> u8 {
39        self as u8
40    }
41}
42
43impl From<DelayedAllOffEffectVariant> for u8 {
44    fn from(val: DelayedAllOffEffectVariant) -> Self {
45        val as u8
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50#[repr(u8)]
51pub enum DyingLightEffectVariant {
52    /// 20% dim up in 0.5s then fade to off in 1 second
53    Dyinglightfadeoff = 0,
54}
55
56impl DyingLightEffectVariant {
57    /// Convert from u8 value
58    pub fn from_u8(value: u8) -> Option<Self> {
59        match value {
60            0 => Some(DyingLightEffectVariant::Dyinglightfadeoff),
61            _ => None,
62        }
63    }
64
65    /// Convert to u8 value
66    pub fn to_u8(self) -> u8 {
67        self as u8
68    }
69}
70
71impl From<DyingLightEffectVariant> for u8 {
72    fn from(val: DyingLightEffectVariant) -> Self {
73        val as u8
74    }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78#[repr(u8)]
79pub enum EffectIdentifier {
80    /// Delayed All Off
81    Delayedalloff = 0,
82    /// Dying Light
83    Dyinglight = 1,
84}
85
86impl EffectIdentifier {
87    /// Convert from u8 value
88    pub fn from_u8(value: u8) -> Option<Self> {
89        match value {
90            0 => Some(EffectIdentifier::Delayedalloff),
91            1 => Some(EffectIdentifier::Dyinglight),
92            _ => None,
93        }
94    }
95
96    /// Convert to u8 value
97    pub fn to_u8(self) -> u8 {
98        self as u8
99    }
100}
101
102impl From<EffectIdentifier> for u8 {
103    fn from(val: EffectIdentifier) -> Self {
104        val as u8
105    }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
109#[repr(u8)]
110pub enum StartUpOnOff {
111    /// Set the OnOff attribute to FALSE
112    Off = 0,
113    /// Set the OnOff attribute to TRUE
114    On = 1,
115    /// If the previous value of the OnOff attribute is equal to FALSE, set the OnOff attribute to TRUE. If the previous value of the OnOff attribute is equal to TRUE, set the OnOff attribute to FALSE (toggle).
116    Toggle = 2,
117}
118
119impl StartUpOnOff {
120    /// Convert from u8 value
121    pub fn from_u8(value: u8) -> Option<Self> {
122        match value {
123            0 => Some(StartUpOnOff::Off),
124            1 => Some(StartUpOnOff::On),
125            2 => Some(StartUpOnOff::Toggle),
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<StartUpOnOff> for u8 {
137    fn from(val: StartUpOnOff) -> Self {
138        val as u8
139    }
140}
141
142// Bitmap definitions
143
144/// OnOffControl bitmap type
145pub type OnOffControl = u8;
146
147/// Constants for OnOffControl
148pub mod onoffcontrol {
149    /// Indicates a command is only accepted when in On state.
150    pub const ACCEPT_ONLY_WHEN_ON: u8 = 0x01;
151}
152
153// Command encoders
154
155/// Encode OffWithEffect command (0x40)
156pub fn encode_off_with_effect(effect_identifier: EffectIdentifier, effect_variant: u8) -> anyhow::Result<Vec<u8>> {
157    let tlv = tlv::TlvItemEnc {
158        tag: 0,
159        value: tlv::TlvItemValueEnc::StructInvisible(vec![
160        (0, tlv::TlvItemValueEnc::UInt8(effect_identifier.to_u8())).into(),
161        (1, tlv::TlvItemValueEnc::UInt8(effect_variant)).into(),
162        ]),
163    };
164    Ok(tlv.encode()?)
165}
166
167/// Encode OnWithTimedOff command (0x42)
168pub fn encode_on_with_timed_off(on_off_control: OnOffControl, on_time: u16, off_wait_time: u16) -> anyhow::Result<Vec<u8>> {
169    let tlv = tlv::TlvItemEnc {
170        tag: 0,
171        value: tlv::TlvItemValueEnc::StructInvisible(vec![
172        (0, tlv::TlvItemValueEnc::UInt8(on_off_control)).into(),
173        (1, tlv::TlvItemValueEnc::UInt16(on_time)).into(),
174        (2, tlv::TlvItemValueEnc::UInt16(off_wait_time)).into(),
175        ]),
176    };
177    Ok(tlv.encode()?)
178}
179
180// Attribute decoders
181
182/// Decode OnOff attribute (0x0000)
183pub fn decode_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
184    if let tlv::TlvItemValue::Bool(v) = inp {
185        Ok(*v)
186    } else {
187        Err(anyhow::anyhow!("Expected Bool"))
188    }
189}
190
191/// Decode GlobalSceneControl attribute (0x4000)
192pub fn decode_global_scene_control(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
193    if let tlv::TlvItemValue::Bool(v) = inp {
194        Ok(*v)
195    } else {
196        Err(anyhow::anyhow!("Expected Bool"))
197    }
198}
199
200/// Decode OnTime attribute (0x4001)
201pub fn decode_on_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
202    if let tlv::TlvItemValue::Int(v) = inp {
203        Ok(*v as u16)
204    } else {
205        Err(anyhow::anyhow!("Expected UInt16"))
206    }
207}
208
209/// Decode OffWaitTime attribute (0x4002)
210pub fn decode_off_wait_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
211    if let tlv::TlvItemValue::Int(v) = inp {
212        Ok(*v as u16)
213    } else {
214        Err(anyhow::anyhow!("Expected UInt16"))
215    }
216}
217
218/// Decode StartUpOnOff attribute (0x4003)
219pub fn decode_start_up_on_off(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<StartUpOnOff>> {
220    if let tlv::TlvItemValue::Int(v) = inp {
221        Ok(StartUpOnOff::from_u8(*v as u8))
222    } else {
223        Ok(None)
224    }
225}
226
227
228// JSON dispatcher function
229
230/// Decode attribute value and return as JSON string
231///
232/// # Parameters
233/// * `cluster_id` - The cluster identifier
234/// * `attribute_id` - The attribute identifier
235/// * `tlv_value` - The TLV value to decode
236///
237/// # Returns
238/// JSON string representation of the decoded value or error
239pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
240    // Verify this is the correct cluster
241    if cluster_id != 0x0006 {
242        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0006, got {}\"}}", cluster_id);
243    }
244
245    match attribute_id {
246        0x0000 => {
247            match decode_on_off(tlv_value) {
248                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
249                Err(e) => format!("{{\"error\": \"{}\"}}", e),
250            }
251        }
252        0x4000 => {
253            match decode_global_scene_control(tlv_value) {
254                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
255                Err(e) => format!("{{\"error\": \"{}\"}}", e),
256            }
257        }
258        0x4001 => {
259            match decode_on_time(tlv_value) {
260                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
261                Err(e) => format!("{{\"error\": \"{}\"}}", e),
262            }
263        }
264        0x4002 => {
265            match decode_off_wait_time(tlv_value) {
266                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
267                Err(e) => format!("{{\"error\": \"{}\"}}", e),
268            }
269        }
270        0x4003 => {
271            match decode_start_up_on_off(tlv_value) {
272                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
273                Err(e) => format!("{{\"error\": \"{}\"}}", e),
274            }
275        }
276        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
277    }
278}
279
280/// Get list of all attributes supported by this cluster
281///
282/// # Returns
283/// Vector of tuples containing (attribute_id, attribute_name)
284pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
285    vec![
286        (0x0000, "OnOff"),
287        (0x4000, "GlobalSceneControl"),
288        (0x4001, "OnTime"),
289        (0x4002, "OffWaitTime"),
290        (0x4003, "StartUpOnOff"),
291    ]
292}
293
294// Command listing
295
296pub fn get_command_list() -> Vec<(u32, &'static str)> {
297    vec![
298        (0x00, "Off"),
299        (0x01, "On"),
300        (0x02, "Toggle"),
301        (0x40, "OffWithEffect"),
302        (0x41, "OnWithRecallGlobalScene"),
303        (0x42, "OnWithTimedOff"),
304    ]
305}
306
307pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
308    match cmd_id {
309        0x00 => Some("Off"),
310        0x01 => Some("On"),
311        0x02 => Some("Toggle"),
312        0x40 => Some("OffWithEffect"),
313        0x41 => Some("OnWithRecallGlobalScene"),
314        0x42 => Some("OnWithTimedOff"),
315        _ => None,
316    }
317}
318
319pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
320    match cmd_id {
321        0x00 => Some(vec![]),
322        0x01 => Some(vec![]),
323        0x02 => Some(vec![]),
324        0x40 => Some(vec![
325            crate::clusters::codec::CommandField { tag: 0, name: "effect_identifier", kind: crate::clusters::codec::FieldKind::Enum { name: "EffectIdentifier", variants: &[(0, "Delayedalloff"), (1, "Dyinglight")] }, optional: false, nullable: false },
326            crate::clusters::codec::CommandField { tag: 1, name: "effect_variant", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
327        ]),
328        0x41 => Some(vec![]),
329        0x42 => Some(vec![
330            crate::clusters::codec::CommandField { tag: 0, name: "on_off_control", kind: crate::clusters::codec::FieldKind::Bitmap { name: "OnOffControl", bits: &[(1, "ACCEPT_ONLY_WHEN_ON")] }, optional: false, nullable: false },
331            crate::clusters::codec::CommandField { tag: 1, name: "on_time", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
332            crate::clusters::codec::CommandField { tag: 2, name: "off_wait_time", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
333        ]),
334        _ => None,
335    }
336}
337
338pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
339    match cmd_id {
340        0x00 => Ok(vec![]),
341        0x01 => Ok(vec![]),
342        0x02 => Ok(vec![]),
343        0x40 => {
344        let effect_identifier = {
345            let n = crate::clusters::codec::json_util::get_u64(args, "effect_identifier")?;
346            EffectIdentifier::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid EffectIdentifier: {}", n))?
347        };
348        let effect_variant = crate::clusters::codec::json_util::get_u8(args, "effect_variant")?;
349        encode_off_with_effect(effect_identifier, effect_variant)
350        }
351        0x41 => Ok(vec![]),
352        0x42 => {
353        let on_off_control = crate::clusters::codec::json_util::get_u8(args, "on_off_control")?;
354        let on_time = crate::clusters::codec::json_util::get_u16(args, "on_time")?;
355        let off_wait_time = crate::clusters::codec::json_util::get_u16(args, "off_wait_time")?;
356        encode_on_with_timed_off(on_off_control, on_time, off_wait_time)
357        }
358        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
359    }
360}
361
362// Typed facade (invokes + reads)
363
364/// Invoke `Off` command on cluster `On/Off`.
365pub async fn off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
366    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_OFF, &[]).await?;
367    Ok(())
368}
369
370/// Invoke `On` command on cluster `On/Off`.
371pub async fn on(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
372    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ON, &[]).await?;
373    Ok(())
374}
375
376/// Invoke `Toggle` command on cluster `On/Off`.
377pub async fn toggle(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
378    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_TOGGLE, &[]).await?;
379    Ok(())
380}
381
382/// Invoke `OffWithEffect` command on cluster `On/Off`.
383pub async fn off_with_effect(conn: &crate::controller::Connection, endpoint: u16, effect_identifier: EffectIdentifier, effect_variant: u8) -> anyhow::Result<()> {
384    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_OFFWITHEFFECT, &encode_off_with_effect(effect_identifier, effect_variant)?).await?;
385    Ok(())
386}
387
388/// Invoke `OnWithRecallGlobalScene` command on cluster `On/Off`.
389pub async fn on_with_recall_global_scene(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
390    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ONWITHRECALLGLOBALSCENE, &[]).await?;
391    Ok(())
392}
393
394/// Invoke `OnWithTimedOff` command on cluster `On/Off`.
395pub async fn on_with_timed_off(conn: &crate::controller::Connection, endpoint: u16, on_off_control: OnOffControl, on_time: u16, off_wait_time: u16) -> anyhow::Result<()> {
396    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_CMD_ID_ONWITHTIMEDOFF, &encode_on_with_timed_off(on_off_control, on_time, off_wait_time)?).await?;
397    Ok(())
398}
399
400/// Read `OnOff` attribute from cluster `On/Off`.
401pub async fn read_on_off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
402    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_ONOFF).await?;
403    decode_on_off(&tlv)
404}
405
406/// Read `GlobalSceneControl` attribute from cluster `On/Off`.
407pub async fn read_global_scene_control(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
408    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_GLOBALSCENECONTROL).await?;
409    decode_global_scene_control(&tlv)
410}
411
412/// Read `OnTime` attribute from cluster `On/Off`.
413pub async fn read_on_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
414    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_ONTIME).await?;
415    decode_on_time(&tlv)
416}
417
418/// Read `OffWaitTime` attribute from cluster `On/Off`.
419pub async fn read_off_wait_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
420    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_OFFWAITTIME).await?;
421    decode_off_wait_time(&tlv)
422}
423
424/// Read `StartUpOnOff` attribute from cluster `On/Off`.
425pub async fn read_start_up_on_off(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<StartUpOnOff>> {
426    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ON_OFF, crate::clusters::defs::CLUSTER_ON_OFF_ATTR_ID_STARTUPONOFF).await?;
427    decode_start_up_on_off(&tlv)
428}
429