From 4e737ca0071b6b4afa2cf1828c9f2209e218ab17 Mon Sep 17 00:00:00 2001 From: FliegendeWurst <2012gdwu+github@posteo.de> Date: Wed, 4 Oct 2023 09:54:47 +0200 Subject: [PATCH] More scheduling --- src/bin/action/mod.rs | 1 + src/bin/main_loop.rs | 120 +++++++++++++++++++++---------------- src/bin/schedule/mod.rs | 32 +++++++--- src/bin/screensaver/mod.rs | 92 +++++++++++++++++++++++++--- 4 files changed, 176 insertions(+), 69 deletions(-) diff --git a/src/bin/action/mod.rs b/src/bin/action/mod.rs index 5e31354..3fd6d63 100644 --- a/src/bin/action/mod.rs +++ b/src/bin/action/mod.rs @@ -1,3 +1,4 @@ +#[derive(Debug, Clone, Copy)] pub enum Action { Screensaver(&'static str), } diff --git a/src/bin/main_loop.rs b/src/bin/main_loop.rs index 633c814..74ae0f8 100644 --- a/src/bin/main_loop.rs +++ b/src/bin/main_loop.rs @@ -1,24 +1,21 @@ use std::{ - thread::sleep_ms, + cell::RefCell, time::{Duration, Instant}, }; +use action::Action; use display_interface_spi::SPIInterfaceNoCS; -use embedded_graphics::{ - mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, - pixelcolor::Rgb565, - prelude::{DrawTarget, Point}, - text::Text, - Drawable, -}; +use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget, Drawable}; use gpiocdev::line::{Bias, EdgeDetection, Value}; -use rand_xoshiro::Xoroshiro128StarStar; +use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar}; use raspi_oled::FrameOutput; use rppal::{ gpio::{Gpio, OutputPin}, hal::Delay, spi::{Bus, Mode, SlaveSelect, Spi}, }; +use schedule::Schedule; +use screensaver::{Screensaver, TimeDisplay}; use ssd1351::display::display::Ssd1351; use time::OffsetDateTime; use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt}; @@ -41,6 +38,54 @@ fn main() { } } +pub trait Context { + fn do_action(&self, action: Action); +} + +struct ContextDefault { + screensavers: Vec>>, + scheduled: Vec>, + active: RefCell>>>, +} + +impl ContextDefault { + fn new() -> Self { + ContextDefault { + screensavers: screensaver::screensavers(), + scheduled: schedule::reminders(), + active: RefCell::new(vec![Box::new(TimeDisplay::new())]), + } + } + + fn loop_iter(&mut self, disp: &mut Oled, rng: &mut Rng) -> bool { + let time = OffsetDateTime::now_utc().to_timezone(BERLIN); + // check schedules + for s in &self.scheduled { + s.check_and_do(&*self, time); + } + let active = self.active.borrow(); + if active.is_empty() { + return false; + } + let a = active.last().unwrap(); + a.draw(disp, rng).unwrap_or(true) + } +} + +impl Context for ContextDefault { + fn do_action(&self, action: Action) { + match action { + Action::Screensaver(id) => { + for s in &self.screensavers { + if s.id() == id { + self.active.borrow_mut().push(s.convert_draw()); + } + } + }, + } + } +} + #[cfg(not(feature = "pc"))] fn pc_main() {} @@ -209,6 +254,10 @@ fn pc_main() { }); } +pub trait Draw> { + fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result; +} + fn rpi_main() { let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap(); let gpio = Gpio::new().unwrap(); @@ -219,7 +268,7 @@ fn rpi_main() { let spii = SPIInterfaceNoCS::new(spi, dc); let mut disp = Ssd1351::new(spii); - // Reset & init + // Reset & init display disp.reset(&mut rst, &mut Delay).unwrap(); disp.turn_on().unwrap(); @@ -228,10 +277,13 @@ fn rpi_main() { fn main_loop(mut disp: Oled) { disp.clear(BLACK).unwrap(); - let mut last_min = 0xff; + + let mut ctx = ContextDefault::new(); + let mut rng = Xoroshiro128StarStar::seed_from_u64(17381); let mut last_button = Instant::now(); let mut menu = vec![]; + // high pins for buttons let _high_outputs = gpiocdev::Request::builder() .on_chip("/dev/gpiochip0") .with_lines(&[23, 24]) @@ -280,48 +332,10 @@ fn main_loop(mut disp: Oled) { if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 { menu.clear(); } - let time = OffsetDateTime::now_utc().to_timezone(BERLIN); - if time.minute() == last_min { - sleep_ms(1000); - continue; + // check schedules + let dirty = ctx.loop_iter(&mut disp, &mut rng); + if dirty { + let _ = disp.flush(); // ignore bus write errors, they are harmless } - last_min = time.minute(); - if let Err(e) = loop_iter(&mut disp) { - println!("error: {:?}", e); - } - let _ = disp.flush(); // ignore bus write errors, they are harmless } } - -fn loop_iter>(disp: &mut D) -> Result<(), D::Error> { - disp.clear(Rgb565::new(0, 0, 0))?; - let time = OffsetDateTime::now_utc().to_timezone(BERLIN); - display_clock(disp, &time) -} - -fn display_clock>(disp: &mut D, time: &OffsetDateTime) -> Result<(), D::Error> { - let text_style_clock = MonoTextStyleBuilder::new() - .font(&FONT_10X20) - .text_color(TIME_COLOR) - .build(); - let hour = time.hour(); - let minute = time.minute(); - let unix_minutes = minute as i32 * 5 / 3; // (time.unix_timestamp() / 60) as i32; - let dx = ((hour % 3) as i32 - 1) * 40 - 2; - let hour = format!("{:02}", hour); - Text::new( - &hour, - Point::new(64 - 20 + dx, 20 + unix_minutes % 100), - text_style_clock, - ) - .draw(disp)?; - Text::new(&":", Point::new(64 - 3 + dx, 18 + unix_minutes % 100), text_style_clock).draw(disp)?; - let minute = format!("{:02}", minute); - Text::new( - &minute, - Point::new(64 + 5 + dx, 20 + unix_minutes % 100), - text_style_clock, - ) - .draw(disp)?; - Ok(()) -} diff --git a/src/bin/schedule/mod.rs b/src/bin/schedule/mod.rs index f30e7d4..b9aa527 100644 --- a/src/bin/schedule/mod.rs +++ b/src/bin/schedule/mod.rs @@ -1,31 +1,45 @@ use time::OffsetDateTime; -use crate::action::Action; +use crate::{action::Action, Context}; /// Task to be executed at certain times. /// Guaranteed to be checked at least once every minute. -trait Schedule { - fn check_and_do(&mut self, time: OffsetDateTime) { +pub trait Schedule { + fn check_and_do(&self, ctx: &dyn Context, time: OffsetDateTime) { if self.check(time) { - self.execute(time); + self.execute(ctx, time); } } fn check(&self, time: OffsetDateTime) -> bool; - fn execute(&mut self, time: OffsetDateTime); + fn execute(&self, ctx: &dyn Context, time: OffsetDateTime); } -struct Reminder { - hour: i32, - minute: i32, +#[derive(Debug, Clone, Copy)] +pub struct Reminder { + hour: u8, + minute: u8, action: Action, } +impl Schedule for Reminder { + fn check(&self, time: OffsetDateTime) -> bool { + time.hour() == self.hour && time.minute() == self.minute + } + + fn execute(&self, ctx: &dyn Context, _time: OffsetDateTime) { + ctx.do_action(self.action); + } +} + impl Reminder { - const fn new(hour: i32, minute: i32, action: Action) -> Self { + const fn new(hour: u8, minute: u8, action: Action) -> Self { Reminder { hour, minute, action } } } static DUOLINGO: Reminder = Reminder::new(11, 30, Action::Screensaver("duolingo")); static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 30, Action::Screensaver("duolingo")); +pub fn reminders() -> Vec> { + vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT)] +} diff --git a/src/bin/screensaver/mod.rs b/src/bin/screensaver/mod.rs index e37f197..4673314 100644 --- a/src/bin/screensaver/mod.rs +++ b/src/bin/screensaver/mod.rs @@ -1,29 +1,43 @@ +use std::cell::RefCell; + +use embedded_graphics::mono_font::ascii::FONT_10X20; use embedded_graphics::{ + mono_font::MonoTextStyleBuilder, pixelcolor::Rgb565, prelude::{DrawTarget, Point, Size}, primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable}, + text::Text, + Drawable, }; use rand_xoshiro::rand_core::RngCore; +use time::{Duration, OffsetDateTime}; +use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt}; -use crate::Rng; +use crate::{Draw, Rng}; -pub trait Screensaver { +pub trait Screensaver>: Draw { fn id(&self) -> &'static str; - - fn draw>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error>; + fn convert_draw(&self) -> Box>; } +#[derive(Debug, Clone, Copy)] pub struct SimpleScreensaver { id: &'static str, data: &'static [u8], } -impl Screensaver for SimpleScreensaver { +impl> Screensaver for SimpleScreensaver { fn id(&self) -> &'static str { self.id } - fn draw>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error> { + fn convert_draw(&self) -> Box> { + Box::new(*self) + } +} + +impl> Draw for SimpleScreensaver { + fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result { for _ in 0..512 { let x = (rng.next_u32() % 128) as usize; let y = (rng.next_u32() % 128) as usize; @@ -45,7 +59,7 @@ impl Screensaver for SimpleScreensaver { p.draw_styled(&s, disp)?; } } - Ok(()) + Ok(true) } } @@ -58,6 +72,70 @@ impl SimpleScreensaver { } } +static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111); + +#[derive(Debug, Clone)] +pub struct TimeDisplay { + last_min: RefCell, +} + +impl TimeDisplay { + pub fn new() -> Self { + TimeDisplay { + last_min: RefCell::new(OffsetDateTime::now_utc().checked_sub(Duration::minutes(2)).unwrap()), + } + } +} + +impl> Screensaver for TimeDisplay { + fn id(&self) -> &'static str { + "time" + } + + fn convert_draw(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl> Draw for TimeDisplay { + fn draw(&self, disp: &mut D, _rng: &mut Rng) -> Result { + let time = OffsetDateTime::now_utc().to_timezone(BERLIN); + if time.minute() == self.last_min.borrow().minute() { + return Ok(false); + } + *self.last_min.borrow_mut() = time; + disp.clear(Rgb565::new(0, 0, 0))?; + let text_style_clock = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(TIME_COLOR) + .build(); + let hour = time.hour(); + let minute = time.minute(); + let unix_minutes = minute as i32 * 5 / 3; // (time.unix_timestamp() / 60) as i32; + let dx = ((hour % 3) as i32 - 1) * 40 - 2; + let hour = format!("{:02}", hour); + Text::new( + &hour, + Point::new(64 - 20 + dx, 20 + unix_minutes % 100), + text_style_clock, + ) + .draw(disp)?; + Text::new(&":", Point::new(64 - 3 + dx, 18 + unix_minutes % 100), text_style_clock).draw(disp)?; + let minute = format!("{:02}", minute); + Text::new( + &minute, + Point::new(64 + 5 + dx, 20 + unix_minutes % 100), + text_style_clock, + ) + .draw(disp)?; + Ok(true) + } +} + pub static STAR: SimpleScreensaver = SimpleScreensaver::new("star", include_bytes!("./star.raw")); pub static RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!("./rpi.raw")); pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw")); + +pub fn screensavers>() -> Vec>> { + vec![Box::new(STAR), Box::new(RPI), Box::new(DUOLINGO)] +}