matc/clusters/codec/
actions_cluster.rs

1//! Matter TLV encoders and decoders for Actions Cluster
2//! Cluster ID: 0x0025
3//!
4//! This file is automatically generated from ActionsCluster.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 ActionError {
16    /// Other reason not listed in the row(s) below
17    Unknown = 0,
18    /// The action was interrupted by another command or interaction
19    Interrupted = 1,
20}
21
22impl ActionError {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(ActionError::Unknown),
27            1 => Some(ActionError::Interrupted),
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<ActionError> for u8 {
39    fn from(val: ActionError) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum ActionState {
47    /// The action is not active
48    Inactive = 0,
49    /// The action is active
50    Active = 1,
51    /// The action has been paused
52    Paused = 2,
53    /// The action has been disabled
54    Disabled = 3,
55}
56
57impl ActionState {
58    /// Convert from u8 value
59    pub fn from_u8(value: u8) -> Option<Self> {
60        match value {
61            0 => Some(ActionState::Inactive),
62            1 => Some(ActionState::Active),
63            2 => Some(ActionState::Paused),
64            3 => Some(ActionState::Disabled),
65            _ => None,
66        }
67    }
68
69    /// Convert to u8 value
70    pub fn to_u8(self) -> u8 {
71        self as u8
72    }
73}
74
75impl From<ActionState> for u8 {
76    fn from(val: ActionState) -> Self {
77        val as u8
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
82#[repr(u8)]
83pub enum ActionType {
84    /// Use this only when none of the other values applies
85    Other = 0,
86    /// Bring the endpoints into a certain state
87    Scene = 1,
88    /// A sequence of states with a certain time pattern
89    Sequence = 2,
90    /// Control an automation (e.g. motion sensor controlling lights)
91    Automation = 3,
92    /// Sequence that will run when something doesn't happen
93    Exception = 4,
94    /// Use the endpoints to send a message to user
95    Notification = 5,
96    /// Higher priority notification
97    Alarm = 6,
98}
99
100impl ActionType {
101    /// Convert from u8 value
102    pub fn from_u8(value: u8) -> Option<Self> {
103        match value {
104            0 => Some(ActionType::Other),
105            1 => Some(ActionType::Scene),
106            2 => Some(ActionType::Sequence),
107            3 => Some(ActionType::Automation),
108            4 => Some(ActionType::Exception),
109            5 => Some(ActionType::Notification),
110            6 => Some(ActionType::Alarm),
111            _ => None,
112        }
113    }
114
115    /// Convert to u8 value
116    pub fn to_u8(self) -> u8 {
117        self as u8
118    }
119}
120
121impl From<ActionType> for u8 {
122    fn from(val: ActionType) -> Self {
123        val as u8
124    }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
128#[repr(u8)]
129pub enum EndpointListType {
130    /// Another group of endpoints
131    Other = 0,
132    /// User-configured group of endpoints where an endpoint can be in only one room
133    Room = 1,
134    /// User-configured group of endpoints where an endpoint can be in any number of zones
135    Zone = 2,
136}
137
138impl EndpointListType {
139    /// Convert from u8 value
140    pub fn from_u8(value: u8) -> Option<Self> {
141        match value {
142            0 => Some(EndpointListType::Other),
143            1 => Some(EndpointListType::Room),
144            2 => Some(EndpointListType::Zone),
145            _ => None,
146        }
147    }
148
149    /// Convert to u8 value
150    pub fn to_u8(self) -> u8 {
151        self as u8
152    }
153}
154
155impl From<EndpointListType> for u8 {
156    fn from(val: EndpointListType) -> Self {
157        val as u8
158    }
159}
160
161// Bitmap definitions
162
163/// CommandBits bitmap type
164pub type CommandBits = u16;
165
166/// Constants for CommandBits
167pub mod commandbits {
168    /// Indicate support for InstantAction command
169    pub const INSTANT_ACTION: u16 = 0x01;
170    /// Indicate support for InstantActionWithTransition command
171    pub const INSTANT_ACTION_WITH_TRANSITION: u16 = 0x02;
172    /// Indicate support for StartAction command
173    pub const START_ACTION: u16 = 0x04;
174    /// Indicate support for StartActionWithDuration command
175    pub const START_ACTION_WITH_DURATION: u16 = 0x08;
176    /// Indicate support for StopAction command
177    pub const STOP_ACTION: u16 = 0x10;
178    /// Indicate support for PauseAction command
179    pub const PAUSE_ACTION: u16 = 0x20;
180    /// Indicate support for PauseActionWithDuration command
181    pub const PAUSE_ACTION_WITH_DURATION: u16 = 0x40;
182    /// Indicate support for ResumeAction command
183    pub const RESUME_ACTION: u16 = 0x80;
184    /// Indicate support for EnableAction command
185    pub const ENABLE_ACTION: u16 = 0x100;
186    /// Indicate support for EnableActionWithDuration command
187    pub const ENABLE_ACTION_WITH_DURATION: u16 = 0x200;
188    /// Indicate support for DisableAction command
189    pub const DISABLE_ACTION: u16 = 0x400;
190    /// Indicate support for DisableActionWithDuration command
191    pub const DISABLE_ACTION_WITH_DURATION: u16 = 0x800;
192}
193
194// Struct definitions
195
196#[derive(Debug, serde::Serialize)]
197pub struct Action {
198    pub action_id: Option<u16>,
199    pub name: Option<String>,
200    pub type_: Option<ActionType>,
201    pub endpoint_list_id: Option<u16>,
202    pub supported_commands: Option<u8>,
203    pub state: Option<ActionState>,
204}
205
206#[derive(Debug, serde::Serialize)]
207pub struct EndpointList {
208    pub endpoint_list_id: Option<u16>,
209    pub name: Option<String>,
210    pub type_: Option<EndpointListType>,
211    pub endpoints: Option<Vec<u16>>,
212}
213
214// Command encoders
215
216/// Encode InstantAction command (0x00)
217pub fn encode_instant_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
218    let tlv = tlv::TlvItemEnc {
219        tag: 0,
220        value: tlv::TlvItemValueEnc::StructInvisible(vec![
221        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
222        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
223        ]),
224    };
225    Ok(tlv.encode()?)
226}
227
228/// Encode InstantActionWithTransition command (0x01)
229pub fn encode_instant_action_with_transition(action_id: u16, invoke_id: u32, transition_time: u16) -> anyhow::Result<Vec<u8>> {
230    let tlv = tlv::TlvItemEnc {
231        tag: 0,
232        value: tlv::TlvItemValueEnc::StructInvisible(vec![
233        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
234        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
235        (2, tlv::TlvItemValueEnc::UInt16(transition_time)).into(),
236        ]),
237    };
238    Ok(tlv.encode()?)
239}
240
241/// Encode StartAction command (0x02)
242pub fn encode_start_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
243    let tlv = tlv::TlvItemEnc {
244        tag: 0,
245        value: tlv::TlvItemValueEnc::StructInvisible(vec![
246        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
247        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
248        ]),
249    };
250    Ok(tlv.encode()?)
251}
252
253/// Encode StartActionWithDuration command (0x03)
254pub fn encode_start_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
255    let tlv = tlv::TlvItemEnc {
256        tag: 0,
257        value: tlv::TlvItemValueEnc::StructInvisible(vec![
258        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
259        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
260        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
261        ]),
262    };
263    Ok(tlv.encode()?)
264}
265
266/// Encode StopAction command (0x04)
267pub fn encode_stop_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
268    let tlv = tlv::TlvItemEnc {
269        tag: 0,
270        value: tlv::TlvItemValueEnc::StructInvisible(vec![
271        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
272        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
273        ]),
274    };
275    Ok(tlv.encode()?)
276}
277
278/// Encode PauseAction command (0x05)
279pub fn encode_pause_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
280    let tlv = tlv::TlvItemEnc {
281        tag: 0,
282        value: tlv::TlvItemValueEnc::StructInvisible(vec![
283        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
284        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
285        ]),
286    };
287    Ok(tlv.encode()?)
288}
289
290/// Encode PauseActionWithDuration command (0x06)
291pub fn encode_pause_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
292    let tlv = tlv::TlvItemEnc {
293        tag: 0,
294        value: tlv::TlvItemValueEnc::StructInvisible(vec![
295        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
296        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
297        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
298        ]),
299    };
300    Ok(tlv.encode()?)
301}
302
303/// Encode ResumeAction command (0x07)
304pub fn encode_resume_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
305    let tlv = tlv::TlvItemEnc {
306        tag: 0,
307        value: tlv::TlvItemValueEnc::StructInvisible(vec![
308        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
309        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
310        ]),
311    };
312    Ok(tlv.encode()?)
313}
314
315/// Encode EnableAction command (0x08)
316pub fn encode_enable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
317    let tlv = tlv::TlvItemEnc {
318        tag: 0,
319        value: tlv::TlvItemValueEnc::StructInvisible(vec![
320        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
321        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
322        ]),
323    };
324    Ok(tlv.encode()?)
325}
326
327/// Encode EnableActionWithDuration command (0x09)
328pub fn encode_enable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
329    let tlv = tlv::TlvItemEnc {
330        tag: 0,
331        value: tlv::TlvItemValueEnc::StructInvisible(vec![
332        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
333        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
334        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
335        ]),
336    };
337    Ok(tlv.encode()?)
338}
339
340/// Encode DisableAction command (0x0A)
341pub fn encode_disable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
342    let tlv = tlv::TlvItemEnc {
343        tag: 0,
344        value: tlv::TlvItemValueEnc::StructInvisible(vec![
345        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
346        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
347        ]),
348    };
349    Ok(tlv.encode()?)
350}
351
352/// Encode DisableActionWithDuration command (0x0B)
353pub fn encode_disable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
354    let tlv = tlv::TlvItemEnc {
355        tag: 0,
356        value: tlv::TlvItemValueEnc::StructInvisible(vec![
357        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
358        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
359        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
360        ]),
361    };
362    Ok(tlv.encode()?)
363}
364
365// Attribute decoders
366
367/// Decode ActionList attribute (0x0000)
368pub fn decode_action_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Action>> {
369    let mut res = Vec::new();
370    if let tlv::TlvItemValue::List(v) = inp {
371        for item in v {
372            res.push(Action {
373                action_id: item.get_int(&[0]).map(|v| v as u16),
374                name: item.get_string_owned(&[1]),
375                type_: item.get_int(&[2]).and_then(|v| ActionType::from_u8(v as u8)),
376                endpoint_list_id: item.get_int(&[3]).map(|v| v as u16),
377                supported_commands: item.get_int(&[4]).map(|v| v as u8),
378                state: item.get_int(&[5]).and_then(|v| ActionState::from_u8(v as u8)),
379            });
380        }
381    }
382    Ok(res)
383}
384
385/// Decode EndpointLists attribute (0x0001)
386pub fn decode_endpoint_lists(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<EndpointList>> {
387    let mut res = Vec::new();
388    if let tlv::TlvItemValue::List(v) = inp {
389        for item in v {
390            res.push(EndpointList {
391                endpoint_list_id: item.get_int(&[0]).map(|v| v as u16),
392                name: item.get_string_owned(&[1]),
393                type_: item.get_int(&[2]).and_then(|v| EndpointListType::from_u8(v as u8)),
394                endpoints: {
395                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
396                        let items: Vec<u16> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u16) } else { None } }).collect();
397                        Some(items)
398                    } else {
399                        None
400                    }
401                },
402            });
403        }
404    }
405    Ok(res)
406}
407
408/// Decode SetupURL attribute (0x0002)
409pub fn decode_setup_url(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
410    if let tlv::TlvItemValue::String(v) = inp {
411        Ok(v.clone())
412    } else {
413        Err(anyhow::anyhow!("Expected String"))
414    }
415}
416
417
418// JSON dispatcher function
419
420/// Decode attribute value and return as JSON string
421///
422/// # Parameters
423/// * `cluster_id` - The cluster identifier
424/// * `attribute_id` - The attribute identifier
425/// * `tlv_value` - The TLV value to decode
426///
427/// # Returns
428/// JSON string representation of the decoded value or error
429pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
430    // Verify this is the correct cluster
431    if cluster_id != 0x0025 {
432        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0025, got {}\"}}", cluster_id);
433    }
434
435    match attribute_id {
436        0x0000 => {
437            match decode_action_list(tlv_value) {
438                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
439                Err(e) => format!("{{\"error\": \"{}\"}}", e),
440            }
441        }
442        0x0001 => {
443            match decode_endpoint_lists(tlv_value) {
444                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
445                Err(e) => format!("{{\"error\": \"{}\"}}", e),
446            }
447        }
448        0x0002 => {
449            match decode_setup_url(tlv_value) {
450                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
451                Err(e) => format!("{{\"error\": \"{}\"}}", e),
452            }
453        }
454        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
455    }
456}
457
458/// Get list of all attributes supported by this cluster
459///
460/// # Returns
461/// Vector of tuples containing (attribute_id, attribute_name)
462pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
463    vec![
464        (0x0000, "ActionList"),
465        (0x0001, "EndpointLists"),
466        (0x0002, "SetupURL"),
467    ]
468}
469
470#[derive(Debug, serde::Serialize)]
471pub struct StateChangedEvent {
472    pub action_id: Option<u16>,
473    pub invoke_id: Option<u32>,
474    pub new_state: Option<ActionState>,
475}
476
477#[derive(Debug, serde::Serialize)]
478pub struct ActionFailedEvent {
479    pub action_id: Option<u16>,
480    pub invoke_id: Option<u32>,
481    pub new_state: Option<ActionState>,
482    pub error: Option<ActionError>,
483}
484
485// Event decoders
486
487/// Decode StateChanged event (0x00, priority: info)
488pub fn decode_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateChangedEvent> {
489    if let tlv::TlvItemValue::List(_fields) = inp {
490        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
491        Ok(StateChangedEvent {
492                                action_id: item.get_int(&[0]).map(|v| v as u16),
493                                invoke_id: item.get_int(&[1]).map(|v| v as u32),
494                                new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
495        })
496    } else {
497        Err(anyhow::anyhow!("Expected struct fields"))
498    }
499}
500
501/// Decode ActionFailed event (0x01, priority: info)
502pub fn decode_action_failed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ActionFailedEvent> {
503    if let tlv::TlvItemValue::List(_fields) = inp {
504        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
505        Ok(ActionFailedEvent {
506                                action_id: item.get_int(&[0]).map(|v| v as u16),
507                                invoke_id: item.get_int(&[1]).map(|v| v as u32),
508                                new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
509                                error: item.get_int(&[3]).and_then(|v| ActionError::from_u8(v as u8)),
510        })
511    } else {
512        Err(anyhow::anyhow!("Expected struct fields"))
513    }
514}
515