matc/clusters/codec/
commodity_tariff.rs

1//! Matter TLV encoders and decoders for Commodity Tariff Cluster
2//! Cluster ID: 0x0700
3//!
4//! This file is automatically generated from CommodityTariff.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Enum definitions
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum AuxiliaryLoadSetting {
16    /// The switch should be in the OFF state
17    Off = 0,
18    /// The switch should be in the ON state
19    On = 1,
20    /// No state is required
21    None = 2,
22}
23
24impl AuxiliaryLoadSetting {
25    /// Convert from u8 value
26    pub fn from_u8(value: u8) -> Option<Self> {
27        match value {
28            0 => Some(AuxiliaryLoadSetting::Off),
29            1 => Some(AuxiliaryLoadSetting::On),
30            2 => Some(AuxiliaryLoadSetting::None),
31            _ => None,
32        }
33    }
34
35    /// Convert to u8 value
36    pub fn to_u8(self) -> u8 {
37        self as u8
38    }
39}
40
41impl From<AuxiliaryLoadSetting> for u8 {
42    fn from(val: AuxiliaryLoadSetting) -> Self {
43        val as u8
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48#[repr(u8)]
49pub enum BlockMode {
50    /// Tariff has no usage blocks
51    Noblock = 0,
52    /// Usage is metered in combined blocks
53    Combined = 1,
54    /// Usage is metered separately by tariff component
55    Individual = 2,
56}
57
58impl BlockMode {
59    /// Convert from u8 value
60    pub fn from_u8(value: u8) -> Option<Self> {
61        match value {
62            0 => Some(BlockMode::Noblock),
63            1 => Some(BlockMode::Combined),
64            2 => Some(BlockMode::Individual),
65            _ => None,
66        }
67    }
68
69    /// Convert to u8 value
70    pub fn to_u8(self) -> u8 {
71        self as u8
72    }
73}
74
75impl From<BlockMode> for u8 {
76    fn from(val: BlockMode) -> Self {
77        val as u8
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
82#[repr(u8)]
83pub enum DayEntryRandomizationType {
84    /// No randomization applied
85    None = 0,
86    /// An unchanging offset
87    Fixed = 1,
88    /// A random value
89    Random = 2,
90    /// A random positive value
91    Randompositive = 3,
92    /// A random negative value
93    Randomnegative = 4,
94}
95
96impl DayEntryRandomizationType {
97    /// Convert from u8 value
98    pub fn from_u8(value: u8) -> Option<Self> {
99        match value {
100            0 => Some(DayEntryRandomizationType::None),
101            1 => Some(DayEntryRandomizationType::Fixed),
102            2 => Some(DayEntryRandomizationType::Random),
103            3 => Some(DayEntryRandomizationType::Randompositive),
104            4 => Some(DayEntryRandomizationType::Randomnegative),
105            _ => None,
106        }
107    }
108
109    /// Convert to u8 value
110    pub fn to_u8(self) -> u8 {
111        self as u8
112    }
113}
114
115impl From<DayEntryRandomizationType> for u8 {
116    fn from(val: DayEntryRandomizationType) -> Self {
117        val as u8
118    }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
122#[repr(u8)]
123pub enum DayType {
124    /// Standard
125    Standard = 0,
126    /// Holiday
127    Holiday = 1,
128    /// Dynamic Pricing
129    Dynamic = 2,
130    /// Individual Events
131    Event = 3,
132}
133
134impl DayType {
135    /// Convert from u8 value
136    pub fn from_u8(value: u8) -> Option<Self> {
137        match value {
138            0 => Some(DayType::Standard),
139            1 => Some(DayType::Holiday),
140            2 => Some(DayType::Dynamic),
141            3 => Some(DayType::Event),
142            _ => None,
143        }
144    }
145
146    /// Convert to u8 value
147    pub fn to_u8(self) -> u8 {
148        self as u8
149    }
150}
151
152impl From<DayType> for u8 {
153    fn from(val: DayType) -> Self {
154        val as u8
155    }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
159#[repr(u8)]
160pub enum PeakPeriodSeverity {
161    /// Unused
162    Unused = 0,
163    /// Low
164    Low = 1,
165    /// Medium
166    Medium = 2,
167    /// High
168    High = 3,
169}
170
171impl PeakPeriodSeverity {
172    /// Convert from u8 value
173    pub fn from_u8(value: u8) -> Option<Self> {
174        match value {
175            0 => Some(PeakPeriodSeverity::Unused),
176            1 => Some(PeakPeriodSeverity::Low),
177            2 => Some(PeakPeriodSeverity::Medium),
178            3 => Some(PeakPeriodSeverity::High),
179            _ => None,
180        }
181    }
182
183    /// Convert to u8 value
184    pub fn to_u8(self) -> u8 {
185        self as u8
186    }
187}
188
189impl From<PeakPeriodSeverity> for u8 {
190    fn from(val: PeakPeriodSeverity) -> Self {
191        val as u8
192    }
193}
194
195// Bitmap definitions
196
197/// DayPatternDayOfWeek bitmap type
198pub type DayPatternDayOfWeek = u8;
199
200/// Constants for DayPatternDayOfWeek
201pub mod daypatterndayofweek {
202    /// Sunday
203    pub const SUNDAY: u8 = 0x01;
204    /// Monday
205    pub const MONDAY: u8 = 0x02;
206    /// Tuesday
207    pub const TUESDAY: u8 = 0x04;
208    /// Wednesday
209    pub const WEDNESDAY: u8 = 0x08;
210    /// Thursday
211    pub const THURSDAY: u8 = 0x10;
212    /// Friday
213    pub const FRIDAY: u8 = 0x20;
214    /// Saturday
215    pub const SATURDAY: u8 = 0x40;
216}
217
218// Struct definitions
219
220#[derive(Debug, serde::Serialize)]
221pub struct AuxiliaryLoadSwitchSettings {
222    pub number: Option<u8>,
223    pub required_state: Option<AuxiliaryLoadSetting>,
224}
225
226#[derive(Debug, serde::Serialize)]
227pub struct AuxiliaryLoadSwitchesSettings {
228    pub switch_states: Option<Vec<AuxiliaryLoadSwitchSettings>>,
229}
230
231#[derive(Debug, serde::Serialize)]
232pub struct CalendarPeriod {
233    pub start_date: Option<u64>,
234    pub day_pattern_i_ds: Option<Vec<u32>>,
235}
236
237#[derive(Debug, serde::Serialize)]
238pub struct DayEntry {
239    pub day_entry_id: Option<u32>,
240    pub start_time: Option<u16>,
241    pub duration: Option<u16>,
242    pub randomization_offset: Option<i16>,
243    pub randomization_type: Option<DayEntryRandomizationType>,
244}
245
246#[derive(Debug, serde::Serialize)]
247pub struct DayPattern {
248    pub day_pattern_id: Option<u32>,
249    pub days_of_week: Option<DayPatternDayOfWeek>,
250    pub day_entry_i_ds: Option<Vec<u32>>,
251}
252
253#[derive(Debug, serde::Serialize)]
254pub struct Day {
255    pub date: Option<u64>,
256    pub day_type: Option<DayType>,
257    pub day_entry_i_ds: Option<Vec<u32>>,
258}
259
260#[derive(Debug, serde::Serialize)]
261pub struct PeakPeriod {
262    pub severity: Option<PeakPeriodSeverity>,
263    pub peak_period: Option<u16>,
264}
265
266#[derive(Debug, serde::Serialize)]
267pub struct TariffComponent {
268    pub tariff_component_id: Option<u32>,
269    pub price: Option<TariffPrice>,
270    pub friendly_credit: Option<bool>,
271    pub auxiliary_load: Option<AuxiliaryLoadSwitchSettings>,
272    pub peak_period: Option<PeakPeriod>,
273    pub threshold: Option<i64>,
274    pub label: Option<String>,
275    pub predicted: Option<bool>,
276}
277
278#[derive(Debug, serde::Serialize)]
279pub struct TariffInformation {
280    pub tariff_label: Option<String>,
281    pub provider_name: Option<String>,
282    pub block_mode: Option<BlockMode>,
283}
284
285#[derive(Debug, serde::Serialize)]
286pub struct TariffPeriod {
287    pub label: Option<String>,
288    pub day_entry_i_ds: Option<Vec<u32>>,
289    pub tariff_component_i_ds: Option<Vec<u32>>,
290}
291
292#[derive(Debug, serde::Serialize)]
293pub struct TariffPrice {
294    pub price_type: Option<u8>,
295    pub price: Option<u8>,
296    pub price_level: Option<i16>,
297}
298
299// Command encoders
300
301/// Encode GetTariffComponent command (0x00)
302pub fn encode_get_tariff_component(tariff_component_id: u32) -> anyhow::Result<Vec<u8>> {
303    let tlv = tlv::TlvItemEnc {
304        tag: 0,
305        value: tlv::TlvItemValueEnc::StructInvisible(vec![
306        (0, tlv::TlvItemValueEnc::UInt32(tariff_component_id)).into(),
307        ]),
308    };
309    Ok(tlv.encode()?)
310}
311
312/// Encode GetDayEntry command (0x01)
313pub fn encode_get_day_entry(day_entry_id: u32) -> anyhow::Result<Vec<u8>> {
314    let tlv = tlv::TlvItemEnc {
315        tag: 0,
316        value: tlv::TlvItemValueEnc::StructInvisible(vec![
317        (0, tlv::TlvItemValueEnc::UInt32(day_entry_id)).into(),
318        ]),
319    };
320    Ok(tlv.encode()?)
321}
322
323// Attribute decoders
324
325/// Decode TariffInfo attribute (0x0000)
326pub fn decode_tariff_info(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<TariffInformation>> {
327    if let tlv::TlvItemValue::List(_fields) = inp {
328        // Struct with fields
329        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
330        Ok(Some(TariffInformation {
331                tariff_label: item.get_string_owned(&[0]),
332                provider_name: item.get_string_owned(&[1]),
333                block_mode: item.get_int(&[3]).and_then(|v| BlockMode::from_u8(v as u8)),
334        }))
335    //} else if let tlv::TlvItemValue::Null = inp {
336    //    // Null value for nullable struct
337    //    Ok(None)
338    } else {
339    Ok(None)
340    //    Err(anyhow::anyhow!("Expected struct fields or null"))
341    }
342}
343
344/// Decode TariffUnit attribute (0x0001)
345pub fn decode_tariff_unit(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
346    if let tlv::TlvItemValue::Int(v) = inp {
347        Ok(Some(*v as u8))
348    } else {
349        Ok(None)
350    }
351}
352
353/// Decode StartDate attribute (0x0002)
354pub fn decode_start_date(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
355    if let tlv::TlvItemValue::Int(v) = inp {
356        Ok(Some(*v))
357    } else {
358        Ok(None)
359    }
360}
361
362/// Decode DayEntries attribute (0x0003)
363pub fn decode_day_entries(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DayEntry>> {
364    let mut res = Vec::new();
365    if let tlv::TlvItemValue::List(v) = inp {
366        for item in v {
367            res.push(DayEntry {
368                day_entry_id: item.get_int(&[0]).map(|v| v as u32),
369                start_time: item.get_int(&[1]).map(|v| v as u16),
370                duration: item.get_int(&[2]).map(|v| v as u16),
371                randomization_offset: item.get_int(&[3]).map(|v| v as i16),
372                randomization_type: item.get_int(&[4]).and_then(|v| DayEntryRandomizationType::from_u8(v as u8)),
373            });
374        }
375    }
376    Ok(res)
377}
378
379/// Decode DayPatterns attribute (0x0004)
380pub fn decode_day_patterns(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DayPattern>> {
381    let mut res = Vec::new();
382    if let tlv::TlvItemValue::List(v) = inp {
383        for item in v {
384            res.push(DayPattern {
385                day_pattern_id: item.get_int(&[0]).map(|v| v as u32),
386                days_of_week: item.get_int(&[1]).map(|v| v as u8),
387                day_entry_i_ds: {
388                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
389                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
390                        Some(items)
391                    } else {
392                        None
393                    }
394                },
395            });
396        }
397    }
398    Ok(res)
399}
400
401/// Decode CalendarPeriods attribute (0x0005)
402pub fn decode_calendar_periods(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CalendarPeriod>> {
403    let mut res = Vec::new();
404    if let tlv::TlvItemValue::List(v) = inp {
405        for item in v {
406            res.push(CalendarPeriod {
407                start_date: item.get_int(&[0]),
408                day_pattern_i_ds: {
409                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
410                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
411                        Some(items)
412                    } else {
413                        None
414                    }
415                },
416            });
417        }
418    }
419    Ok(res)
420}
421
422/// Decode IndividualDays attribute (0x0006)
423pub fn decode_individual_days(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Day>> {
424    let mut res = Vec::new();
425    if let tlv::TlvItemValue::List(v) = inp {
426        for item in v {
427            res.push(Day {
428                date: item.get_int(&[0]),
429                day_type: item.get_int(&[1]).and_then(|v| DayType::from_u8(v as u8)),
430                day_entry_i_ds: {
431                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
432                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
433                        Some(items)
434                    } else {
435                        None
436                    }
437                },
438            });
439        }
440    }
441    Ok(res)
442}
443
444/// Decode CurrentDay attribute (0x0007)
445pub fn decode_current_day(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Day>> {
446    if let tlv::TlvItemValue::List(_fields) = inp {
447        // Struct with fields
448        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
449        Ok(Some(Day {
450                date: item.get_int(&[0]),
451                day_type: item.get_int(&[1]).and_then(|v| DayType::from_u8(v as u8)),
452                day_entry_i_ds: {
453                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
454                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
455                        Some(items)
456                    } else {
457                        None
458                    }
459                },
460        }))
461    //} else if let tlv::TlvItemValue::Null = inp {
462    //    // Null value for nullable struct
463    //    Ok(None)
464    } else {
465    Ok(None)
466    //    Err(anyhow::anyhow!("Expected struct fields or null"))
467    }
468}
469
470/// Decode NextDay attribute (0x0008)
471pub fn decode_next_day(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Day>> {
472    if let tlv::TlvItemValue::List(_fields) = inp {
473        // Struct with fields
474        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
475        Ok(Some(Day {
476                date: item.get_int(&[0]),
477                day_type: item.get_int(&[1]).and_then(|v| DayType::from_u8(v as u8)),
478                day_entry_i_ds: {
479                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
480                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
481                        Some(items)
482                    } else {
483                        None
484                    }
485                },
486        }))
487    //} else if let tlv::TlvItemValue::Null = inp {
488    //    // Null value for nullable struct
489    //    Ok(None)
490    } else {
491    Ok(None)
492    //    Err(anyhow::anyhow!("Expected struct fields or null"))
493    }
494}
495
496/// Decode CurrentDayEntry attribute (0x0009)
497pub fn decode_current_day_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<DayEntry>> {
498    if let tlv::TlvItemValue::List(_fields) = inp {
499        // Struct with fields
500        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
501        Ok(Some(DayEntry {
502                day_entry_id: item.get_int(&[0]).map(|v| v as u32),
503                start_time: item.get_int(&[1]).map(|v| v as u16),
504                duration: item.get_int(&[2]).map(|v| v as u16),
505                randomization_offset: item.get_int(&[3]).map(|v| v as i16),
506                randomization_type: item.get_int(&[4]).and_then(|v| DayEntryRandomizationType::from_u8(v as u8)),
507        }))
508    //} else if let tlv::TlvItemValue::Null = inp {
509    //    // Null value for nullable struct
510    //    Ok(None)
511    } else {
512    Ok(None)
513    //    Err(anyhow::anyhow!("Expected struct fields or null"))
514    }
515}
516
517/// Decode CurrentDayEntryDate attribute (0x000A)
518pub fn decode_current_day_entry_date(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
519    if let tlv::TlvItemValue::Int(v) = inp {
520        Ok(Some(*v))
521    } else {
522        Ok(None)
523    }
524}
525
526/// Decode NextDayEntry attribute (0x000B)
527pub fn decode_next_day_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<DayEntry>> {
528    if let tlv::TlvItemValue::List(_fields) = inp {
529        // Struct with fields
530        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
531        Ok(Some(DayEntry {
532                day_entry_id: item.get_int(&[0]).map(|v| v as u32),
533                start_time: item.get_int(&[1]).map(|v| v as u16),
534                duration: item.get_int(&[2]).map(|v| v as u16),
535                randomization_offset: item.get_int(&[3]).map(|v| v as i16),
536                randomization_type: item.get_int(&[4]).and_then(|v| DayEntryRandomizationType::from_u8(v as u8)),
537        }))
538    //} else if let tlv::TlvItemValue::Null = inp {
539    //    // Null value for nullable struct
540    //    Ok(None)
541    } else {
542    Ok(None)
543    //    Err(anyhow::anyhow!("Expected struct fields or null"))
544    }
545}
546
547/// Decode NextDayEntryDate attribute (0x000C)
548pub fn decode_next_day_entry_date(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
549    if let tlv::TlvItemValue::Int(v) = inp {
550        Ok(Some(*v))
551    } else {
552        Ok(None)
553    }
554}
555
556/// Decode TariffComponents attribute (0x000D)
557pub fn decode_tariff_components(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TariffComponent>> {
558    let mut res = Vec::new();
559    if let tlv::TlvItemValue::List(v) = inp {
560        for item in v {
561            res.push(TariffComponent {
562                tariff_component_id: item.get_int(&[0]).map(|v| v as u32),
563                price: {
564                    if let Some(nested_tlv) = item.get(&[1]) {
565                        if let tlv::TlvItemValue::List(_) = nested_tlv {
566                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
567                            Some(TariffPrice {
568                price_type: nested_item.get_int(&[0]).map(|v| v as u8),
569                price: nested_item.get_int(&[1]).map(|v| v as u8),
570                price_level: nested_item.get_int(&[2]).map(|v| v as i16),
571                            })
572                        } else {
573                            None
574                        }
575                    } else {
576                        None
577                    }
578                },
579                friendly_credit: item.get_bool(&[2]),
580                auxiliary_load: {
581                    if let Some(nested_tlv) = item.get(&[3]) {
582                        if let tlv::TlvItemValue::List(_) = nested_tlv {
583                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
584                            Some(AuxiliaryLoadSwitchSettings {
585                number: nested_item.get_int(&[0]).map(|v| v as u8),
586                required_state: nested_item.get_int(&[1]).and_then(|v| AuxiliaryLoadSetting::from_u8(v as u8)),
587                            })
588                        } else {
589                            None
590                        }
591                    } else {
592                        None
593                    }
594                },
595                peak_period: {
596                    if let Some(nested_tlv) = item.get(&[4]) {
597                        if let tlv::TlvItemValue::List(_) = nested_tlv {
598                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
599                            Some(PeakPeriod {
600                severity: nested_item.get_int(&[0]).and_then(|v| PeakPeriodSeverity::from_u8(v as u8)),
601                peak_period: nested_item.get_int(&[1]).map(|v| v as u16),
602                            })
603                        } else {
604                            None
605                        }
606                    } else {
607                        None
608                    }
609                },
610                threshold: item.get_int(&[6]).map(|v| v as i64),
611                label: item.get_string_owned(&[7]),
612                predicted: item.get_bool(&[8]),
613            });
614        }
615    }
616    Ok(res)
617}
618
619/// Decode TariffPeriods attribute (0x000E)
620pub fn decode_tariff_periods(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TariffPeriod>> {
621    let mut res = Vec::new();
622    if let tlv::TlvItemValue::List(v) = inp {
623        for item in v {
624            res.push(TariffPeriod {
625                label: item.get_string_owned(&[0]),
626                day_entry_i_ds: {
627                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
628                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
629                        Some(items)
630                    } else {
631                        None
632                    }
633                },
634                tariff_component_i_ds: {
635                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
636                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
637                        Some(items)
638                    } else {
639                        None
640                    }
641                },
642            });
643        }
644    }
645    Ok(res)
646}
647
648/// Decode CurrentTariffComponents attribute (0x000F)
649pub fn decode_current_tariff_components(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TariffComponent>> {
650    let mut res = Vec::new();
651    if let tlv::TlvItemValue::List(v) = inp {
652        for item in v {
653            res.push(TariffComponent {
654                tariff_component_id: item.get_int(&[0]).map(|v| v as u32),
655                price: {
656                    if let Some(nested_tlv) = item.get(&[1]) {
657                        if let tlv::TlvItemValue::List(_) = nested_tlv {
658                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
659                            Some(TariffPrice {
660                price_type: nested_item.get_int(&[0]).map(|v| v as u8),
661                price: nested_item.get_int(&[1]).map(|v| v as u8),
662                price_level: nested_item.get_int(&[2]).map(|v| v as i16),
663                            })
664                        } else {
665                            None
666                        }
667                    } else {
668                        None
669                    }
670                },
671                friendly_credit: item.get_bool(&[2]),
672                auxiliary_load: {
673                    if let Some(nested_tlv) = item.get(&[3]) {
674                        if let tlv::TlvItemValue::List(_) = nested_tlv {
675                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
676                            Some(AuxiliaryLoadSwitchSettings {
677                number: nested_item.get_int(&[0]).map(|v| v as u8),
678                required_state: nested_item.get_int(&[1]).and_then(|v| AuxiliaryLoadSetting::from_u8(v as u8)),
679                            })
680                        } else {
681                            None
682                        }
683                    } else {
684                        None
685                    }
686                },
687                peak_period: {
688                    if let Some(nested_tlv) = item.get(&[4]) {
689                        if let tlv::TlvItemValue::List(_) = nested_tlv {
690                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
691                            Some(PeakPeriod {
692                severity: nested_item.get_int(&[0]).and_then(|v| PeakPeriodSeverity::from_u8(v as u8)),
693                peak_period: nested_item.get_int(&[1]).map(|v| v as u16),
694                            })
695                        } else {
696                            None
697                        }
698                    } else {
699                        None
700                    }
701                },
702                threshold: item.get_int(&[6]).map(|v| v as i64),
703                label: item.get_string_owned(&[7]),
704                predicted: item.get_bool(&[8]),
705            });
706        }
707    }
708    Ok(res)
709}
710
711/// Decode NextTariffComponents attribute (0x0010)
712pub fn decode_next_tariff_components(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TariffComponent>> {
713    let mut res = Vec::new();
714    if let tlv::TlvItemValue::List(v) = inp {
715        for item in v {
716            res.push(TariffComponent {
717                tariff_component_id: item.get_int(&[0]).map(|v| v as u32),
718                price: {
719                    if let Some(nested_tlv) = item.get(&[1]) {
720                        if let tlv::TlvItemValue::List(_) = nested_tlv {
721                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
722                            Some(TariffPrice {
723                price_type: nested_item.get_int(&[0]).map(|v| v as u8),
724                price: nested_item.get_int(&[1]).map(|v| v as u8),
725                price_level: nested_item.get_int(&[2]).map(|v| v as i16),
726                            })
727                        } else {
728                            None
729                        }
730                    } else {
731                        None
732                    }
733                },
734                friendly_credit: item.get_bool(&[2]),
735                auxiliary_load: {
736                    if let Some(nested_tlv) = item.get(&[3]) {
737                        if let tlv::TlvItemValue::List(_) = nested_tlv {
738                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
739                            Some(AuxiliaryLoadSwitchSettings {
740                number: nested_item.get_int(&[0]).map(|v| v as u8),
741                required_state: nested_item.get_int(&[1]).and_then(|v| AuxiliaryLoadSetting::from_u8(v as u8)),
742                            })
743                        } else {
744                            None
745                        }
746                    } else {
747                        None
748                    }
749                },
750                peak_period: {
751                    if let Some(nested_tlv) = item.get(&[4]) {
752                        if let tlv::TlvItemValue::List(_) = nested_tlv {
753                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
754                            Some(PeakPeriod {
755                severity: nested_item.get_int(&[0]).and_then(|v| PeakPeriodSeverity::from_u8(v as u8)),
756                peak_period: nested_item.get_int(&[1]).map(|v| v as u16),
757                            })
758                        } else {
759                            None
760                        }
761                    } else {
762                        None
763                    }
764                },
765                threshold: item.get_int(&[6]).map(|v| v as i64),
766                label: item.get_string_owned(&[7]),
767                predicted: item.get_bool(&[8]),
768            });
769        }
770    }
771    Ok(res)
772}
773
774/// Decode DefaultRandomizationOffset attribute (0x0011)
775pub fn decode_default_randomization_offset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i16>> {
776    if let tlv::TlvItemValue::Int(v) = inp {
777        Ok(Some(*v as i16))
778    } else {
779        Ok(None)
780    }
781}
782
783/// Decode DefaultRandomizationType attribute (0x0012)
784pub fn decode_default_randomization_type(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<DayEntryRandomizationType>> {
785    if let tlv::TlvItemValue::Int(v) = inp {
786        Ok(DayEntryRandomizationType::from_u8(*v as u8))
787    } else {
788        Ok(None)
789    }
790}
791
792
793// JSON dispatcher function
794
795/// Decode attribute value and return as JSON string
796///
797/// # Parameters
798/// * `cluster_id` - The cluster identifier
799/// * `attribute_id` - The attribute identifier
800/// * `tlv_value` - The TLV value to decode
801///
802/// # Returns
803/// JSON string representation of the decoded value or error
804pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
805    // Verify this is the correct cluster
806    if cluster_id != 0x0700 {
807        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0700, got {}\"}}", cluster_id);
808    }
809
810    match attribute_id {
811        0x0000 => {
812            match decode_tariff_info(tlv_value) {
813                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
814                Err(e) => format!("{{\"error\": \"{}\"}}", e),
815            }
816        }
817        0x0001 => {
818            match decode_tariff_unit(tlv_value) {
819                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
820                Err(e) => format!("{{\"error\": \"{}\"}}", e),
821            }
822        }
823        0x0002 => {
824            match decode_start_date(tlv_value) {
825                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
826                Err(e) => format!("{{\"error\": \"{}\"}}", e),
827            }
828        }
829        0x0003 => {
830            match decode_day_entries(tlv_value) {
831                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
832                Err(e) => format!("{{\"error\": \"{}\"}}", e),
833            }
834        }
835        0x0004 => {
836            match decode_day_patterns(tlv_value) {
837                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
838                Err(e) => format!("{{\"error\": \"{}\"}}", e),
839            }
840        }
841        0x0005 => {
842            match decode_calendar_periods(tlv_value) {
843                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
844                Err(e) => format!("{{\"error\": \"{}\"}}", e),
845            }
846        }
847        0x0006 => {
848            match decode_individual_days(tlv_value) {
849                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
850                Err(e) => format!("{{\"error\": \"{}\"}}", e),
851            }
852        }
853        0x0007 => {
854            match decode_current_day(tlv_value) {
855                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
856                Err(e) => format!("{{\"error\": \"{}\"}}", e),
857            }
858        }
859        0x0008 => {
860            match decode_next_day(tlv_value) {
861                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
862                Err(e) => format!("{{\"error\": \"{}\"}}", e),
863            }
864        }
865        0x0009 => {
866            match decode_current_day_entry(tlv_value) {
867                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
868                Err(e) => format!("{{\"error\": \"{}\"}}", e),
869            }
870        }
871        0x000A => {
872            match decode_current_day_entry_date(tlv_value) {
873                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
874                Err(e) => format!("{{\"error\": \"{}\"}}", e),
875            }
876        }
877        0x000B => {
878            match decode_next_day_entry(tlv_value) {
879                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
880                Err(e) => format!("{{\"error\": \"{}\"}}", e),
881            }
882        }
883        0x000C => {
884            match decode_next_day_entry_date(tlv_value) {
885                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
886                Err(e) => format!("{{\"error\": \"{}\"}}", e),
887            }
888        }
889        0x000D => {
890            match decode_tariff_components(tlv_value) {
891                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
892                Err(e) => format!("{{\"error\": \"{}\"}}", e),
893            }
894        }
895        0x000E => {
896            match decode_tariff_periods(tlv_value) {
897                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
898                Err(e) => format!("{{\"error\": \"{}\"}}", e),
899            }
900        }
901        0x000F => {
902            match decode_current_tariff_components(tlv_value) {
903                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
904                Err(e) => format!("{{\"error\": \"{}\"}}", e),
905            }
906        }
907        0x0010 => {
908            match decode_next_tariff_components(tlv_value) {
909                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
910                Err(e) => format!("{{\"error\": \"{}\"}}", e),
911            }
912        }
913        0x0011 => {
914            match decode_default_randomization_offset(tlv_value) {
915                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
916                Err(e) => format!("{{\"error\": \"{}\"}}", e),
917            }
918        }
919        0x0012 => {
920            match decode_default_randomization_type(tlv_value) {
921                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
922                Err(e) => format!("{{\"error\": \"{}\"}}", e),
923            }
924        }
925        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
926    }
927}
928
929/// Get list of all attributes supported by this cluster
930///
931/// # Returns
932/// Vector of tuples containing (attribute_id, attribute_name)
933pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
934    vec![
935        (0x0000, "TariffInfo"),
936        (0x0001, "TariffUnit"),
937        (0x0002, "StartDate"),
938        (0x0003, "DayEntries"),
939        (0x0004, "DayPatterns"),
940        (0x0005, "CalendarPeriods"),
941        (0x0006, "IndividualDays"),
942        (0x0007, "CurrentDay"),
943        (0x0008, "NextDay"),
944        (0x0009, "CurrentDayEntry"),
945        (0x000A, "CurrentDayEntryDate"),
946        (0x000B, "NextDayEntry"),
947        (0x000C, "NextDayEntryDate"),
948        (0x000D, "TariffComponents"),
949        (0x000E, "TariffPeriods"),
950        (0x000F, "CurrentTariffComponents"),
951        (0x0010, "NextTariffComponents"),
952        (0x0011, "DefaultRandomizationOffset"),
953        (0x0012, "DefaultRandomizationType"),
954    ]
955}
956
957#[derive(Debug, serde::Serialize)]
958pub struct GetTariffComponentResponse {
959    pub label: Option<String>,
960    pub day_entry_i_ds: Option<Vec<u32>>,
961    pub tariff_component: Option<TariffComponent>,
962}
963
964#[derive(Debug, serde::Serialize)]
965pub struct GetDayEntryResponse {
966    pub day_entry: Option<DayEntry>,
967}
968
969// Command response decoders
970
971/// Decode GetTariffComponentResponse command response (00)
972pub fn decode_get_tariff_component_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetTariffComponentResponse> {
973    if let tlv::TlvItemValue::List(_fields) = inp {
974        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
975        Ok(GetTariffComponentResponse {
976                label: item.get_string_owned(&[0]),
977                day_entry_i_ds: {
978                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
979                        let items: Vec<u32> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u32) } else { None } }).collect();
980                        Some(items)
981                    } else {
982                        None
983                    }
984                },
985                tariff_component: {
986                    if let Some(nested_tlv) = item.get(&[2]) {
987                        if let tlv::TlvItemValue::List(_) = nested_tlv {
988                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
989                            Some(TariffComponent {
990                tariff_component_id: nested_item.get_int(&[0]).map(|v| v as u32),
991                price: {
992                    if let Some(nested_tlv) = nested_item.get(&[1]) {
993                        if let tlv::TlvItemValue::List(_) = nested_tlv {
994                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
995                            Some(TariffPrice {
996                price_type: nested_item.get_int(&[0]).map(|v| v as u8),
997                price: nested_item.get_int(&[1]).map(|v| v as u8),
998                price_level: nested_item.get_int(&[2]).map(|v| v as i16),
999                            })
1000                        } else {
1001                            None
1002                        }
1003                    } else {
1004                        None
1005                    }
1006                },
1007                friendly_credit: nested_item.get_bool(&[2]),
1008                auxiliary_load: {
1009                    if let Some(nested_tlv) = nested_item.get(&[3]) {
1010                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1011                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
1012                            Some(AuxiliaryLoadSwitchSettings {
1013                number: nested_item.get_int(&[0]).map(|v| v as u8),
1014                required_state: nested_item.get_int(&[1]).and_then(|v| AuxiliaryLoadSetting::from_u8(v as u8)),
1015                            })
1016                        } else {
1017                            None
1018                        }
1019                    } else {
1020                        None
1021                    }
1022                },
1023                peak_period: {
1024                    if let Some(nested_tlv) = nested_item.get(&[4]) {
1025                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1026                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
1027                            Some(PeakPeriod {
1028                severity: nested_item.get_int(&[0]).and_then(|v| PeakPeriodSeverity::from_u8(v as u8)),
1029                peak_period: nested_item.get_int(&[1]).map(|v| v as u16),
1030                            })
1031                        } else {
1032                            None
1033                        }
1034                    } else {
1035                        None
1036                    }
1037                },
1038                threshold: nested_item.get_int(&[6]).map(|v| v as i64),
1039                label: nested_item.get_string_owned(&[7]),
1040                predicted: nested_item.get_bool(&[8]),
1041                            })
1042                        } else {
1043                            None
1044                        }
1045                    } else {
1046                        None
1047                    }
1048                },
1049        })
1050    } else {
1051        Err(anyhow::anyhow!("Expected struct fields"))
1052    }
1053}
1054
1055/// Decode GetDayEntryResponse command response (01)
1056pub fn decode_get_day_entry_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetDayEntryResponse> {
1057    if let tlv::TlvItemValue::List(_fields) = inp {
1058        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1059        Ok(GetDayEntryResponse {
1060                day_entry: {
1061                    if let Some(nested_tlv) = item.get(&[0]) {
1062                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1063                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
1064                            Some(DayEntry {
1065                day_entry_id: nested_item.get_int(&[0]).map(|v| v as u32),
1066                start_time: nested_item.get_int(&[1]).map(|v| v as u16),
1067                duration: nested_item.get_int(&[2]).map(|v| v as u16),
1068                randomization_offset: nested_item.get_int(&[3]).map(|v| v as i16),
1069                randomization_type: nested_item.get_int(&[4]).and_then(|v| DayEntryRandomizationType::from_u8(v as u8)),
1070                            })
1071                        } else {
1072                            None
1073                        }
1074                    } else {
1075                        None
1076                    }
1077                },
1078        })
1079    } else {
1080        Err(anyhow::anyhow!("Expected struct fields"))
1081    }
1082}
1083