1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum Granularity {
18 Notimegranularity = 0,
20 Minutesgranularity = 1,
22 Secondsgranularity = 2,
24 Millisecondsgranularity = 3,
26 Microsecondsgranularity = 4,
28}
29
30impl Granularity {
31 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 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 Timenotaccepted = 2,
60}
61
62impl StatusCode {
63 pub fn from_u8(value: u8) -> Option<Self> {
65 match value {
66 2 => Some(StatusCode::Timenotaccepted),
67 _ => None,
68 }
69 }
70
71 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 None = 0,
88 Unknown = 1,
90 Admin = 2,
92 Nodetimecluster = 3,
94 Nonmattersntp = 4,
96 Nonmatterntp = 5,
98 Mattersntp = 6,
100 Matterntp = 7,
102 Mixedntp = 8,
104 Nonmattersntpnts = 9,
106 Nonmatterntpnts = 10,
108 Mattersntpnts = 11,
110 Matterntpnts = 12,
112 Mixedntpnts = 13,
114 Cloudsource = 14,
116 Ptp = 15,
118 Gnss = 16,
120}
121
122impl TimeSource {
123 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 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 Full = 0,
164 Partial = 1,
166 None = 2,
168}
169
170impl TimeZoneDatabase {
171 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 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#[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
222pub fn encode_set_utc_time(utc_time: u64, granularity: Granularity, time_source: Option<TimeSource>) -> anyhow::Result<Vec<u8>> {
226 let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
227 tlv_fields.push((0, tlv::TlvItemValueEnc::UInt64(utc_time)).into());
228 tlv_fields.push((1, tlv::TlvItemValueEnc::UInt8(granularity.to_u8())).into());
229 if let Some(x) = time_source { tlv_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
230 let tlv = tlv::TlvItemEnc {
231 tag: 0,
232 value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
233 };
234 Ok(tlv.encode()?)
235}
236
237pub fn encode_set_trusted_time_source(trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<Vec<u8>> {
239 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
257pub 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
274pub 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
291pub 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
302pub 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
313pub 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
322pub 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
331pub fn decode_trusted_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<TrustedTimeSource>> {
333 if let tlv::TlvItemValue::List(_fields) = inp {
334 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 {
345 Ok(None)
346 }
348}
349
350pub 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
359pub 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
374pub 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
389pub 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
398pub 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
407pub 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
416pub 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
425pub 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
434pub 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
444pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
456 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
544pub 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
566pub 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 = crate::clusters::codec::json_util::get_opt_u64(args, "time_source")?
621 .and_then(|n| TimeSource::from_u8(n as u8));
622 encode_set_utc_time(utc_time, granularity, time_source)
623 }
624 0x01 => Err(anyhow::anyhow!("command \"SetTrustedTimeSource\" has complex args: use raw mode")),
625 0x02 => Err(anyhow::anyhow!("command \"SetTimeZone\" has complex args: use raw mode")),
626 0x04 => Err(anyhow::anyhow!("command \"SetDSTOffset\" has complex args: use raw mode")),
627 0x05 => {
628 let default_ntp = crate::clusters::codec::json_util::get_opt_string(args, "default_ntp")?;
629 encode_set_default_ntp(default_ntp)
630 }
631 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
632 }
633}
634
635#[derive(Debug, serde::Serialize)]
636pub struct SetTimeZoneResponse {
637 pub dst_offset_required: Option<bool>,
638}
639
640pub fn decode_set_time_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTimeZoneResponse> {
644 if let tlv::TlvItemValue::List(_fields) = inp {
645 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
646 Ok(SetTimeZoneResponse {
647 dst_offset_required: item.get_bool(&[0]),
648 })
649 } else {
650 Err(anyhow::anyhow!("Expected struct fields"))
651 }
652}
653
654pub async fn set_utc_time(conn: &crate::controller::Connection, endpoint: u16, utc_time: u64, granularity: Granularity, time_source: Option<TimeSource>) -> anyhow::Result<()> {
658 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?;
659 Ok(())
660}
661
662pub async fn set_trusted_time_source(conn: &crate::controller::Connection, endpoint: u16, trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<()> {
664 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?;
665 Ok(())
666}
667
668pub async fn set_time_zone(conn: &crate::controller::Connection, endpoint: u16, time_zone: Vec<TimeZone>) -> anyhow::Result<SetTimeZoneResponse> {
670 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?;
671 decode_set_time_zone_response(&tlv)
672}
673
674pub async fn set_dst_offset(conn: &crate::controller::Connection, endpoint: u16, dst_offset: Vec<DSTOffset>) -> anyhow::Result<()> {
676 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?;
677 Ok(())
678}
679
680pub async fn set_default_ntp(conn: &crate::controller::Connection, endpoint: u16, default_ntp: Option<String>) -> anyhow::Result<()> {
682 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?;
683 Ok(())
684}
685
686pub async fn read_utc_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
688 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_UTCTIME).await?;
689 decode_utc_time(&tlv)
690}
691
692pub async fn read_granularity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Granularity> {
694 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_GRANULARITY).await?;
695 decode_granularity(&tlv)
696}
697
698pub async fn read_time_source(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TimeSource> {
700 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMESOURCE).await?;
701 decode_time_source(&tlv)
702}
703
704pub async fn read_trusted_time_source(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<TrustedTimeSource>> {
706 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TRUSTEDTIMESOURCE).await?;
707 decode_trusted_time_source(&tlv)
708}
709
710pub async fn read_default_ntp(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<String>> {
712 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DEFAULTNTP).await?;
713 decode_default_ntp(&tlv)
714}
715
716pub async fn read_time_zone(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<TimeZone>> {
718 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONE).await?;
719 decode_time_zone(&tlv)
720}
721
722pub async fn read_dst_offset(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<DSTOffset>> {
724 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DSTOFFSET).await?;
725 decode_dst_offset(&tlv)
726}
727
728pub async fn read_local_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
730 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_LOCALTIME).await?;
731 decode_local_time(&tlv)
732}
733
734pub async fn read_time_zone_database(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<TimeZoneDatabase> {
736 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONEDATABASE).await?;
737 decode_time_zone_database(&tlv)
738}
739
740pub async fn read_ntp_server_available(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
742 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_NTPSERVERAVAILABLE).await?;
743 decode_ntp_server_available(&tlv)
744}
745
746pub async fn read_time_zone_list_max_size(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
748 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_TIMEZONELISTMAXSIZE).await?;
749 decode_time_zone_list_max_size(&tlv)
750}
751
752pub async fn read_dst_offset_list_max_size(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
754 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_DSTOFFSETLISTMAXSIZE).await?;
755 decode_dst_offset_list_max_size(&tlv)
756}
757
758pub async fn read_supports_dns_resolve(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
760 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TIME_SYNCHRONIZATION, crate::clusters::defs::CLUSTER_TIME_SYNCHRONIZATION_ATTR_ID_SUPPORTSDNSRESOLVE).await?;
761 decode_supports_dns_resolve(&tlv)
762}
763
764#[derive(Debug, serde::Serialize)]
765pub struct DSTStatusEvent {
766 pub dst_offset_active: Option<bool>,
767}
768
769#[derive(Debug, serde::Serialize)]
770pub struct TimeZoneStatusEvent {
771 pub offset: Option<i32>,
772 pub name: Option<String>,
773}
774
775pub fn decode_dst_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DSTStatusEvent> {
779 if let tlv::TlvItemValue::List(_fields) = inp {
780 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
781 Ok(DSTStatusEvent {
782 dst_offset_active: item.get_bool(&[0]),
783 })
784 } else {
785 Err(anyhow::anyhow!("Expected struct fields"))
786 }
787}
788
789pub fn decode_time_zone_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneStatusEvent> {
791 if let tlv::TlvItemValue::List(_fields) = inp {
792 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
793 Ok(TimeZoneStatusEvent {
794 offset: item.get_int(&[0]).map(|v| v as i32),
795 name: item.get_string_owned(&[1]),
796 })
797 } else {
798 Err(anyhow::anyhow!("Expected struct fields"))
799 }
800}
801
802
803pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
807 if cluster_id != 0x0038 {
808 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0038, got {}\"}}", cluster_id);
809 }
810
811 match event_id {
812 0x00 => "{}".to_string(),
813 0x01 => {
814 match decode_dst_status_event(tlv_value) {
815 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
816 Err(e) => format!("{{\"error\": \"{}\"}}", e),
817 }
818 }
819 0x02 => {
820 match decode_time_zone_status_event(tlv_value) {
821 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
822 Err(e) => format!("{{\"error\": \"{}\"}}", e),
823 }
824 }
825 0x03 => "{}".to_string(),
826 0x04 => "{}".to_string(),
827 _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
828 }
829}
830
831pub fn get_event_list() -> Vec<(u32, &'static str)> {
836 vec![
837 (0x00, "DSTTableEmpty"),
838 (0x01, "DSTStatus"),
839 (0x02, "TimeZoneStatus"),
840 (0x03, "TimeFailure"),
841 (0x04, "MissingTrustedTimeSource"),
842 ]
843}
844