More scheduling

This commit is contained in:
FliegendeWurst 2023-10-04 09:54:47 +02:00
parent d51b298128
commit 4e737ca007
4 changed files with 176 additions and 69 deletions

View File

@ -1,3 +1,4 @@
#[derive(Debug, Clone, Copy)]
pub enum Action { pub enum Action {
Screensaver(&'static str), Screensaver(&'static str),
} }

View File

@ -1,24 +1,21 @@
use std::{ use std::{
thread::sleep_ms, cell::RefCell,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use action::Action;
use display_interface_spi::SPIInterfaceNoCS; use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{ use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget, Drawable};
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
pixelcolor::Rgb565,
prelude::{DrawTarget, Point},
text::Text,
Drawable,
};
use gpiocdev::line::{Bias, EdgeDetection, Value}; use gpiocdev::line::{Bias, EdgeDetection, Value};
use rand_xoshiro::Xoroshiro128StarStar; use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
use raspi_oled::FrameOutput; use raspi_oled::FrameOutput;
use rppal::{ use rppal::{
gpio::{Gpio, OutputPin}, gpio::{Gpio, OutputPin},
hal::Delay, hal::Delay,
spi::{Bus, Mode, SlaveSelect, Spi}, spi::{Bus, Mode, SlaveSelect, Spi},
}; };
use schedule::Schedule;
use screensaver::{Screensaver, TimeDisplay};
use ssd1351::display::display::Ssd1351; use ssd1351::display::display::Ssd1351;
use time::OffsetDateTime; use time::OffsetDateTime;
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt}; 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<Box<dyn Screensaver<Oled>>>,
scheduled: Vec<Box<dyn Schedule>>,
active: RefCell<Vec<Box<dyn Draw<Oled>>>>,
}
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"))] #[cfg(not(feature = "pc"))]
fn pc_main() {} fn pc_main() {}
@ -209,6 +254,10 @@ fn pc_main() {
}); });
} }
pub trait Draw<D: DrawTarget<Color = Rgb565>> {
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error>;
}
fn rpi_main() { fn rpi_main() {
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap(); let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
let gpio = Gpio::new().unwrap(); let gpio = Gpio::new().unwrap();
@ -219,7 +268,7 @@ fn rpi_main() {
let spii = SPIInterfaceNoCS::new(spi, dc); let spii = SPIInterfaceNoCS::new(spi, dc);
let mut disp = Ssd1351::new(spii); let mut disp = Ssd1351::new(spii);
// Reset & init // Reset & init display
disp.reset(&mut rst, &mut Delay).unwrap(); disp.reset(&mut rst, &mut Delay).unwrap();
disp.turn_on().unwrap(); disp.turn_on().unwrap();
@ -228,10 +277,13 @@ fn rpi_main() {
fn main_loop(mut disp: Oled) { fn main_loop(mut disp: Oled) {
disp.clear(BLACK).unwrap(); 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 last_button = Instant::now();
let mut menu = vec![]; let mut menu = vec![];
// high pins for buttons
let _high_outputs = gpiocdev::Request::builder() let _high_outputs = gpiocdev::Request::builder()
.on_chip("/dev/gpiochip0") .on_chip("/dev/gpiochip0")
.with_lines(&[23, 24]) .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 { if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 {
menu.clear(); menu.clear();
} }
let time = OffsetDateTime::now_utc().to_timezone(BERLIN); // check schedules
if time.minute() == last_min { let dirty = ctx.loop_iter(&mut disp, &mut rng);
sleep_ms(1000); if dirty {
continue; 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<D: DrawTarget<Color = Rgb565>>(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<D: DrawTarget<Color = Rgb565>>(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(())
}

View File

@ -1,31 +1,45 @@
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::action::Action; use crate::{action::Action, Context};
/// Task to be executed at certain times. /// Task to be executed at certain times.
/// Guaranteed to be checked at least once every minute. /// Guaranteed to be checked at least once every minute.
trait Schedule { pub trait Schedule {
fn check_and_do(&mut self, time: OffsetDateTime) { fn check_and_do(&self, ctx: &dyn Context, time: OffsetDateTime) {
if self.check(time) { if self.check(time) {
self.execute(time); self.execute(ctx, time);
} }
} }
fn check(&self, time: OffsetDateTime) -> bool; fn check(&self, time: OffsetDateTime) -> bool;
fn execute(&mut self, time: OffsetDateTime); fn execute(&self, ctx: &dyn Context, time: OffsetDateTime);
} }
struct Reminder { #[derive(Debug, Clone, Copy)]
hour: i32, pub struct Reminder {
minute: i32, hour: u8,
minute: u8,
action: Action, 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 { 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 } Reminder { hour, minute, action }
} }
} }
static DUOLINGO: Reminder = Reminder::new(11, 30, Action::Screensaver("duolingo")); static DUOLINGO: Reminder = Reminder::new(11, 30, Action::Screensaver("duolingo"));
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 30, Action::Screensaver("duolingo")); static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 30, Action::Screensaver("duolingo"));
pub fn reminders() -> Vec<Box<dyn Schedule>> {
vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT)]
}

View File

@ -1,29 +1,43 @@
use std::cell::RefCell;
use embedded_graphics::mono_font::ascii::FONT_10X20;
use embedded_graphics::{ use embedded_graphics::{
mono_font::MonoTextStyleBuilder,
pixelcolor::Rgb565, pixelcolor::Rgb565,
prelude::{DrawTarget, Point, Size}, prelude::{DrawTarget, Point, Size},
primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable}, primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable},
text::Text,
Drawable,
}; };
use rand_xoshiro::rand_core::RngCore; 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<D: DrawTarget<Color = Rgb565>>: Draw<D> {
fn id(&self) -> &'static str; fn id(&self) -> &'static str;
fn convert_draw(&self) -> Box<dyn Draw<D>>;
fn draw<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error>;
} }
#[derive(Debug, Clone, Copy)]
pub struct SimpleScreensaver { pub struct SimpleScreensaver {
id: &'static str, id: &'static str,
data: &'static [u8], data: &'static [u8],
} }
impl Screensaver for SimpleScreensaver { impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for SimpleScreensaver {
fn id(&self) -> &'static str { fn id(&self) -> &'static str {
self.id self.id
} }
fn draw<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error> { fn convert_draw(&self) -> Box<dyn Draw<D>> {
Box::new(*self)
}
}
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for SimpleScreensaver {
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error> {
for _ in 0..512 { for _ in 0..512 {
let x = (rng.next_u32() % 128) as usize; let x = (rng.next_u32() % 128) as usize;
let y = (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)?; 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<OffsetDateTime>,
}
impl TimeDisplay {
pub fn new() -> Self {
TimeDisplay {
last_min: RefCell::new(OffsetDateTime::now_utc().checked_sub(Duration::minutes(2)).unwrap()),
}
}
}
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for TimeDisplay {
fn id(&self) -> &'static str {
"time"
}
fn convert_draw(&self) -> Box<dyn Draw<D>> {
Box::new(self.clone())
}
}
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for TimeDisplay {
fn draw(&self, disp: &mut D, _rng: &mut Rng) -> Result<bool, D::Error> {
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 STAR: SimpleScreensaver = SimpleScreensaver::new("star", include_bytes!("./star.raw"));
pub static RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!("./rpi.raw")); pub static RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!("./rpi.raw"));
pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw")); pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw"));
pub fn screensavers<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Screensaver<D>>> {
vec![Box::new(STAR), Box::new(RPI), Box::new(DUOLINGO)]
}