use core::future::pending; use chrono::NaiveDate; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_time::{Instant, Timer}; use sunrise::{Coordinates, SolarDay, SolarEvent}; use thiserror::Error; pub static CLOCK: Mutex = Mutex::new(Clock::new()); #[derive(Debug, Error)] pub enum ClockError { #[error("invalid time")] InvalidTime, #[error("invalid coordinates")] InvalidCoordinates, #[error("no solar events")] NoSolarEvents, } pub struct Clock { unix_time: i64, // seconds since Unix epoch sync_instant: Instant, sun_sync_instant: Instant, sun_time: (i64, i64), } impl Clock { const fn new() -> Self { Self { unix_time: 0, sync_instant: Instant::MIN, sun_sync_instant: Instant::MIN, sun_time: (0, 0), } } fn update_sun(&mut self) -> Result<(), ClockError> { let (sun_rise, sun_set) = get_sun_time(self.unix_time)?; self.sun_time = (sun_rise, sun_set); self.sun_sync_instant = Instant::now(); Ok(()) } pub fn update(&mut self, unix_time: i64) -> Result<(), ClockError> { self.unix_time = unix_time; self.sync_instant = Instant::now(); self.update_sun() } #[must_use] pub fn now(&self) -> i64 { self.unix_time + ((Instant::now() - self.sync_instant).as_secs()).cast_signed() } pub fn sun_time(&mut self) -> Result<(i64, i64), ClockError> { if self.sun_sync_instant.elapsed().as_secs() >= 60 * 60 * 24 { self.update_sun()?; } Ok(self.sun_time) } } fn get_sun_time(current_time: i64) -> Result<(i64, i64), ClockError> { let epoch_days = i32::try_from(current_time / (60 * 60 * 24)).map_err(|_| ClockError::InvalidTime)?; let date = NaiveDate::from_epoch_days(epoch_days).ok_or(ClockError::InvalidTime)?; // TODO: Get actual coordinates based on the current location let coordinates = Coordinates::new(48.210_033, 16.363_449).ok_or(ClockError::InvalidCoordinates)?; let solar_day = SolarDay::new(coordinates, date); Ok(( solar_day .event_time(SolarEvent::Dawn(sunrise::DawnType::Nautical)) .ok_or(ClockError::NoSolarEvents)? .timestamp() % (60 * 60 * 24), solar_day .event_time(SolarEvent::Dusk(sunrise::DawnType::Nautical)) .ok_or(ClockError::NoSolarEvents)? .timestamp() % (60 * 60 * 24), )) } pub async fn maybe_timer(enabled: bool, delay: u64) { if enabled { Timer::after_secs(delay).await; } else { pending::<()>().await; } }