matc/clusters/codec/
operational_state.rs

1//! Matter TLV encoders and decoders for Operational State Cluster
2//! Cluster ID: 0x0060
3//!
4//! This file is automatically generated from OperationalState.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 ErrorStateEnum {
18    /// The device is not in an error state
19    Noerror = 0,
20    /// The device is unable to start or resume operation
21    Unabletostartorresume = 1,
22    /// The device was unable to complete the current operation
23    Unabletocompleteoperation = 2,
24    /// The device cannot process the command in its current state
25    Commandinvalidinstate = 3,
26}
27
28impl ErrorStateEnum {
29    /// Convert from u8 value
30    pub fn from_u8(value: u8) -> Option<Self> {
31        match value {
32            0 => Some(ErrorStateEnum::Noerror),
33            1 => Some(ErrorStateEnum::Unabletostartorresume),
34            2 => Some(ErrorStateEnum::Unabletocompleteoperation),
35            3 => Some(ErrorStateEnum::Commandinvalidinstate),
36            _ => None,
37        }
38    }
39
40    /// Convert to u8 value
41    pub fn to_u8(self) -> u8 {
42        self as u8
43    }
44}
45
46impl From<ErrorStateEnum> for u8 {
47    fn from(val: ErrorStateEnum) -> Self {
48        val as u8
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53#[repr(u8)]
54pub enum OperationalStateEnum {
55    /// The device is stopped
56    Stopped = 0,
57    /// The device is operating
58    Running = 1,
59    /// The device is paused during an operation
60    Paused = 2,
61    /// The device is in an error state
62    Error = 3,
63}
64
65impl OperationalStateEnum {
66    /// Convert from u8 value
67    pub fn from_u8(value: u8) -> Option<Self> {
68        match value {
69            0 => Some(OperationalStateEnum::Stopped),
70            1 => Some(OperationalStateEnum::Running),
71            2 => Some(OperationalStateEnum::Paused),
72            3 => Some(OperationalStateEnum::Error),
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<OperationalStateEnum> for u8 {
84    fn from(val: OperationalStateEnum) -> Self {
85        val as u8
86    }
87}
88
89// Struct definitions
90
91#[derive(Debug, serde::Serialize)]
92pub struct ErrorState {
93    pub error_state_id: Option<ErrorStateEnum>,
94    pub error_state_label: Option<String>,
95    pub error_state_details: Option<String>,
96}
97
98#[derive(Debug, serde::Serialize)]
99pub struct OperationalState {
100    pub operational_state_id: Option<OperationalStateEnum>,
101    pub operational_state_label: Option<String>,
102}
103
104// Command encoders
105
106// Attribute decoders
107
108/// Decode PhaseList attribute (0x0000)
109pub fn decode_phase_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<String>> {
110    let mut res = Vec::new();
111    if let tlv::TlvItemValue::List(v) = inp {
112        for item in v {
113            if let tlv::TlvItemValue::String(s) = &item.value {
114                res.push(s.clone());
115            }
116        }
117    }
118    Ok(res)
119}
120
121/// Decode CurrentPhase attribute (0x0001)
122pub fn decode_current_phase(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
123    if let tlv::TlvItemValue::Int(v) = inp {
124        Ok(Some(*v as u8))
125    } else {
126        Ok(None)
127    }
128}
129
130/// Decode CountdownTime attribute (0x0002)
131pub fn decode_countdown_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
132    if let tlv::TlvItemValue::Int(v) = inp {
133        Ok(Some(*v as u32))
134    } else {
135        Ok(None)
136    }
137}
138
139/// Decode OperationalStateList attribute (0x0003)
140pub fn decode_operational_state_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<OperationalState>> {
141    let mut res = Vec::new();
142    if let tlv::TlvItemValue::List(v) = inp {
143        for item in v {
144            res.push(OperationalState {
145                operational_state_id: item.get_int(&[0]).and_then(|v| OperationalStateEnum::from_u8(v as u8)),
146                operational_state_label: item.get_string_owned(&[1]),
147            });
148        }
149    }
150    Ok(res)
151}
152
153/// Decode OperationalState attribute (0x0004)
154pub fn decode_operational_state(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalStateEnum> {
155    if let tlv::TlvItemValue::Int(v) = inp {
156        OperationalStateEnum::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
157    } else {
158        Err(anyhow::anyhow!("Expected Integer"))
159    }
160}
161
162/// Decode OperationalError attribute (0x0005)
163pub fn decode_operational_error(inp: &tlv::TlvItemValue) -> anyhow::Result<ErrorState> {
164    if let tlv::TlvItemValue::List(_fields) = inp {
165        // Struct with fields
166        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
167        Ok(ErrorState {
168                error_state_id: item.get_int(&[0]).and_then(|v| ErrorStateEnum::from_u8(v as u8)),
169                error_state_label: item.get_string_owned(&[1]),
170                error_state_details: item.get_string_owned(&[2]),
171        })
172    } else {
173        Err(anyhow::anyhow!("Expected struct fields"))
174    }
175}
176
177
178// JSON dispatcher function
179
180/// Decode attribute value and return as JSON string
181///
182/// # Parameters
183/// * `cluster_id` - The cluster identifier
184/// * `attribute_id` - The attribute identifier
185/// * `tlv_value` - The TLV value to decode
186///
187/// # Returns
188/// JSON string representation of the decoded value or error
189pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
190    // Verify this is the correct cluster
191    if cluster_id != 0x0060 {
192        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0060, got {}\"}}", cluster_id);
193    }
194
195    match attribute_id {
196        0x0000 => {
197            match decode_phase_list(tlv_value) {
198                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
199                Err(e) => format!("{{\"error\": \"{}\"}}", e),
200            }
201        }
202        0x0001 => {
203            match decode_current_phase(tlv_value) {
204                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
205                Err(e) => format!("{{\"error\": \"{}\"}}", e),
206            }
207        }
208        0x0002 => {
209            match decode_countdown_time(tlv_value) {
210                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
211                Err(e) => format!("{{\"error\": \"{}\"}}", e),
212            }
213        }
214        0x0003 => {
215            match decode_operational_state_list(tlv_value) {
216                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
217                Err(e) => format!("{{\"error\": \"{}\"}}", e),
218            }
219        }
220        0x0004 => {
221            match decode_operational_state(tlv_value) {
222                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
223                Err(e) => format!("{{\"error\": \"{}\"}}", e),
224            }
225        }
226        0x0005 => {
227            match decode_operational_error(tlv_value) {
228                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
229                Err(e) => format!("{{\"error\": \"{}\"}}", e),
230            }
231        }
232        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
233    }
234}
235
236/// Get list of all attributes supported by this cluster
237///
238/// # Returns
239/// Vector of tuples containing (attribute_id, attribute_name)
240pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
241    vec![
242        (0x0000, "PhaseList"),
243        (0x0001, "CurrentPhase"),
244        (0x0002, "CountdownTime"),
245        (0x0003, "OperationalStateList"),
246        (0x0004, "OperationalState"),
247        (0x0005, "OperationalError"),
248    ]
249}
250
251// Command listing
252
253pub fn get_command_list() -> Vec<(u32, &'static str)> {
254    vec![
255        (0x00, "Pause"),
256        (0x01, "Stop"),
257        (0x02, "Start"),
258        (0x03, "Resume"),
259    ]
260}
261
262pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
263    match cmd_id {
264        0x00 => Some("Pause"),
265        0x01 => Some("Stop"),
266        0x02 => Some("Start"),
267        0x03 => Some("Resume"),
268        _ => None,
269    }
270}
271
272pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
273    match cmd_id {
274        0x00 => Some(vec![]),
275        0x01 => Some(vec![]),
276        0x02 => Some(vec![]),
277        0x03 => Some(vec![]),
278        _ => None,
279    }
280}
281
282pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
283    match cmd_id {
284        0x00 => Ok(vec![]),
285        0x01 => Ok(vec![]),
286        0x02 => Ok(vec![]),
287        0x03 => Ok(vec![]),
288        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
289    }
290}
291
292#[derive(Debug, serde::Serialize)]
293pub struct OperationalCommandResponse {
294    pub command_response_state: Option<ErrorState>,
295}
296
297// Command response decoders
298
299/// Decode OperationalCommandResponse command response (04)
300pub fn decode_operational_command_response(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalCommandResponse> {
301    if let tlv::TlvItemValue::List(_fields) = inp {
302        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
303        Ok(OperationalCommandResponse {
304                command_response_state: {
305                    if let Some(nested_tlv) = item.get(&[0]) {
306                        if let tlv::TlvItemValue::List(_) = nested_tlv {
307                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
308                            Some(ErrorState {
309                error_state_id: nested_item.get_int(&[0]).and_then(|v| ErrorStateEnum::from_u8(v as u8)),
310                error_state_label: nested_item.get_string_owned(&[1]),
311                error_state_details: nested_item.get_string_owned(&[2]),
312                            })
313                        } else {
314                            None
315                        }
316                    } else {
317                        None
318                    }
319                },
320        })
321    } else {
322        Err(anyhow::anyhow!("Expected struct fields"))
323    }
324}
325
326// Typed facade (invokes + reads)
327
328/// Invoke `Pause` command on cluster `Operational State`.
329pub async fn pause(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OperationalCommandResponse> {
330    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_CMD_ID_PAUSE, &[]).await?;
331    decode_operational_command_response(&tlv)
332}
333
334/// Invoke `Stop` command on cluster `Operational State`.
335pub async fn stop(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OperationalCommandResponse> {
336    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_CMD_ID_STOP, &[]).await?;
337    decode_operational_command_response(&tlv)
338}
339
340/// Invoke `Start` command on cluster `Operational State`.
341pub async fn start(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OperationalCommandResponse> {
342    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_CMD_ID_START, &[]).await?;
343    decode_operational_command_response(&tlv)
344}
345
346/// Invoke `Resume` command on cluster `Operational State`.
347pub async fn resume(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OperationalCommandResponse> {
348    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_CMD_ID_RESUME, &[]).await?;
349    decode_operational_command_response(&tlv)
350}
351
352/// Read `PhaseList` attribute from cluster `Operational State`.
353pub async fn read_phase_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<String>> {
354    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_PHASELIST).await?;
355    decode_phase_list(&tlv)
356}
357
358/// Read `CurrentPhase` attribute from cluster `Operational State`.
359pub async fn read_current_phase(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
360    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_CURRENTPHASE).await?;
361    decode_current_phase(&tlv)
362}
363
364/// Read `CountdownTime` attribute from cluster `Operational State`.
365pub async fn read_countdown_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u32>> {
366    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_COUNTDOWNTIME).await?;
367    decode_countdown_time(&tlv)
368}
369
370/// Read `OperationalStateList` attribute from cluster `Operational State`.
371pub async fn read_operational_state_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<OperationalState>> {
372    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_OPERATIONALSTATELIST).await?;
373    decode_operational_state_list(&tlv)
374}
375
376/// Read `OperationalState` attribute from cluster `Operational State`.
377pub async fn read_operational_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OperationalStateEnum> {
378    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_OPERATIONALSTATE).await?;
379    decode_operational_state(&tlv)
380}
381
382/// Read `OperationalError` attribute from cluster `Operational State`.
383pub async fn read_operational_error(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ErrorState> {
384    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OPERATIONAL_STATE, crate::clusters::defs::CLUSTER_OPERATIONAL_STATE_ATTR_ID_OPERATIONALERROR).await?;
385    decode_operational_error(&tlv)
386}
387
388#[derive(Debug, serde::Serialize)]
389pub struct OperationalErrorEvent {
390    pub error_state: Option<ErrorState>,
391}
392
393#[derive(Debug, serde::Serialize)]
394pub struct OperationCompletionEvent {
395    pub completion_error_code: Option<u8>,
396    pub total_operational_time: Option<u32>,
397    pub paused_time: Option<u32>,
398}
399
400// Event decoders
401
402/// Decode OperationalError event (0x00, priority: critical)
403pub fn decode_operational_error_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationalErrorEvent> {
404    if let tlv::TlvItemValue::List(_fields) = inp {
405        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
406        Ok(OperationalErrorEvent {
407                                error_state: {
408                    if let Some(nested_tlv) = item.get(&[0]) {
409                        if let tlv::TlvItemValue::List(_) = nested_tlv {
410                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
411                            Some(ErrorState {
412                error_state_id: nested_item.get_int(&[0]).and_then(|v| ErrorStateEnum::from_u8(v as u8)),
413                error_state_label: nested_item.get_string_owned(&[1]),
414                error_state_details: nested_item.get_string_owned(&[2]),
415                            })
416                        } else {
417                            None
418                        }
419                    } else {
420                        None
421                    }
422                },
423        })
424    } else {
425        Err(anyhow::anyhow!("Expected struct fields"))
426    }
427}
428
429/// Decode OperationCompletion event (0x01, priority: info)
430pub fn decode_operation_completion_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OperationCompletionEvent> {
431    if let tlv::TlvItemValue::List(_fields) = inp {
432        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
433        Ok(OperationCompletionEvent {
434                                completion_error_code: item.get_int(&[0]).map(|v| v as u8),
435                                total_operational_time: item.get_int(&[1]).map(|v| v as u32),
436                                paused_time: item.get_int(&[2]).map(|v| v as u32),
437        })
438    } else {
439        Err(anyhow::anyhow!("Expected struct fields"))
440    }
441}
442