matc/clusters/codec/
smoke_co_alarm.rs

1//! Matter TLV encoders and decoders for Smoke CO Alarm Cluster
2//! Cluster ID: 0x005C
3//!
4//! This file is automatically generated from SmokeCOAlarm.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 AlarmState {
16    /// Nominal state, the device is not alarming
17    Normal = 0,
18    /// Warning state
19    Warning = 1,
20    /// Critical state
21    Critical = 2,
22}
23
24impl AlarmState {
25    /// Convert from u8 value
26    pub fn from_u8(value: u8) -> Option<Self> {
27        match value {
28            0 => Some(AlarmState::Normal),
29            1 => Some(AlarmState::Warning),
30            2 => Some(AlarmState::Critical),
31            _ => None,
32        }
33    }
34
35    /// Convert to u8 value
36    pub fn to_u8(self) -> u8 {
37        self as u8
38    }
39}
40
41impl From<AlarmState> for u8 {
42    fn from(val: AlarmState) -> Self {
43        val as u8
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48#[repr(u8)]
49pub enum ContaminationState {
50    /// Nominal state, the sensor is not contaminated
51    Normal = 0,
52    /// Low contamination
53    Low = 1,
54    /// Warning state
55    Warning = 2,
56    /// Critical state, will cause nuisance alarms
57    Critical = 3,
58}
59
60impl ContaminationState {
61    /// Convert from u8 value
62    pub fn from_u8(value: u8) -> Option<Self> {
63        match value {
64            0 => Some(ContaminationState::Normal),
65            1 => Some(ContaminationState::Low),
66            2 => Some(ContaminationState::Warning),
67            3 => Some(ContaminationState::Critical),
68            _ => None,
69        }
70    }
71
72    /// Convert to u8 value
73    pub fn to_u8(self) -> u8 {
74        self as u8
75    }
76}
77
78impl From<ContaminationState> for u8 {
79    fn from(val: ContaminationState) -> Self {
80        val as u8
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
85#[repr(u8)]
86pub enum EndOfService {
87    /// Device has not expired
88    Normal = 0,
89    /// Device has reached its end of service
90    Expired = 1,
91}
92
93impl EndOfService {
94    /// Convert from u8 value
95    pub fn from_u8(value: u8) -> Option<Self> {
96        match value {
97            0 => Some(EndOfService::Normal),
98            1 => Some(EndOfService::Expired),
99            _ => None,
100        }
101    }
102
103    /// Convert to u8 value
104    pub fn to_u8(self) -> u8 {
105        self as u8
106    }
107}
108
109impl From<EndOfService> for u8 {
110    fn from(val: EndOfService) -> Self {
111        val as u8
112    }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
116#[repr(u8)]
117pub enum ExpressedState {
118    /// Nominal state, the device is not alarming
119    Normal = 0,
120    /// Smoke Alarm state
121    Smokealarm = 1,
122    /// CO Alarm state
123    Coalarm = 2,
124    /// Battery Alert State
125    Batteryalert = 3,
126    /// Test in Progress
127    Testing = 4,
128    /// Hardware Fault Alert State
129    Hardwarefault = 5,
130    /// End of Service Alert State
131    Endofservice = 6,
132    /// Interconnected Smoke Alarm State
133    Interconnectsmoke = 7,
134    /// Interconnected CO Alarm State
135    Interconnectco = 8,
136}
137
138impl ExpressedState {
139    /// Convert from u8 value
140    pub fn from_u8(value: u8) -> Option<Self> {
141        match value {
142            0 => Some(ExpressedState::Normal),
143            1 => Some(ExpressedState::Smokealarm),
144            2 => Some(ExpressedState::Coalarm),
145            3 => Some(ExpressedState::Batteryalert),
146            4 => Some(ExpressedState::Testing),
147            5 => Some(ExpressedState::Hardwarefault),
148            6 => Some(ExpressedState::Endofservice),
149            7 => Some(ExpressedState::Interconnectsmoke),
150            8 => Some(ExpressedState::Interconnectco),
151            _ => None,
152        }
153    }
154
155    /// Convert to u8 value
156    pub fn to_u8(self) -> u8 {
157        self as u8
158    }
159}
160
161impl From<ExpressedState> for u8 {
162    fn from(val: ExpressedState) -> Self {
163        val as u8
164    }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
168#[repr(u8)]
169pub enum MuteState {
170    /// Not Muted
171    Notmuted = 0,
172    /// Muted
173    Muted = 1,
174}
175
176impl MuteState {
177    /// Convert from u8 value
178    pub fn from_u8(value: u8) -> Option<Self> {
179        match value {
180            0 => Some(MuteState::Notmuted),
181            1 => Some(MuteState::Muted),
182            _ => None,
183        }
184    }
185
186    /// Convert to u8 value
187    pub fn to_u8(self) -> u8 {
188        self as u8
189    }
190}
191
192impl From<MuteState> for u8 {
193    fn from(val: MuteState) -> Self {
194        val as u8
195    }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199#[repr(u8)]
200pub enum Sensitivity {
201    /// High sensitivity
202    High = 0,
203    /// Standard Sensitivity
204    Standard = 1,
205    /// Low sensitivity
206    Low = 2,
207}
208
209impl Sensitivity {
210    /// Convert from u8 value
211    pub fn from_u8(value: u8) -> Option<Self> {
212        match value {
213            0 => Some(Sensitivity::High),
214            1 => Some(Sensitivity::Standard),
215            2 => Some(Sensitivity::Low),
216            _ => None,
217        }
218    }
219
220    /// Convert to u8 value
221    pub fn to_u8(self) -> u8 {
222        self as u8
223    }
224}
225
226impl From<Sensitivity> for u8 {
227    fn from(val: Sensitivity) -> Self {
228        val as u8
229    }
230}
231
232// Command encoders
233
234// Attribute decoders
235
236/// Decode ExpressedState attribute (0x0000)
237pub fn decode_expressed_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ExpressedState> {
238    if let tlv::TlvItemValue::Int(v) = inp {
239        ExpressedState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
240    } else {
241        Err(anyhow::anyhow!("Expected Integer"))
242    }
243}
244
245/// Decode SmokeState attribute (0x0001)
246pub fn decode_smoke_state(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
247    if let tlv::TlvItemValue::Int(v) = inp {
248        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
249    } else {
250        Err(anyhow::anyhow!("Expected Integer"))
251    }
252}
253
254/// Decode COState attribute (0x0002)
255pub fn decode_co_state(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
256    if let tlv::TlvItemValue::Int(v) = inp {
257        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
258    } else {
259        Err(anyhow::anyhow!("Expected Integer"))
260    }
261}
262
263/// Decode BatteryAlert attribute (0x0003)
264pub fn decode_battery_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
265    if let tlv::TlvItemValue::Int(v) = inp {
266        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
267    } else {
268        Err(anyhow::anyhow!("Expected Integer"))
269    }
270}
271
272/// Decode DeviceMuted attribute (0x0004)
273pub fn decode_device_muted(inp: &tlv::TlvItemValue) -> anyhow::Result<MuteState> {
274    if let tlv::TlvItemValue::Int(v) = inp {
275        MuteState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
276    } else {
277        Err(anyhow::anyhow!("Expected Integer"))
278    }
279}
280
281/// Decode TestInProgress attribute (0x0005)
282pub fn decode_test_in_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
283    if let tlv::TlvItemValue::Bool(v) = inp {
284        Ok(*v)
285    } else {
286        Err(anyhow::anyhow!("Expected Bool"))
287    }
288}
289
290/// Decode HardwareFaultAlert attribute (0x0006)
291pub fn decode_hardware_fault_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
292    if let tlv::TlvItemValue::Bool(v) = inp {
293        Ok(*v)
294    } else {
295        Err(anyhow::anyhow!("Expected Bool"))
296    }
297}
298
299/// Decode EndOfServiceAlert attribute (0x0007)
300pub fn decode_end_of_service_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<EndOfService> {
301    if let tlv::TlvItemValue::Int(v) = inp {
302        EndOfService::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
303    } else {
304        Err(anyhow::anyhow!("Expected Integer"))
305    }
306}
307
308/// Decode InterconnectSmokeAlarm attribute (0x0008)
309pub fn decode_interconnect_smoke_alarm(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
310    if let tlv::TlvItemValue::Int(v) = inp {
311        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
312    } else {
313        Err(anyhow::anyhow!("Expected Integer"))
314    }
315}
316
317/// Decode InterconnectCOAlarm attribute (0x0009)
318pub fn decode_interconnect_co_alarm(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
319    if let tlv::TlvItemValue::Int(v) = inp {
320        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
321    } else {
322        Err(anyhow::anyhow!("Expected Integer"))
323    }
324}
325
326/// Decode ContaminationState attribute (0x000A)
327pub fn decode_contamination_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ContaminationState> {
328    if let tlv::TlvItemValue::Int(v) = inp {
329        ContaminationState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
330    } else {
331        Err(anyhow::anyhow!("Expected Integer"))
332    }
333}
334
335/// Decode SmokeSensitivityLevel attribute (0x000B)
336pub fn decode_smoke_sensitivity_level(inp: &tlv::TlvItemValue) -> anyhow::Result<Sensitivity> {
337    if let tlv::TlvItemValue::Int(v) = inp {
338        Sensitivity::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
339    } else {
340        Err(anyhow::anyhow!("Expected Integer"))
341    }
342}
343
344/// Decode ExpiryDate attribute (0x000C)
345pub fn decode_expiry_date(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
346    if let tlv::TlvItemValue::Int(v) = inp {
347        Ok(*v)
348    } else {
349        Err(anyhow::anyhow!("Expected UInt64"))
350    }
351}
352
353
354// JSON dispatcher function
355
356/// Decode attribute value and return as JSON string
357///
358/// # Parameters
359/// * `cluster_id` - The cluster identifier
360/// * `attribute_id` - The attribute identifier
361/// * `tlv_value` - The TLV value to decode
362///
363/// # Returns
364/// JSON string representation of the decoded value or error
365pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
366    // Verify this is the correct cluster
367    if cluster_id != 0x005C {
368        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x005C, got {}\"}}", cluster_id);
369    }
370
371    match attribute_id {
372        0x0000 => {
373            match decode_expressed_state(tlv_value) {
374                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
375                Err(e) => format!("{{\"error\": \"{}\"}}", e),
376            }
377        }
378        0x0001 => {
379            match decode_smoke_state(tlv_value) {
380                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
381                Err(e) => format!("{{\"error\": \"{}\"}}", e),
382            }
383        }
384        0x0002 => {
385            match decode_co_state(tlv_value) {
386                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
387                Err(e) => format!("{{\"error\": \"{}\"}}", e),
388            }
389        }
390        0x0003 => {
391            match decode_battery_alert(tlv_value) {
392                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
393                Err(e) => format!("{{\"error\": \"{}\"}}", e),
394            }
395        }
396        0x0004 => {
397            match decode_device_muted(tlv_value) {
398                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
399                Err(e) => format!("{{\"error\": \"{}\"}}", e),
400            }
401        }
402        0x0005 => {
403            match decode_test_in_progress(tlv_value) {
404                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
405                Err(e) => format!("{{\"error\": \"{}\"}}", e),
406            }
407        }
408        0x0006 => {
409            match decode_hardware_fault_alert(tlv_value) {
410                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
411                Err(e) => format!("{{\"error\": \"{}\"}}", e),
412            }
413        }
414        0x0007 => {
415            match decode_end_of_service_alert(tlv_value) {
416                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
417                Err(e) => format!("{{\"error\": \"{}\"}}", e),
418            }
419        }
420        0x0008 => {
421            match decode_interconnect_smoke_alarm(tlv_value) {
422                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
423                Err(e) => format!("{{\"error\": \"{}\"}}", e),
424            }
425        }
426        0x0009 => {
427            match decode_interconnect_co_alarm(tlv_value) {
428                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
429                Err(e) => format!("{{\"error\": \"{}\"}}", e),
430            }
431        }
432        0x000A => {
433            match decode_contamination_state(tlv_value) {
434                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
435                Err(e) => format!("{{\"error\": \"{}\"}}", e),
436            }
437        }
438        0x000B => {
439            match decode_smoke_sensitivity_level(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        0x000C => {
445            match decode_expiry_date(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        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
451    }
452}
453
454/// Get list of all attributes supported by this cluster
455///
456/// # Returns
457/// Vector of tuples containing (attribute_id, attribute_name)
458pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
459    vec![
460        (0x0000, "ExpressedState"),
461        (0x0001, "SmokeState"),
462        (0x0002, "COState"),
463        (0x0003, "BatteryAlert"),
464        (0x0004, "DeviceMuted"),
465        (0x0005, "TestInProgress"),
466        (0x0006, "HardwareFaultAlert"),
467        (0x0007, "EndOfServiceAlert"),
468        (0x0008, "InterconnectSmokeAlarm"),
469        (0x0009, "InterconnectCOAlarm"),
470        (0x000A, "ContaminationState"),
471        (0x000B, "SmokeSensitivityLevel"),
472        (0x000C, "ExpiryDate"),
473    ]
474}
475
476#[derive(Debug, serde::Serialize)]
477pub struct SmokeAlarmEvent {
478    pub alarm_severity_level: Option<AlarmState>,
479}
480
481#[derive(Debug, serde::Serialize)]
482pub struct COAlarmEvent {
483    pub alarm_severity_level: Option<AlarmState>,
484}
485
486#[derive(Debug, serde::Serialize)]
487pub struct LowBatteryEvent {
488    pub alarm_severity_level: Option<AlarmState>,
489}
490
491#[derive(Debug, serde::Serialize)]
492pub struct InterconnectSmokeAlarmEvent {
493    pub alarm_severity_level: Option<AlarmState>,
494}
495
496#[derive(Debug, serde::Serialize)]
497pub struct InterconnectCOAlarmEvent {
498    pub alarm_severity_level: Option<AlarmState>,
499}
500
501// Event decoders
502
503/// Decode SmokeAlarm event (0x00, priority: critical)
504pub fn decode_smoke_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<SmokeAlarmEvent> {
505    if let tlv::TlvItemValue::List(_fields) = inp {
506        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
507        Ok(SmokeAlarmEvent {
508                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
509        })
510    } else {
511        Err(anyhow::anyhow!("Expected struct fields"))
512    }
513}
514
515/// Decode COAlarm event (0x01, priority: critical)
516pub fn decode_co_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<COAlarmEvent> {
517    if let tlv::TlvItemValue::List(_fields) = inp {
518        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
519        Ok(COAlarmEvent {
520                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
521        })
522    } else {
523        Err(anyhow::anyhow!("Expected struct fields"))
524    }
525}
526
527/// Decode LowBattery event (0x02, priority: info)
528pub fn decode_low_battery_event(inp: &tlv::TlvItemValue) -> anyhow::Result<LowBatteryEvent> {
529    if let tlv::TlvItemValue::List(_fields) = inp {
530        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
531        Ok(LowBatteryEvent {
532                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
533        })
534    } else {
535        Err(anyhow::anyhow!("Expected struct fields"))
536    }
537}
538
539/// Decode InterconnectSmokeAlarm event (0x08, priority: critical)
540pub fn decode_interconnect_smoke_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<InterconnectSmokeAlarmEvent> {
541    if let tlv::TlvItemValue::List(_fields) = inp {
542        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
543        Ok(InterconnectSmokeAlarmEvent {
544                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
545        })
546    } else {
547        Err(anyhow::anyhow!("Expected struct fields"))
548    }
549}
550
551/// Decode InterconnectCOAlarm event (0x09, priority: critical)
552pub fn decode_interconnect_co_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<InterconnectCOAlarmEvent> {
553    if let tlv::TlvItemValue::List(_fields) = inp {
554        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
555        Ok(InterconnectCOAlarmEvent {
556                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
557        })
558    } else {
559        Err(anyhow::anyhow!("Expected struct fields"))
560    }
561}
562