mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-22 02:14:58 +00:00
More scheduling
This commit is contained in:
parent
d51b298128
commit
4e737ca007
@ -1,3 +1,4 @@
|
|||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Screensaver(&'static str),
|
Screensaver(&'static str),
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
|
||||||
}
|
|
||||||
|
@ -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)]
|
||||||
|
}
|
||||||
|
@ -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)]
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user