A RPi Pico powered Lightning Detector
at main 163 lines 4.7 kB view raw
1#![allow(dead_code)] 2 3use core::{ 4 future::poll_fn, 5 sync::atomic::{AtomicBool, Ordering}, 6 task::Poll, 7}; 8 9use embassy_rp::{ 10 peripherals, 11 rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc}, 12}; 13use embassy_strike_driver::traits::TimeSource; 14use embassy_sync::{rwlock::RwLock, waitqueue::AtomicWaker}; 15use embassy_time::Instant; 16use jiff::{ 17 SignedDuration, Timestamp, 18 tz::{Offset, TimeZone}, 19}; 20use sachy_sntp::SntpTimestamp; 21use static_cell::StaticCell; 22 23use crate::{errors::PicoError, locks::RtcLock}; 24 25static WAKER: AtomicWaker = AtomicWaker::new(); 26static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); 27 28struct RtcInner<'device> { 29 rtc: Rtc<'device, peripherals::RTC>, 30 timestamp: Timestamp, 31 offset: Instant, 32} 33 34#[derive(Clone, Copy)] 35pub struct GlobalRtc<'device>(&'device RwLock<RtcLock, RtcInner<'device>>); 36 37impl GlobalRtc<'static> { 38 pub fn init(rtc: Rtc<'static, peripherals::RTC>) -> Self { 39 static RTC: StaticCell<RwLock<RtcLock, RtcInner<'static>>> = StaticCell::new(); 40 41 Self(&*RTC.init_with(|| { 42 RwLock::new(RtcInner { 43 rtc, 44 timestamp: Default::default(), 45 offset: Instant::MIN, 46 }) 47 })) 48 } 49} 50 51impl GlobalRtc<'_> { 52 pub async fn is_ready(&self) -> bool { 53 let inner = self.0.read().await; 54 55 inner.rtc.is_running() && inner.rtc.alarm_scheduled().is_some() 56 } 57 58 async fn get_timestamp(&self) -> Timestamp { 59 let inner = self.0.read().await; 60 61 let elapsed = inner.offset.elapsed().as_micros() as i64; 62 inner.timestamp + SignedDuration::from_micros(elapsed) 63 } 64 65 pub async fn track_time(&self) -> TimeTracker { 66 TimeTracker { 67 timestamp: self.get_timestamp().await, 68 offset: Instant::now(), 69 } 70 } 71 72 pub async fn set_rtc_datetime(&self, timestamp: SntpTimestamp) -> Result<(), PicoError> { 73 let timestamp = timestamp.try_to_unix_timestamp()?; 74 let zoned = timestamp.to_zoned(TimeZone::fixed(Offset::UTC)); 75 76 let mut inner = self.0.write().await; 77 inner.offset = Instant::now(); 78 inner.timestamp = timestamp; 79 inner.rtc.set_datetime(DateTime { 80 year: zoned.year() as u16, 81 month: zoned.month() as u8, 82 day: zoned.day() as u8, 83 day_of_week: match zoned.weekday() { 84 jiff::civil::Weekday::Monday => DayOfWeek::Monday, 85 jiff::civil::Weekday::Tuesday => DayOfWeek::Tuesday, 86 jiff::civil::Weekday::Wednesday => DayOfWeek::Wednesday, 87 jiff::civil::Weekday::Thursday => DayOfWeek::Thursday, 88 jiff::civil::Weekday::Friday => DayOfWeek::Friday, 89 jiff::civil::Weekday::Saturday => DayOfWeek::Saturday, 90 jiff::civil::Weekday::Sunday => DayOfWeek::Sunday, 91 }, 92 hour: zoned.hour() as u8, 93 minute: zoned.minute() as u8, 94 second: zoned.second() as u8, 95 })?; 96 97 // Schedule the next alarm from now in order to reset the RTC 98 // the next day on the same hour, on the hour. 99 inner.rtc.schedule_alarm( 100 DateTimeFilter::default() 101 .hour(zoned.hour() as u8) 102 .minute(0) 103 .second(0), 104 ); 105 106 Ok(()) 107 } 108 109 pub async fn wait_for_reset(&self) { 110 poll_fn(|cx| { 111 if critical_section::with(|_| { 112 let outcome = ALARM_OCCURRED.load(Ordering::SeqCst); 113 114 if outcome { 115 ALARM_OCCURRED.store(false, Ordering::SeqCst); 116 } else { 117 WAKER.register(cx.waker()); 118 } 119 120 outcome 121 }) { 122 Poll::Ready(()) 123 } else { 124 Poll::Pending 125 } 126 }) 127 .await; 128 129 let mut inner = self.0.write().await; 130 inner.rtc.clear_interrupt(); 131 } 132} 133 134pub struct TimeTracker { 135 timestamp: Timestamp, 136 offset: Instant, 137} 138 139impl TimeSource for TimeTracker { 140 type Source = GlobalRtc<'static>; 141 142 #[inline] 143 fn timestamp(&self) -> i64 { 144 let elapsed = self.offset.elapsed().as_micros() as i64; 145 (self.timestamp + SignedDuration::from_micros(elapsed)).as_microsecond() 146 } 147 148 async fn reset_from_source(&mut self, source: Self::Source) { 149 self.timestamp = source.get_timestamp().await; 150 self.offset = Instant::now(); 151 } 152} 153 154pub struct GlobalRtcHandler; 155 156impl embassy_rp::interrupt::typelevel::Handler<embassy_rp::interrupt::typelevel::RTC_IRQ> 157 for GlobalRtcHandler 158{ 159 unsafe fn on_interrupt() { 160 ALARM_OCCURRED.store(true, Ordering::SeqCst); 161 WAKER.wake(); 162 } 163}