#![allow(dead_code)] use core::{ future::poll_fn, sync::atomic::{AtomicBool, Ordering}, task::Poll, }; use embassy_rp::{ peripherals, rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc}, }; use embassy_strike_driver::traits::TimeSource; use embassy_sync::{rwlock::RwLock, waitqueue::AtomicWaker}; use embassy_time::Instant; use jiff::{ SignedDuration, Timestamp, tz::{Offset, TimeZone}, }; use sachy_sntp::SntpTimestamp; use static_cell::StaticCell; use crate::{errors::PicoError, locks::RtcLock}; static WAKER: AtomicWaker = AtomicWaker::new(); static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); struct RtcInner<'device> { rtc: Rtc<'device, peripherals::RTC>, timestamp: Timestamp, offset: Instant, } #[derive(Clone, Copy)] pub struct GlobalRtc<'device>(&'device RwLock>); impl GlobalRtc<'static> { pub fn init(rtc: Rtc<'static, peripherals::RTC>) -> Self { static RTC: StaticCell>> = StaticCell::new(); Self(&*RTC.init_with(|| { RwLock::new(RtcInner { rtc, timestamp: Default::default(), offset: Instant::MIN, }) })) } } impl GlobalRtc<'_> { pub async fn is_ready(&self) -> bool { let inner = self.0.read().await; inner.rtc.is_running() && inner.rtc.alarm_scheduled().is_some() } async fn get_timestamp(&self) -> Timestamp { let inner = self.0.read().await; let elapsed = inner.offset.elapsed().as_micros() as i64; inner.timestamp + SignedDuration::from_micros(elapsed) } pub async fn track_time(&self) -> TimeTracker { TimeTracker { timestamp: self.get_timestamp().await, offset: Instant::now(), } } pub async fn set_rtc_datetime(&self, timestamp: SntpTimestamp) -> Result<(), PicoError> { let timestamp = timestamp.try_to_unix_timestamp()?; let zoned = timestamp.to_zoned(TimeZone::fixed(Offset::UTC)); let mut inner = self.0.write().await; inner.offset = Instant::now(); inner.timestamp = timestamp; inner.rtc.set_datetime(DateTime { year: zoned.year() as u16, month: zoned.month() as u8, day: zoned.day() as u8, day_of_week: match zoned.weekday() { jiff::civil::Weekday::Monday => DayOfWeek::Monday, jiff::civil::Weekday::Tuesday => DayOfWeek::Tuesday, jiff::civil::Weekday::Wednesday => DayOfWeek::Wednesday, jiff::civil::Weekday::Thursday => DayOfWeek::Thursday, jiff::civil::Weekday::Friday => DayOfWeek::Friday, jiff::civil::Weekday::Saturday => DayOfWeek::Saturday, jiff::civil::Weekday::Sunday => DayOfWeek::Sunday, }, hour: zoned.hour() as u8, minute: zoned.minute() as u8, second: zoned.second() as u8, })?; // Schedule the next alarm from now in order to reset the RTC // the next day on the same hour, on the hour. inner.rtc.schedule_alarm( DateTimeFilter::default() .hour(zoned.hour() as u8) .minute(0) .second(0), ); Ok(()) } pub async fn wait_for_reset(&self) { poll_fn(|cx| { if critical_section::with(|_| { let outcome = ALARM_OCCURRED.load(Ordering::SeqCst); if outcome { ALARM_OCCURRED.store(false, Ordering::SeqCst); } else { WAKER.register(cx.waker()); } outcome }) { Poll::Ready(()) } else { Poll::Pending } }) .await; let mut inner = self.0.write().await; inner.rtc.clear_interrupt(); } } pub struct TimeTracker { timestamp: Timestamp, offset: Instant, } impl TimeSource for TimeTracker { type Source = GlobalRtc<'static>; #[inline] fn timestamp(&self) -> i64 { let elapsed = self.offset.elapsed().as_micros() as i64; (self.timestamp + SignedDuration::from_micros(elapsed)).as_microsecond() } async fn reset_from_source(&mut self, source: Self::Source) { self.timestamp = source.get_timestamp().await; self.offset = Instant::now(); } } pub struct GlobalRtcHandler; impl embassy_rp::interrupt::typelevel::Handler for GlobalRtcHandler { unsafe fn on_interrupt() { ALARM_OCCURRED.store(true, Ordering::SeqCst); WAKER.wake(); } }