mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-08 13:20:37 +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
|
||||
sensors.db
|
||||
/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},
|
||||
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();
|
||||
|
@ -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,
|
||||
|
@ -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
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