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