mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-09-18 17:54:48 +00:00
Start scheduler, screensaver and action framework
This commit is contained in:
parent
f87f7b5001
commit
d51b298128
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,3 +4,8 @@ events_weekly.json
|
|||||||
events.json
|
events.json
|
||||||
sensors.db
|
sensors.db
|
||||||
/result
|
/result
|
||||||
|
/box.svg
|
||||||
|
/mockup.png
|
||||||
|
/*.aes
|
||||||
|
/*.py
|
||||||
|
/*.txt
|
3
src/bin/action/mod.rs
Normal file
3
src/bin/action/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub enum Action {
|
||||||
|
Screensaver(&'static str),
|
||||||
|
}
|
@ -15,7 +15,7 @@ use embedded_graphics::{
|
|||||||
text::{renderer::CharacterStyle, Text},
|
text::{renderer::CharacterStyle, Text},
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
use raspi_oled::FrameOutput;
|
|
||||||
use rppal::{
|
use rppal::{
|
||||||
gpio::Gpio,
|
gpio::Gpio,
|
||||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
@ -187,7 +187,7 @@ where
|
|||||||
.font(&FONT_6X9)
|
.font(&FONT_6X9)
|
||||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
.build();
|
.build();
|
||||||
let mut text_style_4x6 = MonoTextStyleBuilder::new()
|
let text_style_4x6 = MonoTextStyleBuilder::new()
|
||||||
.font(&FONT_4X6)
|
.font(&FONT_4X6)
|
||||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
.build();
|
.build();
|
||||||
|
@ -7,11 +7,12 @@ use display_interface_spi::SPIInterfaceNoCS;
|
|||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
|
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
|
||||||
pixelcolor::Rgb565,
|
pixelcolor::Rgb565,
|
||||||
prelude::{DrawTarget, Point, Size},
|
prelude::{DrawTarget, Point},
|
||||||
text::Text,
|
text::Text,
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
use gpiocdev::line::{Bias, EdgeDetection, Value};
|
use gpiocdev::line::{Bias, EdgeDetection, Value};
|
||||||
|
use rand_xoshiro::Xoroshiro128StarStar;
|
||||||
use raspi_oled::FrameOutput;
|
use raspi_oled::FrameOutput;
|
||||||
use rppal::{
|
use rppal::{
|
||||||
gpio::{Gpio, OutputPin},
|
gpio::{Gpio, OutputPin},
|
||||||
@ -22,8 +23,12 @@ 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};
|
||||||
|
|
||||||
static STAR: &'static [u8] = include_bytes!("../star.raw");
|
mod action;
|
||||||
static RPI: &'static [u8] = include_bytes!("../rpi.raw");
|
mod schedule;
|
||||||
|
mod screensaver;
|
||||||
|
|
||||||
|
pub type Oled = Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>;
|
||||||
|
pub type Rng = Xoroshiro128StarStar;
|
||||||
|
|
||||||
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
|
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
|
||||||
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
||||||
@ -43,11 +48,7 @@ fn pc_main() {}
|
|||||||
fn pc_main() {
|
fn pc_main() {
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable};
|
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
|
||||||
use rand_xoshiro::{
|
|
||||||
rand_core::{RngCore, SeedableRng},
|
|
||||||
Xoroshiro128StarStar,
|
|
||||||
};
|
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
@ -55,6 +56,8 @@ fn pc_main() {
|
|||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::screensaver::{Screensaver, DUOLINGO};
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
.with_inner_size(LogicalSize::new(128, 128))
|
.with_inner_size(LogicalSize::new(128, 128))
|
||||||
@ -108,13 +111,16 @@ fn pc_main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// redraw
|
// redraw
|
||||||
if Instant::now().duration_since(start) > Duration::from_millis(iters * 50) {
|
if Instant::now().duration_since(start) > Duration::from_millis(iters * 1000) {
|
||||||
iters += 1;
|
iters += 1;
|
||||||
//loop_iter(&mut disp).unwrap();
|
//loop_iter(&mut disp).unwrap();
|
||||||
|
/*
|
||||||
let mut time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
let mut time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||||
//time += Duration::new(iters * 60, 0);
|
//time += Duration::new(iters * 60, 0);
|
||||||
disp.clear(Rgb565::new(0, 0, 0)).unwrap();
|
disp.clear(Rgb565::new(0, 0, 0)).unwrap();
|
||||||
display_clock(&mut disp, &time).unwrap();
|
display_clock(&mut disp, &time).unwrap();
|
||||||
|
*/
|
||||||
|
DUOLINGO.draw(&mut disp, &mut rng).unwrap();
|
||||||
/*
|
/*
|
||||||
let iters = iters % 300;
|
let iters = iters % 300;
|
||||||
let (s, c) = (iters as f32 * 0.1).sin_cos();
|
let (s, c) = (iters as f32 * 0.1).sin_cos();
|
||||||
@ -220,7 +226,7 @@ fn rpi_main() {
|
|||||||
main_loop(disp);
|
main_loop(disp);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) {
|
fn main_loop(mut disp: Oled) {
|
||||||
disp.clear(BLACK).unwrap();
|
disp.clear(BLACK).unwrap();
|
||||||
let mut last_min = 0xff;
|
let mut last_min = 0xff;
|
||||||
let mut last_button = Instant::now();
|
let mut last_button = Instant::now();
|
||||||
@ -309,12 +315,7 @@ fn display_clock<D: DrawTarget<Color = Rgb565>>(disp: &mut D, time: &OffsetDateT
|
|||||||
text_style_clock,
|
text_style_clock,
|
||||||
)
|
)
|
||||||
.draw(disp)?;
|
.draw(disp)?;
|
||||||
Text::new(
|
Text::new(&":", Point::new(64 - 3 + dx, 18 + unix_minutes % 100), text_style_clock).draw(disp)?;
|
||||||
&":",
|
|
||||||
Point::new(64 - 3 + dx, 18 + unix_minutes % 100),
|
|
||||||
text_style_clock,
|
|
||||||
)
|
|
||||||
.draw(disp)?;
|
|
||||||
let minute = format!("{:02}", minute);
|
let minute = format!("{:02}", minute);
|
||||||
Text::new(
|
Text::new(
|
||||||
&minute,
|
&minute,
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs, thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use display_interface_spi::SPIInterfaceNoCS;
|
use display_interface_spi::SPIInterfaceNoCS;
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
draw_target::DrawTarget,
|
draw_target::DrawTarget,
|
||||||
mono_font::{
|
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
|
||||||
ascii::{FONT_10X20, FONT_5X8, FONT_6X9, FONT_9X15},
|
pixelcolor::Rgb565,
|
||||||
MonoTextStyleBuilder,
|
|
||||||
},
|
|
||||||
pixelcolor::{BinaryColor, Rgb565},
|
|
||||||
prelude::{OriginDimensions, Point, Primitive, Size},
|
|
||||||
primitives::{PrimitiveStyleBuilder, Rectangle},
|
|
||||||
text::Text,
|
text::Text,
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
use embedded_hal::digital::v2::OutputPin;
|
|
||||||
use image::{ImageBuffer, Rgb};
|
|
||||||
use linux_embedded_hal::I2cdev;
|
|
||||||
use rppal::{
|
use rppal::{
|
||||||
gpio::Gpio,
|
gpio::Gpio,
|
||||||
hal::Delay,
|
hal::Delay,
|
||||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
};
|
};
|
||||||
//use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}};
|
//use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}};
|
||||||
use time::{format_description, OffsetDateTime, PrimitiveDateTime};
|
|
||||||
//use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
//use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
31
src/bin/schedule/mod.rs
Normal file
31
src/bin/schedule/mod.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::action::Action;
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
if self.check(time) {
|
||||||
|
self.execute(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self, time: OffsetDateTime) -> bool;
|
||||||
|
fn execute(&mut self, time: OffsetDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Reminder {
|
||||||
|
hour: i32,
|
||||||
|
minute: i32,
|
||||||
|
action: Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reminder {
|
||||||
|
const fn new(hour: i32, minute: i32, 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"));
|
BIN
src/bin/screensaver/duolingo.raw
Normal file
BIN
src/bin/screensaver/duolingo.raw
Normal file
Binary file not shown.
63
src/bin/screensaver/mod.rs
Normal file
63
src/bin/screensaver/mod.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use embedded_graphics::{
|
||||||
|
pixelcolor::Rgb565,
|
||||||
|
prelude::{DrawTarget, Point, Size},
|
||||||
|
primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable},
|
||||||
|
};
|
||||||
|
use rand_xoshiro::rand_core::RngCore;
|
||||||
|
|
||||||
|
use crate::Rng;
|
||||||
|
|
||||||
|
pub trait Screensaver {
|
||||||
|
fn id(&self) -> &'static str;
|
||||||
|
|
||||||
|
fn draw<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, rng: &mut Rng) -> Result<(), D::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SimpleScreensaver {
|
||||||
|
id: &'static str,
|
||||||
|
data: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screensaver 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> {
|
||||||
|
for _ in 0..512 {
|
||||||
|
let x = (rng.next_u32() % 128) as usize;
|
||||||
|
let y = (rng.next_u32() % 128) as usize;
|
||||||
|
let dx = (rng.next_u32() % 8) as i32 - 4;
|
||||||
|
let dy = (rng.next_u32() % 8) as i32 - 4;
|
||||||
|
let red = self.data[y * 128 * 3 + x * 3 + 0];
|
||||||
|
let green = self.data[y * 128 * 3 + x * 3 + 1];
|
||||||
|
let blue = self.data[y * 128 * 3 + x * 3 + 2];
|
||||||
|
if red | green | blue != 0 {
|
||||||
|
let color = rng.next_u32();
|
||||||
|
let r;
|
||||||
|
let g;
|
||||||
|
let b;
|
||||||
|
r = (red >> 3).overflowing_add(color as u8 & 0b11).0;
|
||||||
|
g = (green >> 2).overflowing_add(((color >> 2) & 0b11) as u8).0;
|
||||||
|
b = (blue >> 3).overflowing_add(((color >> 4) & 0b11) as u8).0;
|
||||||
|
let p = Rectangle::new(Point::new(x as i32 + dx, y as i32 + dy), Size::new(1, 1));
|
||||||
|
let s = PrimitiveStyleBuilder::new().fill_color(Rgb565::new(r, g, b)).build();
|
||||||
|
p.draw_styled(&s, disp)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleScreensaver {
|
||||||
|
const fn new(id: &'static str, data: &'static [u8]) -> Self {
|
||||||
|
if data.len() != 128 * 128 * 3 {
|
||||||
|
panic!("invalid screensaver size");
|
||||||
|
}
|
||||||
|
SimpleScreensaver { id, data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
79
src/bin/status_check.rs
Normal file
79
src/bin/status_check.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use std::{
|
||||||
|
process::{Command, Stdio},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rusqlite::Connection;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
if args.len() < 2 {
|
||||||
|
panic!("missing argument: database path");
|
||||||
|
}
|
||||||
|
let database = Connection::open(&args[1]).expect("failed to open database");
|
||||||
|
|
||||||
|
let timestamp: i64 = database
|
||||||
|
.query_row(
|
||||||
|
"SELECT time FROM sensor_readings ORDER BY time DESC LIMIT 1",
|
||||||
|
[],
|
||||||
|
|row| Ok(row.get(0).unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let time = std::time::SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
|
|
||||||
|
let recent_reading = time - timestamp <= 12 * 60;
|
||||||
|
let pi_up = Command::new("ping")
|
||||||
|
.args(["-c1", "raspberrypi.fritz.box"])
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap()
|
||||||
|
.success();
|
||||||
|
let traffic_good = if pi_up {
|
||||||
|
let x = Command::new("ssh")
|
||||||
|
.args(["pi@raspberrypi", "vnstat --json h 2"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait_with_output()
|
||||||
|
.unwrap();
|
||||||
|
if x.status.success() {
|
||||||
|
if let Ok(x) = serde_json::from_slice::<serde_json::Value>(&x.stdout) {
|
||||||
|
let it = x.pointer("/interfaces/0/traffic/hour/0/tx");
|
||||||
|
it.map(|x| x.as_u64()).flatten().unwrap_or(0) / (60 * 60 * 1000 * 1000 / 8) > 5
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let nixos_up = Command::new("ping")
|
||||||
|
.args(["-c1", "nixos.fritz.box"])
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap()
|
||||||
|
.success();
|
||||||
|
let sync_good = if nixos_up {
|
||||||
|
if let Ok(x) = ureq::get("http://nixos.fritz.box:12783/").call() {
|
||||||
|
x.status() < 400
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let status = format!(
|
||||||
|
"{} {} {} {} {}",
|
||||||
|
recent_reading, pi_up, traffic_good, nixos_up, sync_good
|
||||||
|
);
|
||||||
|
std::fs::write("/run/user/1000/status.json", status).unwrap();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user