use embassy_time::{Duration, Timer};
use crate::crypto::Crypto;
use crate::dm::clusters::decl::time_synchronization::{
GranularityEnum, TimeSourceEnum, TimeSynchronizationClient as _,
};
use crate::dm::AttrChangeNotifier;
use crate::error::Error;
use crate::persist::{KvBlobStoreAccess, Persist};
use crate::transport::exchange::Exchange;
use crate::Matter;
pub struct TimeSyncClient<'a, C> {
matter: &'a Matter<'a>,
crypto: C,
}
impl<'a, C: Crypto> TimeSyncClient<'a, C> {
pub const fn new(matter: &'a Matter<'a>, crypto: C) -> Self {
Self { matter, crypto }
}
pub async fn run<S, N>(&self, period: Duration, kv: S, notify: &N) -> Result<(), Error>
where
S: KvBlobStoreAccess,
N: AttrChangeNotifier,
{
loop {
let period = if let Err(e) = self.refresh_once(&kv, notify).await {
warn!("TimeSync client: refresh failed: {}", e);
Duration::from_secs(60).min(period)
} else {
period
};
Timer::after(period).await;
}
}
pub async fn refresh_once<S, N>(&self, kv: S, notify: &N) -> Result<(), Error>
where
S: KvBlobStoreAccess,
N: AttrChangeNotifier,
{
let Some(tts) = self
.matter
.with_state(|state| state.rtc.trusted_time_source())
else {
return Ok(());
};
info!(
"TimeSync client: refreshing from fabric {}, node 0x{:016x}, endpoint {}",
tts.fab_idx, tts.node_id, tts.endpoint
);
let exchange =
Exchange::initiate(self.matter, &self.crypto, tts.fab_idx, tts.node_id).await?;
let result = exchange
.time_synchronization()
.utc_time_read(tts.endpoint)
.await?;
if let Some(utc_us) = result.into_option() {
let mut persist = Persist::new(kv);
self.matter.with_rtc(|rtc| {
rtc.set_utc_time_persist(
utc_us,
GranularityEnum::SecondsGranularity,
TimeSourceEnum::NodeTimeCluster,
&mut persist,
notify,
)
})?;
persist.run()?;
info!(
"TimeSync client: applied UTCTime = {} \u{00b5}s from trusted source",
utc_us
);
} else {
warn!("TimeSync client: trusted source returned null UTCTime");
}
Ok(())
}
}