Start scheduler, screensaver and action framework

This commit is contained in:
FliegendeWurst 2023-10-04 08:59:56 +02:00
parent f87f7b5001
commit d51b298128
11 changed files with 205 additions and 30 deletions

5
.gitignore vendored
View File

@ -4,3 +4,8 @@ events_weekly.json
events.json
sensors.db
/result
/box.svg
/mockup.png
/*.aes
/*.py
/*.txt

3
src/bin/action/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub enum Action {
Screensaver(&'static str),
}

View File

@ -15,7 +15,7 @@ use embedded_graphics::{
text::{renderer::CharacterStyle, Text},
Drawable,
};
use raspi_oled::FrameOutput;
use rppal::{
gpio::Gpio,
spi::{Bus, Mode, SlaveSelect, Spi},
@ -187,7 +187,7 @@ where
.font(&FONT_6X9)
.text_color(Rgb565::new(0xff, 0xff, 0xff))
.build();
let mut text_style_4x6 = MonoTextStyleBuilder::new()
let text_style_4x6 = MonoTextStyleBuilder::new()
.font(&FONT_4X6)
.text_color(Rgb565::new(0xff, 0xff, 0xff))
.build();

View File

@ -7,11 +7,12 @@ use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
pixelcolor::Rgb565,
prelude::{DrawTarget, Point, Size},
prelude::{DrawTarget, Point},
text::Text,
Drawable,
};
use gpiocdev::line::{Bias, EdgeDetection, Value};
use rand_xoshiro::Xoroshiro128StarStar;
use raspi_oled::FrameOutput;
use rppal::{
gpio::{Gpio, OutputPin},
@ -22,8 +23,12 @@ use ssd1351::display::display::Ssd1351;
use time::OffsetDateTime;
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
static STAR: &'static [u8] = include_bytes!("../star.raw");
static RPI: &'static [u8] = include_bytes!("../rpi.raw");
mod action;
mod schedule;
mod screensaver;
pub type Oled = Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>;
pub type Rng = Xoroshiro128StarStar;
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
@ -43,11 +48,7 @@ fn pc_main() {}
fn pc_main() {
use std::num::NonZeroU32;
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable};
use rand_xoshiro::{
rand_core::{RngCore, SeedableRng},
Xoroshiro128StarStar,
};
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
@ -55,6 +56,8 @@ fn pc_main() {
window::WindowBuilder,
};
use crate::screensaver::{Screensaver, DUOLINGO};
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(128, 128))
@ -108,13 +111,16 @@ fn pc_main() {
.unwrap();
// 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;
//loop_iter(&mut disp).unwrap();
/*
let mut time = OffsetDateTime::now_utc().to_timezone(BERLIN);
//time += Duration::new(iters * 60, 0);
disp.clear(Rgb565::new(0, 0, 0)).unwrap();
display_clock(&mut disp, &time).unwrap();
*/
DUOLINGO.draw(&mut disp, &mut rng).unwrap();
/*
let iters = iters % 300;
let (s, c) = (iters as f32 * 0.1).sin_cos();
@ -220,7 +226,7 @@ fn rpi_main() {
main_loop(disp);
}
fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) {
fn main_loop(mut disp: Oled) {
disp.clear(BLACK).unwrap();
let mut last_min = 0xff;
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,
)
.draw(disp)?;
Text::new(
&":",
Point::new(64 - 3 + dx, 18 + 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,

View File

@ -1,31 +1,24 @@
use std::{
fs, thread,
thread,
time::{Duration, Instant},
};
use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{
draw_target::DrawTarget,
mono_font::{
ascii::{FONT_10X20, FONT_5X8, FONT_6X9, FONT_9X15},
MonoTextStyleBuilder,
},
pixelcolor::{BinaryColor, Rgb565},
prelude::{OriginDimensions, Point, Primitive, Size},
primitives::{PrimitiveStyleBuilder, Rectangle},
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
pixelcolor::Rgb565,
text::Text,
Drawable,
};
use embedded_hal::digital::v2::OutputPin;
use image::{ImageBuffer, Rgb};
use linux_embedded_hal::I2cdev;
use rppal::{
gpio::Gpio,
hal::Delay,
spi::{Bus, Mode, SlaveSelect, Spi},
};
//use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}};
use time::{format_description, OffsetDateTime, PrimitiveDateTime};
//use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
fn main() {

31
src/bin/schedule/mod.rs Normal file
View 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"));

Binary file not shown.

View 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
View 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();
}