diff --git a/firmware/CHANGELOG.md b/firmware/CHANGELOG.md index 85e6aa0..5e254a7 100644 --- a/firmware/CHANGELOG.md +++ b/firmware/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Automatically refresh article and user information once a day + ## 0.2.0 - 2024-11-27 - Show random greetings to user diff --git a/firmware/src/main.rs b/firmware/src/main.rs index e1cf71b..1b6878d 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -49,6 +49,7 @@ mod json; mod keypad; mod nfc; mod pn532; +mod schedule; mod screen; mod time; mod ui; @@ -217,6 +218,9 @@ async fn main(spawner: Spawner) { let mut buzzer = buzzer::Buzzer::new(peripherals.LEDC, peripherals.GPIO4); let _ = buzzer.startup().await; + // Initialize scheduler + let mut schedule = schedule::Daily::new(); + // Create UI let mut ui = ui::Ui::new( rng, @@ -229,6 +233,7 @@ async fn main(spawner: Spawner) { &mut vereinsflieger, &mut articles, &mut users, + &mut schedule, ); loop { diff --git a/firmware/src/schedule.rs b/firmware/src/schedule.rs new file mode 100644 index 0000000..7f2955f --- /dev/null +++ b/firmware/src/schedule.rs @@ -0,0 +1,70 @@ +use core::fmt; +use embassy_time::Timer; +use embassy_time::{Duration, Instant}; +use log::info; + +/// Simple time interval of 24h +#[cfg(not(debug_assertions))] +const DAILY_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); +#[cfg(debug_assertions)] +const DAILY_INTERVAL: Duration = Duration::from_secs(30 * 60); + +/// Duration display helper +struct DisplayDuration(Duration); + +impl fmt::Display for DisplayDuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hours = self.0.as_secs() / 3600; + let min = self.0.as_secs() % 3600 / 60; + let secs = self.0.as_secs() % 60; + write!(f, "{hours}h{min}m{secs}s") + } +} + +/// Scheduler for daily events +#[derive(Debug)] +pub struct Daily { + next: Instant, +} + +impl Daily { + /// Create new daily scheduler + pub fn new() -> Self { + let mut daily = Self { + next: Instant::now(), + }; + daily.schedule_next(); + daily + } + + /// Returns true when schedule time is expired + pub fn is_expired(&self) -> bool { + self.next <= Instant::now() + } + + /// Time left until schedule time + pub fn time_left(&self) -> Duration { + self.next.saturating_duration_since(Instant::now()) + } + + /// Timer that can be awaited on to wait for schedule time + pub fn timer(&self) -> Timer { + Timer::at(self.next) + } + + /// After expiring, schedule next event + pub fn schedule_next(&mut self) { + if self.is_expired() { + // Simple schedule: run again 24h later + self.next += DAILY_INTERVAL; + } + if self.is_expired() { + // Simple schedule: run in 24h from now + self.next = Instant::now() + DAILY_INTERVAL; + } + info!( + "Schedule: next daily event scheduled in {} from now", + DisplayDuration(self.time_left()) + ); + } +} diff --git a/firmware/src/ui.rs b/firmware/src/ui.rs index b093329..a7d2be0 100644 --- a/firmware/src/ui.rs +++ b/firmware/src/ui.rs @@ -5,6 +5,7 @@ use crate::error::Error; use crate::http::Http; use crate::keypad::{Key, Keypad}; use crate::nfc::Nfc; +use crate::schedule::Daily; use crate::screen; use crate::user::{UserId, Users}; use crate::vereinsflieger::Vereinsflieger; @@ -49,6 +50,7 @@ pub struct Ui<'a, RNG, I2C, IRQ> { vereinsflieger: &'a mut Vereinsflieger<'a>, articles: &'a mut Articles<1>, users: &'a mut Users, + schedule: &'a mut Daily, } impl<'a, RNG: RngCore, I2C: I2c, IRQ: Wait> Ui<'a, RNG, I2C, IRQ> { @@ -65,6 +67,7 @@ impl<'a, RNG: RngCore, I2C: I2c, IRQ: Wait> Ui<'a, RNG, I2C, vereinsflieger: &'a mut Vereinsflieger<'a>, articles: &'a mut Articles<1>, users: &'a mut Users, + schedule: &'a mut Daily, ) -> Self { Self { rng, @@ -77,6 +80,7 @@ impl<'a, RNG: RngCore, I2C: I2c, IRQ: Wait> Ui<'a, RNG, I2C, vereinsflieger, articles, users, + schedule, } } @@ -191,8 +195,14 @@ impl<'a, RNG: RngCore, I2C: I2c, IRQ: Wait> Ui<'a, RNG, I2C, /// Run the user interface flow pub async fn run(&mut self) -> Result<(), Error> { - // Wait for id card and verify identification - let user_id = self.authenticate_user().await?; + // Either wait for id card read or schedule time + let schedule_timer = self.schedule.timer(); + let user_id = match select(self.authenticate_user(), schedule_timer).await { + // Id card read + Either::First(res) => res?, + // Schedule time + Either::Second(()) => return self.schedule().await, + }; // Get user information let user = self.users.get(user_id); @@ -222,6 +232,20 @@ impl<'a, RNG: RngCore, I2C: I2c, IRQ: Wait> Ui<'a, RNG, I2C, Ok(()) } + + /// Run schedule + pub async fn schedule(&mut self) -> Result<(), Error> { + if self.schedule.is_expired() { + info!("UI: Running schedule..."); + + // Schedule next event + self.schedule.schedule_next(); + + // Refresh article and user information + self.refresh_articles_and_users().await?; + } + Ok(()) + } } impl> Ui<'_, RNG, I2C, IRQ> {