mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-08 13:20:37 +00:00
More scheduling
This commit is contained in:
parent
d51b298128
commit
4e737ca007
@ -1,3 +1,4 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Action {
|
||||
Screensaver(&'static str),
|
||||
}
|
||||
|
@ -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<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"))]
|
||||
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() {
|
||||
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<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(())
|
||||
}
|
||||
|
@ -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<Box<dyn Schedule>> {
|
||||
vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT)]
|
||||
}
|
||||
|
@ -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<D: DrawTarget<Color = Rgb565>>: Draw<D> {
|
||||
fn id(&self) -> &'static str;
|
||||
|
||||
fn draw<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error>;
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SimpleScreensaver {
|
||||
id: &'static str,
|
||||
data: &'static [u8],
|
||||
}
|
||||
|
||||
impl Screensaver for SimpleScreensaver {
|
||||
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for SimpleScreensaver {
|
||||
fn id(&self) -> &'static str {
|
||||
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 {
|
||||
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<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 RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!("./rpi.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)]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user