A RPi Pico powered Lightning Detector
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}