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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum Granularity {
18    /// 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.
19    Notimegranularity = 0,
20    /// 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.
21    Minutesgranularity = 1,
22    /// This indicates the node is synchronized to an upstream source using a low resolution protocol. UTC Time is accurate to ± 5 seconds.
23    Secondsgranularity = 2,
24    /// 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.
25    Millisecondsgranularity = 3,
26    /// 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.
27    Microsecondsgranularity = 4,
28}
29
30impl Granularity {
31    /// Convert from u8 value
32    pub fn from_u8(value: u8) -> Option<Self> {
33        match value {
34            0 => Some(Granularity::Notimegranularity),
35            1 => Some(Granularity::Minutesgranularity),
36            2 => Some(Granularity::Secondsgranularity),
37            3 => Some(Granularity::Millisecondsgranularity),
38            4 => Some(Granularity::Microsecondsgranularity),
39            _ => None,
40        }
41    }
42
43    /// Convert to u8 value
44    pub fn to_u8(self) -> u8 {
45        self as u8
46    }
47}
48
49impl From<Granularity> for u8 {
50    fn from(val: Granularity) -> Self {
51        val as u8
52    }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
56#[repr(u8)]
57pub enum StatusCode {
58    /// Node rejected the attempt to set the UTC time
59    Timenotaccepted = 2,
60}
61
62impl StatusCode {
63    /// Convert from u8 value
64    pub fn from_u8(value: u8) -> Option<Self> {
65        match value {
66            2 => Some(StatusCode::Timenotaccepted),
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<StatusCode> for u8 {
78    fn from(val: StatusCode) -> Self {
79        val as u8
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84#[repr(u8)]
85pub enum TimeSource {
86    /// Node is not currently synchronized with a UTC Time source.
87    None = 0,
88    /// Node uses an unlisted time source.
89    Unknown = 1,
90    /// Node received time from a client using the SetUTCTime Command.
91    Admin = 2,
92    /// Synchronized time by querying the Time Synchronization cluster of another Node.
93    Nodetimecluster = 3,
94    /// SNTP from a server not in the Matter network. NTS is not used.
95    Nonmattersntp = 4,
96    /// NTP from servers not in the Matter network. None of the servers used NTS.
97    Nonmatterntp = 5,
98    /// SNTP from a server within the Matter network. NTS is not used.
99    Mattersntp = 6,
100    /// NTP from servers within the Matter network. None of the servers used NTS.
101    Matterntp = 7,
102    /// NTP from multiple servers in the Matter network and external. None of the servers used NTS.
103    Mixedntp = 8,
104    /// SNTP from a server not in the Matter network. NTS is used.
105    Nonmattersntpnts = 9,
106    /// NTP from servers not in the Matter network. NTS is used on at least one server.
107    Nonmatterntpnts = 10,
108    /// SNTP from a server within the Matter network. NTS is used.
109    Mattersntpnts = 11,
110    /// NTP from a server within the Matter network. NTS is used on at least one server.
111    Matterntpnts = 12,
112    /// NTP from multiple servers in the Matter network and external. NTS is used on at least one server.
113    Mixedntpnts = 13,
114    /// Time synchronization comes from a vendor cloud-based source (e.g. "Date" header in authenticated HTTPS connection).
115    Cloudsource = 14,
116    /// Time synchronization comes from PTP.
117    Ptp = 15,
118    /// Time synchronization comes from a GNSS source.
119    Gnss = 16,
120}
121
122impl TimeSource {
123    /// Convert from u8 value
124    pub fn from_u8(value: u8) -> Option<Self> {
125        match value {
126            0 => Some(TimeSource::None),
127            1 => Some(TimeSource::Unknown),
128            2 => Some(TimeSource::Admin),
129            3 => Some(TimeSource::Nodetimecluster),
130            4 => Some(TimeSource::Nonmattersntp),
131            5 => Some(TimeSource::Nonmatterntp),
132            6 => Some(TimeSource::Mattersntp),
133            7 => Some(TimeSource::Matterntp),
134            8 => Some(TimeSource::Mixedntp),
135            9 => Some(TimeSource::Nonmattersntpnts),
136            10 => Some(TimeSource::Nonmatterntpnts),
137            11 => Some(TimeSource::Mattersntpnts),
138            12 => Some(TimeSource::Matterntpnts),
139            13 => Some(TimeSource::Mixedntpnts),
140            14 => Some(TimeSource::Cloudsource),
141            15 => Some(TimeSource::Ptp),
142            16 => Some(TimeSource::Gnss),
143            _ => None,
144        }
145    }
146
147    /// Convert to u8 value
148    pub fn to_u8(self) -> u8 {
149        self as u8
150    }
151}
152
153impl From<TimeSource> for u8 {
154    fn from(val: TimeSource) -> Self {
155        val as u8
156    }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
160#[repr(u8)]
161pub enum TimeZoneDatabase {
162    /// Node has a full list of the available time zones
163    Full = 0,
164    /// Node has a partial list of the available time zones
165    Partial = 1,
166    /// Node does not have a time zone database
167    None = 2,
168}
169
170impl TimeZoneDatabase {
171    /// Convert from u8 value
172    pub fn from_u8(value: u8) -> Option<Self> {
173        match value {
174            0 => Some(TimeZoneDatabase::Full),
175            1 => Some(TimeZoneDatabase::Partial),
176            2 => Some(TimeZoneDatabase::None),
177            _ => None,
178        }
179    }
180
181    /// Convert to u8 value
182    pub fn to_u8(self) -> u8 {
183        self as u8
184    }
185}
186
187impl From<TimeZoneDatabase> for u8 {
188    fn from(val: TimeZoneDatabase) -> Self {
189        val as u8
190    }
191}
192
193// Struct definitions
194
195#[derive(Debug, serde::Serialize)]
196pub struct DSTOffset {
197    pub offset: Option<i32>,
198    pub valid_starting: Option<u64>,
199    pub valid_until: Option<u64>,
200}
201
202#[derive(Debug, serde::Serialize)]
203pub struct FabricScopedTrustedTimeSource {
204    pub node_id: Option<u64>,
205    pub endpoint: Option<u16>,
206}
207
208#[derive(Debug, serde::Serialize)]
209pub struct TimeZone {
210    pub offset: Option<i32>,
211    pub valid_at: Option<u64>,
212    pub name: Option<String>,
213}
214
215#[derive(Debug, serde::Serialize)]
216pub struct TrustedTimeSource {
217    pub fabric_index: Option<u8>,
218    pub node_id: Option<u64>,
219    pub endpoint: Option<u16>,
220}
221
222// Command encoders
223
224/// Encode SetUTCTime command (0x00)
225pub fn encode_set_utc_time(utc_time: u64, granularity: Granularity, time_source: TimeSource) -> anyhow::Result<Vec<u8>> {
226    let tlv = tlv::TlvItemEnc {
227        tag: 0,
228        value: tlv::TlvItemValueEnc::StructInvisible(vec![
229        (0, tlv::TlvItemValueEnc::UInt64(utc_time)).into(),
230        (1, tlv::TlvItemValueEnc::UInt8(granularity.to_u8())).into(),
231        (2, tlv::TlvItemValueEnc::UInt8(time_source.to_u8())).into(),
232        ]),
233    };
234    Ok(tlv.encode()?)
235}
236
237/// Encode SetTrustedTimeSource command (0x01)
238pub fn encode_set_trusted_time_source(trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<Vec<u8>> {
239            // Encode optional struct FabricScopedTrustedTimeSourceStruct
240            let trusted_time_source_enc = if let Some(s) = trusted_time_source {
241                let mut fields = Vec::new();
242                if let Some(x) = s.node_id { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
243                if let Some(x) = s.endpoint { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
244                tlv::TlvItemValueEnc::StructInvisible(fields)
245            } else {
246                tlv::TlvItemValueEnc::StructInvisible(Vec::new())
247            };
248    let tlv = tlv::TlvItemEnc {
249        tag: 0,
250        value: tlv::TlvItemValueEnc::StructInvisible(vec![
251        (0, trusted_time_source_enc).into(),
252        ]),
253    };
254    Ok(tlv.encode()?)
255}
256
257/// Encode SetTimeZone command (0x02)
258pub fn encode_set_time_zone(time_zone: Vec<TimeZone>) -> anyhow::Result<Vec<u8>> {
259    let tlv = tlv::TlvItemEnc {
260        tag: 0,
261        value: tlv::TlvItemValueEnc::StructInvisible(vec![
262        (0, tlv::TlvItemValueEnc::Array(time_zone.into_iter().map(|v| {
263                    let mut fields = Vec::new();
264                    if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
265                    if let Some(x) = v.valid_at { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
266                    if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
267                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
268                }).collect())).into(),
269        ]),
270    };
271    Ok(tlv.encode()?)
272}
273
274/// Encode SetDSTOffset command (0x04)
275pub fn encode_set_dst_offset(dst_offset: Vec<DSTOffset>) -> anyhow::Result<Vec<u8>> {
276    let tlv = tlv::TlvItemEnc {
277        tag: 0,
278        value: tlv::TlvItemValueEnc::StructInvisible(vec![
279        (0, tlv::TlvItemValueEnc::Array(dst_offset.into_iter().map(|v| {
280                    let mut fields = Vec::new();
281                    if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
282                    if let Some(x) = v.valid_starting { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
283                    if let Some(x) = v.valid_until { fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
284                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
285                }).collect())).into(),
286        ]),
287    };
288    Ok(tlv.encode()?)
289}
290
291/// Encode SetDefaultNTP command (0x05)
292pub fn encode_set_default_ntp(default_ntp: Option<String>) -> anyhow::Result<Vec<u8>> {
293    let tlv = tlv::TlvItemEnc {
294        tag: 0,
295        value: tlv::TlvItemValueEnc::StructInvisible(vec![
296        (0, tlv::TlvItemValueEnc::String(default_ntp.unwrap_or("".to_string()))).into(),
297        ]),
298    };
299    Ok(tlv.encode()?)
300}
301
302// Attribute decoders
303
304/// Decode UTCTime attribute (0x0000)
305pub fn decode_utc_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        Ok(Some(*v))
308    } else {
309        Ok(None)
310    }
311}
312
313/// Decode Granularity attribute (0x0001)
314pub fn decode_granularity(inp: &tlv::TlvItemValue) -> anyhow::Result<Granularity> {
315    if let tlv::TlvItemValue::Int(v) = inp {
316        Granularity::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
317    } else {
318        Err(anyhow::anyhow!("Expected Integer"))
319    }
320}
321
322/// Decode TimeSource attribute (0x0002)
323pub fn decode_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeSource> {
324    if let tlv::TlvItemValue::Int(v) = inp {
325        TimeSource::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
326    } else {
327        Err(anyhow::anyhow!("Expected Integer"))
328    }
329}
330
331/// Decode TrustedTimeSource attribute (0x0003)
332pub fn decode_trusted_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<TrustedTimeSource>> {
333    if let tlv::TlvItemValue::List(_fields) = inp {
334        // Struct with fields
335        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
336        Ok(Some(TrustedTimeSource {
337                fabric_index: item.get_int(&[0]).map(|v| v as u8),
338                node_id: item.get_int(&[1]),
339                endpoint: item.get_int(&[2]).map(|v| v as u16),
340        }))
341    //} else if let tlv::TlvItemValue::Null = inp {
342    //    // Null value for nullable struct
343    //    Ok(None)
344    } else {
345    Ok(None)
346    //    Err(anyhow::anyhow!("Expected struct fields or null"))
347    }
348}
349
350/// Decode DefaultNTP attribute (0x0004)
351pub fn decode_default_ntp(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<String>> {
352    if let tlv::TlvItemValue::String(v) = inp {
353        Ok(Some(v.clone()))
354    } else {
355        Ok(None)
356    }
357}
358
359/// Decode TimeZone attribute (0x0005)
360pub fn decode_time_zone(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeZone>> {
361    let mut res = Vec::new();
362    if let tlv::TlvItemValue::List(v) = inp {
363        for item in v {
364            res.push(TimeZone {
365                offset: item.get_int(&[0]).map(|v| v as i32),
366                valid_at: item.get_int(&[1]),
367                name: item.get_string_owned(&[2]),
368            });
369        }
370    }
371    Ok(res)
372}
373
374/// Decode DSTOffset attribute (0x0006)
375pub fn decode_dst_offset(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DSTOffset>> {
376    let mut res = Vec::new();
377    if let tlv::TlvItemValue::List(v) = inp {
378        for item in v {
379            res.push(DSTOffset {
380                offset: item.get_int(&[0]).map(|v| v as i32),
381                valid_starting: item.get_int(&[1]),
382                valid_until: item.get_int(&[2]),
383            });
384        }
385    }
386    Ok(res)
387}
388
389/// Decode LocalTime attribute (0x0007)
390pub fn decode_local_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
391    if let tlv::TlvItemValue::Int(v) = inp {
392        Ok(Some(*v))
393    } else {
394        Ok(None)
395    }
396}
397
398/// Decode TimeZoneDatabase attribute (0x0008)
399pub fn decode_time_zone_database(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneDatabase> {
400    if let tlv::TlvItemValue::Int(v) = inp {
401        TimeZoneDatabase::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
402    } else {
403        Err(anyhow::anyhow!("Expected Integer"))
404    }
405}
406
407/// Decode NTPServerAvailable attribute (0x0009)
408pub fn decode_ntp_server_available(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
409    if let tlv::TlvItemValue::Bool(v) = inp {
410        Ok(*v)
411    } else {
412        Err(anyhow::anyhow!("Expected Bool"))
413    }
414}
415
416/// Decode TimeZoneListMaxSize attribute (0x000A)
417pub fn decode_time_zone_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
418    if let tlv::TlvItemValue::Int(v) = inp {
419        Ok(*v as u8)
420    } else {
421        Err(anyhow::anyhow!("Expected UInt8"))
422    }
423}
424
425/// Decode DSTOffsetListMaxSize attribute (0x000B)
426pub fn decode_dst_offset_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
427    if let tlv::TlvItemValue::Int(v) = inp {
428        Ok(*v as u8)
429    } else {
430        Err(anyhow::anyhow!("Expected UInt8"))
431    }
432}
433
434/// Decode SupportsDNSResolve attribute (0x000C)
435pub fn decode_supports_dns_resolve(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
436    if let tlv::TlvItemValue::Bool(v) = inp {
437        Ok(*v)
438    } else {
439        Err(anyhow::anyhow!("Expected Bool"))
440    }
441}
442
443
444// JSON dispatcher function
445
446/// Decode attribute value and return as JSON string
447///
448/// # Parameters
449/// * `cluster_id` - The cluster identifier
450/// * `attribute_id` - The attribute identifier
451/// * `tlv_value` - The TLV value to decode
452///
453/// # Returns
454/// JSON string representation of the decoded value or error
455pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
456    // Verify this is the correct cluster
457    if cluster_id != 0x0038 {
458        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0038, got {}\"}}", cluster_id);
459    }
460
461    match attribute_id {
462        0x0000 => {
463            match decode_utc_time(tlv_value) {
464                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
465                Err(e) => format!("{{\"error\": \"{}\"}}", e),
466            }
467        }
468        0x0001 => {
469            match decode_granularity(tlv_value) {
470                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
471                Err(e) => format!("{{\"error\": \"{}\"}}", e),
472            }
473        }
474        0x0002 => {
475            match decode_time_source(tlv_value) {
476                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
477                Err(e) => format!("{{\"error\": \"{}\"}}", e),
478            }
479        }
480        0x0003 => {
481            match decode_trusted_time_source(tlv_value) {
482                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
483                Err(e) => format!("{{\"error\": \"{}\"}}", e),
484            }
485        }
486        0x0004 => {
487            match decode_default_ntp(tlv_value) {
488                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
489                Err(e) => format!("{{\"error\": \"{}\"}}", e),
490            }
491        }
492        0x0005 => {
493            match decode_time_zone(tlv_value) {
494                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
495                Err(e) => format!("{{\"error\": \"{}\"}}", e),
496            }
497        }
498        0x0006 => {
499            match decode_dst_offset(tlv_value) {
500                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
501                Err(e) => format!("{{\"error\": \"{}\"}}", e),
502            }
503        }
504        0x0007 => {
505            match decode_local_time(tlv_value) {
506                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
507                Err(e) => format!("{{\"error\": \"{}\"}}", e),
508            }
509        }
510        0x0008 => {
511            match decode_time_zone_database(tlv_value) {
512                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
513                Err(e) => format!("{{\"error\": \"{}\"}}", e),
514            }
515        }
516        0x0009 => {
517            match decode_ntp_server_available(tlv_value) {
518                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
519                Err(e) => format!("{{\"error\": \"{}\"}}", e),
520            }
521        }
522        0x000A => {
523            match decode_time_zone_list_max_size(tlv_value) {
524                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
525                Err(e) => format!("{{\"error\": \"{}\"}}", e),
526            }
527        }
528        0x000B => {
529            match decode_dst_offset_list_max_size(tlv_value) {
530                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
531                Err(e) => format!("{{\"error\": \"{}\"}}", e),
532            }
533        }
534        0x000C => {
535            match decode_supports_dns_resolve(tlv_value) {
536                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
537                Err(e) => format!("{{\"error\": \"{}\"}}", e),
538            }
539        }
540        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
541    }
542}
543
544/// Get list of all attributes supported by this cluster
545///
546/// # Returns
547/// Vector of tuples containing (attribute_id, attribute_name)
548pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
549    vec![
550        (0x0000, "UTCTime"),
551        (0x0001, "Granularity"),
552        (0x0002, "TimeSource"),
553        (0x0003, "TrustedTimeSource"),
554        (0x0004, "DefaultNTP"),
555        (0x0005, "TimeZone"),
556        (0x0006, "DSTOffset"),
557        (0x0007, "LocalTime"),
558        (0x0008, "TimeZoneDatabase"),
559        (0x0009, "NTPServerAvailable"),
560        (0x000A, "TimeZoneListMaxSize"),
561        (0x000B, "DSTOffsetListMaxSize"),
562        (0x000C, "SupportsDNSResolve"),
563    ]
564}
565
566// Command listing
567
568pub fn get_command_list() -> Vec<(u32, &'static str)> {
569    vec![
570        (0x00, "SetUTCTime"),
571        (0x01, "SetTrustedTimeSource"),
572        (0x02, "SetTimeZone"),
573        (0x04, "SetDSTOffset"),
574        (0x05, "SetDefaultNTP"),
575    ]
576}
577
578pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
579    match cmd_id {
580        0x00 => Some("SetUTCTime"),
581        0x01 => Some("SetTrustedTimeSource"),
582        0x02 => Some("SetTimeZone"),
583        0x04 => Some("SetDSTOffset"),
584        0x05 => Some("SetDefaultNTP"),
585        _ => None,
586    }
587}
588
589pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
590    match cmd_id {
591        0x00 => Some(vec![
592            crate::clusters::codec::CommandField { tag: 0, name: "utc_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
593            crate::clusters::codec::CommandField { tag: 1, name: "granularity", kind: crate::clusters::codec::FieldKind::Enum { name: "Granularity", variants: &[(0, "Notimegranularity"), (1, "Minutesgranularity"), (2, "Secondsgranularity"), (3, "Millisecondsgranularity"), (4, "Microsecondsgranularity")] }, optional: false, nullable: false },
594            crate::clusters::codec::CommandField { tag: 2, name: "time_source", kind: crate::clusters::codec::FieldKind::Enum { name: "TimeSource", variants: &[(0, "None"), (1, "Unknown"), (2, "Admin"), (3, "Nodetimecluster"), (4, "Nonmattersntp"), (5, "Nonmatterntp"), (6, "Mattersntp"), (7, "Matterntp"), (8, "Mixedntp"), (9, "Nonmattersntpnts"), (10, "Nonmatterntpnts"), (11, "Mattersntpnts"), (12, "Matterntpnts"), (13, "Mixedntpnts"), (14, "Cloudsource"), (15, "Ptp"), (16, "Gnss")] }, optional: true, nullable: false },
595        ]),
596        0x01 => Some(vec![
597            crate::clusters::codec::CommandField { tag: 0, name: "trusted_time_source", kind: crate::clusters::codec::FieldKind::Struct { name: "FabricScopedTrustedTimeSourceStruct" }, optional: false, nullable: true },
598        ]),
599        0x02 => Some(vec![
600            crate::clusters::codec::CommandField { tag: 0, name: "time_zone", kind: crate::clusters::codec::FieldKind::List { entry_type: "TimeZoneStruct" }, optional: false, nullable: false },
601        ]),
602        0x04 => Some(vec![
603            crate::clusters::codec::CommandField { tag: 0, name: "dst_offset", kind: crate::clusters::codec::FieldKind::List { entry_type: "DSTOffsetStruct" }, optional: false, nullable: false },
604        ]),
605        0x05 => Some(vec![
606            crate::clusters::codec::CommandField { tag: 0, name: "default_ntp", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: true },
607        ]),
608        _ => None,
609    }
610}
611
612pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
613    match cmd_id {
614        0x00 => {
615        let utc_time = crate::clusters::codec::json_util::get_u64(args, "utc_time")?;
616        let granularity = {
617            let n = crate::clusters::codec::json_util::get_u64(args, "granularity")?;
618            Granularity::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid Granularity: {}", n))?
619        };
620        let time_source = {
621            let n = crate::clusters::codec::json_util::get_u64(args, "time_source")?;
622            TimeSource::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid TimeSource: {}", n))?
623        };
624        encode_set_utc_time(utc_time, granularity, time_source)
625        }
626        0x01 => Err(anyhow::anyhow!("command \"SetTrustedTimeSource\" has complex args: use raw mode")),
627        0x02 => Err(anyhow::anyhow!("command \"SetTimeZone\" has complex args: use raw mode")),
628        0x04 => Err(anyhow::anyhow!("command \"SetDSTOffset\" has complex args: use raw mode")),
629        0x05 => {
630        let default_ntp = crate::clusters::codec::json_util::get_opt_string(args, "default_ntp")?;
631        encode_set_default_ntp(default_ntp)
632        }
633        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
634    }
635}
636
637#[derive(Debug, serde::Serialize)]
638pub struct SetTimeZoneResponse {
639    pub dst_offset_required: Option<bool>,
640}
641
642// Command response decoders
643
644/// Decode SetTimeZoneResponse command response (03)
645pub fn decode_set_time_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTimeZoneResponse> {
646    if let tlv::TlvItemValue::List(_fields) = inp {
647        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
648        Ok(SetTimeZoneResponse {
649                dst_offset_required: item.get_bool(&[0]),
650        })
651    } else {
652        Err(anyhow::anyhow!("Expected struct fields"))
653    }
654}
655
656// Typed facade (invokes + reads)
657
658/// Invoke `SetUTCTime` command on cluster `Time Synchronization`.
659pub async fn set_utc_time(conn: &crate::controller::Connection, endpoint: u16, utc_time: u64, granularity: Granularity, time_source: TimeSource) -> anyhow::Result<()> {
660    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_CMD_ID_SETUTCTIME, &encode_set_utc_time(utc_time, granularity, time_source)?).await?;
661    Ok(())
662}
663
664/// Invoke `SetTrustedTimeSource` command on cluster `Time Synchronization`.
665pub async fn set_trusted_time_source(conn: &crate::controller::Connection, endpoint: u16, trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<()> {
666    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_CMD_ID_SETTRUSTEDTIMESOURCE, &encode_set_trusted_time_source(trusted_time_source)?).await?;
667    Ok(())
668}
669
670/// Invoke `SetTimeZone` command on cluster `Time Synchronization`.
671pub async fn set_time_zone(conn: &crate::controller::Connection, endpoint: u16, time_zone: Vec<TimeZone>) -> anyhow::Result<SetTimeZoneResponse> {
672    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_CMD_ID_SETTIMEZONE, &encode_set_time_zone(time_zone)?).await?;
673    decode_set_time_zone_response(&tlv)
674}
675
676/// Invoke `SetDSTOffset` command on cluster `Time Synchronization`.
677pub async fn set_dst_offset(conn: &crate::controller::Connection, endpoint: u16, dst_offset: Vec<DSTOffset>) -> anyhow::Result<()> {
678    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_CMD_ID_SETDSTOFFSET, &encode_set_dst_offset(dst_offset)?).await?;
679    Ok(())
680}
681
682/// Invoke `SetDefaultNTP` command on cluster `Time Synchronization`.
683pub async fn set_default_ntp(conn: &crate::controller::Connection, endpoint: u16, default_ntp: Option<String>) -> anyhow::Result<()> {
684    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_CMD_ID_SETDEFAULTNTP, &encode_set_default_ntp(default_ntp)?).await?;
685    Ok(())
686}
687
688/// Read `UTCTime` attribute from cluster `Time Synchronization`.
689pub async fn read_utc_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
690    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_UTCTIME).await?;
691    decode_utc_time(&tlv)
692}
693
694/// Read `Granularity` attribute from cluster `Time Synchronization`.
695pub async fn read_granularity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Granularity> {
696    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_GRANULARITY).await?;
697    decode_granularity(&tlv)
698}
699
700/// Read `TimeSource` attribute from cluster `Time Synchronization`.
701pub async fn read_time_source(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TimeSource> {
702    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMESOURCE).await?;
703    decode_time_source(&tlv)
704}
705
706/// Read `TrustedTimeSource` attribute from cluster `Time Synchronization`.
707pub async fn read_trusted_time_source(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<TrustedTimeSource>> {
708    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TRUSTEDTIMESOURCE).await?;
709    decode_trusted_time_source(&tlv)
710}
711
712/// Read `DefaultNTP` attribute from cluster `Time Synchronization`.
713pub async fn read_default_ntp(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<String>> {
714    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DEFAULTNTP).await?;
715    decode_default_ntp(&tlv)
716}
717
718/// Read `TimeZone` attribute from cluster `Time Synchronization`.
719pub async fn read_time_zone(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<TimeZone>> {
720    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONE).await?;
721    decode_time_zone(&tlv)
722}
723
724/// Read `DSTOffset` attribute from cluster `Time Synchronization`.
725pub async fn read_dst_offset(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<DSTOffset>> {
726    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DSTOFFSET).await?;
727    decode_dst_offset(&tlv)
728}
729
730/// Read `LocalTime` attribute from cluster `Time Synchronization`.
731pub async fn read_local_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
732    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_LOCALTIME).await?;
733    decode_local_time(&tlv)
734}
735
736/// Read `TimeZoneDatabase` attribute from cluster `Time Synchronization`.
737pub async fn read_time_zone_database(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TimeZoneDatabase> {
738    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONEDATABASE).await?;
739    decode_time_zone_database(&tlv)
740}
741
742/// Read `NTPServerAvailable` attribute from cluster `Time Synchronization`.
743pub async fn read_ntp_server_available(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
744    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_NTPSERVERAVAILABLE).await?;
745    decode_ntp_server_available(&tlv)
746}
747
748/// Read `TimeZoneListMaxSize` attribute from cluster `Time Synchronization`.
749pub async fn read_time_zone_list_max_size(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
750    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONELISTMAXSIZE).await?;
751    decode_time_zone_list_max_size(&tlv)
752}
753
754/// Read `DSTOffsetListMaxSize` attribute from cluster `Time Synchronization`.
755pub async fn read_dst_offset_list_max_size(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
756    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DSTOFFSETLISTMAXSIZE).await?;
757    decode_dst_offset_list_max_size(&tlv)
758}
759
760/// Read `SupportsDNSResolve` attribute from cluster `Time Synchronization`.
761pub async fn read_supports_dns_resolve(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
762    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_SUPPORTSDNSRESOLVE).await?;
763    decode_supports_dns_resolve(&tlv)
764}
765
766#[derive(Debug, serde::Serialize)]
767pub struct DSTStatusEvent {
768    pub dst_offset_active: Option<bool>,
769}
770
771#[derive(Debug, serde::Serialize)]
772pub struct TimeZoneStatusEvent {
773    pub offset: Option<i32>,
774    pub name: Option<String>,
775}
776
777// Event decoders
778
779/// Decode DSTStatus event (0x01, priority: info)
780pub fn decode_dst_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DSTStatusEvent> {
781    if let tlv::TlvItemValue::List(_fields) = inp {
782        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
783        Ok(DSTStatusEvent {
784                                dst_offset_active: item.get_bool(&[0]),
785        })
786    } else {
787        Err(anyhow::anyhow!("Expected struct fields"))
788    }
789}
790
791/// Decode TimeZoneStatus event (0x02, priority: info)
792pub fn decode_time_zone_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneStatusEvent> {
793    if let tlv::TlvItemValue::List(_fields) = inp {
794        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
795        Ok(TimeZoneStatusEvent {
796                                offset: item.get_int(&[0]).map(|v| v as i32),
797                                name: item.get_string_owned(&[1]),
798        })
799    } else {
800        Err(anyhow::anyhow!("Expected struct fields"))
801    }
802}
803