matc/clusters/codec/
icd_management.rs

1//! Matter TLV encoders and decoders for ICD Management Cluster
2//! Cluster ID: 0x0046
3//!
4//! This file is automatically generated from ICDManagement.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 ClientType {
16    /// The client is typically resident, always-on, fixed infrastructure in the home.
17    Permanent = 0,
18    /// The client is mobile or non-resident or not always-on and may not always be available in the home.
19    Ephemeral = 1,
20}
21
22impl ClientType {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(ClientType::Permanent),
27            1 => Some(ClientType::Ephemeral),
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<ClientType> for u8 {
39    fn from(val: ClientType) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum OperatingMode {
47    /// ICD is operating as a Short Idle Time ICD.
48    Sit = 0,
49    /// ICD is operating as a Long Idle Time ICD.
50    Lit = 1,
51}
52
53impl OperatingMode {
54    /// Convert from u8 value
55    pub fn from_u8(value: u8) -> Option<Self> {
56        match value {
57            0 => Some(OperatingMode::Sit),
58            1 => Some(OperatingMode::Lit),
59            _ => None,
60        }
61    }
62
63    /// Convert to u8 value
64    pub fn to_u8(self) -> u8 {
65        self as u8
66    }
67}
68
69impl From<OperatingMode> for u8 {
70    fn from(val: OperatingMode) -> Self {
71        val as u8
72    }
73}
74
75// Bitmap definitions
76
77/// UserActiveModeTrigger bitmap type
78pub type UserActiveModeTrigger = u32;
79
80/// Constants for UserActiveModeTrigger
81pub mod useractivemodetrigger {
82    /// Power Cycle to transition the device to ActiveMode
83    pub const POWER_CYCLE: u32 = 0x01;
84    /// Settings menu on the device informs how to transition the device to ActiveMode
85    pub const SETTINGS_MENU: u32 = 0x02;
86    /// Custom Instruction on how to transition the device to ActiveMode
87    pub const CUSTOM_INSTRUCTION: u32 = 0x04;
88    /// Device Manual informs how to transition the device to ActiveMode
89    pub const DEVICE_MANUAL: u32 = 0x08;
90    /// Actuate Sensor to transition the device to ActiveMode
91    pub const ACTUATE_SENSOR: u32 = 0x10;
92    /// Actuate Sensor for N seconds to transition the device to ActiveMode
93    pub const ACTUATE_SENSOR_SECONDS: u32 = 0x20;
94    /// Actuate Sensor N times to transition the device to ActiveMode
95    pub const ACTUATE_SENSOR_TIMES: u32 = 0x40;
96    /// Actuate Sensor until light blinks to transition the device to ActiveMode
97    pub const ACTUATE_SENSOR_LIGHTS_BLINK: u32 = 0x80;
98    /// Press Reset Button to transition the device to ActiveMode
99    pub const RESET_BUTTON: u32 = 0x100;
100    /// Press Reset Button until light blinks to transition the device to ActiveMode
101    pub const RESET_BUTTON_LIGHTS_BLINK: u32 = 0x200;
102    /// Press Reset Button for N seconds to transition the device to ActiveMode
103    pub const RESET_BUTTON_SECONDS: u32 = 0x400;
104    /// Press Reset Button N times to transition the device to ActiveMode
105    pub const RESET_BUTTON_TIMES: u32 = 0x800;
106    /// Press Setup Button to transition the device to ActiveMode
107    pub const SETUP_BUTTON: u32 = 0x1000;
108    /// Press Setup Button for N seconds to transition the device to ActiveMode
109    pub const SETUP_BUTTON_SECONDS: u32 = 0x2000;
110    /// Press Setup Button until light blinks to transition the device to ActiveMode
111    pub const SETUP_BUTTON_LIGHTS_BLINK: u32 = 0x4000;
112    /// Press Setup Button N times to transition the device to ActiveMode
113    pub const SETUP_BUTTON_TIMES: u32 = 0x8000;
114    /// Press the N Button to transition the device to ActiveMode
115    pub const APP_DEFINED_BUTTON: u32 = 0x10000;
116}
117
118// Struct definitions
119
120#[derive(Debug, serde::Serialize)]
121pub struct MonitoringRegistration {
122    pub check_in_node_id: Option<u64>,
123    pub monitored_subject: Option<u64>,
124    pub key: Option<u8>,
125    pub client_type: Option<ClientType>,
126}
127
128// Command encoders
129
130/// Encode RegisterClient command (0x00)
131pub fn encode_register_client(check_in_node_id: u64, monitored_subject: u64, key: Vec<u8>, verification_key: Vec<u8>, client_type: ClientType) -> anyhow::Result<Vec<u8>> {
132    let tlv = tlv::TlvItemEnc {
133        tag: 0,
134        value: tlv::TlvItemValueEnc::StructInvisible(vec![
135        (0, tlv::TlvItemValueEnc::UInt64(check_in_node_id)).into(),
136        (1, tlv::TlvItemValueEnc::UInt64(monitored_subject)).into(),
137        (2, tlv::TlvItemValueEnc::OctetString(key)).into(),
138        (3, tlv::TlvItemValueEnc::OctetString(verification_key)).into(),
139        (4, tlv::TlvItemValueEnc::UInt8(client_type.to_u8())).into(),
140        ]),
141    };
142    Ok(tlv.encode()?)
143}
144
145/// Encode UnregisterClient command (0x02)
146pub fn encode_unregister_client(check_in_node_id: u64, verification_key: Vec<u8>) -> anyhow::Result<Vec<u8>> {
147    let tlv = tlv::TlvItemEnc {
148        tag: 0,
149        value: tlv::TlvItemValueEnc::StructInvisible(vec![
150        (0, tlv::TlvItemValueEnc::UInt64(check_in_node_id)).into(),
151        (1, tlv::TlvItemValueEnc::OctetString(verification_key)).into(),
152        ]),
153    };
154    Ok(tlv.encode()?)
155}
156
157/// Encode StayActiveRequest command (0x03)
158pub fn encode_stay_active_request(stay_active_duration: u32) -> anyhow::Result<Vec<u8>> {
159    let tlv = tlv::TlvItemEnc {
160        tag: 0,
161        value: tlv::TlvItemValueEnc::StructInvisible(vec![
162        (0, tlv::TlvItemValueEnc::UInt32(stay_active_duration)).into(),
163        ]),
164    };
165    Ok(tlv.encode()?)
166}
167
168// Attribute decoders
169
170/// Decode IdleModeDuration attribute (0x0000)
171pub fn decode_idle_mode_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
172    if let tlv::TlvItemValue::Int(v) = inp {
173        Ok(*v as u32)
174    } else {
175        Err(anyhow::anyhow!("Expected UInt32"))
176    }
177}
178
179/// Decode ActiveModeDuration attribute (0x0001)
180pub fn decode_active_mode_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
181    if let tlv::TlvItemValue::Int(v) = inp {
182        Ok(*v as u32)
183    } else {
184        Err(anyhow::anyhow!("Expected UInt32"))
185    }
186}
187
188/// Decode ActiveModeThreshold attribute (0x0002)
189pub fn decode_active_mode_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
190    if let tlv::TlvItemValue::Int(v) = inp {
191        Ok(*v as u16)
192    } else {
193        Err(anyhow::anyhow!("Expected UInt16"))
194    }
195}
196
197/// Decode RegisteredClients attribute (0x0003)
198pub fn decode_registered_clients(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<MonitoringRegistration>> {
199    let mut res = Vec::new();
200    if let tlv::TlvItemValue::List(v) = inp {
201        for item in v {
202            res.push(MonitoringRegistration {
203                check_in_node_id: item.get_int(&[1]),
204                monitored_subject: item.get_int(&[2]),
205                key: item.get_int(&[3]).map(|v| v as u8),
206                client_type: item.get_int(&[4]).and_then(|v| ClientType::from_u8(v as u8)),
207            });
208        }
209    }
210    Ok(res)
211}
212
213/// Decode ICDCounter attribute (0x0004)
214pub fn decode_icd_counter(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
215    if let tlv::TlvItemValue::Int(v) = inp {
216        Ok(*v as u32)
217    } else {
218        Err(anyhow::anyhow!("Expected UInt32"))
219    }
220}
221
222/// Decode ClientsSupportedPerFabric attribute (0x0005)
223pub fn decode_clients_supported_per_fabric(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
224    if let tlv::TlvItemValue::Int(v) = inp {
225        Ok(*v as u16)
226    } else {
227        Err(anyhow::anyhow!("Expected UInt16"))
228    }
229}
230
231/// Decode UserActiveModeTriggerHint attribute (0x0006)
232pub fn decode_user_active_mode_trigger_hint(inp: &tlv::TlvItemValue) -> anyhow::Result<UserActiveModeTrigger> {
233    if let tlv::TlvItemValue::Int(v) = inp {
234        Ok(*v as u32)
235    } else {
236        Err(anyhow::anyhow!("Expected Integer"))
237    }
238}
239
240/// Decode UserActiveModeTriggerInstruction attribute (0x0007)
241pub fn decode_user_active_mode_trigger_instruction(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
242    if let tlv::TlvItemValue::String(v) = inp {
243        Ok(v.clone())
244    } else {
245        Err(anyhow::anyhow!("Expected String"))
246    }
247}
248
249/// Decode OperatingMode attribute (0x0008)
250pub fn decode_operating_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<OperatingMode> {
251    if let tlv::TlvItemValue::Int(v) = inp {
252        OperatingMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
253    } else {
254        Err(anyhow::anyhow!("Expected Integer"))
255    }
256}
257
258/// Decode MaximumCheckInBackoff attribute (0x0009)
259pub fn decode_maximum_check_in_backoff(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
260    if let tlv::TlvItemValue::Int(v) = inp {
261        Ok(*v as u32)
262    } else {
263        Err(anyhow::anyhow!("Expected UInt32"))
264    }
265}
266
267
268// JSON dispatcher function
269
270/// Decode attribute value and return as JSON string
271///
272/// # Parameters
273/// * `cluster_id` - The cluster identifier
274/// * `attribute_id` - The attribute identifier
275/// * `tlv_value` - The TLV value to decode
276///
277/// # Returns
278/// JSON string representation of the decoded value or error
279pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
280    // Verify this is the correct cluster
281    if cluster_id != 0x0046 {
282        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0046, got {}\"}}", cluster_id);
283    }
284
285    match attribute_id {
286        0x0000 => {
287            match decode_idle_mode_duration(tlv_value) {
288                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
289                Err(e) => format!("{{\"error\": \"{}\"}}", e),
290            }
291        }
292        0x0001 => {
293            match decode_active_mode_duration(tlv_value) {
294                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
295                Err(e) => format!("{{\"error\": \"{}\"}}", e),
296            }
297        }
298        0x0002 => {
299            match decode_active_mode_threshold(tlv_value) {
300                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
301                Err(e) => format!("{{\"error\": \"{}\"}}", e),
302            }
303        }
304        0x0003 => {
305            match decode_registered_clients(tlv_value) {
306                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
307                Err(e) => format!("{{\"error\": \"{}\"}}", e),
308            }
309        }
310        0x0004 => {
311            match decode_icd_counter(tlv_value) {
312                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
313                Err(e) => format!("{{\"error\": \"{}\"}}", e),
314            }
315        }
316        0x0005 => {
317            match decode_clients_supported_per_fabric(tlv_value) {
318                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
319                Err(e) => format!("{{\"error\": \"{}\"}}", e),
320            }
321        }
322        0x0006 => {
323            match decode_user_active_mode_trigger_hint(tlv_value) {
324                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
325                Err(e) => format!("{{\"error\": \"{}\"}}", e),
326            }
327        }
328        0x0007 => {
329            match decode_user_active_mode_trigger_instruction(tlv_value) {
330                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
331                Err(e) => format!("{{\"error\": \"{}\"}}", e),
332            }
333        }
334        0x0008 => {
335            match decode_operating_mode(tlv_value) {
336                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
337                Err(e) => format!("{{\"error\": \"{}\"}}", e),
338            }
339        }
340        0x0009 => {
341            match decode_maximum_check_in_backoff(tlv_value) {
342                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
343                Err(e) => format!("{{\"error\": \"{}\"}}", e),
344            }
345        }
346        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
347    }
348}
349
350/// Get list of all attributes supported by this cluster
351///
352/// # Returns
353/// Vector of tuples containing (attribute_id, attribute_name)
354pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
355    vec![
356        (0x0000, "IdleModeDuration"),
357        (0x0001, "ActiveModeDuration"),
358        (0x0002, "ActiveModeThreshold"),
359        (0x0003, "RegisteredClients"),
360        (0x0004, "ICDCounter"),
361        (0x0005, "ClientsSupportedPerFabric"),
362        (0x0006, "UserActiveModeTriggerHint"),
363        (0x0007, "UserActiveModeTriggerInstruction"),
364        (0x0008, "OperatingMode"),
365        (0x0009, "MaximumCheckInBackoff"),
366    ]
367}
368
369#[derive(Debug, serde::Serialize)]
370pub struct RegisterClientResponse {
371    pub icd_counter: Option<u32>,
372}
373
374#[derive(Debug, serde::Serialize)]
375pub struct StayActiveResponse {
376    pub promised_active_duration: Option<u32>,
377}
378
379// Command response decoders
380
381/// Decode RegisterClientResponse command response (01)
382pub fn decode_register_client_response(inp: &tlv::TlvItemValue) -> anyhow::Result<RegisterClientResponse> {
383    if let tlv::TlvItemValue::List(_fields) = inp {
384        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
385        Ok(RegisterClientResponse {
386                icd_counter: item.get_int(&[0]).map(|v| v as u32),
387        })
388    } else {
389        Err(anyhow::anyhow!("Expected struct fields"))
390    }
391}
392
393/// Decode StayActiveResponse command response (04)
394pub fn decode_stay_active_response(inp: &tlv::TlvItemValue) -> anyhow::Result<StayActiveResponse> {
395    if let tlv::TlvItemValue::List(_fields) = inp {
396        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
397        Ok(StayActiveResponse {
398                promised_active_duration: item.get_int(&[0]).map(|v| v as u32),
399        })
400    } else {
401        Err(anyhow::anyhow!("Expected struct fields"))
402    }
403}
404