matc/clusters/codec/
thermostat.rs

1//! Matter TLV encoders and decoders for Thermostat Cluster
2//! Cluster ID: 0x0201
3//!
4//! This file is automatically generated from Thermostat.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum ACCapacityFormat {
19    /// British Thermal Unit per Hour
20    Btuh = 0,
21}
22
23impl ACCapacityFormat {
24    /// Convert from u8 value
25    pub fn from_u8(value: u8) -> Option<Self> {
26        match value {
27            0 => Some(ACCapacityFormat::Btuh),
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<ACCapacityFormat> for u8 {
39    fn from(val: ACCapacityFormat) -> Self {
40        val as u8
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum ACCompressorType {
47    /// Unknown compressor type
48    Unknown = 0,
49    /// Max working ambient 43 °C
50    T1 = 1,
51    /// Max working ambient 35 °C
52    T2 = 2,
53    /// Max working ambient 52 °C
54    T3 = 3,
55}
56
57impl ACCompressorType {
58    /// Convert from u8 value
59    pub fn from_u8(value: u8) -> Option<Self> {
60        match value {
61            0 => Some(ACCompressorType::Unknown),
62            1 => Some(ACCompressorType::T1),
63            2 => Some(ACCompressorType::T2),
64            3 => Some(ACCompressorType::T3),
65            _ => None,
66        }
67    }
68
69    /// Convert to u8 value
70    pub fn to_u8(self) -> u8 {
71        self as u8
72    }
73}
74
75impl From<ACCompressorType> for u8 {
76    fn from(val: ACCompressorType) -> Self {
77        val as u8
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
82#[repr(u8)]
83pub enum ACLouverPosition {
84    /// Fully Closed
85    Closed = 1,
86    /// Fully Open
87    Open = 2,
88    /// Quarter Open
89    Quarter = 3,
90    /// Half Open
91    Half = 4,
92    /// Three Quarters Open
93    Threequarters = 5,
94}
95
96impl ACLouverPosition {
97    /// Convert from u8 value
98    pub fn from_u8(value: u8) -> Option<Self> {
99        match value {
100            1 => Some(ACLouverPosition::Closed),
101            2 => Some(ACLouverPosition::Open),
102            3 => Some(ACLouverPosition::Quarter),
103            4 => Some(ACLouverPosition::Half),
104            5 => Some(ACLouverPosition::Threequarters),
105            _ => None,
106        }
107    }
108
109    /// Convert to u8 value
110    pub fn to_u8(self) -> u8 {
111        self as u8
112    }
113}
114
115impl From<ACLouverPosition> for u8 {
116    fn from(val: ACLouverPosition) -> Self {
117        val as u8
118    }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
122#[repr(u8)]
123pub enum ACRefrigerantType {
124    /// Unknown Refrigerant Type
125    Unknown = 0,
126    /// R22 Refrigerant
127    R22 = 1,
128    /// R410a Refrigerant
129    R410a = 2,
130    /// R407c Refrigerant
131    R407c = 3,
132}
133
134impl ACRefrigerantType {
135    /// Convert from u8 value
136    pub fn from_u8(value: u8) -> Option<Self> {
137        match value {
138            0 => Some(ACRefrigerantType::Unknown),
139            1 => Some(ACRefrigerantType::R22),
140            2 => Some(ACRefrigerantType::R410a),
141            3 => Some(ACRefrigerantType::R407c),
142            _ => None,
143        }
144    }
145
146    /// Convert to u8 value
147    pub fn to_u8(self) -> u8 {
148        self as u8
149    }
150}
151
152impl From<ACRefrigerantType> for u8 {
153    fn from(val: ACRefrigerantType) -> Self {
154        val as u8
155    }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
159#[repr(u8)]
160pub enum ACType {
161    /// Unknown AC Type
162    Unknown = 0,
163    /// Cooling and Fixed Speed
164    Coolingfixed = 1,
165    /// Heat Pump and Fixed Speed
166    Heatpumpfixed = 2,
167    /// Cooling and Inverter
168    Coolinginverter = 3,
169    /// Heat Pump and Inverter
170    Heatpumpinverter = 4,
171}
172
173impl ACType {
174    /// Convert from u8 value
175    pub fn from_u8(value: u8) -> Option<Self> {
176        match value {
177            0 => Some(ACType::Unknown),
178            1 => Some(ACType::Coolingfixed),
179            2 => Some(ACType::Heatpumpfixed),
180            3 => Some(ACType::Coolinginverter),
181            4 => Some(ACType::Heatpumpinverter),
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<ACType> for u8 {
193    fn from(val: ACType) -> Self {
194        val as u8
195    }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199#[repr(u8)]
200pub enum ControlSequenceOfOperation {
201    /// Heat and Emergency are not possible
202    Coolingonly = 0,
203    /// Heat and Emergency are not possible
204    Coolingwithreheat = 1,
205    /// Cool and precooling (see Terms) are not possible
206    Heatingonly = 2,
207    /// Cool and precooling are not possible
208    Heatingwithreheat = 3,
209    /// All modes are possible
210    Coolingandheating = 4,
211    /// All modes are possible
212    Coolingandheatingwithreheat = 5,
213}
214
215impl ControlSequenceOfOperation {
216    /// Convert from u8 value
217    pub fn from_u8(value: u8) -> Option<Self> {
218        match value {
219            0 => Some(ControlSequenceOfOperation::Coolingonly),
220            1 => Some(ControlSequenceOfOperation::Coolingwithreheat),
221            2 => Some(ControlSequenceOfOperation::Heatingonly),
222            3 => Some(ControlSequenceOfOperation::Heatingwithreheat),
223            4 => Some(ControlSequenceOfOperation::Coolingandheating),
224            5 => Some(ControlSequenceOfOperation::Coolingandheatingwithreheat),
225            _ => None,
226        }
227    }
228
229    /// Convert to u8 value
230    pub fn to_u8(self) -> u8 {
231        self as u8
232    }
233}
234
235impl From<ControlSequenceOfOperation> for u8 {
236    fn from(val: ControlSequenceOfOperation) -> Self {
237        val as u8
238    }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
242#[repr(u8)]
243pub enum PresetScenario {
244    /// The thermostat-controlled area is occupied
245    Occupied = 1,
246    /// The thermostat-controlled area is unoccupied
247    Unoccupied = 2,
248    /// Users are likely to be sleeping
249    Sleep = 3,
250    /// Users are likely to be waking up
251    Wake = 4,
252    /// Users are on vacation
253    Vacation = 5,
254    /// Users are likely to be going to sleep
255    Goingtosleep = 6,
256    /// Custom presets
257    Userdefined = 254,
258}
259
260impl PresetScenario {
261    /// Convert from u8 value
262    pub fn from_u8(value: u8) -> Option<Self> {
263        match value {
264            1 => Some(PresetScenario::Occupied),
265            2 => Some(PresetScenario::Unoccupied),
266            3 => Some(PresetScenario::Sleep),
267            4 => Some(PresetScenario::Wake),
268            5 => Some(PresetScenario::Vacation),
269            6 => Some(PresetScenario::Goingtosleep),
270            254 => Some(PresetScenario::Userdefined),
271            _ => None,
272        }
273    }
274
275    /// Convert to u8 value
276    pub fn to_u8(self) -> u8 {
277        self as u8
278    }
279}
280
281impl From<PresetScenario> for u8 {
282    fn from(val: PresetScenario) -> Self {
283        val as u8
284    }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
288#[repr(u8)]
289pub enum SetpointChangeSource {
290    /// Manual, user-initiated setpoint change via the thermostat
291    Manual = 0,
292    /// Schedule/internal programming-initiated setpoint change
293    Schedule = 1,
294    /// Externally-initiated setpoint change (e.g., DRLC cluster command, attribute write)
295    External = 2,
296}
297
298impl SetpointChangeSource {
299    /// Convert from u8 value
300    pub fn from_u8(value: u8) -> Option<Self> {
301        match value {
302            0 => Some(SetpointChangeSource::Manual),
303            1 => Some(SetpointChangeSource::Schedule),
304            2 => Some(SetpointChangeSource::External),
305            _ => None,
306        }
307    }
308
309    /// Convert to u8 value
310    pub fn to_u8(self) -> u8 {
311        self as u8
312    }
313}
314
315impl From<SetpointChangeSource> for u8 {
316    fn from(val: SetpointChangeSource) -> Self {
317        val as u8
318    }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
322#[repr(u8)]
323pub enum SetpointRaiseLowerMode {
324    /// Adjust Heat Setpoint
325    Heat = 0,
326    /// Adjust Cool Setpoint
327    Cool = 1,
328    /// Adjust Heat Setpoint and Cool Setpoint
329    Both = 2,
330}
331
332impl SetpointRaiseLowerMode {
333    /// Convert from u8 value
334    pub fn from_u8(value: u8) -> Option<Self> {
335        match value {
336            0 => Some(SetpointRaiseLowerMode::Heat),
337            1 => Some(SetpointRaiseLowerMode::Cool),
338            2 => Some(SetpointRaiseLowerMode::Both),
339            _ => None,
340        }
341    }
342
343    /// Convert to u8 value
344    pub fn to_u8(self) -> u8 {
345        self as u8
346    }
347}
348
349impl From<SetpointRaiseLowerMode> for u8 {
350    fn from(val: SetpointRaiseLowerMode) -> Self {
351        val as u8
352    }
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
356#[repr(u8)]
357pub enum StartOfWeek {
358    Sunday = 0,
359    Monday = 1,
360    Tuesday = 2,
361    Wednesday = 3,
362    Thursday = 4,
363    Friday = 5,
364    Saturday = 6,
365}
366
367impl StartOfWeek {
368    /// Convert from u8 value
369    pub fn from_u8(value: u8) -> Option<Self> {
370        match value {
371            0 => Some(StartOfWeek::Sunday),
372            1 => Some(StartOfWeek::Monday),
373            2 => Some(StartOfWeek::Tuesday),
374            3 => Some(StartOfWeek::Wednesday),
375            4 => Some(StartOfWeek::Thursday),
376            5 => Some(StartOfWeek::Friday),
377            6 => Some(StartOfWeek::Saturday),
378            _ => None,
379        }
380    }
381
382    /// Convert to u8 value
383    pub fn to_u8(self) -> u8 {
384        self as u8
385    }
386}
387
388impl From<StartOfWeek> for u8 {
389    fn from(val: StartOfWeek) -> Self {
390        val as u8
391    }
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
395#[repr(u8)]
396pub enum SystemMode {
397    /// The Thermostat does not generate demand for Cooling or Heating
398    Off = 0,
399    /// Demand is generated for either Cooling or Heating, as required
400    Auto = 1,
401    /// Demand is only generated for Cooling
402    Cool = 3,
403    /// Demand is only generated for Heating
404    Heat = 4,
405    /// 2nd stage heating is in use to achieve desired temperature
406    Emergencyheat = 5,
407    /// (see Terms)
408    Precooling = 6,
409    Fanonly = 7,
410    Dry = 8,
411    Sleep = 9,
412}
413
414impl SystemMode {
415    /// Convert from u8 value
416    pub fn from_u8(value: u8) -> Option<Self> {
417        match value {
418            0 => Some(SystemMode::Off),
419            1 => Some(SystemMode::Auto),
420            3 => Some(SystemMode::Cool),
421            4 => Some(SystemMode::Heat),
422            5 => Some(SystemMode::Emergencyheat),
423            6 => Some(SystemMode::Precooling),
424            7 => Some(SystemMode::Fanonly),
425            8 => Some(SystemMode::Dry),
426            9 => Some(SystemMode::Sleep),
427            _ => None,
428        }
429    }
430
431    /// Convert to u8 value
432    pub fn to_u8(self) -> u8 {
433        self as u8
434    }
435}
436
437impl From<SystemMode> for u8 {
438    fn from(val: SystemMode) -> Self {
439        val as u8
440    }
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
444#[repr(u8)]
445pub enum TemperatureSetpointHold {
446    /// Follow scheduling program
447    Setpointholdoff = 0,
448    /// Maintain current setpoint, regardless of schedule transitions
449    Setpointholdon = 1,
450}
451
452impl TemperatureSetpointHold {
453    /// Convert from u8 value
454    pub fn from_u8(value: u8) -> Option<Self> {
455        match value {
456            0 => Some(TemperatureSetpointHold::Setpointholdoff),
457            1 => Some(TemperatureSetpointHold::Setpointholdon),
458            _ => None,
459        }
460    }
461
462    /// Convert to u8 value
463    pub fn to_u8(self) -> u8 {
464        self as u8
465    }
466}
467
468impl From<TemperatureSetpointHold> for u8 {
469    fn from(val: TemperatureSetpointHold) -> Self {
470        val as u8
471    }
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
475#[repr(u8)]
476pub enum ThermostatRunningMode {
477    /// The Thermostat does not generate demand for Cooling or Heating
478    Off = 0,
479    /// Demand is only generated for Cooling
480    Cool = 3,
481    /// Demand is only generated for Heating
482    Heat = 4,
483}
484
485impl ThermostatRunningMode {
486    /// Convert from u8 value
487    pub fn from_u8(value: u8) -> Option<Self> {
488        match value {
489            0 => Some(ThermostatRunningMode::Off),
490            3 => Some(ThermostatRunningMode::Cool),
491            4 => Some(ThermostatRunningMode::Heat),
492            _ => None,
493        }
494    }
495
496    /// Convert to u8 value
497    pub fn to_u8(self) -> u8 {
498        self as u8
499    }
500}
501
502impl From<ThermostatRunningMode> for u8 {
503    fn from(val: ThermostatRunningMode) -> Self {
504        val as u8
505    }
506}
507
508// Bitmap definitions
509
510/// ACErrorCode bitmap type
511pub type ACErrorCode = u8;
512
513/// Constants for ACErrorCode
514pub mod acerrorcode {
515    /// Compressor Failure or Refrigerant Leakage
516    pub const COMPRESSOR_FAIL: u8 = 0x01;
517    /// Room Temperature Sensor Failure
518    pub const ROOM_SENSOR_FAIL: u8 = 0x02;
519    /// Outdoor Temperature Sensor Failure
520    pub const OUTDOOR_SENSOR_FAIL: u8 = 0x04;
521    /// Indoor Coil Temperature Sensor Failure
522    pub const COIL_SENSOR_FAIL: u8 = 0x08;
523    /// Fan Failure
524    pub const FAN_FAIL: u8 = 0x10;
525}
526
527/// Occupancy bitmap type
528pub type Occupancy = u8;
529
530/// Constants for Occupancy
531pub mod occupancy {
532    /// Indicates the occupancy state
533    pub const OCCUPIED: u8 = 0x01;
534}
535
536/// PresetTypeFeatures bitmap type
537pub type PresetTypeFeatures = u8;
538
539/// Constants for PresetTypeFeatures
540pub mod presettypefeatures {
541    /// Preset may be automatically activated by the thermostat
542    pub const AUTOMATIC: u8 = 0x01;
543    /// Preset supports user-provided names
544    pub const SUPPORTS_NAMES: u8 = 0x02;
545}
546
547/// ProgrammingOperationMode bitmap type
548pub type ProgrammingOperationMode = u8;
549
550/// Constants for ProgrammingOperationMode
551pub mod programmingoperationmode {
552    /// Schedule programming mode. This enables any programmed weekly schedule configurations.
553    pub const SCHEDULE_ACTIVE: u8 = 0x01;
554    /// Auto/recovery mode
555    pub const AUTO_RECOVERY: u8 = 0x02;
556    /// Economy/EnergyStar mode
557    pub const ECONOMY: u8 = 0x04;
558}
559
560/// RelayState bitmap type
561pub type RelayState = u8;
562
563/// Constants for RelayState
564pub mod relaystate {
565    /// Heat Stage On
566    pub const HEAT: u8 = 0x01;
567    /// Cool Stage On
568    pub const COOL: u8 = 0x02;
569    /// Fan Stage On
570    pub const FAN: u8 = 0x04;
571    /// Heat 2nd Stage On
572    pub const HEAT_STAGE2: u8 = 0x08;
573    /// Cool 2nd Stage On
574    pub const COOL_STAGE2: u8 = 0x10;
575    /// Fan 2nd Stage On
576    pub const FAN_STAGE2: u8 = 0x20;
577    /// Fan 3rd Stage On
578    pub const FAN_STAGE3: u8 = 0x40;
579}
580
581/// RemoteSensing bitmap type
582pub type RemoteSensing = u8;
583
584/// Constants for RemoteSensing
585pub mod remotesensing {
586    /// Calculated Local Temperature is derived from a remote node
587    pub const LOCAL_TEMPERATURE: u8 = 0x01;
588    /// OutdoorTemperature is derived from a remote node
589    pub const OUTDOOR_TEMPERATURE: u8 = 0x02;
590    /// Occupancy is derived from a remote node
591    pub const OCCUPANCY: u8 = 0x04;
592}
593
594/// ScheduleDayOfWeek bitmap type
595pub type ScheduleDayOfWeek = u8;
596
597/// Constants for ScheduleDayOfWeek
598pub mod scheduledayofweek {
599    /// Sunday
600    pub const SUNDAY: u8 = 0x01;
601    /// Monday
602    pub const MONDAY: u8 = 0x02;
603    /// Tuesday
604    pub const TUESDAY: u8 = 0x04;
605    /// Wednesday
606    pub const WEDNESDAY: u8 = 0x08;
607    /// Thursday
608    pub const THURSDAY: u8 = 0x10;
609    /// Friday
610    pub const FRIDAY: u8 = 0x20;
611    /// Saturday
612    pub const SATURDAY: u8 = 0x40;
613    /// Away or Vacation
614    pub const AWAY: u8 = 0x80;
615}
616
617/// ScheduleMode bitmap type
618pub type ScheduleMode = u8;
619
620/// Constants for ScheduleMode
621pub mod schedulemode {
622    /// Adjust Heat Setpoint
623    pub const HEAT_SETPOINT_PRESENT: u8 = 0x01;
624    /// Adjust Cool Setpoint
625    pub const COOL_SETPOINT_PRESENT: u8 = 0x02;
626}
627
628/// ScheduleTypeFeatures bitmap type
629pub type ScheduleTypeFeatures = u8;
630
631/// Constants for ScheduleTypeFeatures
632pub mod scheduletypefeatures {
633    /// Supports presets
634    pub const SUPPORTS_PRESETS: u8 = 0x01;
635    /// Supports setpoints
636    pub const SUPPORTS_SETPOINTS: u8 = 0x02;
637    /// Supports user-provided names
638    pub const SUPPORTS_NAMES: u8 = 0x04;
639    /// Supports transitioning to SystemModeOff
640    pub const SUPPORTS_OFF: u8 = 0x08;
641}
642
643// Struct definitions
644
645#[derive(Debug, serde::Serialize)]
646pub struct Preset {
647    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
648    pub preset_handle: Option<Vec<u8>>,
649    pub preset_scenario: Option<PresetScenario>,
650    pub name: Option<String>,
651    pub cooling_setpoint: Option<i16>,
652    pub heating_setpoint: Option<i16>,
653    pub built_in: Option<bool>,
654}
655
656#[derive(Debug, serde::Serialize)]
657pub struct PresetType {
658    pub preset_scenario: Option<PresetScenario>,
659    pub number_of_presets: Option<u8>,
660    pub preset_type_features: Option<PresetTypeFeatures>,
661}
662
663#[derive(Debug, serde::Serialize)]
664pub struct Schedule {
665    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
666    pub schedule_handle: Option<Vec<u8>>,
667    pub system_mode: Option<SystemMode>,
668    pub name: Option<String>,
669    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
670    pub preset_handle: Option<Vec<u8>>,
671    pub transitions: Option<Vec<ScheduleTransition>>,
672    pub built_in: Option<bool>,
673}
674
675#[derive(Debug, serde::Serialize)]
676pub struct ScheduleTransition {
677    pub day_of_week: Option<ScheduleDayOfWeek>,
678    pub transition_time: Option<u16>,
679    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
680    pub preset_handle: Option<Vec<u8>>,
681    pub system_mode: Option<SystemMode>,
682    pub cooling_setpoint: Option<i16>,
683    pub heating_setpoint: Option<i16>,
684}
685
686#[derive(Debug, serde::Serialize)]
687pub struct ScheduleType {
688    pub system_mode: Option<SystemMode>,
689    pub number_of_schedules: Option<u8>,
690    pub schedule_type_features: Option<ScheduleTypeFeatures>,
691}
692
693#[derive(Debug, serde::Serialize)]
694pub struct WeeklyScheduleTransition {
695    pub transition_time: Option<u16>,
696    pub heat_setpoint: Option<i16>,
697    pub cool_setpoint: Option<i16>,
698}
699
700// Command encoders
701
702/// Encode SetpointRaiseLower command (0x00)
703pub fn encode_setpoint_raise_lower(mode: SetpointRaiseLowerMode, amount: i8) -> anyhow::Result<Vec<u8>> {
704    let tlv = tlv::TlvItemEnc {
705        tag: 0,
706        value: tlv::TlvItemValueEnc::StructInvisible(vec![
707        (0, tlv::TlvItemValueEnc::UInt8(mode.to_u8())).into(),
708        (1, tlv::TlvItemValueEnc::Int8(amount)).into(),
709        ]),
710    };
711    Ok(tlv.encode()?)
712}
713
714/// Encode SetActiveScheduleRequest command (0x05)
715pub fn encode_set_active_schedule_request(schedule_handle: Vec<u8>) -> anyhow::Result<Vec<u8>> {
716    let tlv = tlv::TlvItemEnc {
717        tag: 0,
718        value: tlv::TlvItemValueEnc::StructInvisible(vec![
719        (0, tlv::TlvItemValueEnc::OctetString(schedule_handle)).into(),
720        ]),
721    };
722    Ok(tlv.encode()?)
723}
724
725/// Encode SetActivePresetRequest command (0x06)
726pub fn encode_set_active_preset_request(preset_handle: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
727    let tlv = tlv::TlvItemEnc {
728        tag: 0,
729        value: tlv::TlvItemValueEnc::StructInvisible(vec![
730        (0, tlv::TlvItemValueEnc::OctetString(preset_handle.unwrap_or(vec![]))).into(),
731        ]),
732    };
733    Ok(tlv.encode()?)
734}
735
736// Attribute decoders
737
738/// Decode LocalTemperature attribute (0x0000)
739pub fn decode_local_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
740    if let tlv::TlvItemValue::Int(v) = inp {
741        Ok(Some(*v as i16))
742    } else {
743        Ok(None)
744    }
745}
746
747/// Decode OutdoorTemperature attribute (0x0001)
748pub fn decode_outdoor_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
749    if let tlv::TlvItemValue::Int(v) = inp {
750        Ok(Some(*v as i16))
751    } else {
752        Ok(None)
753    }
754}
755
756/// Decode Occupancy attribute (0x0002)
757pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
758    if let tlv::TlvItemValue::Int(v) = inp {
759        Ok(*v as u8)
760    } else {
761        Err(anyhow::anyhow!("Expected Integer"))
762    }
763}
764
765/// Decode AbsMinHeatSetpointLimit attribute (0x0003)
766pub fn decode_abs_min_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
767    if let tlv::TlvItemValue::Int(v) = inp {
768        Ok(*v as i16)
769    } else {
770        Err(anyhow::anyhow!("Expected Int16"))
771    }
772}
773
774/// Decode AbsMaxHeatSetpointLimit attribute (0x0004)
775pub fn decode_abs_max_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
776    if let tlv::TlvItemValue::Int(v) = inp {
777        Ok(*v as i16)
778    } else {
779        Err(anyhow::anyhow!("Expected Int16"))
780    }
781}
782
783/// Decode AbsMinCoolSetpointLimit attribute (0x0005)
784pub fn decode_abs_min_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
785    if let tlv::TlvItemValue::Int(v) = inp {
786        Ok(*v as i16)
787    } else {
788        Err(anyhow::anyhow!("Expected Int16"))
789    }
790}
791
792/// Decode AbsMaxCoolSetpointLimit attribute (0x0006)
793pub fn decode_abs_max_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
794    if let tlv::TlvItemValue::Int(v) = inp {
795        Ok(*v as i16)
796    } else {
797        Err(anyhow::anyhow!("Expected Int16"))
798    }
799}
800
801/// Decode PICoolingDemand attribute (0x0007)
802pub fn decode_pi_cooling_demand(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
803    if let tlv::TlvItemValue::Int(v) = inp {
804        Ok(*v as u8)
805    } else {
806        Err(anyhow::anyhow!("Expected UInt8"))
807    }
808}
809
810/// Decode PIHeatingDemand attribute (0x0008)
811pub fn decode_pi_heating_demand(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
812    if let tlv::TlvItemValue::Int(v) = inp {
813        Ok(*v as u8)
814    } else {
815        Err(anyhow::anyhow!("Expected UInt8"))
816    }
817}
818
819/// Decode HVACSystemTypeConfiguration attribute (0x0009)
820pub fn decode_hvac_system_type_configuration(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
821    if let tlv::TlvItemValue::Int(v) = inp {
822        Ok(*v as u8)
823    } else {
824        Err(anyhow::anyhow!("Expected UInt8"))
825    }
826}
827
828/// Decode LocalTemperatureCalibration attribute (0x0010)
829pub fn decode_local_temperature_calibration(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
830    if let tlv::TlvItemValue::Int(v) = inp {
831        Ok(*v as u8)
832    } else {
833        Err(anyhow::anyhow!("Expected UInt8"))
834    }
835}
836
837/// Decode OccupiedCoolingSetpoint attribute (0x0011)
838pub fn decode_occupied_cooling_setpoint(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
839    if let tlv::TlvItemValue::Int(v) = inp {
840        Ok(*v as i16)
841    } else {
842        Err(anyhow::anyhow!("Expected Int16"))
843    }
844}
845
846/// Decode OccupiedHeatingSetpoint attribute (0x0012)
847pub fn decode_occupied_heating_setpoint(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
848    if let tlv::TlvItemValue::Int(v) = inp {
849        Ok(*v as i16)
850    } else {
851        Err(anyhow::anyhow!("Expected Int16"))
852    }
853}
854
855/// Decode UnoccupiedCoolingSetpoint attribute (0x0013)
856pub fn decode_unoccupied_cooling_setpoint(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
857    if let tlv::TlvItemValue::Int(v) = inp {
858        Ok(*v as i16)
859    } else {
860        Err(anyhow::anyhow!("Expected Int16"))
861    }
862}
863
864/// Decode UnoccupiedHeatingSetpoint attribute (0x0014)
865pub fn decode_unoccupied_heating_setpoint(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
866    if let tlv::TlvItemValue::Int(v) = inp {
867        Ok(*v as i16)
868    } else {
869        Err(anyhow::anyhow!("Expected Int16"))
870    }
871}
872
873/// Decode MinHeatSetpointLimit attribute (0x0015)
874pub fn decode_min_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
875    if let tlv::TlvItemValue::Int(v) = inp {
876        Ok(*v as i16)
877    } else {
878        Err(anyhow::anyhow!("Expected Int16"))
879    }
880}
881
882/// Decode MaxHeatSetpointLimit attribute (0x0016)
883pub fn decode_max_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
884    if let tlv::TlvItemValue::Int(v) = inp {
885        Ok(*v as i16)
886    } else {
887        Err(anyhow::anyhow!("Expected Int16"))
888    }
889}
890
891/// Decode MinCoolSetpointLimit attribute (0x0017)
892pub fn decode_min_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
893    if let tlv::TlvItemValue::Int(v) = inp {
894        Ok(*v as i16)
895    } else {
896        Err(anyhow::anyhow!("Expected Int16"))
897    }
898}
899
900/// Decode MaxCoolSetpointLimit attribute (0x0018)
901pub fn decode_max_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
902    if let tlv::TlvItemValue::Int(v) = inp {
903        Ok(*v as i16)
904    } else {
905        Err(anyhow::anyhow!("Expected Int16"))
906    }
907}
908
909/// Decode MinSetpointDeadBand attribute (0x0019)
910pub fn decode_min_setpoint_dead_band(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
911    if let tlv::TlvItemValue::Int(v) = inp {
912        Ok(*v as u8)
913    } else {
914        Err(anyhow::anyhow!("Expected UInt8"))
915    }
916}
917
918/// Decode RemoteSensing attribute (0x001A)
919pub fn decode_remote_sensing(inp: &tlv::TlvItemValue) -> anyhow::Result<RemoteSensing> {
920    if let tlv::TlvItemValue::Int(v) = inp {
921        Ok(*v as u8)
922    } else {
923        Err(anyhow::anyhow!("Expected Integer"))
924    }
925}
926
927/// Decode ControlSequenceOfOperation attribute (0x001B)
928pub fn decode_control_sequence_of_operation(inp: &tlv::TlvItemValue) -> anyhow::Result<ControlSequenceOfOperation> {
929    if let tlv::TlvItemValue::Int(v) = inp {
930        ControlSequenceOfOperation::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
931    } else {
932        Err(anyhow::anyhow!("Expected Integer"))
933    }
934}
935
936/// Decode SystemMode attribute (0x001C)
937pub fn decode_system_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<SystemMode> {
938    if let tlv::TlvItemValue::Int(v) = inp {
939        SystemMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
940    } else {
941        Err(anyhow::anyhow!("Expected Integer"))
942    }
943}
944
945/// Decode ThermostatRunningMode attribute (0x001E)
946pub fn decode_thermostat_running_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<ThermostatRunningMode> {
947    if let tlv::TlvItemValue::Int(v) = inp {
948        ThermostatRunningMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
949    } else {
950        Err(anyhow::anyhow!("Expected Integer"))
951    }
952}
953
954/// Decode TemperatureSetpointHold attribute (0x0023)
955pub fn decode_temperature_setpoint_hold(inp: &tlv::TlvItemValue) -> anyhow::Result<TemperatureSetpointHold> {
956    if let tlv::TlvItemValue::Int(v) = inp {
957        TemperatureSetpointHold::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
958    } else {
959        Err(anyhow::anyhow!("Expected Integer"))
960    }
961}
962
963/// Decode TemperatureSetpointHoldDuration attribute (0x0024)
964pub fn decode_temperature_setpoint_hold_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u16>> {
965    if let tlv::TlvItemValue::Int(v) = inp {
966        Ok(Some(*v as u16))
967    } else {
968        Ok(None)
969    }
970}
971
972/// Decode ThermostatProgrammingOperationMode attribute (0x0025)
973pub fn decode_thermostat_programming_operation_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgrammingOperationMode> {
974    if let tlv::TlvItemValue::Int(v) = inp {
975        Ok(*v as u8)
976    } else {
977        Err(anyhow::anyhow!("Expected Integer"))
978    }
979}
980
981/// Decode ThermostatRunningState attribute (0x0029)
982pub fn decode_thermostat_running_state(inp: &tlv::TlvItemValue) -> anyhow::Result<RelayState> {
983    if let tlv::TlvItemValue::Int(v) = inp {
984        Ok(*v as u8)
985    } else {
986        Err(anyhow::anyhow!("Expected Integer"))
987    }
988}
989
990/// Decode SetpointChangeSource attribute (0x0030)
991pub fn decode_setpoint_change_source(inp: &tlv::TlvItemValue) -> anyhow::Result<SetpointChangeSource> {
992    if let tlv::TlvItemValue::Int(v) = inp {
993        SetpointChangeSource::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
994    } else {
995        Err(anyhow::anyhow!("Expected Integer"))
996    }
997}
998
999/// Decode SetpointChangeAmount attribute (0x0031)
1000pub fn decode_setpoint_change_amount(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
1001    if let tlv::TlvItemValue::Int(v) = inp {
1002        Ok(Some(*v as u8))
1003    } else {
1004        Ok(None)
1005    }
1006}
1007
1008/// Decode SetpointChangeSourceTimestamp attribute (0x0032)
1009pub fn decode_setpoint_change_source_timestamp(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
1010    if let tlv::TlvItemValue::Int(v) = inp {
1011        Ok(*v)
1012    } else {
1013        Err(anyhow::anyhow!("Expected UInt64"))
1014    }
1015}
1016
1017/// Decode OccupiedSetback attribute (0x0034)
1018pub fn decode_occupied_setback(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1019    if let tlv::TlvItemValue::Int(v) = inp {
1020        Ok(*v as u8)
1021    } else {
1022        Err(anyhow::anyhow!("Expected UInt8"))
1023    }
1024}
1025
1026/// Decode OccupiedSetbackMin attribute (0x0035)
1027pub fn decode_occupied_setback_min(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1028    if let tlv::TlvItemValue::Int(v) = inp {
1029        Ok(*v as u8)
1030    } else {
1031        Err(anyhow::anyhow!("Expected UInt8"))
1032    }
1033}
1034
1035/// Decode OccupiedSetbackMax attribute (0x0036)
1036pub fn decode_occupied_setback_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1037    if let tlv::TlvItemValue::Int(v) = inp {
1038        Ok(*v as u8)
1039    } else {
1040        Err(anyhow::anyhow!("Expected UInt8"))
1041    }
1042}
1043
1044/// Decode UnoccupiedSetback attribute (0x0037)
1045pub fn decode_unoccupied_setback(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1046    if let tlv::TlvItemValue::Int(v) = inp {
1047        Ok(*v as u8)
1048    } else {
1049        Err(anyhow::anyhow!("Expected UInt8"))
1050    }
1051}
1052
1053/// Decode UnoccupiedSetbackMin attribute (0x0038)
1054pub fn decode_unoccupied_setback_min(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1055    if let tlv::TlvItemValue::Int(v) = inp {
1056        Ok(*v as u8)
1057    } else {
1058        Err(anyhow::anyhow!("Expected UInt8"))
1059    }
1060}
1061
1062/// Decode UnoccupiedSetbackMax attribute (0x0039)
1063pub fn decode_unoccupied_setback_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1064    if let tlv::TlvItemValue::Int(v) = inp {
1065        Ok(*v as u8)
1066    } else {
1067        Err(anyhow::anyhow!("Expected UInt8"))
1068    }
1069}
1070
1071/// Decode EmergencyHeatDelta attribute (0x003A)
1072pub fn decode_emergency_heat_delta(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1073    if let tlv::TlvItemValue::Int(v) = inp {
1074        Ok(*v as u8)
1075    } else {
1076        Err(anyhow::anyhow!("Expected UInt8"))
1077    }
1078}
1079
1080/// Decode ACType attribute (0x0040)
1081pub fn decode_ac_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACType> {
1082    if let tlv::TlvItemValue::Int(v) = inp {
1083        ACType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1084    } else {
1085        Err(anyhow::anyhow!("Expected Integer"))
1086    }
1087}
1088
1089/// Decode ACCapacity attribute (0x0041)
1090pub fn decode_ac_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
1091    if let tlv::TlvItemValue::Int(v) = inp {
1092        Ok(*v as u16)
1093    } else {
1094        Err(anyhow::anyhow!("Expected UInt16"))
1095    }
1096}
1097
1098/// Decode ACRefrigerantType attribute (0x0042)
1099pub fn decode_ac_refrigerant_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACRefrigerantType> {
1100    if let tlv::TlvItemValue::Int(v) = inp {
1101        ACRefrigerantType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1102    } else {
1103        Err(anyhow::anyhow!("Expected Integer"))
1104    }
1105}
1106
1107/// Decode ACCompressorType attribute (0x0043)
1108pub fn decode_ac_compressor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACCompressorType> {
1109    if let tlv::TlvItemValue::Int(v) = inp {
1110        ACCompressorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1111    } else {
1112        Err(anyhow::anyhow!("Expected Integer"))
1113    }
1114}
1115
1116/// Decode ACErrorCode attribute (0x0044)
1117pub fn decode_ac_error_code(inp: &tlv::TlvItemValue) -> anyhow::Result<ACErrorCode> {
1118    if let tlv::TlvItemValue::Int(v) = inp {
1119        Ok(*v as u8)
1120    } else {
1121        Err(anyhow::anyhow!("Expected Integer"))
1122    }
1123}
1124
1125/// Decode ACLouverPosition attribute (0x0045)
1126pub fn decode_aclouver_position(inp: &tlv::TlvItemValue) -> anyhow::Result<ACLouverPosition> {
1127    if let tlv::TlvItemValue::Int(v) = inp {
1128        ACLouverPosition::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1129    } else {
1130        Err(anyhow::anyhow!("Expected Integer"))
1131    }
1132}
1133
1134/// Decode ACCoilTemperature attribute (0x0046)
1135pub fn decode_ac_coil_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
1136    if let tlv::TlvItemValue::Int(v) = inp {
1137        Ok(Some(*v as i16))
1138    } else {
1139        Ok(None)
1140    }
1141}
1142
1143/// Decode ACCapacityFormat attribute (0x0047)
1144pub fn decode_ac_capacity_format(inp: &tlv::TlvItemValue) -> anyhow::Result<ACCapacityFormat> {
1145    if let tlv::TlvItemValue::Int(v) = inp {
1146        ACCapacityFormat::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1147    } else {
1148        Err(anyhow::anyhow!("Expected Integer"))
1149    }
1150}
1151
1152/// Decode PresetTypes attribute (0x0048)
1153pub fn decode_preset_types(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<PresetType>> {
1154    let mut res = Vec::new();
1155    if let tlv::TlvItemValue::List(v) = inp {
1156        for item in v {
1157            res.push(PresetType {
1158                preset_scenario: item.get_int(&[0]).and_then(|v| PresetScenario::from_u8(v as u8)),
1159                number_of_presets: item.get_int(&[1]).map(|v| v as u8),
1160                preset_type_features: item.get_int(&[2]).map(|v| v as u8),
1161            });
1162        }
1163    }
1164    Ok(res)
1165}
1166
1167/// Decode ScheduleTypes attribute (0x0049)
1168pub fn decode_schedule_types(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ScheduleType>> {
1169    let mut res = Vec::new();
1170    if let tlv::TlvItemValue::List(v) = inp {
1171        for item in v {
1172            res.push(ScheduleType {
1173                system_mode: item.get_int(&[0]).and_then(|v| SystemMode::from_u8(v as u8)),
1174                number_of_schedules: item.get_int(&[1]).map(|v| v as u8),
1175                schedule_type_features: item.get_int(&[2]).map(|v| v as u8),
1176            });
1177        }
1178    }
1179    Ok(res)
1180}
1181
1182/// Decode NumberOfPresets attribute (0x004A)
1183pub fn decode_number_of_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1184    if let tlv::TlvItemValue::Int(v) = inp {
1185        Ok(*v as u8)
1186    } else {
1187        Err(anyhow::anyhow!("Expected UInt8"))
1188    }
1189}
1190
1191/// Decode NumberOfSchedules attribute (0x004B)
1192pub fn decode_number_of_schedules(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1193    if let tlv::TlvItemValue::Int(v) = inp {
1194        Ok(*v as u8)
1195    } else {
1196        Err(anyhow::anyhow!("Expected UInt8"))
1197    }
1198}
1199
1200/// Decode NumberOfScheduleTransitions attribute (0x004C)
1201pub fn decode_number_of_schedule_transitions(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1202    if let tlv::TlvItemValue::Int(v) = inp {
1203        Ok(*v as u8)
1204    } else {
1205        Err(anyhow::anyhow!("Expected UInt8"))
1206    }
1207}
1208
1209/// Decode NumberOfScheduleTransitionPerDay attribute (0x004D)
1210pub fn decode_number_of_schedule_transition_per_day(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
1211    if let tlv::TlvItemValue::Int(v) = inp {
1212        Ok(Some(*v as u8))
1213    } else {
1214        Ok(None)
1215    }
1216}
1217
1218/// Decode ActivePresetHandle attribute (0x004E)
1219pub fn decode_active_preset_handle(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
1220    if let tlv::TlvItemValue::OctetString(v) = inp {
1221        Ok(Some(v.clone()))
1222    } else {
1223        Ok(None)
1224    }
1225}
1226
1227/// Decode ActiveScheduleHandle attribute (0x004F)
1228pub fn decode_active_schedule_handle(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
1229    if let tlv::TlvItemValue::OctetString(v) = inp {
1230        Ok(Some(v.clone()))
1231    } else {
1232        Ok(None)
1233    }
1234}
1235
1236/// Decode Presets attribute (0x0050)
1237pub fn decode_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Preset>> {
1238    let mut res = Vec::new();
1239    if let tlv::TlvItemValue::List(v) = inp {
1240        for item in v {
1241            res.push(Preset {
1242                preset_handle: item.get_octet_string_owned(&[0]),
1243                preset_scenario: item.get_int(&[1]).and_then(|v| PresetScenario::from_u8(v as u8)),
1244                name: item.get_string_owned(&[2]),
1245                cooling_setpoint: item.get_int(&[3]).map(|v| v as i16),
1246                heating_setpoint: item.get_int(&[4]).map(|v| v as i16),
1247                built_in: item.get_bool(&[5]),
1248            });
1249        }
1250    }
1251    Ok(res)
1252}
1253
1254/// Decode Schedules attribute (0x0051)
1255pub fn decode_schedules(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Schedule>> {
1256    let mut res = Vec::new();
1257    if let tlv::TlvItemValue::List(v) = inp {
1258        for item in v {
1259            res.push(Schedule {
1260                schedule_handle: item.get_octet_string_owned(&[0]),
1261                system_mode: item.get_int(&[1]).and_then(|v| SystemMode::from_u8(v as u8)),
1262                name: item.get_string_owned(&[2]),
1263                preset_handle: item.get_octet_string_owned(&[3]),
1264                transitions: {
1265                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
1266                        let mut items = Vec::new();
1267                        for list_item in l {
1268                            items.push(ScheduleTransition {
1269                day_of_week: list_item.get_int(&[0]).map(|v| v as u8),
1270                transition_time: list_item.get_int(&[1]).map(|v| v as u16),
1271                preset_handle: list_item.get_octet_string_owned(&[2]),
1272                system_mode: list_item.get_int(&[3]).and_then(|v| SystemMode::from_u8(v as u8)),
1273                cooling_setpoint: list_item.get_int(&[4]).map(|v| v as i16),
1274                heating_setpoint: list_item.get_int(&[5]).map(|v| v as i16),
1275                            });
1276                        }
1277                        Some(items)
1278                    } else {
1279                        None
1280                    }
1281                },
1282                built_in: item.get_bool(&[5]),
1283            });
1284        }
1285    }
1286    Ok(res)
1287}
1288
1289/// Decode SetpointHoldExpiryTimestamp attribute (0x0052)
1290pub fn decode_setpoint_hold_expiry_timestamp(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
1291    if let tlv::TlvItemValue::Int(v) = inp {
1292        Ok(Some(*v))
1293    } else {
1294        Ok(None)
1295    }
1296}
1297
1298
1299// JSON dispatcher function
1300
1301/// Decode attribute value and return as JSON string
1302///
1303/// # Parameters
1304/// * `cluster_id` - The cluster identifier
1305/// * `attribute_id` - The attribute identifier
1306/// * `tlv_value` - The TLV value to decode
1307///
1308/// # Returns
1309/// JSON string representation of the decoded value or error
1310pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
1311    // Verify this is the correct cluster
1312    if cluster_id != 0x0201 {
1313        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0201, got {}\"}}", cluster_id);
1314    }
1315
1316    match attribute_id {
1317        0x0000 => {
1318            match decode_local_temperature(tlv_value) {
1319                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1320                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1321            }
1322        }
1323        0x0001 => {
1324            match decode_outdoor_temperature(tlv_value) {
1325                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1326                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1327            }
1328        }
1329        0x0002 => {
1330            match decode_occupancy(tlv_value) {
1331                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1332                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1333            }
1334        }
1335        0x0003 => {
1336            match decode_abs_min_heat_setpoint_limit(tlv_value) {
1337                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1338                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1339            }
1340        }
1341        0x0004 => {
1342            match decode_abs_max_heat_setpoint_limit(tlv_value) {
1343                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1344                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1345            }
1346        }
1347        0x0005 => {
1348            match decode_abs_min_cool_setpoint_limit(tlv_value) {
1349                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1350                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1351            }
1352        }
1353        0x0006 => {
1354            match decode_abs_max_cool_setpoint_limit(tlv_value) {
1355                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1356                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1357            }
1358        }
1359        0x0007 => {
1360            match decode_pi_cooling_demand(tlv_value) {
1361                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1362                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1363            }
1364        }
1365        0x0008 => {
1366            match decode_pi_heating_demand(tlv_value) {
1367                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1368                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1369            }
1370        }
1371        0x0009 => {
1372            match decode_hvac_system_type_configuration(tlv_value) {
1373                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1374                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1375            }
1376        }
1377        0x0010 => {
1378            match decode_local_temperature_calibration(tlv_value) {
1379                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1380                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1381            }
1382        }
1383        0x0011 => {
1384            match decode_occupied_cooling_setpoint(tlv_value) {
1385                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1386                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1387            }
1388        }
1389        0x0012 => {
1390            match decode_occupied_heating_setpoint(tlv_value) {
1391                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1392                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1393            }
1394        }
1395        0x0013 => {
1396            match decode_unoccupied_cooling_setpoint(tlv_value) {
1397                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1398                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1399            }
1400        }
1401        0x0014 => {
1402            match decode_unoccupied_heating_setpoint(tlv_value) {
1403                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1404                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1405            }
1406        }
1407        0x0015 => {
1408            match decode_min_heat_setpoint_limit(tlv_value) {
1409                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1410                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1411            }
1412        }
1413        0x0016 => {
1414            match decode_max_heat_setpoint_limit(tlv_value) {
1415                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1416                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1417            }
1418        }
1419        0x0017 => {
1420            match decode_min_cool_setpoint_limit(tlv_value) {
1421                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1422                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1423            }
1424        }
1425        0x0018 => {
1426            match decode_max_cool_setpoint_limit(tlv_value) {
1427                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1428                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1429            }
1430        }
1431        0x0019 => {
1432            match decode_min_setpoint_dead_band(tlv_value) {
1433                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1434                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1435            }
1436        }
1437        0x001A => {
1438            match decode_remote_sensing(tlv_value) {
1439                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1440                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1441            }
1442        }
1443        0x001B => {
1444            match decode_control_sequence_of_operation(tlv_value) {
1445                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1446                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1447            }
1448        }
1449        0x001C => {
1450            match decode_system_mode(tlv_value) {
1451                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1452                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1453            }
1454        }
1455        0x001E => {
1456            match decode_thermostat_running_mode(tlv_value) {
1457                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1458                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1459            }
1460        }
1461        0x0023 => {
1462            match decode_temperature_setpoint_hold(tlv_value) {
1463                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1464                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1465            }
1466        }
1467        0x0024 => {
1468            match decode_temperature_setpoint_hold_duration(tlv_value) {
1469                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1470                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1471            }
1472        }
1473        0x0025 => {
1474            match decode_thermostat_programming_operation_mode(tlv_value) {
1475                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1476                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1477            }
1478        }
1479        0x0029 => {
1480            match decode_thermostat_running_state(tlv_value) {
1481                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1482                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1483            }
1484        }
1485        0x0030 => {
1486            match decode_setpoint_change_source(tlv_value) {
1487                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1488                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1489            }
1490        }
1491        0x0031 => {
1492            match decode_setpoint_change_amount(tlv_value) {
1493                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1494                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1495            }
1496        }
1497        0x0032 => {
1498            match decode_setpoint_change_source_timestamp(tlv_value) {
1499                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1500                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1501            }
1502        }
1503        0x0034 => {
1504            match decode_occupied_setback(tlv_value) {
1505                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1506                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1507            }
1508        }
1509        0x0035 => {
1510            match decode_occupied_setback_min(tlv_value) {
1511                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1512                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1513            }
1514        }
1515        0x0036 => {
1516            match decode_occupied_setback_max(tlv_value) {
1517                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1518                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1519            }
1520        }
1521        0x0037 => {
1522            match decode_unoccupied_setback(tlv_value) {
1523                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1524                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1525            }
1526        }
1527        0x0038 => {
1528            match decode_unoccupied_setback_min(tlv_value) {
1529                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1530                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1531            }
1532        }
1533        0x0039 => {
1534            match decode_unoccupied_setback_max(tlv_value) {
1535                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1536                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1537            }
1538        }
1539        0x003A => {
1540            match decode_emergency_heat_delta(tlv_value) {
1541                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1542                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1543            }
1544        }
1545        0x0040 => {
1546            match decode_ac_type(tlv_value) {
1547                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1548                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1549            }
1550        }
1551        0x0041 => {
1552            match decode_ac_capacity(tlv_value) {
1553                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1554                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1555            }
1556        }
1557        0x0042 => {
1558            match decode_ac_refrigerant_type(tlv_value) {
1559                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1560                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1561            }
1562        }
1563        0x0043 => {
1564            match decode_ac_compressor_type(tlv_value) {
1565                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1566                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1567            }
1568        }
1569        0x0044 => {
1570            match decode_ac_error_code(tlv_value) {
1571                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1572                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1573            }
1574        }
1575        0x0045 => {
1576            match decode_aclouver_position(tlv_value) {
1577                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1578                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1579            }
1580        }
1581        0x0046 => {
1582            match decode_ac_coil_temperature(tlv_value) {
1583                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1584                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1585            }
1586        }
1587        0x0047 => {
1588            match decode_ac_capacity_format(tlv_value) {
1589                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1590                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1591            }
1592        }
1593        0x0048 => {
1594            match decode_preset_types(tlv_value) {
1595                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1596                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1597            }
1598        }
1599        0x0049 => {
1600            match decode_schedule_types(tlv_value) {
1601                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1602                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1603            }
1604        }
1605        0x004A => {
1606            match decode_number_of_presets(tlv_value) {
1607                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1608                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1609            }
1610        }
1611        0x004B => {
1612            match decode_number_of_schedules(tlv_value) {
1613                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1614                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1615            }
1616        }
1617        0x004C => {
1618            match decode_number_of_schedule_transitions(tlv_value) {
1619                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1620                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1621            }
1622        }
1623        0x004D => {
1624            match decode_number_of_schedule_transition_per_day(tlv_value) {
1625                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1626                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1627            }
1628        }
1629        0x004E => {
1630            match decode_active_preset_handle(tlv_value) {
1631                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1632                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1633            }
1634        }
1635        0x004F => {
1636            match decode_active_schedule_handle(tlv_value) {
1637                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1638                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1639            }
1640        }
1641        0x0050 => {
1642            match decode_presets(tlv_value) {
1643                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1644                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1645            }
1646        }
1647        0x0051 => {
1648            match decode_schedules(tlv_value) {
1649                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1650                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1651            }
1652        }
1653        0x0052 => {
1654            match decode_setpoint_hold_expiry_timestamp(tlv_value) {
1655                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1656                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1657            }
1658        }
1659        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
1660    }
1661}
1662
1663/// Get list of all attributes supported by this cluster
1664///
1665/// # Returns
1666/// Vector of tuples containing (attribute_id, attribute_name)
1667pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
1668    vec![
1669        (0x0000, "LocalTemperature"),
1670        (0x0001, "OutdoorTemperature"),
1671        (0x0002, "Occupancy"),
1672        (0x0003, "AbsMinHeatSetpointLimit"),
1673        (0x0004, "AbsMaxHeatSetpointLimit"),
1674        (0x0005, "AbsMinCoolSetpointLimit"),
1675        (0x0006, "AbsMaxCoolSetpointLimit"),
1676        (0x0007, "PICoolingDemand"),
1677        (0x0008, "PIHeatingDemand"),
1678        (0x0009, "HVACSystemTypeConfiguration"),
1679        (0x0010, "LocalTemperatureCalibration"),
1680        (0x0011, "OccupiedCoolingSetpoint"),
1681        (0x0012, "OccupiedHeatingSetpoint"),
1682        (0x0013, "UnoccupiedCoolingSetpoint"),
1683        (0x0014, "UnoccupiedHeatingSetpoint"),
1684        (0x0015, "MinHeatSetpointLimit"),
1685        (0x0016, "MaxHeatSetpointLimit"),
1686        (0x0017, "MinCoolSetpointLimit"),
1687        (0x0018, "MaxCoolSetpointLimit"),
1688        (0x0019, "MinSetpointDeadBand"),
1689        (0x001A, "RemoteSensing"),
1690        (0x001B, "ControlSequenceOfOperation"),
1691        (0x001C, "SystemMode"),
1692        (0x001E, "ThermostatRunningMode"),
1693        (0x0023, "TemperatureSetpointHold"),
1694        (0x0024, "TemperatureSetpointHoldDuration"),
1695        (0x0025, "ThermostatProgrammingOperationMode"),
1696        (0x0029, "ThermostatRunningState"),
1697        (0x0030, "SetpointChangeSource"),
1698        (0x0031, "SetpointChangeAmount"),
1699        (0x0032, "SetpointChangeSourceTimestamp"),
1700        (0x0034, "OccupiedSetback"),
1701        (0x0035, "OccupiedSetbackMin"),
1702        (0x0036, "OccupiedSetbackMax"),
1703        (0x0037, "UnoccupiedSetback"),
1704        (0x0038, "UnoccupiedSetbackMin"),
1705        (0x0039, "UnoccupiedSetbackMax"),
1706        (0x003A, "EmergencyHeatDelta"),
1707        (0x0040, "ACType"),
1708        (0x0041, "ACCapacity"),
1709        (0x0042, "ACRefrigerantType"),
1710        (0x0043, "ACCompressorType"),
1711        (0x0044, "ACErrorCode"),
1712        (0x0045, "ACLouverPosition"),
1713        (0x0046, "ACCoilTemperature"),
1714        (0x0047, "ACCapacityFormat"),
1715        (0x0048, "PresetTypes"),
1716        (0x0049, "ScheduleTypes"),
1717        (0x004A, "NumberOfPresets"),
1718        (0x004B, "NumberOfSchedules"),
1719        (0x004C, "NumberOfScheduleTransitions"),
1720        (0x004D, "NumberOfScheduleTransitionPerDay"),
1721        (0x004E, "ActivePresetHandle"),
1722        (0x004F, "ActiveScheduleHandle"),
1723        (0x0050, "Presets"),
1724        (0x0051, "Schedules"),
1725        (0x0052, "SetpointHoldExpiryTimestamp"),
1726    ]
1727}
1728