Skip to main content

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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum ACCapacityFormat {
21    /// British Thermal Unit per Hour
22    Btuh = 0,
23}
24
25impl ACCapacityFormat {
26    /// Convert from u8 value
27    pub fn from_u8(value: u8) -> Option<Self> {
28        match value {
29            0 => Some(ACCapacityFormat::Btuh),
30            _ => None,
31        }
32    }
33
34    /// Convert to u8 value
35    pub fn to_u8(self) -> u8 {
36        self as u8
37    }
38}
39
40impl From<ACCapacityFormat> for u8 {
41    fn from(val: ACCapacityFormat) -> Self {
42        val as u8
43    }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum ACCompressorType {
49    /// Unknown compressor type
50    Unknown = 0,
51    /// Max working ambient 43 °C
52    T1 = 1,
53    /// Max working ambient 35 °C
54    T2 = 2,
55    /// Max working ambient 52 °C
56    T3 = 3,
57}
58
59impl ACCompressorType {
60    /// Convert from u8 value
61    pub fn from_u8(value: u8) -> Option<Self> {
62        match value {
63            0 => Some(ACCompressorType::Unknown),
64            1 => Some(ACCompressorType::T1),
65            2 => Some(ACCompressorType::T2),
66            3 => Some(ACCompressorType::T3),
67            _ => None,
68        }
69    }
70
71    /// Convert to u8 value
72    pub fn to_u8(self) -> u8 {
73        self as u8
74    }
75}
76
77impl From<ACCompressorType> for u8 {
78    fn from(val: ACCompressorType) -> Self {
79        val as u8
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84#[repr(u8)]
85pub enum ACLouverPosition {
86    /// Fully Closed
87    Closed = 1,
88    /// Fully Open
89    Open = 2,
90    /// Quarter Open
91    Quarter = 3,
92    /// Half Open
93    Half = 4,
94    /// Three Quarters Open
95    Threequarters = 5,
96}
97
98impl ACLouverPosition {
99    /// Convert from u8 value
100    pub fn from_u8(value: u8) -> Option<Self> {
101        match value {
102            1 => Some(ACLouverPosition::Closed),
103            2 => Some(ACLouverPosition::Open),
104            3 => Some(ACLouverPosition::Quarter),
105            4 => Some(ACLouverPosition::Half),
106            5 => Some(ACLouverPosition::Threequarters),
107            _ => None,
108        }
109    }
110
111    /// Convert to u8 value
112    pub fn to_u8(self) -> u8 {
113        self as u8
114    }
115}
116
117impl From<ACLouverPosition> for u8 {
118    fn from(val: ACLouverPosition) -> Self {
119        val as u8
120    }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
124#[repr(u8)]
125pub enum ACRefrigerantType {
126    /// Unknown Refrigerant Type
127    Unknown = 0,
128    /// R22 Refrigerant
129    R22 = 1,
130    /// R410a Refrigerant
131    R410a = 2,
132    /// R407c Refrigerant
133    R407c = 3,
134}
135
136impl ACRefrigerantType {
137    /// Convert from u8 value
138    pub fn from_u8(value: u8) -> Option<Self> {
139        match value {
140            0 => Some(ACRefrigerantType::Unknown),
141            1 => Some(ACRefrigerantType::R22),
142            2 => Some(ACRefrigerantType::R410a),
143            3 => Some(ACRefrigerantType::R407c),
144            _ => None,
145        }
146    }
147
148    /// Convert to u8 value
149    pub fn to_u8(self) -> u8 {
150        self as u8
151    }
152}
153
154impl From<ACRefrigerantType> for u8 {
155    fn from(val: ACRefrigerantType) -> Self {
156        val as u8
157    }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
161#[repr(u8)]
162pub enum ACType {
163    /// Unknown AC Type
164    Unknown = 0,
165    /// Cooling and Fixed Speed
166    Coolingfixed = 1,
167    /// Heat Pump and Fixed Speed
168    Heatpumpfixed = 2,
169    /// Cooling and Inverter
170    Coolinginverter = 3,
171    /// Heat Pump and Inverter
172    Heatpumpinverter = 4,
173}
174
175impl ACType {
176    /// Convert from u8 value
177    pub fn from_u8(value: u8) -> Option<Self> {
178        match value {
179            0 => Some(ACType::Unknown),
180            1 => Some(ACType::Coolingfixed),
181            2 => Some(ACType::Heatpumpfixed),
182            3 => Some(ACType::Coolinginverter),
183            4 => Some(ACType::Heatpumpinverter),
184            _ => None,
185        }
186    }
187
188    /// Convert to u8 value
189    pub fn to_u8(self) -> u8 {
190        self as u8
191    }
192}
193
194impl From<ACType> for u8 {
195    fn from(val: ACType) -> Self {
196        val as u8
197    }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
201#[repr(u8)]
202pub enum ControlSequenceOfOperation {
203    /// Heat and Emergency are not possible
204    Coolingonly = 0,
205    /// Heat and Emergency are not possible
206    Coolingwithreheat = 1,
207    /// Cool and precooling (see Terms) are not possible
208    Heatingonly = 2,
209    /// Cool and precooling are not possible
210    Heatingwithreheat = 3,
211    /// All modes are possible
212    Coolingandheating = 4,
213    /// All modes are possible
214    Coolingandheatingwithreheat = 5,
215}
216
217impl ControlSequenceOfOperation {
218    /// Convert from u8 value
219    pub fn from_u8(value: u8) -> Option<Self> {
220        match value {
221            0 => Some(ControlSequenceOfOperation::Coolingonly),
222            1 => Some(ControlSequenceOfOperation::Coolingwithreheat),
223            2 => Some(ControlSequenceOfOperation::Heatingonly),
224            3 => Some(ControlSequenceOfOperation::Heatingwithreheat),
225            4 => Some(ControlSequenceOfOperation::Coolingandheating),
226            5 => Some(ControlSequenceOfOperation::Coolingandheatingwithreheat),
227            _ => None,
228        }
229    }
230
231    /// Convert to u8 value
232    pub fn to_u8(self) -> u8 {
233        self as u8
234    }
235}
236
237impl From<ControlSequenceOfOperation> for u8 {
238    fn from(val: ControlSequenceOfOperation) -> Self {
239        val as u8
240    }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
244#[repr(u8)]
245pub enum PresetScenario {
246    /// The thermostat-controlled area is occupied
247    Occupied = 1,
248    /// The thermostat-controlled area is unoccupied
249    Unoccupied = 2,
250    /// Users are likely to be sleeping
251    Sleep = 3,
252    /// Users are likely to be waking up
253    Wake = 4,
254    /// Users are on vacation
255    Vacation = 5,
256    /// Users are likely to be going to sleep
257    Goingtosleep = 6,
258    /// Custom presets
259    Userdefined = 254,
260}
261
262impl PresetScenario {
263    /// Convert from u8 value
264    pub fn from_u8(value: u8) -> Option<Self> {
265        match value {
266            1 => Some(PresetScenario::Occupied),
267            2 => Some(PresetScenario::Unoccupied),
268            3 => Some(PresetScenario::Sleep),
269            4 => Some(PresetScenario::Wake),
270            5 => Some(PresetScenario::Vacation),
271            6 => Some(PresetScenario::Goingtosleep),
272            254 => Some(PresetScenario::Userdefined),
273            _ => None,
274        }
275    }
276
277    /// Convert to u8 value
278    pub fn to_u8(self) -> u8 {
279        self as u8
280    }
281}
282
283impl From<PresetScenario> for u8 {
284    fn from(val: PresetScenario) -> Self {
285        val as u8
286    }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
290#[repr(u8)]
291pub enum SetpointChangeSource {
292    /// Manual, user-initiated setpoint change via the thermostat
293    Manual = 0,
294    /// Schedule/internal programming-initiated setpoint change
295    Schedule = 1,
296    /// Externally-initiated setpoint change (e.g., DRLC cluster command, attribute write)
297    External = 2,
298}
299
300impl SetpointChangeSource {
301    /// Convert from u8 value
302    pub fn from_u8(value: u8) -> Option<Self> {
303        match value {
304            0 => Some(SetpointChangeSource::Manual),
305            1 => Some(SetpointChangeSource::Schedule),
306            2 => Some(SetpointChangeSource::External),
307            _ => None,
308        }
309    }
310
311    /// Convert to u8 value
312    pub fn to_u8(self) -> u8 {
313        self as u8
314    }
315}
316
317impl From<SetpointChangeSource> for u8 {
318    fn from(val: SetpointChangeSource) -> Self {
319        val as u8
320    }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
324#[repr(u8)]
325pub enum SetpointRaiseLowerMode {
326    /// Adjust Heat Setpoint
327    Heat = 0,
328    /// Adjust Cool Setpoint
329    Cool = 1,
330    /// Adjust Heat Setpoint and Cool Setpoint
331    Both = 2,
332}
333
334impl SetpointRaiseLowerMode {
335    /// Convert from u8 value
336    pub fn from_u8(value: u8) -> Option<Self> {
337        match value {
338            0 => Some(SetpointRaiseLowerMode::Heat),
339            1 => Some(SetpointRaiseLowerMode::Cool),
340            2 => Some(SetpointRaiseLowerMode::Both),
341            _ => None,
342        }
343    }
344
345    /// Convert to u8 value
346    pub fn to_u8(self) -> u8 {
347        self as u8
348    }
349}
350
351impl From<SetpointRaiseLowerMode> for u8 {
352    fn from(val: SetpointRaiseLowerMode) -> Self {
353        val as u8
354    }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
358#[repr(u8)]
359pub enum StartOfWeek {
360    Sunday = 0,
361    Monday = 1,
362    Tuesday = 2,
363    Wednesday = 3,
364    Thursday = 4,
365    Friday = 5,
366    Saturday = 6,
367}
368
369impl StartOfWeek {
370    /// Convert from u8 value
371    pub fn from_u8(value: u8) -> Option<Self> {
372        match value {
373            0 => Some(StartOfWeek::Sunday),
374            1 => Some(StartOfWeek::Monday),
375            2 => Some(StartOfWeek::Tuesday),
376            3 => Some(StartOfWeek::Wednesday),
377            4 => Some(StartOfWeek::Thursday),
378            5 => Some(StartOfWeek::Friday),
379            6 => Some(StartOfWeek::Saturday),
380            _ => None,
381        }
382    }
383
384    /// Convert to u8 value
385    pub fn to_u8(self) -> u8 {
386        self as u8
387    }
388}
389
390impl From<StartOfWeek> for u8 {
391    fn from(val: StartOfWeek) -> Self {
392        val as u8
393    }
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
397#[repr(u8)]
398pub enum SystemMode {
399    /// The Thermostat does not generate demand for Cooling or Heating
400    Off = 0,
401    /// Demand is generated for either Cooling or Heating, as required
402    Auto = 1,
403    /// Demand is only generated for Cooling
404    Cool = 3,
405    /// Demand is only generated for Heating
406    Heat = 4,
407    /// 2nd stage heating is in use to achieve desired temperature
408    Emergencyheat = 5,
409    /// (see Terms)
410    Precooling = 6,
411    Fanonly = 7,
412    Dry = 8,
413    Sleep = 9,
414}
415
416impl SystemMode {
417    /// Convert from u8 value
418    pub fn from_u8(value: u8) -> Option<Self> {
419        match value {
420            0 => Some(SystemMode::Off),
421            1 => Some(SystemMode::Auto),
422            3 => Some(SystemMode::Cool),
423            4 => Some(SystemMode::Heat),
424            5 => Some(SystemMode::Emergencyheat),
425            6 => Some(SystemMode::Precooling),
426            7 => Some(SystemMode::Fanonly),
427            8 => Some(SystemMode::Dry),
428            9 => Some(SystemMode::Sleep),
429            _ => None,
430        }
431    }
432
433    /// Convert to u8 value
434    pub fn to_u8(self) -> u8 {
435        self as u8
436    }
437}
438
439impl From<SystemMode> for u8 {
440    fn from(val: SystemMode) -> Self {
441        val as u8
442    }
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
446#[repr(u8)]
447pub enum TemperatureSetpointHold {
448    /// Follow scheduling program
449    Setpointholdoff = 0,
450    /// Maintain current setpoint, regardless of schedule transitions
451    Setpointholdon = 1,
452}
453
454impl TemperatureSetpointHold {
455    /// Convert from u8 value
456    pub fn from_u8(value: u8) -> Option<Self> {
457        match value {
458            0 => Some(TemperatureSetpointHold::Setpointholdoff),
459            1 => Some(TemperatureSetpointHold::Setpointholdon),
460            _ => None,
461        }
462    }
463
464    /// Convert to u8 value
465    pub fn to_u8(self) -> u8 {
466        self as u8
467    }
468}
469
470impl From<TemperatureSetpointHold> for u8 {
471    fn from(val: TemperatureSetpointHold) -> Self {
472        val as u8
473    }
474}
475
476#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
477#[repr(u8)]
478pub enum ThermostatRunningMode {
479    /// The Thermostat does not generate demand for Cooling or Heating
480    Off = 0,
481    /// Demand is only generated for Cooling
482    Cool = 3,
483    /// Demand is only generated for Heating
484    Heat = 4,
485}
486
487impl ThermostatRunningMode {
488    /// Convert from u8 value
489    pub fn from_u8(value: u8) -> Option<Self> {
490        match value {
491            0 => Some(ThermostatRunningMode::Off),
492            3 => Some(ThermostatRunningMode::Cool),
493            4 => Some(ThermostatRunningMode::Heat),
494            _ => None,
495        }
496    }
497
498    /// Convert to u8 value
499    pub fn to_u8(self) -> u8 {
500        self as u8
501    }
502}
503
504impl From<ThermostatRunningMode> for u8 {
505    fn from(val: ThermostatRunningMode) -> Self {
506        val as u8
507    }
508}
509
510// Bitmap definitions
511
512/// ACErrorCode bitmap type
513pub type ACErrorCode = u8;
514
515/// Constants for ACErrorCode
516pub mod acerrorcode {
517    /// Compressor Failure or Refrigerant Leakage
518    pub const COMPRESSOR_FAIL: u8 = 0x01;
519    /// Room Temperature Sensor Failure
520    pub const ROOM_SENSOR_FAIL: u8 = 0x02;
521    /// Outdoor Temperature Sensor Failure
522    pub const OUTDOOR_SENSOR_FAIL: u8 = 0x04;
523    /// Indoor Coil Temperature Sensor Failure
524    pub const COIL_SENSOR_FAIL: u8 = 0x08;
525    /// Fan Failure
526    pub const FAN_FAIL: u8 = 0x10;
527}
528
529/// Occupancy bitmap type
530pub type Occupancy = u8;
531
532/// Constants for Occupancy
533pub mod occupancy {
534    /// Indicates the occupancy state
535    pub const OCCUPIED: u8 = 0x01;
536}
537
538/// PresetTypeFeatures bitmap type
539pub type PresetTypeFeatures = u8;
540
541/// Constants for PresetTypeFeatures
542pub mod presettypefeatures {
543    /// Preset may be automatically activated by the thermostat
544    pub const AUTOMATIC: u8 = 0x01;
545    /// Preset supports user-provided names
546    pub const SUPPORTS_NAMES: u8 = 0x02;
547}
548
549/// RelayState bitmap type
550pub type RelayState = u8;
551
552/// Constants for RelayState
553pub mod relaystate {
554    /// Heat Stage On
555    pub const HEAT: u8 = 0x01;
556    /// Cool Stage On
557    pub const COOL: u8 = 0x02;
558    /// Fan Stage On
559    pub const FAN: u8 = 0x04;
560    /// Heat 2nd Stage On
561    pub const HEAT_STAGE2: u8 = 0x08;
562    /// Cool 2nd Stage On
563    pub const COOL_STAGE2: u8 = 0x10;
564    /// Fan 2nd Stage On
565    pub const FAN_STAGE2: u8 = 0x20;
566    /// Fan 3rd Stage On
567    pub const FAN_STAGE3: u8 = 0x40;
568}
569
570/// RemoteSensing bitmap type
571pub type RemoteSensing = u8;
572
573/// Constants for RemoteSensing
574pub mod remotesensing {
575    /// Calculated Local Temperature is derived from a remote node
576    pub const LOCAL_TEMPERATURE: u8 = 0x01;
577    /// OutdoorTemperature is derived from a remote node
578    pub const OUTDOOR_TEMPERATURE: u8 = 0x02;
579    /// Occupancy is derived from a remote node
580    pub const OCCUPANCY: u8 = 0x04;
581}
582
583/// ScheduleDayOfWeek bitmap type
584pub type ScheduleDayOfWeek = u8;
585
586/// Constants for ScheduleDayOfWeek
587pub mod scheduledayofweek {
588    /// Sunday
589    pub const SUNDAY: u8 = 0x01;
590    /// Monday
591    pub const MONDAY: u8 = 0x02;
592    /// Tuesday
593    pub const TUESDAY: u8 = 0x04;
594    /// Wednesday
595    pub const WEDNESDAY: u8 = 0x08;
596    /// Thursday
597    pub const THURSDAY: u8 = 0x10;
598    /// Friday
599    pub const FRIDAY: u8 = 0x20;
600    /// Saturday
601    pub const SATURDAY: u8 = 0x40;
602    /// Away or Vacation
603    pub const AWAY: u8 = 0x80;
604}
605
606/// ScheduleMode bitmap type
607pub type ScheduleMode = u8;
608
609/// Constants for ScheduleMode
610pub mod schedulemode {
611    /// Adjust Heat Setpoint
612    pub const HEAT_SETPOINT_PRESENT: u8 = 0x01;
613    /// Adjust Cool Setpoint
614    pub const COOL_SETPOINT_PRESENT: u8 = 0x02;
615}
616
617/// ScheduleTypeFeatures bitmap type
618pub type ScheduleTypeFeatures = u8;
619
620/// Constants for ScheduleTypeFeatures
621pub mod scheduletypefeatures {
622    /// Supports presets
623    pub const SUPPORTS_PRESETS: u8 = 0x01;
624    /// Supports setpoints
625    pub const SUPPORTS_SETPOINTS: u8 = 0x02;
626    /// Supports user-provided names
627    pub const SUPPORTS_NAMES: u8 = 0x04;
628    /// Supports transitioning to SystemModeOff
629    pub const SUPPORTS_OFF: u8 = 0x08;
630}
631
632/// ThermostatSuggestionNotFollowingReason bitmap type
633pub type ThermostatSuggestionNotFollowingReason = u8;
634
635/// Constants for ThermostatSuggestionNotFollowingReason
636pub mod thermostatsuggestionnotfollowingreason {
637    /// Thermostat is responding to a Demand Response event
638    pub const DEMAND_RESPONSE_EVENT: u8 = 0x01;
639    /// Thermostat has an ongoing setpoint hold
640    pub const ONGOING_HOLD: u8 = 0x02;
641    /// Thermostat is following a schedule
642    pub const SCHEDULE: u8 = 0x04;
643    /// Thermostat is following the occupancy signal
644    pub const OCCUPANCY: u8 = 0x08;
645    /// Thermostat is set to Vacation mode
646    pub const VACATION_MODE: u8 = 0x10;
647    /// Thermostat is following a Time Of Use based cost savings plan
648    pub const TIME_OF_USE_COST_SAVINGS: u8 = 0x20;
649    /// Thermostat is precooling or preheating based on an energy forecast signal
650    pub const PRE_COOLING_OR_PRE_HEATING: u8 = 0x40;
651    /// Thermostat has conflicting suggestions and is unable to choose one
652    pub const CONFLICTING_SUGGESTIONS: u8 = 0x80;
653}
654
655// Struct definitions
656
657#[derive(Debug, serde::Serialize)]
658pub struct Preset {
659    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
660    pub preset_handle: Option<Vec<u8>>,
661    pub preset_scenario: Option<PresetScenario>,
662    pub name: Option<String>,
663    pub cooling_setpoint: Option<i16>,
664    pub heating_setpoint: Option<i16>,
665    pub built_in: Option<bool>,
666}
667
668#[derive(Debug, serde::Serialize)]
669pub struct PresetType {
670    pub preset_scenario: Option<PresetScenario>,
671    pub number_of_presets: Option<u8>,
672    pub preset_type_features: Option<PresetTypeFeatures>,
673}
674
675#[derive(Debug, serde::Serialize)]
676pub struct Schedule {
677    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
678    pub schedule_handle: Option<Vec<u8>>,
679    pub system_mode: Option<SystemMode>,
680    pub name: Option<String>,
681    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
682    pub preset_handle: Option<Vec<u8>>,
683    pub transitions: Option<Vec<ScheduleTransition>>,
684    pub built_in: Option<bool>,
685}
686
687#[derive(Debug, serde::Serialize)]
688pub struct ScheduleTransition {
689    pub day_of_week: Option<ScheduleDayOfWeek>,
690    pub transition_time: Option<u16>,
691    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
692    pub preset_handle: Option<Vec<u8>>,
693    pub system_mode: Option<SystemMode>,
694    pub cooling_setpoint: Option<i16>,
695    pub heating_setpoint: Option<i16>,
696}
697
698#[derive(Debug, serde::Serialize)]
699pub struct ScheduleType {
700    pub system_mode: Option<SystemMode>,
701    pub number_of_schedules: Option<u8>,
702    pub schedule_type_features: Option<ScheduleTypeFeatures>,
703}
704
705#[derive(Debug, serde::Serialize)]
706pub struct ThermostatSuggestion {
707    pub unique_id: Option<u8>,
708    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
709    pub preset_handle: Option<Vec<u8>>,
710    pub effective_time: Option<u64>,
711    pub expiration_time: Option<u64>,
712}
713
714#[derive(Debug, serde::Serialize)]
715pub struct WeeklyScheduleTransition {
716    pub transition_time: Option<u16>,
717    pub heat_setpoint: Option<i16>,
718    pub cool_setpoint: Option<i16>,
719}
720
721// Command encoders
722
723/// Encode SetpointRaiseLower command (0x00)
724pub fn encode_setpoint_raise_lower(mode: SetpointRaiseLowerMode, amount: i8) -> anyhow::Result<Vec<u8>> {
725    let tlv = tlv::TlvItemEnc {
726        tag: 0,
727        value: tlv::TlvItemValueEnc::StructInvisible(vec![
728        (0, tlv::TlvItemValueEnc::UInt8(mode.to_u8())).into(),
729        (1, tlv::TlvItemValueEnc::Int8(amount)).into(),
730        ]),
731    };
732    Ok(tlv.encode()?)
733}
734
735/// Encode SetActiveScheduleRequest command (0x05)
736pub fn encode_set_active_schedule_request(schedule_handle: Vec<u8>) -> anyhow::Result<Vec<u8>> {
737    let tlv = tlv::TlvItemEnc {
738        tag: 0,
739        value: tlv::TlvItemValueEnc::StructInvisible(vec![
740        (0, tlv::TlvItemValueEnc::OctetString(schedule_handle)).into(),
741        ]),
742    };
743    Ok(tlv.encode()?)
744}
745
746/// Encode SetActivePresetRequest command (0x06)
747pub fn encode_set_active_preset_request(preset_handle: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
748    let tlv = tlv::TlvItemEnc {
749        tag: 0,
750        value: tlv::TlvItemValueEnc::StructInvisible(vec![
751        (0, tlv::TlvItemValueEnc::OctetString(preset_handle.unwrap_or(vec![]))).into(),
752        ]),
753    };
754    Ok(tlv.encode()?)
755}
756
757/// Encode AddThermostatSuggestion command (0x07)
758pub fn encode_add_thermostat_suggestion(preset_handle: Vec<u8>, effective_time: Option<u64>, expiration_in_minutes: u16) -> anyhow::Result<Vec<u8>> {
759    let tlv = tlv::TlvItemEnc {
760        tag: 0,
761        value: tlv::TlvItemValueEnc::StructInvisible(vec![
762        (0, tlv::TlvItemValueEnc::OctetString(preset_handle)).into(),
763        (1, tlv::TlvItemValueEnc::UInt64(effective_time.unwrap_or(0))).into(),
764        (2, tlv::TlvItemValueEnc::UInt16(expiration_in_minutes)).into(),
765        ]),
766    };
767    Ok(tlv.encode()?)
768}
769
770/// Encode RemoveThermostatSuggestion command (0x08)
771pub fn encode_remove_thermostat_suggestion(unique_id: u8) -> anyhow::Result<Vec<u8>> {
772    let tlv = tlv::TlvItemEnc {
773        tag: 0,
774        value: tlv::TlvItemValueEnc::StructInvisible(vec![
775        (0, tlv::TlvItemValueEnc::UInt8(unique_id)).into(),
776        ]),
777    };
778    Ok(tlv.encode()?)
779}
780
781// Attribute decoders
782
783/// Decode LocalTemperature attribute (0x0000)
784pub fn decode_local_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
785    if let tlv::TlvItemValue::Int(v) = inp {
786        Ok(Some(*v as i16))
787    } else {
788        Ok(None)
789    }
790}
791
792/// Decode OutdoorTemperature attribute (0x0001)
793pub fn decode_outdoor_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
794    if let tlv::TlvItemValue::Int(v) = inp {
795        Ok(Some(*v as i16))
796    } else {
797        Ok(None)
798    }
799}
800
801/// Decode Occupancy attribute (0x0002)
802pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
803    if let tlv::TlvItemValue::Int(v) = inp {
804        Ok(*v as u8)
805    } else {
806        Err(anyhow::anyhow!("Expected Integer"))
807    }
808}
809
810/// Decode AbsMinHeatSetpointLimit attribute (0x0003)
811pub fn decode_abs_min_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
812    if let tlv::TlvItemValue::Int(v) = inp {
813        Ok(*v as i16)
814    } else {
815        Err(anyhow::anyhow!("Expected Int16"))
816    }
817}
818
819/// Decode AbsMaxHeatSetpointLimit attribute (0x0004)
820pub fn decode_abs_max_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
821    if let tlv::TlvItemValue::Int(v) = inp {
822        Ok(*v as i16)
823    } else {
824        Err(anyhow::anyhow!("Expected Int16"))
825    }
826}
827
828/// Decode AbsMinCoolSetpointLimit attribute (0x0005)
829pub fn decode_abs_min_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
830    if let tlv::TlvItemValue::Int(v) = inp {
831        Ok(*v as i16)
832    } else {
833        Err(anyhow::anyhow!("Expected Int16"))
834    }
835}
836
837/// Decode AbsMaxCoolSetpointLimit attribute (0x0006)
838pub fn decode_abs_max_cool_setpoint_limit(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 PICoolingDemand attribute (0x0007)
847pub fn decode_pi_cooling_demand(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
848    if let tlv::TlvItemValue::Int(v) = inp {
849        Ok(*v as u8)
850    } else {
851        Err(anyhow::anyhow!("Expected UInt8"))
852    }
853}
854
855/// Decode PIHeatingDemand attribute (0x0008)
856pub fn decode_pi_heating_demand(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
857    if let tlv::TlvItemValue::Int(v) = inp {
858        Ok(*v as u8)
859    } else {
860        Err(anyhow::anyhow!("Expected UInt8"))
861    }
862}
863
864/// Decode HVACSystemTypeConfiguration attribute (0x0009)
865pub fn decode_hvac_system_type_configuration(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
866    if let tlv::TlvItemValue::Int(v) = inp {
867        Ok(*v as u8)
868    } else {
869        Err(anyhow::anyhow!("Expected UInt8"))
870    }
871}
872
873/// Decode LocalTemperatureCalibration attribute (0x0010)
874pub fn decode_local_temperature_calibration(inp: &tlv::TlvItemValue) -> anyhow::Result<i8> {
875    if let tlv::TlvItemValue::Int(v) = inp {
876        Ok(*v as i8)
877    } else {
878        Err(anyhow::anyhow!("Expected Int8"))
879    }
880}
881
882/// Decode OccupiedCoolingSetpoint attribute (0x0011)
883pub fn decode_occupied_cooling_setpoint(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 OccupiedHeatingSetpoint attribute (0x0012)
892pub fn decode_occupied_heating_setpoint(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 UnoccupiedCoolingSetpoint attribute (0x0013)
901pub fn decode_unoccupied_cooling_setpoint(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 UnoccupiedHeatingSetpoint attribute (0x0014)
910pub fn decode_unoccupied_heating_setpoint(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
911    if let tlv::TlvItemValue::Int(v) = inp {
912        Ok(*v as i16)
913    } else {
914        Err(anyhow::anyhow!("Expected Int16"))
915    }
916}
917
918/// Decode MinHeatSetpointLimit attribute (0x0015)
919pub fn decode_min_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
920    if let tlv::TlvItemValue::Int(v) = inp {
921        Ok(*v as i16)
922    } else {
923        Err(anyhow::anyhow!("Expected Int16"))
924    }
925}
926
927/// Decode MaxHeatSetpointLimit attribute (0x0016)
928pub fn decode_max_heat_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
929    if let tlv::TlvItemValue::Int(v) = inp {
930        Ok(*v as i16)
931    } else {
932        Err(anyhow::anyhow!("Expected Int16"))
933    }
934}
935
936/// Decode MinCoolSetpointLimit attribute (0x0017)
937pub fn decode_min_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
938    if let tlv::TlvItemValue::Int(v) = inp {
939        Ok(*v as i16)
940    } else {
941        Err(anyhow::anyhow!("Expected Int16"))
942    }
943}
944
945/// Decode MaxCoolSetpointLimit attribute (0x0018)
946pub fn decode_max_cool_setpoint_limit(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
947    if let tlv::TlvItemValue::Int(v) = inp {
948        Ok(*v as i16)
949    } else {
950        Err(anyhow::anyhow!("Expected Int16"))
951    }
952}
953
954/// Decode MinSetpointDeadBand attribute (0x0019)
955pub fn decode_min_setpoint_dead_band(inp: &tlv::TlvItemValue) -> anyhow::Result<i8> {
956    if let tlv::TlvItemValue::Int(v) = inp {
957        Ok(*v as i8)
958    } else {
959        Err(anyhow::anyhow!("Expected Int8"))
960    }
961}
962
963/// Decode RemoteSensing attribute (0x001A)
964pub fn decode_remote_sensing(inp: &tlv::TlvItemValue) -> anyhow::Result<RemoteSensing> {
965    if let tlv::TlvItemValue::Int(v) = inp {
966        Ok(*v as u8)
967    } else {
968        Err(anyhow::anyhow!("Expected Integer"))
969    }
970}
971
972/// Decode ControlSequenceOfOperation attribute (0x001B)
973pub fn decode_control_sequence_of_operation(inp: &tlv::TlvItemValue) -> anyhow::Result<ControlSequenceOfOperation> {
974    if let tlv::TlvItemValue::Int(v) = inp {
975        ControlSequenceOfOperation::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
976    } else {
977        Err(anyhow::anyhow!("Expected Integer"))
978    }
979}
980
981/// Decode SystemMode attribute (0x001C)
982pub fn decode_system_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<SystemMode> {
983    if let tlv::TlvItemValue::Int(v) = inp {
984        SystemMode::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
985    } else {
986        Err(anyhow::anyhow!("Expected Integer"))
987    }
988}
989
990/// Decode ThermostatRunningMode attribute (0x001E)
991pub fn decode_thermostat_running_mode(inp: &tlv::TlvItemValue) -> anyhow::Result<ThermostatRunningMode> {
992    if let tlv::TlvItemValue::Int(v) = inp {
993        ThermostatRunningMode::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 TemperatureSetpointHold attribute (0x0023)
1000pub fn decode_temperature_setpoint_hold(inp: &tlv::TlvItemValue) -> anyhow::Result<TemperatureSetpointHold> {
1001    if let tlv::TlvItemValue::Int(v) = inp {
1002        TemperatureSetpointHold::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1003    } else {
1004        Err(anyhow::anyhow!("Expected Integer"))
1005    }
1006}
1007
1008/// Decode TemperatureSetpointHoldDuration attribute (0x0024)
1009pub fn decode_temperature_setpoint_hold_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u16>> {
1010    if let tlv::TlvItemValue::Int(v) = inp {
1011        Ok(Some(*v as u16))
1012    } else {
1013        Ok(None)
1014    }
1015}
1016
1017/// Decode ThermostatProgrammingOperationMode attribute (0x0025)
1018pub fn decode_thermostat_programming_operation_mode(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 ThermostatRunningState attribute (0x0029)
1027pub fn decode_thermostat_running_state(inp: &tlv::TlvItemValue) -> anyhow::Result<RelayState> {
1028    if let tlv::TlvItemValue::Int(v) = inp {
1029        Ok(*v as u8)
1030    } else {
1031        Err(anyhow::anyhow!("Expected Integer"))
1032    }
1033}
1034
1035/// Decode SetpointChangeSource attribute (0x0030)
1036pub fn decode_setpoint_change_source(inp: &tlv::TlvItemValue) -> anyhow::Result<SetpointChangeSource> {
1037    if let tlv::TlvItemValue::Int(v) = inp {
1038        SetpointChangeSource::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1039    } else {
1040        Err(anyhow::anyhow!("Expected Integer"))
1041    }
1042}
1043
1044/// Decode SetpointChangeAmount attribute (0x0031)
1045pub fn decode_setpoint_change_amount(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
1046    if let tlv::TlvItemValue::Int(v) = inp {
1047        Ok(Some(*v as u8))
1048    } else {
1049        Ok(None)
1050    }
1051}
1052
1053/// Decode SetpointChangeSourceTimestamp attribute (0x0032)
1054pub fn decode_setpoint_change_source_timestamp(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
1055    if let tlv::TlvItemValue::Int(v) = inp {
1056        Ok(*v)
1057    } else {
1058        Err(anyhow::anyhow!("Expected UInt64"))
1059    }
1060}
1061
1062/// Decode OccupiedSetback attribute (0x0034)
1063pub fn decode_occupied_setback(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 OccupiedSetbackMin attribute (0x0035)
1072pub fn decode_occupied_setback_min(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 OccupiedSetbackMax attribute (0x0036)
1081pub fn decode_occupied_setback_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1082    if let tlv::TlvItemValue::Int(v) = inp {
1083        Ok(*v as u8)
1084    } else {
1085        Err(anyhow::anyhow!("Expected UInt8"))
1086    }
1087}
1088
1089/// Decode UnoccupiedSetback attribute (0x0037)
1090pub fn decode_unoccupied_setback(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1091    if let tlv::TlvItemValue::Int(v) = inp {
1092        Ok(*v as u8)
1093    } else {
1094        Err(anyhow::anyhow!("Expected UInt8"))
1095    }
1096}
1097
1098/// Decode UnoccupiedSetbackMin attribute (0x0038)
1099pub fn decode_unoccupied_setback_min(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1100    if let tlv::TlvItemValue::Int(v) = inp {
1101        Ok(*v as u8)
1102    } else {
1103        Err(anyhow::anyhow!("Expected UInt8"))
1104    }
1105}
1106
1107/// Decode UnoccupiedSetbackMax attribute (0x0039)
1108pub fn decode_unoccupied_setback_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1109    if let tlv::TlvItemValue::Int(v) = inp {
1110        Ok(*v as u8)
1111    } else {
1112        Err(anyhow::anyhow!("Expected UInt8"))
1113    }
1114}
1115
1116/// Decode EmergencyHeatDelta attribute (0x003A)
1117pub fn decode_emergency_heat_delta(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1118    if let tlv::TlvItemValue::Int(v) = inp {
1119        Ok(*v as u8)
1120    } else {
1121        Err(anyhow::anyhow!("Expected UInt8"))
1122    }
1123}
1124
1125/// Decode ACType attribute (0x0040)
1126pub fn decode_ac_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACType> {
1127    if let tlv::TlvItemValue::Int(v) = inp {
1128        ACType::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 ACCapacity attribute (0x0041)
1135pub fn decode_ac_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
1136    if let tlv::TlvItemValue::Int(v) = inp {
1137        Ok(*v as u16)
1138    } else {
1139        Err(anyhow::anyhow!("Expected UInt16"))
1140    }
1141}
1142
1143/// Decode ACRefrigerantType attribute (0x0042)
1144pub fn decode_ac_refrigerant_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACRefrigerantType> {
1145    if let tlv::TlvItemValue::Int(v) = inp {
1146        ACRefrigerantType::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 ACCompressorType attribute (0x0043)
1153pub fn decode_ac_compressor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ACCompressorType> {
1154    if let tlv::TlvItemValue::Int(v) = inp {
1155        ACCompressorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1156    } else {
1157        Err(anyhow::anyhow!("Expected Integer"))
1158    }
1159}
1160
1161/// Decode ACErrorCode attribute (0x0044)
1162pub fn decode_ac_error_code(inp: &tlv::TlvItemValue) -> anyhow::Result<ACErrorCode> {
1163    if let tlv::TlvItemValue::Int(v) = inp {
1164        Ok(*v as u8)
1165    } else {
1166        Err(anyhow::anyhow!("Expected Integer"))
1167    }
1168}
1169
1170/// Decode ACLouverPosition attribute (0x0045)
1171pub fn decode_aclouver_position(inp: &tlv::TlvItemValue) -> anyhow::Result<ACLouverPosition> {
1172    if let tlv::TlvItemValue::Int(v) = inp {
1173        ACLouverPosition::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1174    } else {
1175        Err(anyhow::anyhow!("Expected Integer"))
1176    }
1177}
1178
1179/// Decode ACCoilTemperature attribute (0x0046)
1180pub fn decode_ac_coil_temperature(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
1181    if let tlv::TlvItemValue::Int(v) = inp {
1182        Ok(Some(*v as i16))
1183    } else {
1184        Ok(None)
1185    }
1186}
1187
1188/// Decode ACCapacityFormat attribute (0x0047)
1189pub fn decode_ac_capacity_format(inp: &tlv::TlvItemValue) -> anyhow::Result<ACCapacityFormat> {
1190    if let tlv::TlvItemValue::Int(v) = inp {
1191        ACCapacityFormat::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
1192    } else {
1193        Err(anyhow::anyhow!("Expected Integer"))
1194    }
1195}
1196
1197/// Decode PresetTypes attribute (0x0048)
1198pub fn decode_preset_types(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<PresetType>> {
1199    let mut res = Vec::new();
1200    if let tlv::TlvItemValue::List(v) = inp {
1201        for item in v {
1202            res.push(PresetType {
1203                preset_scenario: item.get_int(&[0]).and_then(|v| PresetScenario::from_u8(v as u8)),
1204                number_of_presets: item.get_int(&[1]).map(|v| v as u8),
1205                preset_type_features: item.get_int(&[2]).map(|v| v as u8),
1206            });
1207        }
1208    }
1209    Ok(res)
1210}
1211
1212/// Decode ScheduleTypes attribute (0x0049)
1213pub fn decode_schedule_types(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ScheduleType>> {
1214    let mut res = Vec::new();
1215    if let tlv::TlvItemValue::List(v) = inp {
1216        for item in v {
1217            res.push(ScheduleType {
1218                system_mode: item.get_int(&[0]).and_then(|v| SystemMode::from_u8(v as u8)),
1219                number_of_schedules: item.get_int(&[1]).map(|v| v as u8),
1220                schedule_type_features: item.get_int(&[2]).map(|v| v as u8),
1221            });
1222        }
1223    }
1224    Ok(res)
1225}
1226
1227/// Decode NumberOfPresets attribute (0x004A)
1228pub fn decode_number_of_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1229    if let tlv::TlvItemValue::Int(v) = inp {
1230        Ok(*v as u8)
1231    } else {
1232        Err(anyhow::anyhow!("Expected UInt8"))
1233    }
1234}
1235
1236/// Decode NumberOfSchedules attribute (0x004B)
1237pub fn decode_number_of_schedules(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1238    if let tlv::TlvItemValue::Int(v) = inp {
1239        Ok(*v as u8)
1240    } else {
1241        Err(anyhow::anyhow!("Expected UInt8"))
1242    }
1243}
1244
1245/// Decode NumberOfScheduleTransitions attribute (0x004C)
1246pub fn decode_number_of_schedule_transitions(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1247    if let tlv::TlvItemValue::Int(v) = inp {
1248        Ok(*v as u8)
1249    } else {
1250        Err(anyhow::anyhow!("Expected UInt8"))
1251    }
1252}
1253
1254/// Decode NumberOfScheduleTransitionPerDay attribute (0x004D)
1255pub fn decode_number_of_schedule_transition_per_day(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
1256    if let tlv::TlvItemValue::Int(v) = inp {
1257        Ok(Some(*v as u8))
1258    } else {
1259        Ok(None)
1260    }
1261}
1262
1263/// Decode ActivePresetHandle attribute (0x004E)
1264pub fn decode_active_preset_handle(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
1265    if let tlv::TlvItemValue::OctetString(v) = inp {
1266        Ok(Some(v.clone()))
1267    } else {
1268        Ok(None)
1269    }
1270}
1271
1272/// Decode ActiveScheduleHandle attribute (0x004F)
1273pub fn decode_active_schedule_handle(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
1274    if let tlv::TlvItemValue::OctetString(v) = inp {
1275        Ok(Some(v.clone()))
1276    } else {
1277        Ok(None)
1278    }
1279}
1280
1281/// Decode Presets attribute (0x0050)
1282pub fn decode_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Preset>> {
1283    let mut res = Vec::new();
1284    if let tlv::TlvItemValue::List(v) = inp {
1285        for item in v {
1286            res.push(Preset {
1287                preset_handle: item.get_octet_string_owned(&[0]),
1288                preset_scenario: item.get_int(&[1]).and_then(|v| PresetScenario::from_u8(v as u8)),
1289                name: item.get_string_owned(&[2]),
1290                cooling_setpoint: item.get_int(&[3]).map(|v| v as i16),
1291                heating_setpoint: item.get_int(&[4]).map(|v| v as i16),
1292                built_in: item.get_bool(&[5]),
1293            });
1294        }
1295    }
1296    Ok(res)
1297}
1298
1299/// Decode Schedules attribute (0x0051)
1300pub fn decode_schedules(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Schedule>> {
1301    let mut res = Vec::new();
1302    if let tlv::TlvItemValue::List(v) = inp {
1303        for item in v {
1304            res.push(Schedule {
1305                schedule_handle: item.get_octet_string_owned(&[0]),
1306                system_mode: item.get_int(&[1]).and_then(|v| SystemMode::from_u8(v as u8)),
1307                name: item.get_string_owned(&[2]),
1308                preset_handle: item.get_octet_string_owned(&[3]),
1309                transitions: {
1310                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
1311                        let mut items = Vec::new();
1312                        for list_item in l {
1313                            items.push(ScheduleTransition {
1314                day_of_week: list_item.get_int(&[0]).map(|v| v as u8),
1315                transition_time: list_item.get_int(&[1]).map(|v| v as u16),
1316                preset_handle: list_item.get_octet_string_owned(&[2]),
1317                system_mode: list_item.get_int(&[3]).and_then(|v| SystemMode::from_u8(v as u8)),
1318                cooling_setpoint: list_item.get_int(&[4]).map(|v| v as i16),
1319                heating_setpoint: list_item.get_int(&[5]).map(|v| v as i16),
1320                            });
1321                        }
1322                        Some(items)
1323                    } else {
1324                        None
1325                    }
1326                },
1327                built_in: item.get_bool(&[5]),
1328            });
1329        }
1330    }
1331    Ok(res)
1332}
1333
1334/// Decode SetpointHoldExpiryTimestamp attribute (0x0052)
1335pub fn decode_setpoint_hold_expiry_timestamp(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
1336    if let tlv::TlvItemValue::Int(v) = inp {
1337        Ok(Some(*v))
1338    } else {
1339        Ok(None)
1340    }
1341}
1342
1343/// Decode MaxThermostatSuggestions attribute (0x0053)
1344pub fn decode_max_thermostat_suggestions(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
1345    if let tlv::TlvItemValue::Int(v) = inp {
1346        Ok(*v as u8)
1347    } else {
1348        Err(anyhow::anyhow!("Expected UInt8"))
1349    }
1350}
1351
1352/// Decode ThermostatSuggestions attribute (0x0054)
1353pub fn decode_thermostat_suggestions(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ThermostatSuggestion>> {
1354    let mut res = Vec::new();
1355    if let tlv::TlvItemValue::List(v) = inp {
1356        for item in v {
1357            res.push(ThermostatSuggestion {
1358                unique_id: item.get_int(&[0]).map(|v| v as u8),
1359                preset_handle: item.get_octet_string_owned(&[1]),
1360                effective_time: item.get_int(&[2]),
1361                expiration_time: item.get_int(&[3]),
1362            });
1363        }
1364    }
1365    Ok(res)
1366}
1367
1368/// Decode CurrentThermostatSuggestion attribute (0x0055)
1369pub fn decode_current_thermostat_suggestion(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ThermostatSuggestion>> {
1370    if let tlv::TlvItemValue::List(_fields) = inp {
1371        // Struct with fields
1372        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1373        Ok(Some(ThermostatSuggestion {
1374                unique_id: item.get_int(&[0]).map(|v| v as u8),
1375                preset_handle: item.get_octet_string_owned(&[1]),
1376                effective_time: item.get_int(&[2]),
1377                expiration_time: item.get_int(&[3]),
1378        }))
1379    //} else if let tlv::TlvItemValue::Null = inp {
1380    //    // Null value for nullable struct
1381    //    Ok(None)
1382    } else {
1383    Ok(None)
1384    //    Err(anyhow::anyhow!("Expected struct fields or null"))
1385    }
1386}
1387
1388/// Decode ThermostatSuggestionNotFollowingReason attribute (0x0056)
1389pub fn decode_thermostat_suggestion_not_following_reason(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ThermostatSuggestionNotFollowingReason>> {
1390    if let tlv::TlvItemValue::Int(v) = inp {
1391        Ok(Some(*v as u8))
1392    } else {
1393        Ok(None)
1394    }
1395}
1396
1397
1398// JSON dispatcher function
1399
1400/// Decode attribute value and return as JSON string
1401///
1402/// # Parameters
1403/// * `cluster_id` - The cluster identifier
1404/// * `attribute_id` - The attribute identifier
1405/// * `tlv_value` - The TLV value to decode
1406///
1407/// # Returns
1408/// JSON string representation of the decoded value or error
1409pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
1410    // Verify this is the correct cluster
1411    if cluster_id != 0x0201 {
1412        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0201, got {}\"}}", cluster_id);
1413    }
1414
1415    match attribute_id {
1416        0x0000 => {
1417            match decode_local_temperature(tlv_value) {
1418                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1419                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1420            }
1421        }
1422        0x0001 => {
1423            match decode_outdoor_temperature(tlv_value) {
1424                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1425                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1426            }
1427        }
1428        0x0002 => {
1429            match decode_occupancy(tlv_value) {
1430                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1431                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1432            }
1433        }
1434        0x0003 => {
1435            match decode_abs_min_heat_setpoint_limit(tlv_value) {
1436                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1437                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1438            }
1439        }
1440        0x0004 => {
1441            match decode_abs_max_heat_setpoint_limit(tlv_value) {
1442                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1443                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1444            }
1445        }
1446        0x0005 => {
1447            match decode_abs_min_cool_setpoint_limit(tlv_value) {
1448                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1449                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1450            }
1451        }
1452        0x0006 => {
1453            match decode_abs_max_cool_setpoint_limit(tlv_value) {
1454                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1455                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1456            }
1457        }
1458        0x0007 => {
1459            match decode_pi_cooling_demand(tlv_value) {
1460                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1461                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1462            }
1463        }
1464        0x0008 => {
1465            match decode_pi_heating_demand(tlv_value) {
1466                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1467                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1468            }
1469        }
1470        0x0009 => {
1471            match decode_hvac_system_type_configuration(tlv_value) {
1472                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1473                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1474            }
1475        }
1476        0x0010 => {
1477            match decode_local_temperature_calibration(tlv_value) {
1478                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1479                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1480            }
1481        }
1482        0x0011 => {
1483            match decode_occupied_cooling_setpoint(tlv_value) {
1484                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1485                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1486            }
1487        }
1488        0x0012 => {
1489            match decode_occupied_heating_setpoint(tlv_value) {
1490                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1491                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1492            }
1493        }
1494        0x0013 => {
1495            match decode_unoccupied_cooling_setpoint(tlv_value) {
1496                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1497                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1498            }
1499        }
1500        0x0014 => {
1501            match decode_unoccupied_heating_setpoint(tlv_value) {
1502                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1503                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1504            }
1505        }
1506        0x0015 => {
1507            match decode_min_heat_setpoint_limit(tlv_value) {
1508                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1509                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1510            }
1511        }
1512        0x0016 => {
1513            match decode_max_heat_setpoint_limit(tlv_value) {
1514                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1515                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1516            }
1517        }
1518        0x0017 => {
1519            match decode_min_cool_setpoint_limit(tlv_value) {
1520                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1521                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1522            }
1523        }
1524        0x0018 => {
1525            match decode_max_cool_setpoint_limit(tlv_value) {
1526                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1527                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1528            }
1529        }
1530        0x0019 => {
1531            match decode_min_setpoint_dead_band(tlv_value) {
1532                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1533                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1534            }
1535        }
1536        0x001A => {
1537            match decode_remote_sensing(tlv_value) {
1538                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1539                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1540            }
1541        }
1542        0x001B => {
1543            match decode_control_sequence_of_operation(tlv_value) {
1544                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1545                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1546            }
1547        }
1548        0x001C => {
1549            match decode_system_mode(tlv_value) {
1550                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1551                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1552            }
1553        }
1554        0x001E => {
1555            match decode_thermostat_running_mode(tlv_value) {
1556                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1557                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1558            }
1559        }
1560        0x0023 => {
1561            match decode_temperature_setpoint_hold(tlv_value) {
1562                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1563                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1564            }
1565        }
1566        0x0024 => {
1567            match decode_temperature_setpoint_hold_duration(tlv_value) {
1568                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1569                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1570            }
1571        }
1572        0x0025 => {
1573            match decode_thermostat_programming_operation_mode(tlv_value) {
1574                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1575                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1576            }
1577        }
1578        0x0029 => {
1579            match decode_thermostat_running_state(tlv_value) {
1580                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1581                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1582            }
1583        }
1584        0x0030 => {
1585            match decode_setpoint_change_source(tlv_value) {
1586                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1587                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1588            }
1589        }
1590        0x0031 => {
1591            match decode_setpoint_change_amount(tlv_value) {
1592                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1593                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1594            }
1595        }
1596        0x0032 => {
1597            match decode_setpoint_change_source_timestamp(tlv_value) {
1598                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1599                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1600            }
1601        }
1602        0x0034 => {
1603            match decode_occupied_setback(tlv_value) {
1604                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1605                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1606            }
1607        }
1608        0x0035 => {
1609            match decode_occupied_setback_min(tlv_value) {
1610                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1611                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1612            }
1613        }
1614        0x0036 => {
1615            match decode_occupied_setback_max(tlv_value) {
1616                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1617                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1618            }
1619        }
1620        0x0037 => {
1621            match decode_unoccupied_setback(tlv_value) {
1622                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1623                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1624            }
1625        }
1626        0x0038 => {
1627            match decode_unoccupied_setback_min(tlv_value) {
1628                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1629                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1630            }
1631        }
1632        0x0039 => {
1633            match decode_unoccupied_setback_max(tlv_value) {
1634                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1635                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1636            }
1637        }
1638        0x003A => {
1639            match decode_emergency_heat_delta(tlv_value) {
1640                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1641                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1642            }
1643        }
1644        0x0040 => {
1645            match decode_ac_type(tlv_value) {
1646                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1647                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1648            }
1649        }
1650        0x0041 => {
1651            match decode_ac_capacity(tlv_value) {
1652                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1653                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1654            }
1655        }
1656        0x0042 => {
1657            match decode_ac_refrigerant_type(tlv_value) {
1658                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1659                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1660            }
1661        }
1662        0x0043 => {
1663            match decode_ac_compressor_type(tlv_value) {
1664                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1665                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1666            }
1667        }
1668        0x0044 => {
1669            match decode_ac_error_code(tlv_value) {
1670                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1671                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1672            }
1673        }
1674        0x0045 => {
1675            match decode_aclouver_position(tlv_value) {
1676                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1677                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1678            }
1679        }
1680        0x0046 => {
1681            match decode_ac_coil_temperature(tlv_value) {
1682                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1683                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1684            }
1685        }
1686        0x0047 => {
1687            match decode_ac_capacity_format(tlv_value) {
1688                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1689                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1690            }
1691        }
1692        0x0048 => {
1693            match decode_preset_types(tlv_value) {
1694                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1695                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1696            }
1697        }
1698        0x0049 => {
1699            match decode_schedule_types(tlv_value) {
1700                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1701                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1702            }
1703        }
1704        0x004A => {
1705            match decode_number_of_presets(tlv_value) {
1706                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1707                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1708            }
1709        }
1710        0x004B => {
1711            match decode_number_of_schedules(tlv_value) {
1712                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1713                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1714            }
1715        }
1716        0x004C => {
1717            match decode_number_of_schedule_transitions(tlv_value) {
1718                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1719                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1720            }
1721        }
1722        0x004D => {
1723            match decode_number_of_schedule_transition_per_day(tlv_value) {
1724                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1725                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1726            }
1727        }
1728        0x004E => {
1729            match decode_active_preset_handle(tlv_value) {
1730                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1731                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1732            }
1733        }
1734        0x004F => {
1735            match decode_active_schedule_handle(tlv_value) {
1736                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1737                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1738            }
1739        }
1740        0x0050 => {
1741            match decode_presets(tlv_value) {
1742                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1743                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1744            }
1745        }
1746        0x0051 => {
1747            match decode_schedules(tlv_value) {
1748                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1749                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1750            }
1751        }
1752        0x0052 => {
1753            match decode_setpoint_hold_expiry_timestamp(tlv_value) {
1754                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1755                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1756            }
1757        }
1758        0x0053 => {
1759            match decode_max_thermostat_suggestions(tlv_value) {
1760                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1761                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1762            }
1763        }
1764        0x0054 => {
1765            match decode_thermostat_suggestions(tlv_value) {
1766                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1767                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1768            }
1769        }
1770        0x0055 => {
1771            match decode_current_thermostat_suggestion(tlv_value) {
1772                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1773                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1774            }
1775        }
1776        0x0056 => {
1777            match decode_thermostat_suggestion_not_following_reason(tlv_value) {
1778                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1779                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1780            }
1781        }
1782        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
1783    }
1784}
1785
1786/// Get list of all attributes supported by this cluster
1787///
1788/// # Returns
1789/// Vector of tuples containing (attribute_id, attribute_name)
1790pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
1791    vec![
1792        (0x0000, "LocalTemperature"),
1793        (0x0001, "OutdoorTemperature"),
1794        (0x0002, "Occupancy"),
1795        (0x0003, "AbsMinHeatSetpointLimit"),
1796        (0x0004, "AbsMaxHeatSetpointLimit"),
1797        (0x0005, "AbsMinCoolSetpointLimit"),
1798        (0x0006, "AbsMaxCoolSetpointLimit"),
1799        (0x0007, "PICoolingDemand"),
1800        (0x0008, "PIHeatingDemand"),
1801        (0x0009, "HVACSystemTypeConfiguration"),
1802        (0x0010, "LocalTemperatureCalibration"),
1803        (0x0011, "OccupiedCoolingSetpoint"),
1804        (0x0012, "OccupiedHeatingSetpoint"),
1805        (0x0013, "UnoccupiedCoolingSetpoint"),
1806        (0x0014, "UnoccupiedHeatingSetpoint"),
1807        (0x0015, "MinHeatSetpointLimit"),
1808        (0x0016, "MaxHeatSetpointLimit"),
1809        (0x0017, "MinCoolSetpointLimit"),
1810        (0x0018, "MaxCoolSetpointLimit"),
1811        (0x0019, "MinSetpointDeadBand"),
1812        (0x001A, "RemoteSensing"),
1813        (0x001B, "ControlSequenceOfOperation"),
1814        (0x001C, "SystemMode"),
1815        (0x001E, "ThermostatRunningMode"),
1816        (0x0023, "TemperatureSetpointHold"),
1817        (0x0024, "TemperatureSetpointHoldDuration"),
1818        (0x0025, "ThermostatProgrammingOperationMode"),
1819        (0x0029, "ThermostatRunningState"),
1820        (0x0030, "SetpointChangeSource"),
1821        (0x0031, "SetpointChangeAmount"),
1822        (0x0032, "SetpointChangeSourceTimestamp"),
1823        (0x0034, "OccupiedSetback"),
1824        (0x0035, "OccupiedSetbackMin"),
1825        (0x0036, "OccupiedSetbackMax"),
1826        (0x0037, "UnoccupiedSetback"),
1827        (0x0038, "UnoccupiedSetbackMin"),
1828        (0x0039, "UnoccupiedSetbackMax"),
1829        (0x003A, "EmergencyHeatDelta"),
1830        (0x0040, "ACType"),
1831        (0x0041, "ACCapacity"),
1832        (0x0042, "ACRefrigerantType"),
1833        (0x0043, "ACCompressorType"),
1834        (0x0044, "ACErrorCode"),
1835        (0x0045, "ACLouverPosition"),
1836        (0x0046, "ACCoilTemperature"),
1837        (0x0047, "ACCapacityFormat"),
1838        (0x0048, "PresetTypes"),
1839        (0x0049, "ScheduleTypes"),
1840        (0x004A, "NumberOfPresets"),
1841        (0x004B, "NumberOfSchedules"),
1842        (0x004C, "NumberOfScheduleTransitions"),
1843        (0x004D, "NumberOfScheduleTransitionPerDay"),
1844        (0x004E, "ActivePresetHandle"),
1845        (0x004F, "ActiveScheduleHandle"),
1846        (0x0050, "Presets"),
1847        (0x0051, "Schedules"),
1848        (0x0052, "SetpointHoldExpiryTimestamp"),
1849        (0x0053, "MaxThermostatSuggestions"),
1850        (0x0054, "ThermostatSuggestions"),
1851        (0x0055, "CurrentThermostatSuggestion"),
1852        (0x0056, "ThermostatSuggestionNotFollowingReason"),
1853    ]
1854}
1855
1856// Command listing
1857
1858pub fn get_command_list() -> Vec<(u32, &'static str)> {
1859    vec![
1860        (0x00, "SetpointRaiseLower"),
1861        (0x05, "SetActiveScheduleRequest"),
1862        (0x06, "SetActivePresetRequest"),
1863        (0x07, "AddThermostatSuggestion"),
1864        (0x08, "RemoveThermostatSuggestion"),
1865    ]
1866}
1867
1868pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
1869    match cmd_id {
1870        0x00 => Some("SetpointRaiseLower"),
1871        0x05 => Some("SetActiveScheduleRequest"),
1872        0x06 => Some("SetActivePresetRequest"),
1873        0x07 => Some("AddThermostatSuggestion"),
1874        0x08 => Some("RemoveThermostatSuggestion"),
1875        _ => None,
1876    }
1877}
1878
1879pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
1880    match cmd_id {
1881        0x00 => Some(vec![
1882            crate::clusters::codec::CommandField { tag: 0, name: "mode", kind: crate::clusters::codec::FieldKind::Enum { name: "SetpointRaiseLowerMode", variants: &[(0, "Heat"), (1, "Cool"), (2, "Both")] }, optional: false, nullable: false },
1883            crate::clusters::codec::CommandField { tag: 1, name: "amount", kind: crate::clusters::codec::FieldKind::I8, optional: false, nullable: false },
1884        ]),
1885        0x05 => Some(vec![
1886            crate::clusters::codec::CommandField { tag: 0, name: "schedule_handle", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
1887        ]),
1888        0x06 => Some(vec![
1889            crate::clusters::codec::CommandField { tag: 0, name: "preset_handle", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: true },
1890        ]),
1891        0x07 => Some(vec![
1892            crate::clusters::codec::CommandField { tag: 0, name: "preset_handle", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
1893            crate::clusters::codec::CommandField { tag: 1, name: "effective_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: true },
1894            crate::clusters::codec::CommandField { tag: 2, name: "expiration_in_minutes", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
1895        ]),
1896        0x08 => Some(vec![
1897            crate::clusters::codec::CommandField { tag: 0, name: "unique_id", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
1898        ]),
1899        _ => None,
1900    }
1901}
1902
1903pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
1904    match cmd_id {
1905        0x00 => {
1906        let mode = {
1907            let n = crate::clusters::codec::json_util::get_u64(args, "mode")?;
1908            SetpointRaiseLowerMode::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid SetpointRaiseLowerMode: {}", n))?
1909        };
1910        let amount = crate::clusters::codec::json_util::get_i8(args, "amount")?;
1911        encode_setpoint_raise_lower(mode, amount)
1912        }
1913        0x05 => {
1914        let schedule_handle = crate::clusters::codec::json_util::get_octstr(args, "schedule_handle")?;
1915        encode_set_active_schedule_request(schedule_handle)
1916        }
1917        0x06 => {
1918        let preset_handle = crate::clusters::codec::json_util::get_opt_octstr(args, "preset_handle")?;
1919        encode_set_active_preset_request(preset_handle)
1920        }
1921        0x07 => {
1922        let preset_handle = crate::clusters::codec::json_util::get_octstr(args, "preset_handle")?;
1923        let effective_time = crate::clusters::codec::json_util::get_opt_u64(args, "effective_time")?;
1924        let expiration_in_minutes = crate::clusters::codec::json_util::get_u16(args, "expiration_in_minutes")?;
1925        encode_add_thermostat_suggestion(preset_handle, effective_time, expiration_in_minutes)
1926        }
1927        0x08 => {
1928        let unique_id = crate::clusters::codec::json_util::get_u8(args, "unique_id")?;
1929        encode_remove_thermostat_suggestion(unique_id)
1930        }
1931        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
1932    }
1933}
1934
1935#[derive(Debug, serde::Serialize)]
1936pub struct AddThermostatSuggestionResponse {
1937    pub unique_id: Option<u8>,
1938}
1939
1940// Command response decoders
1941
1942/// Decode AddThermostatSuggestionResponse command response (02)
1943pub fn decode_add_thermostat_suggestion_response(inp: &tlv::TlvItemValue) -> anyhow::Result<AddThermostatSuggestionResponse> {
1944    if let tlv::TlvItemValue::List(_fields) = inp {
1945        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1946        Ok(AddThermostatSuggestionResponse {
1947                unique_id: item.get_int(&[0]).map(|v| v as u8),
1948        })
1949    } else {
1950        Err(anyhow::anyhow!("Expected struct fields"))
1951    }
1952}
1953
1954// Typed facade (invokes + reads)
1955
1956/// Invoke `SetpointRaiseLower` command on cluster `Thermostat`.
1957pub async fn setpoint_raise_lower(conn: &crate::controller::Connection, endpoint: u16, mode: SetpointRaiseLowerMode, amount: i8) -> anyhow::Result<()> {
1958    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_CMD_ID_SETPOINTRAISELOWER, &encode_setpoint_raise_lower(mode, amount)?).await?;
1959    Ok(())
1960}
1961
1962/// Invoke `SetActiveScheduleRequest` command on cluster `Thermostat`.
1963pub async fn set_active_schedule_request(conn: &crate::controller::Connection, endpoint: u16, schedule_handle: Vec<u8>) -> anyhow::Result<()> {
1964    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_CMD_ID_SETACTIVESCHEDULEREQUEST, &encode_set_active_schedule_request(schedule_handle)?).await?;
1965    Ok(())
1966}
1967
1968/// Invoke `SetActivePresetRequest` command on cluster `Thermostat`.
1969pub async fn set_active_preset_request(conn: &crate::controller::Connection, endpoint: u16, preset_handle: Option<Vec<u8>>) -> anyhow::Result<()> {
1970    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_CMD_ID_SETACTIVEPRESETREQUEST, &encode_set_active_preset_request(preset_handle)?).await?;
1971    Ok(())
1972}
1973
1974/// Invoke `AddThermostatSuggestion` command on cluster `Thermostat`.
1975pub async fn add_thermostat_suggestion(conn: &crate::controller::Connection, endpoint: u16, preset_handle: Vec<u8>, effective_time: Option<u64>, expiration_in_minutes: u16) -> anyhow::Result<AddThermostatSuggestionResponse> {
1976    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_CMD_ID_ADDTHERMOSTATSUGGESTION, &encode_add_thermostat_suggestion(preset_handle, effective_time, expiration_in_minutes)?).await?;
1977    decode_add_thermostat_suggestion_response(&tlv)
1978}
1979
1980/// Invoke `RemoveThermostatSuggestion` command on cluster `Thermostat`.
1981pub async fn remove_thermostat_suggestion(conn: &crate::controller::Connection, endpoint: u16, unique_id: u8) -> anyhow::Result<()> {
1982    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_CMD_ID_REMOVETHERMOSTATSUGGESTION, &encode_remove_thermostat_suggestion(unique_id)?).await?;
1983    Ok(())
1984}
1985
1986/// Read `LocalTemperature` attribute from cluster `Thermostat`.
1987pub async fn read_local_temperature(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<i16>> {
1988    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_LOCALTEMPERATURE).await?;
1989    decode_local_temperature(&tlv)
1990}
1991
1992/// Read `OutdoorTemperature` attribute from cluster `Thermostat`.
1993pub async fn read_outdoor_temperature(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<i16>> {
1994    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OUTDOORTEMPERATURE).await?;
1995    decode_outdoor_temperature(&tlv)
1996}
1997
1998/// Read `Occupancy` attribute from cluster `Thermostat`.
1999pub async fn read_occupancy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Occupancy> {
2000    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPANCY).await?;
2001    decode_occupancy(&tlv)
2002}
2003
2004/// Read `AbsMinHeatSetpointLimit` attribute from cluster `Thermostat`.
2005pub async fn read_abs_min_heat_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2006    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ABSMINHEATSETPOINTLIMIT).await?;
2007    decode_abs_min_heat_setpoint_limit(&tlv)
2008}
2009
2010/// Read `AbsMaxHeatSetpointLimit` attribute from cluster `Thermostat`.
2011pub async fn read_abs_max_heat_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2012    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ABSMAXHEATSETPOINTLIMIT).await?;
2013    decode_abs_max_heat_setpoint_limit(&tlv)
2014}
2015
2016/// Read `AbsMinCoolSetpointLimit` attribute from cluster `Thermostat`.
2017pub async fn read_abs_min_cool_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2018    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ABSMINCOOLSETPOINTLIMIT).await?;
2019    decode_abs_min_cool_setpoint_limit(&tlv)
2020}
2021
2022/// Read `AbsMaxCoolSetpointLimit` attribute from cluster `Thermostat`.
2023pub async fn read_abs_max_cool_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2024    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ABSMAXCOOLSETPOINTLIMIT).await?;
2025    decode_abs_max_cool_setpoint_limit(&tlv)
2026}
2027
2028/// Read `PICoolingDemand` attribute from cluster `Thermostat`.
2029pub async fn read_pi_cooling_demand(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2030    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_PICOOLINGDEMAND).await?;
2031    decode_pi_cooling_demand(&tlv)
2032}
2033
2034/// Read `PIHeatingDemand` attribute from cluster `Thermostat`.
2035pub async fn read_pi_heating_demand(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2036    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_PIHEATINGDEMAND).await?;
2037    decode_pi_heating_demand(&tlv)
2038}
2039
2040/// Read `HVACSystemTypeConfiguration` attribute from cluster `Thermostat`.
2041pub async fn read_hvac_system_type_configuration(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2042    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_HVACSYSTEMTYPECONFIGURATION).await?;
2043    decode_hvac_system_type_configuration(&tlv)
2044}
2045
2046/// Read `LocalTemperatureCalibration` attribute from cluster `Thermostat`.
2047pub async fn read_local_temperature_calibration(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i8> {
2048    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_LOCALTEMPERATURECALIBRATION).await?;
2049    decode_local_temperature_calibration(&tlv)
2050}
2051
2052/// Read `OccupiedCoolingSetpoint` attribute from cluster `Thermostat`.
2053pub async fn read_occupied_cooling_setpoint(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2054    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPIEDCOOLINGSETPOINT).await?;
2055    decode_occupied_cooling_setpoint(&tlv)
2056}
2057
2058/// Read `OccupiedHeatingSetpoint` attribute from cluster `Thermostat`.
2059pub async fn read_occupied_heating_setpoint(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2060    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPIEDHEATINGSETPOINT).await?;
2061    decode_occupied_heating_setpoint(&tlv)
2062}
2063
2064/// Read `UnoccupiedCoolingSetpoint` attribute from cluster `Thermostat`.
2065pub async fn read_unoccupied_cooling_setpoint(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2066    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_UNOCCUPIEDCOOLINGSETPOINT).await?;
2067    decode_unoccupied_cooling_setpoint(&tlv)
2068}
2069
2070/// Read `UnoccupiedHeatingSetpoint` attribute from cluster `Thermostat`.
2071pub async fn read_unoccupied_heating_setpoint(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2072    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_UNOCCUPIEDHEATINGSETPOINT).await?;
2073    decode_unoccupied_heating_setpoint(&tlv)
2074}
2075
2076/// Read `MinHeatSetpointLimit` attribute from cluster `Thermostat`.
2077pub async fn read_min_heat_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2078    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MINHEATSETPOINTLIMIT).await?;
2079    decode_min_heat_setpoint_limit(&tlv)
2080}
2081
2082/// Read `MaxHeatSetpointLimit` attribute from cluster `Thermostat`.
2083pub async fn read_max_heat_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2084    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MAXHEATSETPOINTLIMIT).await?;
2085    decode_max_heat_setpoint_limit(&tlv)
2086}
2087
2088/// Read `MinCoolSetpointLimit` attribute from cluster `Thermostat`.
2089pub async fn read_min_cool_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2090    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MINCOOLSETPOINTLIMIT).await?;
2091    decode_min_cool_setpoint_limit(&tlv)
2092}
2093
2094/// Read `MaxCoolSetpointLimit` attribute from cluster `Thermostat`.
2095pub async fn read_max_cool_setpoint_limit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i16> {
2096    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MAXCOOLSETPOINTLIMIT).await?;
2097    decode_max_cool_setpoint_limit(&tlv)
2098}
2099
2100/// Read `MinSetpointDeadBand` attribute from cluster `Thermostat`.
2101pub async fn read_min_setpoint_dead_band(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<i8> {
2102    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MINSETPOINTDEADBAND).await?;
2103    decode_min_setpoint_dead_band(&tlv)
2104}
2105
2106/// Read `RemoteSensing` attribute from cluster `Thermostat`.
2107pub async fn read_remote_sensing(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<RemoteSensing> {
2108    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_REMOTESENSING).await?;
2109    decode_remote_sensing(&tlv)
2110}
2111
2112/// Read `ControlSequenceOfOperation` attribute from cluster `Thermostat`.
2113pub async fn read_control_sequence_of_operation(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ControlSequenceOfOperation> {
2114    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_CONTROLSEQUENCEOFOPERATION).await?;
2115    decode_control_sequence_of_operation(&tlv)
2116}
2117
2118/// Read `SystemMode` attribute from cluster `Thermostat`.
2119pub async fn read_system_mode(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<SystemMode> {
2120    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SYSTEMMODE).await?;
2121    decode_system_mode(&tlv)
2122}
2123
2124/// Read `ThermostatRunningMode` attribute from cluster `Thermostat`.
2125pub async fn read_thermostat_running_mode(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ThermostatRunningMode> {
2126    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_THERMOSTATRUNNINGMODE).await?;
2127    decode_thermostat_running_mode(&tlv)
2128}
2129
2130/// Read `TemperatureSetpointHold` attribute from cluster `Thermostat`.
2131pub async fn read_temperature_setpoint_hold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TemperatureSetpointHold> {
2132    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_TEMPERATURESETPOINTHOLD).await?;
2133    decode_temperature_setpoint_hold(&tlv)
2134}
2135
2136/// Read `TemperatureSetpointHoldDuration` attribute from cluster `Thermostat`.
2137pub async fn read_temperature_setpoint_hold_duration(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u16>> {
2138    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_TEMPERATURESETPOINTHOLDDURATION).await?;
2139    decode_temperature_setpoint_hold_duration(&tlv)
2140}
2141
2142/// Read `ThermostatProgrammingOperationMode` attribute from cluster `Thermostat`.
2143pub async fn read_thermostat_programming_operation_mode(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2144    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_THERMOSTATPROGRAMMINGOPERATIONMODE).await?;
2145    decode_thermostat_programming_operation_mode(&tlv)
2146}
2147
2148/// Read `ThermostatRunningState` attribute from cluster `Thermostat`.
2149pub async fn read_thermostat_running_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<RelayState> {
2150    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_THERMOSTATRUNNINGSTATE).await?;
2151    decode_thermostat_running_state(&tlv)
2152}
2153
2154/// Read `SetpointChangeSource` attribute from cluster `Thermostat`.
2155pub async fn read_setpoint_change_source(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<SetpointChangeSource> {
2156    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SETPOINTCHANGESOURCE).await?;
2157    decode_setpoint_change_source(&tlv)
2158}
2159
2160/// Read `SetpointChangeAmount` attribute from cluster `Thermostat`.
2161pub async fn read_setpoint_change_amount(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
2162    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SETPOINTCHANGEAMOUNT).await?;
2163    decode_setpoint_change_amount(&tlv)
2164}
2165
2166/// Read `SetpointChangeSourceTimestamp` attribute from cluster `Thermostat`.
2167pub async fn read_setpoint_change_source_timestamp(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u64> {
2168    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SETPOINTCHANGESOURCETIMESTAMP).await?;
2169    decode_setpoint_change_source_timestamp(&tlv)
2170}
2171
2172/// Read `OccupiedSetback` attribute from cluster `Thermostat`.
2173pub async fn read_occupied_setback(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2174    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPIEDSETBACK).await?;
2175    decode_occupied_setback(&tlv)
2176}
2177
2178/// Read `OccupiedSetbackMin` attribute from cluster `Thermostat`.
2179pub async fn read_occupied_setback_min(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2180    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPIEDSETBACKMIN).await?;
2181    decode_occupied_setback_min(&tlv)
2182}
2183
2184/// Read `OccupiedSetbackMax` attribute from cluster `Thermostat`.
2185pub async fn read_occupied_setback_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2186    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_OCCUPIEDSETBACKMAX).await?;
2187    decode_occupied_setback_max(&tlv)
2188}
2189
2190/// Read `UnoccupiedSetback` attribute from cluster `Thermostat`.
2191pub async fn read_unoccupied_setback(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2192    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_UNOCCUPIEDSETBACK).await?;
2193    decode_unoccupied_setback(&tlv)
2194}
2195
2196/// Read `UnoccupiedSetbackMin` attribute from cluster `Thermostat`.
2197pub async fn read_unoccupied_setback_min(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2198    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_UNOCCUPIEDSETBACKMIN).await?;
2199    decode_unoccupied_setback_min(&tlv)
2200}
2201
2202/// Read `UnoccupiedSetbackMax` attribute from cluster `Thermostat`.
2203pub async fn read_unoccupied_setback_max(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2204    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_UNOCCUPIEDSETBACKMAX).await?;
2205    decode_unoccupied_setback_max(&tlv)
2206}
2207
2208/// Read `EmergencyHeatDelta` attribute from cluster `Thermostat`.
2209pub async fn read_emergency_heat_delta(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2210    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_EMERGENCYHEATDELTA).await?;
2211    decode_emergency_heat_delta(&tlv)
2212}
2213
2214/// Read `ACType` attribute from cluster `Thermostat`.
2215pub async fn read_ac_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACType> {
2216    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACTYPE).await?;
2217    decode_ac_type(&tlv)
2218}
2219
2220/// Read `ACCapacity` attribute from cluster `Thermostat`.
2221pub async fn read_ac_capacity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
2222    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACCAPACITY).await?;
2223    decode_ac_capacity(&tlv)
2224}
2225
2226/// Read `ACRefrigerantType` attribute from cluster `Thermostat`.
2227pub async fn read_ac_refrigerant_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACRefrigerantType> {
2228    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACREFRIGERANTTYPE).await?;
2229    decode_ac_refrigerant_type(&tlv)
2230}
2231
2232/// Read `ACCompressorType` attribute from cluster `Thermostat`.
2233pub async fn read_ac_compressor_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACCompressorType> {
2234    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACCOMPRESSORTYPE).await?;
2235    decode_ac_compressor_type(&tlv)
2236}
2237
2238/// Read `ACErrorCode` attribute from cluster `Thermostat`.
2239pub async fn read_ac_error_code(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACErrorCode> {
2240    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACERRORCODE).await?;
2241    decode_ac_error_code(&tlv)
2242}
2243
2244/// Read `ACLouverPosition` attribute from cluster `Thermostat`.
2245pub async fn read_aclouver_position(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACLouverPosition> {
2246    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACLOUVERPOSITION).await?;
2247    decode_aclouver_position(&tlv)
2248}
2249
2250/// Read `ACCoilTemperature` attribute from cluster `Thermostat`.
2251pub async fn read_ac_coil_temperature(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<i16>> {
2252    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACCOILTEMPERATURE).await?;
2253    decode_ac_coil_temperature(&tlv)
2254}
2255
2256/// Read `ACCapacityFormat` attribute from cluster `Thermostat`.
2257pub async fn read_ac_capacity_format(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ACCapacityFormat> {
2258    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACCAPACITYFORMAT).await?;
2259    decode_ac_capacity_format(&tlv)
2260}
2261
2262/// Read `PresetTypes` attribute from cluster `Thermostat`.
2263pub async fn read_preset_types(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<PresetType>> {
2264    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_PRESETTYPES).await?;
2265    decode_preset_types(&tlv)
2266}
2267
2268/// Read `ScheduleTypes` attribute from cluster `Thermostat`.
2269pub async fn read_schedule_types(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ScheduleType>> {
2270    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SCHEDULETYPES).await?;
2271    decode_schedule_types(&tlv)
2272}
2273
2274/// Read `NumberOfPresets` attribute from cluster `Thermostat`.
2275pub async fn read_number_of_presets(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2276    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_NUMBEROFPRESETS).await?;
2277    decode_number_of_presets(&tlv)
2278}
2279
2280/// Read `NumberOfSchedules` attribute from cluster `Thermostat`.
2281pub async fn read_number_of_schedules(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2282    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_NUMBEROFSCHEDULES).await?;
2283    decode_number_of_schedules(&tlv)
2284}
2285
2286/// Read `NumberOfScheduleTransitions` attribute from cluster `Thermostat`.
2287pub async fn read_number_of_schedule_transitions(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2288    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_NUMBEROFSCHEDULETRANSITIONS).await?;
2289    decode_number_of_schedule_transitions(&tlv)
2290}
2291
2292/// Read `NumberOfScheduleTransitionPerDay` attribute from cluster `Thermostat`.
2293pub async fn read_number_of_schedule_transition_per_day(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
2294    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_NUMBEROFSCHEDULETRANSITIONPERDAY).await?;
2295    decode_number_of_schedule_transition_per_day(&tlv)
2296}
2297
2298/// Read `ActivePresetHandle` attribute from cluster `Thermostat`.
2299pub async fn read_active_preset_handle(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Vec<u8>>> {
2300    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACTIVEPRESETHANDLE).await?;
2301    decode_active_preset_handle(&tlv)
2302}
2303
2304/// Read `ActiveScheduleHandle` attribute from cluster `Thermostat`.
2305pub async fn read_active_schedule_handle(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Vec<u8>>> {
2306    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_ACTIVESCHEDULEHANDLE).await?;
2307    decode_active_schedule_handle(&tlv)
2308}
2309
2310/// Read `Presets` attribute from cluster `Thermostat`.
2311pub async fn read_presets(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Preset>> {
2312    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_PRESETS).await?;
2313    decode_presets(&tlv)
2314}
2315
2316/// Read `Schedules` attribute from cluster `Thermostat`.
2317pub async fn read_schedules(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Schedule>> {
2318    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SCHEDULES).await?;
2319    decode_schedules(&tlv)
2320}
2321
2322/// Read `SetpointHoldExpiryTimestamp` attribute from cluster `Thermostat`.
2323pub async fn read_setpoint_hold_expiry_timestamp(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
2324    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_SETPOINTHOLDEXPIRYTIMESTAMP).await?;
2325    decode_setpoint_hold_expiry_timestamp(&tlv)
2326}
2327
2328/// Read `MaxThermostatSuggestions` attribute from cluster `Thermostat`.
2329pub async fn read_max_thermostat_suggestions(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
2330    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_MAXTHERMOSTATSUGGESTIONS).await?;
2331    decode_max_thermostat_suggestions(&tlv)
2332}
2333
2334/// Read `ThermostatSuggestions` attribute from cluster `Thermostat`.
2335pub async fn read_thermostat_suggestions(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ThermostatSuggestion>> {
2336    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_THERMOSTATSUGGESTIONS).await?;
2337    decode_thermostat_suggestions(&tlv)
2338}
2339
2340/// Read `CurrentThermostatSuggestion` attribute from cluster `Thermostat`.
2341pub async fn read_current_thermostat_suggestion(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<ThermostatSuggestion>> {
2342    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_CURRENTTHERMOSTATSUGGESTION).await?;
2343    decode_current_thermostat_suggestion(&tlv)
2344}
2345
2346/// Read `ThermostatSuggestionNotFollowingReason` attribute from cluster `Thermostat`.
2347pub async fn read_thermostat_suggestion_not_following_reason(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<ThermostatSuggestionNotFollowingReason>> {
2348    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_THERMOSTAT, crate::clusters::defs::CLUSTER_THERMOSTAT_ATTR_ID_THERMOSTATSUGGESTIONNOTFOLLOWINGREASON).await?;
2349    decode_thermostat_suggestion_not_following_reason(&tlv)
2350}
2351
2352#[derive(Debug, serde::Serialize)]
2353pub struct SystemModeChangeEvent {
2354    pub previous_system_mode: Option<SystemMode>,
2355    pub current_system_mode: Option<SystemMode>,
2356}
2357
2358#[derive(Debug, serde::Serialize)]
2359pub struct LocalTemperatureChangeEvent {
2360    pub current_local_temperature: Option<i16>,
2361}
2362
2363#[derive(Debug, serde::Serialize)]
2364pub struct OccupancyChangeEvent {
2365    pub previous_occupancy: Option<Occupancy>,
2366    pub current_occupancy: Option<Occupancy>,
2367}
2368
2369#[derive(Debug, serde::Serialize)]
2370pub struct SetpointChangeEvent {
2371    pub system_mode: Option<SystemMode>,
2372    pub occupancy: Option<Occupancy>,
2373    pub previous_setpoint: Option<i16>,
2374    pub current_setpoint: Option<i16>,
2375}
2376
2377#[derive(Debug, serde::Serialize)]
2378pub struct RunningStateChangeEvent {
2379    pub previous_running_state: Option<RelayState>,
2380    pub current_running_state: Option<RelayState>,
2381}
2382
2383#[derive(Debug, serde::Serialize)]
2384pub struct RunningModeChangeEvent {
2385    pub previous_running_mode: Option<ThermostatRunningMode>,
2386    pub current_running_mode: Option<ThermostatRunningMode>,
2387}
2388
2389#[derive(Debug, serde::Serialize)]
2390pub struct ActiveScheduleChangeEvent {
2391    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
2392    pub previous_schedule_handle: Option<Vec<u8>>,
2393    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
2394    pub current_schedule_handle: Option<Vec<u8>>,
2395}
2396
2397#[derive(Debug, serde::Serialize)]
2398pub struct ActivePresetChangeEvent {
2399    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
2400    pub previous_preset_handle: Option<Vec<u8>>,
2401    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
2402    pub current_preset_handle: Option<Vec<u8>>,
2403}
2404
2405// Event decoders
2406
2407/// Decode SystemModeChange event (0x00, priority: info)
2408pub fn decode_system_mode_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<SystemModeChangeEvent> {
2409    if let tlv::TlvItemValue::List(_fields) = inp {
2410        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2411        Ok(SystemModeChangeEvent {
2412                                previous_system_mode: item.get_int(&[0]).and_then(|v| SystemMode::from_u8(v as u8)),
2413                                current_system_mode: item.get_int(&[1]).and_then(|v| SystemMode::from_u8(v as u8)),
2414        })
2415    } else {
2416        Err(anyhow::anyhow!("Expected struct fields"))
2417    }
2418}
2419
2420/// Decode LocalTemperatureChange event (0x01, priority: info)
2421pub fn decode_local_temperature_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<LocalTemperatureChangeEvent> {
2422    if let tlv::TlvItemValue::List(_fields) = inp {
2423        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2424        Ok(LocalTemperatureChangeEvent {
2425                                current_local_temperature: item.get_int(&[0]).map(|v| v as i16),
2426        })
2427    } else {
2428        Err(anyhow::anyhow!("Expected struct fields"))
2429    }
2430}
2431
2432/// Decode OccupancyChange event (0x02, priority: info)
2433pub fn decode_occupancy_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangeEvent> {
2434    if let tlv::TlvItemValue::List(_fields) = inp {
2435        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2436        Ok(OccupancyChangeEvent {
2437                                previous_occupancy: item.get_int(&[0]).map(|v| v as u8),
2438                                current_occupancy: item.get_int(&[1]).map(|v| v as u8),
2439        })
2440    } else {
2441        Err(anyhow::anyhow!("Expected struct fields"))
2442    }
2443}
2444
2445/// Decode SetpointChange event (0x03, priority: info)
2446pub fn decode_setpoint_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<SetpointChangeEvent> {
2447    if let tlv::TlvItemValue::List(_fields) = inp {
2448        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2449        Ok(SetpointChangeEvent {
2450                                system_mode: item.get_int(&[0]).and_then(|v| SystemMode::from_u8(v as u8)),
2451                                occupancy: item.get_int(&[1]).map(|v| v as u8),
2452                                previous_setpoint: item.get_int(&[2]).map(|v| v as i16),
2453                                current_setpoint: item.get_int(&[3]).map(|v| v as i16),
2454        })
2455    } else {
2456        Err(anyhow::anyhow!("Expected struct fields"))
2457    }
2458}
2459
2460/// Decode RunningStateChange event (0x04, priority: info)
2461pub fn decode_running_state_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<RunningStateChangeEvent> {
2462    if let tlv::TlvItemValue::List(_fields) = inp {
2463        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2464        Ok(RunningStateChangeEvent {
2465                                previous_running_state: item.get_int(&[0]).map(|v| v as u8),
2466                                current_running_state: item.get_int(&[1]).map(|v| v as u8),
2467        })
2468    } else {
2469        Err(anyhow::anyhow!("Expected struct fields"))
2470    }
2471}
2472
2473/// Decode RunningModeChange event (0x05, priority: info)
2474pub fn decode_running_mode_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<RunningModeChangeEvent> {
2475    if let tlv::TlvItemValue::List(_fields) = inp {
2476        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2477        Ok(RunningModeChangeEvent {
2478                                previous_running_mode: item.get_int(&[0]).and_then(|v| ThermostatRunningMode::from_u8(v as u8)),
2479                                current_running_mode: item.get_int(&[1]).and_then(|v| ThermostatRunningMode::from_u8(v as u8)),
2480        })
2481    } else {
2482        Err(anyhow::anyhow!("Expected struct fields"))
2483    }
2484}
2485
2486/// Decode ActiveScheduleChange event (0x06, priority: info)
2487pub fn decode_active_schedule_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ActiveScheduleChangeEvent> {
2488    if let tlv::TlvItemValue::List(_fields) = inp {
2489        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2490        Ok(ActiveScheduleChangeEvent {
2491                                previous_schedule_handle: item.get_octet_string_owned(&[0]),
2492                                current_schedule_handle: item.get_octet_string_owned(&[1]),
2493        })
2494    } else {
2495        Err(anyhow::anyhow!("Expected struct fields"))
2496    }
2497}
2498
2499/// Decode ActivePresetChange event (0x07, priority: info)
2500pub fn decode_active_preset_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ActivePresetChangeEvent> {
2501    if let tlv::TlvItemValue::List(_fields) = inp {
2502        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
2503        Ok(ActivePresetChangeEvent {
2504                                previous_preset_handle: item.get_octet_string_owned(&[0]),
2505                                current_preset_handle: item.get_octet_string_owned(&[1]),
2506        })
2507    } else {
2508        Err(anyhow::anyhow!("Expected struct fields"))
2509    }
2510}
2511
2512
2513// Event JSON dispatcher
2514
2515/// Decode event value and return as JSON string
2516pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
2517    if cluster_id != 0x0201 {
2518        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0201, got {}\"}}", cluster_id);
2519    }
2520
2521    match event_id {
2522        0x00 => {
2523            match decode_system_mode_change_event(tlv_value) {
2524                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2525                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2526            }
2527        }
2528        0x01 => {
2529            match decode_local_temperature_change_event(tlv_value) {
2530                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2531                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2532            }
2533        }
2534        0x02 => {
2535            match decode_occupancy_change_event(tlv_value) {
2536                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2537                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2538            }
2539        }
2540        0x03 => {
2541            match decode_setpoint_change_event(tlv_value) {
2542                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2543                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2544            }
2545        }
2546        0x04 => {
2547            match decode_running_state_change_event(tlv_value) {
2548                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2549                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2550            }
2551        }
2552        0x05 => {
2553            match decode_running_mode_change_event(tlv_value) {
2554                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2555                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2556            }
2557        }
2558        0x06 => {
2559            match decode_active_schedule_change_event(tlv_value) {
2560                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2561                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2562            }
2563        }
2564        0x07 => {
2565            match decode_active_preset_change_event(tlv_value) {
2566                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
2567                Err(e) => format!("{{\"error\": \"{}\"}}", e),
2568            }
2569        }
2570        _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
2571    }
2572}
2573
2574/// Get list of all events supported by this cluster
2575///
2576/// # Returns
2577/// Vector of tuples containing (event_id, event_name)
2578pub fn get_event_list() -> Vec<(u32, &'static str)> {
2579    vec![
2580        (0x00, "SystemModeChange"),
2581        (0x01, "LocalTemperatureChange"),
2582        (0x02, "OccupancyChange"),
2583        (0x03, "SetpointChange"),
2584        (0x04, "RunningStateChange"),
2585        (0x05, "RunningModeChange"),
2586        (0x06, "ActiveScheduleChange"),
2587        (0x07, "ActivePresetChange"),
2588    ]
2589}
2590