Skip to main content

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    /// Hardware is not able to detect Smoke or CO
139    Inoperative = 9,
140}
141
142impl ExpressedState {
143    /// Convert from u8 value
144    pub fn from_u8(value: u8) -> Option<Self> {
145        match value {
146            0 => Some(ExpressedState::Normal),
147            1 => Some(ExpressedState::Smokealarm),
148            2 => Some(ExpressedState::Coalarm),
149            3 => Some(ExpressedState::Batteryalert),
150            4 => Some(ExpressedState::Testing),
151            5 => Some(ExpressedState::Hardwarefault),
152            6 => Some(ExpressedState::Endofservice),
153            7 => Some(ExpressedState::Interconnectsmoke),
154            8 => Some(ExpressedState::Interconnectco),
155            9 => Some(ExpressedState::Inoperative),
156            _ => None,
157        }
158    }
159
160    /// Convert to u8 value
161    pub fn to_u8(self) -> u8 {
162        self as u8
163    }
164}
165
166impl From<ExpressedState> for u8 {
167    fn from(val: ExpressedState) -> Self {
168        val as u8
169    }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
173#[repr(u8)]
174pub enum MuteState {
175    /// Not Muted
176    Notmuted = 0,
177    /// Muted
178    Muted = 1,
179}
180
181impl MuteState {
182    /// Convert from u8 value
183    pub fn from_u8(value: u8) -> Option<Self> {
184        match value {
185            0 => Some(MuteState::Notmuted),
186            1 => Some(MuteState::Muted),
187            _ => None,
188        }
189    }
190
191    /// Convert to u8 value
192    pub fn to_u8(self) -> u8 {
193        self as u8
194    }
195}
196
197impl From<MuteState> for u8 {
198    fn from(val: MuteState) -> Self {
199        val as u8
200    }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
204#[repr(u8)]
205pub enum Sensitivity {
206    /// High sensitivity
207    High = 0,
208    /// Standard Sensitivity
209    Standard = 1,
210    /// Low sensitivity
211    Low = 2,
212}
213
214impl Sensitivity {
215    /// Convert from u8 value
216    pub fn from_u8(value: u8) -> Option<Self> {
217        match value {
218            0 => Some(Sensitivity::High),
219            1 => Some(Sensitivity::Standard),
220            2 => Some(Sensitivity::Low),
221            _ => None,
222        }
223    }
224
225    /// Convert to u8 value
226    pub fn to_u8(self) -> u8 {
227        self as u8
228    }
229}
230
231impl From<Sensitivity> for u8 {
232    fn from(val: Sensitivity) -> Self {
233        val as u8
234    }
235}
236
237// Command encoders
238
239// Attribute decoders
240
241/// Decode ExpressedState attribute (0x0000)
242pub fn decode_expressed_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ExpressedState> {
243    if let tlv::TlvItemValue::Int(v) = inp {
244        ExpressedState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
245    } else {
246        Err(anyhow::anyhow!("Expected Integer"))
247    }
248}
249
250/// Decode SmokeState attribute (0x0001)
251pub fn decode_smoke_state(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
252    if let tlv::TlvItemValue::Int(v) = inp {
253        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
254    } else {
255        Err(anyhow::anyhow!("Expected Integer"))
256    }
257}
258
259/// Decode COState attribute (0x0002)
260pub fn decode_co_state(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
261    if let tlv::TlvItemValue::Int(v) = inp {
262        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
263    } else {
264        Err(anyhow::anyhow!("Expected Integer"))
265    }
266}
267
268/// Decode BatteryAlert attribute (0x0003)
269pub fn decode_battery_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
270    if let tlv::TlvItemValue::Int(v) = inp {
271        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
272    } else {
273        Err(anyhow::anyhow!("Expected Integer"))
274    }
275}
276
277/// Decode DeviceMuted attribute (0x0004)
278pub fn decode_device_muted(inp: &tlv::TlvItemValue) -> anyhow::Result<MuteState> {
279    if let tlv::TlvItemValue::Int(v) = inp {
280        MuteState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
281    } else {
282        Err(anyhow::anyhow!("Expected Integer"))
283    }
284}
285
286/// Decode TestInProgress attribute (0x0005)
287pub fn decode_test_in_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
288    if let tlv::TlvItemValue::Bool(v) = inp {
289        Ok(*v)
290    } else {
291        Err(anyhow::anyhow!("Expected Bool"))
292    }
293}
294
295/// Decode HardwareFaultAlert attribute (0x0006)
296pub fn decode_hardware_fault_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
297    if let tlv::TlvItemValue::Bool(v) = inp {
298        Ok(*v)
299    } else {
300        Err(anyhow::anyhow!("Expected Bool"))
301    }
302}
303
304/// Decode EndOfServiceAlert attribute (0x0007)
305pub fn decode_end_of_service_alert(inp: &tlv::TlvItemValue) -> anyhow::Result<EndOfService> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        EndOfService::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
308    } else {
309        Err(anyhow::anyhow!("Expected Integer"))
310    }
311}
312
313/// Decode InterconnectSmokeAlarm attribute (0x0008)
314pub fn decode_interconnect_smoke_alarm(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
315    if let tlv::TlvItemValue::Int(v) = inp {
316        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
317    } else {
318        Err(anyhow::anyhow!("Expected Integer"))
319    }
320}
321
322/// Decode InterconnectCOAlarm attribute (0x0009)
323pub fn decode_interconnect_co_alarm(inp: &tlv::TlvItemValue) -> anyhow::Result<AlarmState> {
324    if let tlv::TlvItemValue::Int(v) = inp {
325        AlarmState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
326    } else {
327        Err(anyhow::anyhow!("Expected Integer"))
328    }
329}
330
331/// Decode ContaminationState attribute (0x000A)
332pub fn decode_contamination_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ContaminationState> {
333    if let tlv::TlvItemValue::Int(v) = inp {
334        ContaminationState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
335    } else {
336        Err(anyhow::anyhow!("Expected Integer"))
337    }
338}
339
340/// Decode SmokeSensitivityLevel attribute (0x000B)
341pub fn decode_smoke_sensitivity_level(inp: &tlv::TlvItemValue) -> anyhow::Result<Sensitivity> {
342    if let tlv::TlvItemValue::Int(v) = inp {
343        Sensitivity::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
344    } else {
345        Err(anyhow::anyhow!("Expected Integer"))
346    }
347}
348
349/// Decode ExpiryDate attribute (0x000C)
350pub fn decode_expiry_date(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
351    if let tlv::TlvItemValue::Int(v) = inp {
352        Ok(*v)
353    } else {
354        Err(anyhow::anyhow!("Expected UInt64"))
355    }
356}
357
358/// Decode Unmounted attribute (0x000D)
359pub fn decode_unmounted(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
360    if let tlv::TlvItemValue::Bool(v) = inp {
361        Ok(*v)
362    } else {
363        Err(anyhow::anyhow!("Expected Bool"))
364    }
365}
366
367
368// JSON dispatcher function
369
370/// Decode attribute value and return as JSON string
371///
372/// # Parameters
373/// * `cluster_id` - The cluster identifier
374/// * `attribute_id` - The attribute identifier
375/// * `tlv_value` - The TLV value to decode
376///
377/// # Returns
378/// JSON string representation of the decoded value or error
379pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
380    // Verify this is the correct cluster
381    if cluster_id != 0x005C {
382        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x005C, got {}\"}}", cluster_id);
383    }
384
385    match attribute_id {
386        0x0000 => {
387            match decode_expressed_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        0x0001 => {
393            match decode_smoke_state(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        0x0002 => {
399            match decode_co_state(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        0x0003 => {
405            match decode_battery_alert(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        0x0004 => {
411            match decode_device_muted(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        0x0005 => {
417            match decode_test_in_progress(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        0x0006 => {
423            match decode_hardware_fault_alert(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        0x0007 => {
429            match decode_end_of_service_alert(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        0x0008 => {
435            match decode_interconnect_smoke_alarm(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        0x0009 => {
441            match decode_interconnect_co_alarm(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        0x000A => {
447            match decode_contamination_state(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        0x000B => {
453            match decode_smoke_sensitivity_level(tlv_value) {
454                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
455                Err(e) => format!("{{\"error\": \"{}\"}}", e),
456            }
457        }
458        0x000C => {
459            match decode_expiry_date(tlv_value) {
460                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
461                Err(e) => format!("{{\"error\": \"{}\"}}", e),
462            }
463        }
464        0x000D => {
465            match decode_unmounted(tlv_value) {
466                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
467                Err(e) => format!("{{\"error\": \"{}\"}}", e),
468            }
469        }
470        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
471    }
472}
473
474/// Get list of all attributes supported by this cluster
475///
476/// # Returns
477/// Vector of tuples containing (attribute_id, attribute_name)
478pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
479    vec![
480        (0x0000, "ExpressedState"),
481        (0x0001, "SmokeState"),
482        (0x0002, "COState"),
483        (0x0003, "BatteryAlert"),
484        (0x0004, "DeviceMuted"),
485        (0x0005, "TestInProgress"),
486        (0x0006, "HardwareFaultAlert"),
487        (0x0007, "EndOfServiceAlert"),
488        (0x0008, "InterconnectSmokeAlarm"),
489        (0x0009, "InterconnectCOAlarm"),
490        (0x000A, "ContaminationState"),
491        (0x000B, "SmokeSensitivityLevel"),
492        (0x000C, "ExpiryDate"),
493        (0x000D, "Unmounted"),
494    ]
495}
496
497// Command listing
498
499pub fn get_command_list() -> Vec<(u32, &'static str)> {
500    vec![
501        (0x00, "SelfTestRequest"),
502    ]
503}
504
505pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
506    match cmd_id {
507        0x00 => Some("SelfTestRequest"),
508        _ => None,
509    }
510}
511
512pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
513    match cmd_id {
514        0x00 => Some(vec![]),
515        _ => None,
516    }
517}
518
519pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
520    match cmd_id {
521        0x00 => Ok(vec![]),
522        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
523    }
524}
525
526// Typed facade (invokes + reads)
527
528/// Invoke `SelfTestRequest` command on cluster `Smoke CO Alarm`.
529pub async fn self_test_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
530    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_SMOKE_CO_ALARM, crate::clusters::defs::CLUSTER_SMOKE_CO_ALARM_CMD_ID_SELFTESTREQUEST, &[]).await?;
531    Ok(())
532}
533
534/// Read `ExpressedState` attribute from cluster `Smoke CO Alarm`.
535pub async fn read_expressed_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ExpressedState> {
536    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?;
537    decode_expressed_state(&tlv)
538}
539
540/// Read `SmokeState` attribute from cluster `Smoke CO Alarm`.
541pub async fn read_smoke_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AlarmState> {
542    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?;
543    decode_smoke_state(&tlv)
544}
545
546/// Read `COState` attribute from cluster `Smoke CO Alarm`.
547pub async fn read_co_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AlarmState> {
548    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?;
549    decode_co_state(&tlv)
550}
551
552/// Read `BatteryAlert` attribute from cluster `Smoke CO Alarm`.
553pub async fn read_battery_alert(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AlarmState> {
554    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?;
555    decode_battery_alert(&tlv)
556}
557
558/// Read `DeviceMuted` attribute from cluster `Smoke CO Alarm`.
559pub async fn read_device_muted(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<MuteState> {
560    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?;
561    decode_device_muted(&tlv)
562}
563
564/// Read `TestInProgress` attribute from cluster `Smoke CO Alarm`.
565pub async fn read_test_in_progress(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
566    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?;
567    decode_test_in_progress(&tlv)
568}
569
570/// Read `HardwareFaultAlert` attribute from cluster `Smoke CO Alarm`.
571pub async fn read_hardware_fault_alert(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
572    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?;
573    decode_hardware_fault_alert(&tlv)
574}
575
576/// Read `EndOfServiceAlert` attribute from cluster `Smoke CO Alarm`.
577pub async fn read_end_of_service_alert(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<EndOfService> {
578    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?;
579    decode_end_of_service_alert(&tlv)
580}
581
582/// Read `InterconnectSmokeAlarm` attribute from cluster `Smoke CO Alarm`.
583pub async fn read_interconnect_smoke_alarm(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AlarmState> {
584    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?;
585    decode_interconnect_smoke_alarm(&tlv)
586}
587
588/// Read `InterconnectCOAlarm` attribute from cluster `Smoke CO Alarm`.
589pub async fn read_interconnect_co_alarm(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<AlarmState> {
590    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?;
591    decode_interconnect_co_alarm(&tlv)
592}
593
594/// Read `ContaminationState` attribute from cluster `Smoke CO Alarm`.
595pub async fn read_contamination_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ContaminationState> {
596    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?;
597    decode_contamination_state(&tlv)
598}
599
600/// Read `SmokeSensitivityLevel` attribute from cluster `Smoke CO Alarm`.
601pub async fn read_smoke_sensitivity_level(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Sensitivity> {
602    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?;
603    decode_smoke_sensitivity_level(&tlv)
604}
605
606/// Read `ExpiryDate` attribute from cluster `Smoke CO Alarm`.
607pub async fn read_expiry_date(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u64> {
608    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?;
609    decode_expiry_date(&tlv)
610}
611
612/// Read `Unmounted` attribute from cluster `Smoke CO Alarm`.
613pub async fn read_unmounted(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
614    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SMOKE_CO_ALARM, crate::clusters::defs::CLUSTER_SMOKE_CO_ALARM_ATTR_ID_UNMOUNTED).await?;
615    decode_unmounted(&tlv)
616}
617
618#[derive(Debug, serde::Serialize)]
619pub struct SmokeAlarmEvent {
620    pub alarm_severity_level: Option<AlarmState>,
621}
622
623#[derive(Debug, serde::Serialize)]
624pub struct COAlarmEvent {
625    pub alarm_severity_level: Option<AlarmState>,
626}
627
628#[derive(Debug, serde::Serialize)]
629pub struct LowBatteryEvent {
630    pub alarm_severity_level: Option<AlarmState>,
631}
632
633#[derive(Debug, serde::Serialize)]
634pub struct InterconnectSmokeAlarmEvent {
635    pub alarm_severity_level: Option<AlarmState>,
636}
637
638#[derive(Debug, serde::Serialize)]
639pub struct InterconnectCOAlarmEvent {
640    pub alarm_severity_level: Option<AlarmState>,
641}
642
643// Event decoders
644
645/// Decode SmokeAlarm event (0x00, priority: critical)
646pub fn decode_smoke_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<SmokeAlarmEvent> {
647    if let tlv::TlvItemValue::List(_fields) = inp {
648        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
649        Ok(SmokeAlarmEvent {
650                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
651        })
652    } else {
653        Err(anyhow::anyhow!("Expected struct fields"))
654    }
655}
656
657/// Decode COAlarm event (0x01, priority: critical)
658pub fn decode_co_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<COAlarmEvent> {
659    if let tlv::TlvItemValue::List(_fields) = inp {
660        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
661        Ok(COAlarmEvent {
662                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
663        })
664    } else {
665        Err(anyhow::anyhow!("Expected struct fields"))
666    }
667}
668
669/// Decode LowBattery event (0x02, priority: info)
670pub fn decode_low_battery_event(inp: &tlv::TlvItemValue) -> anyhow::Result<LowBatteryEvent> {
671    if let tlv::TlvItemValue::List(_fields) = inp {
672        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
673        Ok(LowBatteryEvent {
674                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
675        })
676    } else {
677        Err(anyhow::anyhow!("Expected struct fields"))
678    }
679}
680
681/// Decode InterconnectSmokeAlarm event (0x08, priority: critical)
682pub fn decode_interconnect_smoke_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<InterconnectSmokeAlarmEvent> {
683    if let tlv::TlvItemValue::List(_fields) = inp {
684        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
685        Ok(InterconnectSmokeAlarmEvent {
686                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
687        })
688    } else {
689        Err(anyhow::anyhow!("Expected struct fields"))
690    }
691}
692
693/// Decode InterconnectCOAlarm event (0x09, priority: critical)
694pub fn decode_interconnect_co_alarm_event(inp: &tlv::TlvItemValue) -> anyhow::Result<InterconnectCOAlarmEvent> {
695    if let tlv::TlvItemValue::List(_fields) = inp {
696        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
697        Ok(InterconnectCOAlarmEvent {
698                                alarm_severity_level: item.get_int(&[0]).and_then(|v| AlarmState::from_u8(v as u8)),
699        })
700    } else {
701        Err(anyhow::anyhow!("Expected struct fields"))
702    }
703}
704
705
706// Event JSON dispatcher
707
708/// Decode event value and return as JSON string
709pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
710    if cluster_id != 0x005C {
711        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x005C, got {}\"}}", cluster_id);
712    }
713
714    match event_id {
715        0x00 => {
716            match decode_smoke_alarm_event(tlv_value) {
717                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
718                Err(e) => format!("{{\"error\": \"{}\"}}", e),
719            }
720        }
721        0x01 => {
722            match decode_co_alarm_event(tlv_value) {
723                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
724                Err(e) => format!("{{\"error\": \"{}\"}}", e),
725            }
726        }
727        0x02 => {
728            match decode_low_battery_event(tlv_value) {
729                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
730                Err(e) => format!("{{\"error\": \"{}\"}}", e),
731            }
732        }
733        0x03 => "{}".to_string(),
734        0x04 => "{}".to_string(),
735        0x05 => "{}".to_string(),
736        0x06 => "{}".to_string(),
737        0x07 => "{}".to_string(),
738        0x08 => {
739            match decode_interconnect_smoke_alarm_event(tlv_value) {
740                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
741                Err(e) => format!("{{\"error\": \"{}\"}}", e),
742            }
743        }
744        0x09 => {
745            match decode_interconnect_co_alarm_event(tlv_value) {
746                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
747                Err(e) => format!("{{\"error\": \"{}\"}}", e),
748            }
749        }
750        0x0A => "{}".to_string(),
751        _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
752    }
753}
754
755/// Get list of all events supported by this cluster
756///
757/// # Returns
758/// Vector of tuples containing (event_id, event_name)
759pub fn get_event_list() -> Vec<(u32, &'static str)> {
760    vec![
761        (0x00, "SmokeAlarm"),
762        (0x01, "COAlarm"),
763        (0x02, "LowBattery"),
764        (0x03, "HardwareFault"),
765        (0x04, "EndOfService"),
766        (0x05, "SelfTestComplete"),
767        (0x06, "AlarmMuted"),
768        (0x07, "MuteEnded"),
769        (0x08, "InterconnectSmokeAlarm"),
770        (0x09, "InterconnectCOAlarm"),
771        (0x0A, "AllClear"),
772    ]
773}
774