diff --git a/.gitignore b/.gitignore index d509312..d4d0d73 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ events_weekly.json events.json sensors.db /result +/box.svg +/mockup.png +/*.aes +/*.py +/*.txt \ No newline at end of file diff --git a/src/bin/action/mod.rs b/src/bin/action/mod.rs new file mode 100644 index 0000000..5e31354 --- /dev/null +++ b/src/bin/action/mod.rs @@ -0,0 +1,3 @@ +pub enum Action { + Screensaver(&'static str), +} diff --git a/src/bin/display_all.rs b/src/bin/display_all.rs index 17ed79b..36d20dc 100644 --- a/src/bin/display_all.rs +++ b/src/bin/display_all.rs @@ -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(); diff --git a/src/bin/main_loop.rs b/src/bin/main_loop.rs index f867d8c..633c814 100644 --- a/src/bin/main_loop.rs +++ b/src/bin/main_loop.rs @@ -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>; +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>) { +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>(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, diff --git a/src/bin/rgb_test.rs b/src/bin/rgb_test.rs index 5b583a2..62a9297 100644 --- a/src/bin/rgb_test.rs +++ b/src/bin/rgb_test.rs @@ -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() { diff --git a/src/bin/schedule/mod.rs b/src/bin/schedule/mod.rs new file mode 100644 index 0000000..f30e7d4 --- /dev/null +++ b/src/bin/schedule/mod.rs @@ -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")); diff --git a/src/bin/screensaver/duolingo.raw b/src/bin/screensaver/duolingo.raw new file mode 100644 index 0000000..1344903 Binary files /dev/null and b/src/bin/screensaver/duolingo.raw differ diff --git a/src/bin/screensaver/mod.rs b/src/bin/screensaver/mod.rs new file mode 100644 index 0000000..e37f197 --- /dev/null +++ b/src/bin/screensaver/mod.rs @@ -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>(&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>(&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")); diff --git a/src/rpi.raw b/src/bin/screensaver/rpi.raw similarity index 100% rename from src/rpi.raw rename to src/bin/screensaver/rpi.raw diff --git a/src/star.raw b/src/bin/screensaver/star.raw similarity index 100% rename from src/star.raw rename to src/bin/screensaver/star.raw diff --git a/src/bin/status_check.rs b/src/bin/status_check.rs new file mode 100644 index 0000000..b712bc5 --- /dev/null +++ b/src/bin/status_check.rs @@ -0,0 +1,79 @@ +use std::{ + process::{Command, Stdio}, + time::SystemTime, +}; + +use rusqlite::Connection; + +fn main() { + let args = std::env::args().collect::>(); + 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::(&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(); +}