1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum AlarmState {
16 Normal = 0,
18 Warning = 1,
20 Critical = 2,
22}
23
24impl AlarmState {
25 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 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 Normal = 0,
52 Low = 1,
54 Warning = 2,
56 Critical = 3,
58}
59
60impl ContaminationState {
61 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 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 Normal = 0,
89 Expired = 1,
91}
92
93impl EndOfService {
94 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 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 Normal = 0,
120 Smokealarm = 1,
122 Coalarm = 2,
124 Batteryalert = 3,
126 Testing = 4,
128 Hardwarefault = 5,
130 Endofservice = 6,
132 Interconnectsmoke = 7,
134 Interconnectco = 8,
136}
137
138impl ExpressedState {
139 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 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 Notmuted = 0,
172 Muted = 1,
174}
175
176impl MuteState {
177 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 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 = 0,
203 Standard = 1,
205 Low = 2,
207}
208
209impl Sensitivity {
210 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 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
232pub 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
245pub 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
254pub 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
263pub 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
272pub 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
281pub 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
290pub 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
299pub 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
308pub 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
317pub 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
326pub 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
335pub 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
344pub 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
354pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
366 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
454pub 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
501pub 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
515pub 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
527pub 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
539pub 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
551pub 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