matc/clusters/codec/
time_sync.rs

1//! Matter TLV encoders and decoders for Time Synchronization Cluster
2//! Cluster ID: 0x0038
3//!
4//! This file is automatically generated from TimeSync.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 Granularity {
16    /// This indicates that the node is not currently synchronized with a UTC Time source and its clock is based on the Last Known Good UTC Time only.
17    Notimegranularity = 0,
18    /// This indicates the node was synchronized to an upstream source in the past, but sufficient clock drift has occurred such that the clock error is now > 5 seconds.
19    Minutesgranularity = 1,
20    /// This indicates the node is synchronized to an upstream source using a low resolution protocol. UTC Time is accurate to ± 5 seconds.
21    Secondsgranularity = 2,
22    /// This indicates the node is synchronized to an upstream source using high resolution time-synchronization protocol such as NTP, or has built-in GNSS with some amount of jitter applying its GNSS timestamp. UTC Time is accurate to ± 50 ms.
23    Millisecondsgranularity = 3,
24    /// This indicates the node is synchronized to an upstream source using a highly precise time-synchronization protocol such as PTP, or has built-in GNSS. UTC time is accurate to ± 10 μs.
25    Microsecondsgranularity = 4,
26}
27
28impl Granularity {
29    /// Convert from u8 value
30    pub fn from_u8(value: u8) -> Option<Self> {
31        match value {
32            0 => Some(Granularity::Notimegranularity),
33            1 => Some(Granularity::Minutesgranularity),
34            2 => Some(Granularity::Secondsgranularity),
35            3 => Some(Granularity::Millisecondsgranularity),
36            4 => Some(Granularity::Microsecondsgranularity),
37            _ => None,
38        }
39    }
40
41    /// Convert to u8 value
42    pub fn to_u8(self) -> u8 {
43        self as u8
44    }
45}
46
47impl From<Granularity> for u8 {
48    fn from(val: Granularity) -> Self {
49        val as u8
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54#[repr(u8)]
55pub enum StatusCode {
56    /// Node rejected the attempt to set the UTC time
57    Timenotaccepted = 2,
58}
59
60impl StatusCode {
61    /// Convert from u8 value
62    pub fn from_u8(value: u8) -> Option<Self> {
63        match value {
64            2 => Some(StatusCode::Timenotaccepted),
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<StatusCode> for u8 {
76    fn from(val: StatusCode) -> Self {
77        val as u8
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
82#[repr(u8)]
83pub enum TimeSource {
84    /// Node is not currently synchronized with a UTC Time source.
85    None = 0,
86    /// Node uses an unlisted time source.
87    Unknown = 1,
88    /// Node received time from a client using the SetUTCTime Command.
89    Admin = 2,
90    /// Synchronized time by querying the Time Synchronization cluster of another Node.
91    Nodetimecluster = 3,
92    /// SNTP from a server not in the Matter network. NTS is not used.
93    Nonmattersntp = 4,
94    /// NTP from servers not in the Matter network. None of the servers used NTS.
95    Nonmatterntp = 5,
96    /// SNTP from a server within the Matter network. NTS is not used.
97    Mattersntp = 6,
98    /// NTP from servers within the Matter network. None of the servers used NTS.
99    Matterntp = 7,
100    /// NTP from multiple servers in the Matter network and external. None of the servers used NTS.
101    Mixedntp = 8,
102    /// SNTP from a server not in the Matter network. NTS is used.
103    Nonmattersntpnts = 9,
104    /// NTP from servers not in the Matter network. NTS is used on at least one server.
105    Nonmatterntpnts = 10,
106    /// SNTP from a server within the Matter network. NTS is used.
107    Mattersntpnts = 11,
108    /// NTP from a server within the Matter network. NTS is used on at least one server.
109    Matterntpnts = 12,
110    /// NTP from multiple servers in the Matter network and external. NTS is used on at least one server.
111    Mixedntpnts = 13,
112    /// Time synchronization comes from a vendor cloud-based source (e.g. "Date" header in authenticated HTTPS connection).
113    Cloudsource = 14,
114    /// Time synchronization comes from PTP.
115    Ptp = 15,
116    /// Time synchronization comes from a GNSS source.
117    Gnss = 16,
118}
119
120impl TimeSource {
121    /// Convert from u8 value
122    pub fn from_u8(value: u8) -> Option<Self> {
123        match value {
124            0 => Some(TimeSource::None),
125            1 => Some(TimeSource::Unknown),
126            2 => Some(TimeSource::Admin),
127            3 => Some(TimeSource::Nodetimecluster),
128            4 => Some(TimeSource::Nonmattersntp),
129            5 => Some(TimeSource::Nonmatterntp),
130            6 => Some(TimeSource::Mattersntp),
131            7 => Some(TimeSource::Matterntp),
132            8 => Some(TimeSource::Mixedntp),
133            9 => Some(TimeSource::Nonmattersntpnts),
134            10 => Some(TimeSource::Nonmatterntpnts),
135            11 => Some(TimeSource::Mattersntpnts),
136            12 => Some(TimeSource::Matterntpnts),
137            13 => Some(TimeSource::Mixedntpnts),
138            14 => Some(TimeSource::Cloudsource),
139            15 => Some(TimeSource::Ptp),
140            16 => Some(TimeSource::Gnss),
141            _ => None,
142        }
143    }
144
145    /// Convert to u8 value
146    pub fn to_u8(self) -> u8 {
147        self as u8
148    }
149}
150
151impl From<TimeSource> for u8 {
152    fn from(val: TimeSource) -> Self {
153        val as u8
154    }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
158#[repr(u8)]
159pub enum TimeZoneDatabase {
160    /// Node has a full list of the available time zones
161    Full = 0,
162    /// Node has a partial list of the available time zones
163    Partial = 1,
164    /// Node does not have a time zone database
165    None = 2,
166}
167
168impl TimeZoneDatabase {
169    /// Convert from u8 value
170    pub fn from_u8(value: u8) -> Option<Self> {
171        match value {
172            0 => Some(TimeZoneDatabase::Full),
173            1 => Some(TimeZoneDatabase::Partial),
174            2 => Some(TimeZoneDatabase::None),
175            _ => None,
176        }
177    }
178
179    /// Convert to u8 value
180    pub fn to_u8(self) -> u8 {
181        self as u8
182    }
183}
184
185impl From<TimeZoneDatabase> for u8 {
186    fn from(val: TimeZoneDatabase) -> Self {
187        val as u8
188    }
189}
190
191// Struct definitions
192
193#[derive(Debug, serde::Serialize)]
194pub struct DSTOffset {
195    pub offset: Option<i32>,
196    pub valid_starting: Option<u64>,
197    pub valid_until: Option<u64>,
198}
199
200#[derive(Debug, serde::Serialize)]
201pub struct FabricScopedTrustedTimeSource {
202    pub node_id: Option<u64>,
203    pub endpoint: Option<u16>,
204}
205
206#[derive(Debug, serde::Serialize)]
207pub struct TimeZone {
208    pub offset: Option<i32>,
209    pub valid_at: Option<u64>,
210    pub name: Option<String>,
211}
212
213#[derive(Debug, serde::Serialize)]
214pub struct TrustedTimeSource {
215    pub fabric_index: Option<u8>,
216    pub node_id: Option<u64>,
217    pub endpoint: Option<u16>,
218}
219
220// Command encoders
221
222/// Encode SetUTCTime command (0x00)
223pub fn encode_set_utc_time(utc_time: u64, granularity: Granularity, time_source: TimeSource) -> anyhow::Result<Vec<u8>> {
224    let tlv = tlv::TlvItemEnc {
225        tag: 0,
226        value: tlv::TlvItemValueEnc::StructInvisible(vec![
227        (0, tlv::TlvItemValueEnc::UInt64(utc_time)).into(),
228        (1, tlv::TlvItemValueEnc::UInt8(granularity.to_u8())).into(),
229        (2, tlv::TlvItemValueEnc::UInt8(time_source.to_u8())).into(),
230        ]),
231    };
232    Ok(tlv.encode()?)
233}
234
235/// Encode SetTrustedTimeSource command (0x01)
236pub fn encode_set_trusted_time_source(trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<Vec<u8>> {
237            // Encode optional struct FabricScopedTrustedTimeSourceStruct
238            let trusted_time_source_enc = if let Some(s) = trusted_time_source {
239                let mut fields = Vec::new();
240                if let Some(x) = s.node_id { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
241                if let Some(x) = s.endpoint { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
242                tlv::TlvItemValueEnc::StructInvisible(fields)
243            } else {
244                tlv::TlvItemValueEnc::StructInvisible(Vec::new())
245            };
246    let tlv = tlv::TlvItemEnc {
247        tag: 0,
248        value: tlv::TlvItemValueEnc::StructInvisible(vec![
249        (0, trusted_time_source_enc).into(),
250        ]),
251    };
252    Ok(tlv.encode()?)
253}
254
255/// Encode SetTimeZone command (0x02)
256pub fn encode_set_time_zone(time_zone: Vec<TimeZone>) -> anyhow::Result<Vec<u8>> {
257    let tlv = tlv::TlvItemEnc {
258        tag: 0,
259        value: tlv::TlvItemValueEnc::StructInvisible(vec![
260        (0, tlv::TlvItemValueEnc::Array(time_zone.into_iter().map(|v| {
261                    let mut fields = Vec::new();
262                    if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
263                    if let Some(x) = v.valid_at { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
264                    if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
265                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
266                }).collect())).into(),
267        ]),
268    };
269    Ok(tlv.encode()?)
270}
271
272/// Encode SetDSTOffset command (0x04)
273pub fn encode_set_dst_offset(dst_offset: Vec<DSTOffset>) -> anyhow::Result<Vec<u8>> {
274    let tlv = tlv::TlvItemEnc {
275        tag: 0,
276        value: tlv::TlvItemValueEnc::StructInvisible(vec![
277        (0, tlv::TlvItemValueEnc::Array(dst_offset.into_iter().map(|v| {
278                    let mut fields = Vec::new();
279                    if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
280                    if let Some(x) = v.valid_starting { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
281                    if let Some(x) = v.valid_until { fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
282                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
283                }).collect())).into(),
284        ]),
285    };
286    Ok(tlv.encode()?)
287}
288
289/// Encode SetDefaultNTP command (0x05)
290pub fn encode_set_default_ntp(default_ntp: Option<String>) -> anyhow::Result<Vec<u8>> {
291    let tlv = tlv::TlvItemEnc {
292        tag: 0,
293        value: tlv::TlvItemValueEnc::StructInvisible(vec![
294        (0, tlv::TlvItemValueEnc::String(default_ntp.unwrap_or("".to_string()))).into(),
295        ]),
296    };
297    Ok(tlv.encode()?)
298}
299
300// Attribute decoders
301
302/// Decode UTCTime attribute (0x0000)
303pub fn decode_utc_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
304    if let tlv::TlvItemValue::Int(v) = inp {
305        Ok(Some(*v))
306    } else {
307        Ok(None)
308    }
309}
310
311/// Decode Granularity attribute (0x0001)
312pub fn decode_granularity(inp: &tlv::TlvItemValue) -> anyhow::Result<Granularity> {
313    if let tlv::TlvItemValue::Int(v) = inp {
314        Granularity::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
315    } else {
316        Err(anyhow::anyhow!("Expected Integer"))
317    }
318}
319
320/// Decode TimeSource attribute (0x0002)
321pub fn decode_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeSource> {
322    if let tlv::TlvItemValue::Int(v) = inp {
323        TimeSource::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
324    } else {
325        Err(anyhow::anyhow!("Expected Integer"))
326    }
327}
328
329/// Decode TrustedTimeSource attribute (0x0003)
330pub fn decode_trusted_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<TrustedTimeSource>> {
331    if let tlv::TlvItemValue::List(_fields) = inp {
332        // Struct with fields
333        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
334        Ok(Some(TrustedTimeSource {
335                fabric_index: item.get_int(&[0]).map(|v| v as u8),
336                node_id: item.get_int(&[1]),
337                endpoint: item.get_int(&[2]).map(|v| v as u16),
338        }))
339    //} else if let tlv::TlvItemValue::Null = inp {
340    //    // Null value for nullable struct
341    //    Ok(None)
342    } else {
343    Ok(None)
344    //    Err(anyhow::anyhow!("Expected struct fields or null"))
345    }
346}
347
348/// Decode DefaultNTP attribute (0x0004)
349pub fn decode_default_ntp(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<String>> {
350    if let tlv::TlvItemValue::String(v) = inp {
351        Ok(Some(v.clone()))
352    } else {
353        Ok(None)
354    }
355}
356
357/// Decode TimeZone attribute (0x0005)
358pub fn decode_time_zone(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeZone>> {
359    let mut res = Vec::new();
360    if let tlv::TlvItemValue::List(v) = inp {
361        for item in v {
362            res.push(TimeZone {
363                offset: item.get_int(&[0]).map(|v| v as i32),
364                valid_at: item.get_int(&[1]),
365                name: item.get_string_owned(&[2]),
366            });
367        }
368    }
369    Ok(res)
370}
371
372/// Decode DSTOffset attribute (0x0006)
373pub fn decode_dst_offset(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DSTOffset>> {
374    let mut res = Vec::new();
375    if let tlv::TlvItemValue::List(v) = inp {
376        for item in v {
377            res.push(DSTOffset {
378                offset: item.get_int(&[0]).map(|v| v as i32),
379                valid_starting: item.get_int(&[1]),
380                valid_until: item.get_int(&[2]),
381            });
382        }
383    }
384    Ok(res)
385}
386
387/// Decode LocalTime attribute (0x0007)
388pub fn decode_local_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
389    if let tlv::TlvItemValue::Int(v) = inp {
390        Ok(Some(*v))
391    } else {
392        Ok(None)
393    }
394}
395
396/// Decode TimeZoneDatabase attribute (0x0008)
397pub fn decode_time_zone_database(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneDatabase> {
398    if let tlv::TlvItemValue::Int(v) = inp {
399        TimeZoneDatabase::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
400    } else {
401        Err(anyhow::anyhow!("Expected Integer"))
402    }
403}
404
405/// Decode NTPServerAvailable attribute (0x0009)
406pub fn decode_ntp_server_available(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
407    if let tlv::TlvItemValue::Bool(v) = inp {
408        Ok(*v)
409    } else {
410        Err(anyhow::anyhow!("Expected Bool"))
411    }
412}
413
414/// Decode TimeZoneListMaxSize attribute (0x000A)
415pub fn decode_time_zone_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
416    if let tlv::TlvItemValue::Int(v) = inp {
417        Ok(*v as u8)
418    } else {
419        Err(anyhow::anyhow!("Expected UInt8"))
420    }
421}
422
423/// Decode DSTOffsetListMaxSize attribute (0x000B)
424pub fn decode_dst_offset_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
425    if let tlv::TlvItemValue::Int(v) = inp {
426        Ok(*v as u8)
427    } else {
428        Err(anyhow::anyhow!("Expected UInt8"))
429    }
430}
431
432/// Decode SupportsDNSResolve attribute (0x000C)
433pub fn decode_supports_dns_resolve(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
434    if let tlv::TlvItemValue::Bool(v) = inp {
435        Ok(*v)
436    } else {
437        Err(anyhow::anyhow!("Expected Bool"))
438    }
439}
440
441
442// JSON dispatcher function
443
444/// Decode attribute value and return as JSON string
445///
446/// # Parameters
447/// * `cluster_id` - The cluster identifier
448/// * `attribute_id` - The attribute identifier
449/// * `tlv_value` - The TLV value to decode
450///
451/// # Returns
452/// JSON string representation of the decoded value or error
453pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
454    // Verify this is the correct cluster
455    if cluster_id != 0x0038 {
456        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0038, got {}\"}}", cluster_id);
457    }
458
459    match attribute_id {
460        0x0000 => {
461            match decode_utc_time(tlv_value) {
462                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
463                Err(e) => format!("{{\"error\": \"{}\"}}", e),
464            }
465        }
466        0x0001 => {
467            match decode_granularity(tlv_value) {
468                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
469                Err(e) => format!("{{\"error\": \"{}\"}}", e),
470            }
471        }
472        0x0002 => {
473            match decode_time_source(tlv_value) {
474                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
475                Err(e) => format!("{{\"error\": \"{}\"}}", e),
476            }
477        }
478        0x0003 => {
479            match decode_trusted_time_source(tlv_value) {
480                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
481                Err(e) => format!("{{\"error\": \"{}\"}}", e),
482            }
483        }
484        0x0004 => {
485            match decode_default_ntp(tlv_value) {
486                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
487                Err(e) => format!("{{\"error\": \"{}\"}}", e),
488            }
489        }
490        0x0005 => {
491            match decode_time_zone(tlv_value) {
492                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
493                Err(e) => format!("{{\"error\": \"{}\"}}", e),
494            }
495        }
496        0x0006 => {
497            match decode_dst_offset(tlv_value) {
498                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
499                Err(e) => format!("{{\"error\": \"{}\"}}", e),
500            }
501        }
502        0x0007 => {
503            match decode_local_time(tlv_value) {
504                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
505                Err(e) => format!("{{\"error\": \"{}\"}}", e),
506            }
507        }
508        0x0008 => {
509            match decode_time_zone_database(tlv_value) {
510                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
511                Err(e) => format!("{{\"error\": \"{}\"}}", e),
512            }
513        }
514        0x0009 => {
515            match decode_ntp_server_available(tlv_value) {
516                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
517                Err(e) => format!("{{\"error\": \"{}\"}}", e),
518            }
519        }
520        0x000A => {
521            match decode_time_zone_list_max_size(tlv_value) {
522                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
523                Err(e) => format!("{{\"error\": \"{}\"}}", e),
524            }
525        }
526        0x000B => {
527            match decode_dst_offset_list_max_size(tlv_value) {
528                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
529                Err(e) => format!("{{\"error\": \"{}\"}}", e),
530            }
531        }
532        0x000C => {
533            match decode_supports_dns_resolve(tlv_value) {
534                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
535                Err(e) => format!("{{\"error\": \"{}\"}}", e),
536            }
537        }
538        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
539    }
540}
541
542/// Get list of all attributes supported by this cluster
543///
544/// # Returns
545/// Vector of tuples containing (attribute_id, attribute_name)
546pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
547    vec![
548        (0x0000, "UTCTime"),
549        (0x0001, "Granularity"),
550        (0x0002, "TimeSource"),
551        (0x0003, "TrustedTimeSource"),
552        (0x0004, "DefaultNTP"),
553        (0x0005, "TimeZone"),
554        (0x0006, "DSTOffset"),
555        (0x0007, "LocalTime"),
556        (0x0008, "TimeZoneDatabase"),
557        (0x0009, "NTPServerAvailable"),
558        (0x000A, "TimeZoneListMaxSize"),
559        (0x000B, "DSTOffsetListMaxSize"),
560        (0x000C, "SupportsDNSResolve"),
561    ]
562}
563
564#[derive(Debug, serde::Serialize)]
565pub struct SetTimeZoneResponse {
566    pub dst_offset_required: Option<bool>,
567}
568
569// Command response decoders
570
571/// Decode SetTimeZoneResponse command response (03)
572pub fn decode_set_time_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTimeZoneResponse> {
573    if let tlv::TlvItemValue::List(_fields) = inp {
574        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
575        Ok(SetTimeZoneResponse {
576                dst_offset_required: item.get_bool(&[0]),
577        })
578    } else {
579        Err(anyhow::anyhow!("Expected struct fields"))
580    }
581}
582
583#[derive(Debug, serde::Serialize)]
584pub struct DSTStatusEvent {
585    pub dst_offset_active: Option<bool>,
586}
587
588#[derive(Debug, serde::Serialize)]
589pub struct TimeZoneStatusEvent {
590    pub offset: Option<i32>,
591    pub name: Option<String>,
592}
593
594// Event decoders
595
596/// Decode DSTStatus event (0x01, priority: info)
597pub fn decode_dst_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DSTStatusEvent> {
598    if let tlv::TlvItemValue::List(_fields) = inp {
599        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
600        Ok(DSTStatusEvent {
601                                dst_offset_active: item.get_bool(&[0]),
602        })
603    } else {
604        Err(anyhow::anyhow!("Expected struct fields"))
605    }
606}
607
608/// Decode TimeZoneStatus event (0x02, priority: info)
609pub fn decode_time_zone_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneStatusEvent> {
610    if let tlv::TlvItemValue::List(_fields) = inp {
611        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
612        Ok(TimeZoneStatusEvent {
613                                offset: item.get_int(&[0]).map(|v| v as i32),
614                                name: item.get_string_owned(&[1]),
615        })
616    } else {
617        Err(anyhow::anyhow!("Expected struct fields"))
618    }
619}
620