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
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 ActionError {
18    /// Other reason not listed in the row(s) below
19    Unknown = 0,
20    /// The action was interrupted by another command or interaction
21    Interrupted = 1,
22}
23
24impl ActionError {
25    /// Convert from u8 value
26    pub fn from_u8(value: u8) -> Option<Self> {
27        match value {
28            0 => Some(ActionError::Unknown),
29            1 => Some(ActionError::Interrupted),
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<ActionError> for u8 {
41    fn from(val: ActionError) -> Self {
42        val as u8
43    }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum ActionState {
49    /// The action is not active
50    Inactive = 0,
51    /// The action is active
52    Active = 1,
53    /// The action has been paused
54    Paused = 2,
55    /// The action has been disabled
56    Disabled = 3,
57}
58
59impl ActionState {
60    /// Convert from u8 value
61    pub fn from_u8(value: u8) -> Option<Self> {
62        match value {
63            0 => Some(ActionState::Inactive),
64            1 => Some(ActionState::Active),
65            2 => Some(ActionState::Paused),
66            3 => Some(ActionState::Disabled),
67            _ => None,
68        }
69    }
70
71    /// Convert to u8 value
72    pub fn to_u8(self) -> u8 {
73        self as u8
74    }
75}
76
77impl From<ActionState> for u8 {
78    fn from(val: ActionState) -> Self {
79        val as u8
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84#[repr(u8)]
85pub enum ActionType {
86    /// Use this only when none of the other values applies
87    Other = 0,
88    /// Bring the endpoints into a certain state
89    Scene = 1,
90    /// A sequence of states with a certain time pattern
91    Sequence = 2,
92    /// Control an automation (e.g. motion sensor controlling lights)
93    Automation = 3,
94    /// Sequence that will run when something doesn't happen
95    Exception = 4,
96    /// Use the endpoints to send a message to user
97    Notification = 5,
98    /// Higher priority notification
99    Alarm = 6,
100}
101
102impl ActionType {
103    /// Convert from u8 value
104    pub fn from_u8(value: u8) -> Option<Self> {
105        match value {
106            0 => Some(ActionType::Other),
107            1 => Some(ActionType::Scene),
108            2 => Some(ActionType::Sequence),
109            3 => Some(ActionType::Automation),
110            4 => Some(ActionType::Exception),
111            5 => Some(ActionType::Notification),
112            6 => Some(ActionType::Alarm),
113            _ => None,
114        }
115    }
116
117    /// Convert to u8 value
118    pub fn to_u8(self) -> u8 {
119        self as u8
120    }
121}
122
123impl From<ActionType> for u8 {
124    fn from(val: ActionType) -> Self {
125        val as u8
126    }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
130#[repr(u8)]
131pub enum EndpointListType {
132    /// Another group of endpoints
133    Other = 0,
134    /// User-configured group of endpoints where an endpoint can be in only one room
135    Room = 1,
136    /// User-configured group of endpoints where an endpoint can be in any number of zones
137    Zone = 2,
138}
139
140impl EndpointListType {
141    /// Convert from u8 value
142    pub fn from_u8(value: u8) -> Option<Self> {
143        match value {
144            0 => Some(EndpointListType::Other),
145            1 => Some(EndpointListType::Room),
146            2 => Some(EndpointListType::Zone),
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<EndpointListType> for u8 {
158    fn from(val: EndpointListType) -> Self {
159        val as u8
160    }
161}
162
163// Bitmap definitions
164
165/// CommandBits bitmap type
166pub type CommandBits = u16;
167
168/// Constants for CommandBits
169pub mod commandbits {
170    /// Indicate support for InstantAction command
171    pub const INSTANT_ACTION: u16 = 0x01;
172    /// Indicate support for InstantActionWithTransition command
173    pub const INSTANT_ACTION_WITH_TRANSITION: u16 = 0x02;
174    /// Indicate support for StartAction command
175    pub const START_ACTION: u16 = 0x04;
176    /// Indicate support for StartActionWithDuration command
177    pub const START_ACTION_WITH_DURATION: u16 = 0x08;
178    /// Indicate support for StopAction command
179    pub const STOP_ACTION: u16 = 0x10;
180    /// Indicate support for PauseAction command
181    pub const PAUSE_ACTION: u16 = 0x20;
182    /// Indicate support for PauseActionWithDuration command
183    pub const PAUSE_ACTION_WITH_DURATION: u16 = 0x40;
184    /// Indicate support for ResumeAction command
185    pub const RESUME_ACTION: u16 = 0x80;
186    /// Indicate support for EnableAction command
187    pub const ENABLE_ACTION: u16 = 0x100;
188    /// Indicate support for EnableActionWithDuration command
189    pub const ENABLE_ACTION_WITH_DURATION: u16 = 0x200;
190    /// Indicate support for DisableAction command
191    pub const DISABLE_ACTION: u16 = 0x400;
192    /// Indicate support for DisableActionWithDuration command
193    pub const DISABLE_ACTION_WITH_DURATION: u16 = 0x800;
194}
195
196// Struct definitions
197
198#[derive(Debug, serde::Serialize)]
199pub struct Action {
200    pub action_id: Option<u16>,
201    pub name: Option<String>,
202    pub type_: Option<ActionType>,
203    pub endpoint_list_id: Option<u16>,
204    pub supported_commands: Option<u8>,
205    pub state: Option<ActionState>,
206}
207
208#[derive(Debug, serde::Serialize)]
209pub struct EndpointList {
210    pub endpoint_list_id: Option<u16>,
211    pub name: Option<String>,
212    pub type_: Option<EndpointListType>,
213    pub endpoints: Option<Vec<u16>>,
214}
215
216// Command encoders
217
218/// Encode InstantAction command (0x00)
219pub fn encode_instant_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
220    let tlv = tlv::TlvItemEnc {
221        tag: 0,
222        value: tlv::TlvItemValueEnc::StructInvisible(vec![
223        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
224        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
225        ]),
226    };
227    Ok(tlv.encode()?)
228}
229
230/// Encode InstantActionWithTransition command (0x01)
231pub fn encode_instant_action_with_transition(action_id: u16, invoke_id: u32, transition_time: u16) -> anyhow::Result<Vec<u8>> {
232    let tlv = tlv::TlvItemEnc {
233        tag: 0,
234        value: tlv::TlvItemValueEnc::StructInvisible(vec![
235        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
236        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
237        (2, tlv::TlvItemValueEnc::UInt16(transition_time)).into(),
238        ]),
239    };
240    Ok(tlv.encode()?)
241}
242
243/// Encode StartAction command (0x02)
244pub fn encode_start_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
245    let tlv = tlv::TlvItemEnc {
246        tag: 0,
247        value: tlv::TlvItemValueEnc::StructInvisible(vec![
248        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
249        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
250        ]),
251    };
252    Ok(tlv.encode()?)
253}
254
255/// Encode StartActionWithDuration command (0x03)
256pub fn encode_start_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
257    let tlv = tlv::TlvItemEnc {
258        tag: 0,
259        value: tlv::TlvItemValueEnc::StructInvisible(vec![
260        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
261        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
262        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
263        ]),
264    };
265    Ok(tlv.encode()?)
266}
267
268/// Encode StopAction command (0x04)
269pub fn encode_stop_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
270    let tlv = tlv::TlvItemEnc {
271        tag: 0,
272        value: tlv::TlvItemValueEnc::StructInvisible(vec![
273        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
274        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
275        ]),
276    };
277    Ok(tlv.encode()?)
278}
279
280/// Encode PauseAction command (0x05)
281pub fn encode_pause_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
282    let tlv = tlv::TlvItemEnc {
283        tag: 0,
284        value: tlv::TlvItemValueEnc::StructInvisible(vec![
285        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
286        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
287        ]),
288    };
289    Ok(tlv.encode()?)
290}
291
292/// Encode PauseActionWithDuration command (0x06)
293pub fn encode_pause_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
294    let tlv = tlv::TlvItemEnc {
295        tag: 0,
296        value: tlv::TlvItemValueEnc::StructInvisible(vec![
297        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
298        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
299        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
300        ]),
301    };
302    Ok(tlv.encode()?)
303}
304
305/// Encode ResumeAction command (0x07)
306pub fn encode_resume_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
307    let tlv = tlv::TlvItemEnc {
308        tag: 0,
309        value: tlv::TlvItemValueEnc::StructInvisible(vec![
310        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
311        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
312        ]),
313    };
314    Ok(tlv.encode()?)
315}
316
317/// Encode EnableAction command (0x08)
318pub fn encode_enable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
319    let tlv = tlv::TlvItemEnc {
320        tag: 0,
321        value: tlv::TlvItemValueEnc::StructInvisible(vec![
322        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
323        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
324        ]),
325    };
326    Ok(tlv.encode()?)
327}
328
329/// Encode EnableActionWithDuration command (0x09)
330pub fn encode_enable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
331    let tlv = tlv::TlvItemEnc {
332        tag: 0,
333        value: tlv::TlvItemValueEnc::StructInvisible(vec![
334        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
335        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
336        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
337        ]),
338    };
339    Ok(tlv.encode()?)
340}
341
342/// Encode DisableAction command (0x0A)
343pub fn encode_disable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
344    let tlv = tlv::TlvItemEnc {
345        tag: 0,
346        value: tlv::TlvItemValueEnc::StructInvisible(vec![
347        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
348        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
349        ]),
350    };
351    Ok(tlv.encode()?)
352}
353
354/// Encode DisableActionWithDuration command (0x0B)
355pub fn encode_disable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
356    let tlv = tlv::TlvItemEnc {
357        tag: 0,
358        value: tlv::TlvItemValueEnc::StructInvisible(vec![
359        (0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
360        (1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
361        (2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
362        ]),
363    };
364    Ok(tlv.encode()?)
365}
366
367// Attribute decoders
368
369/// Decode ActionList attribute (0x0000)
370pub fn decode_action_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Action>> {
371    let mut res = Vec::new();
372    if let tlv::TlvItemValue::List(v) = inp {
373        for item in v {
374            res.push(Action {
375                action_id: item.get_int(&[0]).map(|v| v as u16),
376                name: item.get_string_owned(&[1]),
377                type_: item.get_int(&[2]).and_then(|v| ActionType::from_u8(v as u8)),
378                endpoint_list_id: item.get_int(&[3]).map(|v| v as u16),
379                supported_commands: item.get_int(&[4]).map(|v| v as u8),
380                state: item.get_int(&[5]).and_then(|v| ActionState::from_u8(v as u8)),
381            });
382        }
383    }
384    Ok(res)
385}
386
387/// Decode EndpointLists attribute (0x0001)
388pub fn decode_endpoint_lists(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<EndpointList>> {
389    let mut res = Vec::new();
390    if let tlv::TlvItemValue::List(v) = inp {
391        for item in v {
392            res.push(EndpointList {
393                endpoint_list_id: item.get_int(&[0]).map(|v| v as u16),
394                name: item.get_string_owned(&[1]),
395                type_: item.get_int(&[2]).and_then(|v| EndpointListType::from_u8(v as u8)),
396                endpoints: {
397                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
398                        let items: Vec<u16> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u16) } else { None } }).collect();
399                        Some(items)
400                    } else {
401                        None
402                    }
403                },
404            });
405        }
406    }
407    Ok(res)
408}
409
410/// Decode SetupURL attribute (0x0002)
411pub fn decode_setup_url(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
412    if let tlv::TlvItemValue::String(v) = inp {
413        Ok(v.clone())
414    } else {
415        Err(anyhow::anyhow!("Expected String"))
416    }
417}
418
419
420// JSON dispatcher function
421
422/// Decode attribute value and return as JSON string
423///
424/// # Parameters
425/// * `cluster_id` - The cluster identifier
426/// * `attribute_id` - The attribute identifier
427/// * `tlv_value` - The TLV value to decode
428///
429/// # Returns
430/// JSON string representation of the decoded value or error
431pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
432    // Verify this is the correct cluster
433    if cluster_id != 0x0025 {
434        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0025, got {}\"}}", cluster_id);
435    }
436
437    match attribute_id {
438        0x0000 => {
439            match decode_action_list(tlv_value) {
440                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
441                Err(e) => format!("{{\"error\": \"{}\"}}", e),
442            }
443        }
444        0x0001 => {
445            match decode_endpoint_lists(tlv_value) {
446                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
447                Err(e) => format!("{{\"error\": \"{}\"}}", e),
448            }
449        }
450        0x0002 => {
451            match decode_setup_url(tlv_value) {
452                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
453                Err(e) => format!("{{\"error\": \"{}\"}}", e),
454            }
455        }
456        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
457    }
458}
459
460/// Get list of all attributes supported by this cluster
461///
462/// # Returns
463/// Vector of tuples containing (attribute_id, attribute_name)
464pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
465    vec![
466        (0x0000, "ActionList"),
467        (0x0001, "EndpointLists"),
468        (0x0002, "SetupURL"),
469    ]
470}
471
472// Command listing
473
474pub fn get_command_list() -> Vec<(u32, &'static str)> {
475    vec![
476        (0x00, "InstantAction"),
477        (0x01, "InstantActionWithTransition"),
478        (0x02, "StartAction"),
479        (0x03, "StartActionWithDuration"),
480        (0x04, "StopAction"),
481        (0x05, "PauseAction"),
482        (0x06, "PauseActionWithDuration"),
483        (0x07, "ResumeAction"),
484        (0x08, "EnableAction"),
485        (0x09, "EnableActionWithDuration"),
486        (0x0A, "DisableAction"),
487        (0x0B, "DisableActionWithDuration"),
488    ]
489}
490
491pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
492    match cmd_id {
493        0x00 => Some("InstantAction"),
494        0x01 => Some("InstantActionWithTransition"),
495        0x02 => Some("StartAction"),
496        0x03 => Some("StartActionWithDuration"),
497        0x04 => Some("StopAction"),
498        0x05 => Some("PauseAction"),
499        0x06 => Some("PauseActionWithDuration"),
500        0x07 => Some("ResumeAction"),
501        0x08 => Some("EnableAction"),
502        0x09 => Some("EnableActionWithDuration"),
503        0x0A => Some("DisableAction"),
504        0x0B => Some("DisableActionWithDuration"),
505        _ => None,
506    }
507}
508
509pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
510    match cmd_id {
511        0x00 => Some(vec![
512            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
513            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
514        ]),
515        0x01 => Some(vec![
516            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
517            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
518            crate::clusters::codec::CommandField { tag: 2, name: "transition_time", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
519        ]),
520        0x02 => Some(vec![
521            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
522            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
523        ]),
524        0x03 => Some(vec![
525            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
526            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
527            crate::clusters::codec::CommandField { tag: 2, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
528        ]),
529        0x04 => Some(vec![
530            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
531            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
532        ]),
533        0x05 => Some(vec![
534            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
535            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
536        ]),
537        0x06 => Some(vec![
538            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
539            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
540            crate::clusters::codec::CommandField { tag: 2, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
541        ]),
542        0x07 => Some(vec![
543            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
544            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
545        ]),
546        0x08 => Some(vec![
547            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
548            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
549        ]),
550        0x09 => Some(vec![
551            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
552            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
553            crate::clusters::codec::CommandField { tag: 2, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
554        ]),
555        0x0A => Some(vec![
556            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
557            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
558        ]),
559        0x0B => Some(vec![
560            crate::clusters::codec::CommandField { tag: 0, name: "action_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
561            crate::clusters::codec::CommandField { tag: 1, name: "invoke_id", kind: crate::clusters::codec::FieldKind::U32, optional: true, nullable: false },
562            crate::clusters::codec::CommandField { tag: 2, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
563        ]),
564        _ => None,
565    }
566}
567
568pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
569    match cmd_id {
570        0x00 => {
571        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
572        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
573        encode_instant_action(action_id, invoke_id)
574        }
575        0x01 => {
576        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
577        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
578        let transition_time = crate::clusters::codec::json_util::get_u16(args, "transition_time")?;
579        encode_instant_action_with_transition(action_id, invoke_id, transition_time)
580        }
581        0x02 => {
582        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
583        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
584        encode_start_action(action_id, invoke_id)
585        }
586        0x03 => {
587        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
588        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
589        let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
590        encode_start_action_with_duration(action_id, invoke_id, duration)
591        }
592        0x04 => {
593        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
594        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
595        encode_stop_action(action_id, invoke_id)
596        }
597        0x05 => {
598        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
599        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
600        encode_pause_action(action_id, invoke_id)
601        }
602        0x06 => {
603        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
604        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
605        let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
606        encode_pause_action_with_duration(action_id, invoke_id, duration)
607        }
608        0x07 => {
609        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
610        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
611        encode_resume_action(action_id, invoke_id)
612        }
613        0x08 => {
614        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
615        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
616        encode_enable_action(action_id, invoke_id)
617        }
618        0x09 => {
619        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
620        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
621        let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
622        encode_enable_action_with_duration(action_id, invoke_id, duration)
623        }
624        0x0A => {
625        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
626        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
627        encode_disable_action(action_id, invoke_id)
628        }
629        0x0B => {
630        let action_id = crate::clusters::codec::json_util::get_u16(args, "action_id")?;
631        let invoke_id = crate::clusters::codec::json_util::get_u32(args, "invoke_id")?;
632        let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
633        encode_disable_action_with_duration(action_id, invoke_id, duration)
634        }
635        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
636    }
637}
638
639// Typed facade (invokes + reads)
640
641/// Invoke `InstantAction` command on cluster `Actions`.
642pub async fn instant_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
643    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_INSTANTACTION, &encode_instant_action(action_id, invoke_id)?).await?;
644    Ok(())
645}
646
647/// Invoke `InstantActionWithTransition` command on cluster `Actions`.
648pub async fn instant_action_with_transition(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32, transition_time: u16) -> anyhow::Result<()> {
649    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_INSTANTACTIONWITHTRANSITION, &encode_instant_action_with_transition(action_id, invoke_id, transition_time)?).await?;
650    Ok(())
651}
652
653/// Invoke `StartAction` command on cluster `Actions`.
654pub async fn start_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
655    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_STARTACTION, &encode_start_action(action_id, invoke_id)?).await?;
656    Ok(())
657}
658
659/// Invoke `StartActionWithDuration` command on cluster `Actions`.
660pub async fn start_action_with_duration(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<()> {
661    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_STARTACTIONWITHDURATION, &encode_start_action_with_duration(action_id, invoke_id, duration)?).await?;
662    Ok(())
663}
664
665/// Invoke `StopAction` command on cluster `Actions`.
666pub async fn stop_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
667    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_STOPACTION, &encode_stop_action(action_id, invoke_id)?).await?;
668    Ok(())
669}
670
671/// Invoke `PauseAction` command on cluster `Actions`.
672pub async fn pause_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
673    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_PAUSEACTION, &encode_pause_action(action_id, invoke_id)?).await?;
674    Ok(())
675}
676
677/// Invoke `PauseActionWithDuration` command on cluster `Actions`.
678pub async fn pause_action_with_duration(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<()> {
679    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_PAUSEACTIONWITHDURATION, &encode_pause_action_with_duration(action_id, invoke_id, duration)?).await?;
680    Ok(())
681}
682
683/// Invoke `ResumeAction` command on cluster `Actions`.
684pub async fn resume_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
685    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_RESUMEACTION, &encode_resume_action(action_id, invoke_id)?).await?;
686    Ok(())
687}
688
689/// Invoke `EnableAction` command on cluster `Actions`.
690pub async fn enable_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
691    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_ENABLEACTION, &encode_enable_action(action_id, invoke_id)?).await?;
692    Ok(())
693}
694
695/// Invoke `EnableActionWithDuration` command on cluster `Actions`.
696pub async fn enable_action_with_duration(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<()> {
697    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_ENABLEACTIONWITHDURATION, &encode_enable_action_with_duration(action_id, invoke_id, duration)?).await?;
698    Ok(())
699}
700
701/// Invoke `DisableAction` command on cluster `Actions`.
702pub async fn disable_action(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32) -> anyhow::Result<()> {
703    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_DISABLEACTION, &encode_disable_action(action_id, invoke_id)?).await?;
704    Ok(())
705}
706
707/// Invoke `DisableActionWithDuration` command on cluster `Actions`.
708pub async fn disable_action_with_duration(conn: &crate::controller::Connection, endpoint: u16, action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<()> {
709    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_CMD_ID_DISABLEACTIONWITHDURATION, &encode_disable_action_with_duration(action_id, invoke_id, duration)?).await?;
710    Ok(())
711}
712
713/// Read `ActionList` attribute from cluster `Actions`.
714pub async fn read_action_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Action>> {
715    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_ATTR_ID_ACTIONLIST).await?;
716    decode_action_list(&tlv)
717}
718
719/// Read `EndpointLists` attribute from cluster `Actions`.
720pub async fn read_endpoint_lists(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<EndpointList>> {
721    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_ATTR_ID_ENDPOINTLISTS).await?;
722    decode_endpoint_lists(&tlv)
723}
724
725/// Read `SetupURL` attribute from cluster `Actions`.
726pub async fn read_setup_url(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<String> {
727    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACTIONS, crate::clusters::defs::CLUSTER_ACTIONS_ATTR_ID_SETUPURL).await?;
728    decode_setup_url(&tlv)
729}
730
731#[derive(Debug, serde::Serialize)]
732pub struct StateChangedEvent {
733    pub action_id: Option<u16>,
734    pub invoke_id: Option<u32>,
735    pub new_state: Option<ActionState>,
736}
737
738#[derive(Debug, serde::Serialize)]
739pub struct ActionFailedEvent {
740    pub action_id: Option<u16>,
741    pub invoke_id: Option<u32>,
742    pub new_state: Option<ActionState>,
743    pub error: Option<ActionError>,
744}
745
746// Event decoders
747
748/// Decode StateChanged event (0x00, priority: info)
749pub fn decode_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateChangedEvent> {
750    if let tlv::TlvItemValue::List(_fields) = inp {
751        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
752        Ok(StateChangedEvent {
753                                action_id: item.get_int(&[0]).map(|v| v as u16),
754                                invoke_id: item.get_int(&[1]).map(|v| v as u32),
755                                new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
756        })
757    } else {
758        Err(anyhow::anyhow!("Expected struct fields"))
759    }
760}
761
762/// Decode ActionFailed event (0x01, priority: info)
763pub fn decode_action_failed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ActionFailedEvent> {
764    if let tlv::TlvItemValue::List(_fields) = inp {
765        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
766        Ok(ActionFailedEvent {
767                                action_id: item.get_int(&[0]).map(|v| v as u16),
768                                invoke_id: item.get_int(&[1]).map(|v| v as u32),
769                                new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
770                                error: item.get_int(&[3]).and_then(|v| ActionError::from_u8(v as u8)),
771        })
772    } else {
773        Err(anyhow::anyhow!("Expected struct fields"))
774    }
775}
776