mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-22 02:14:58 +00:00
Compare commits
No commits in common. "9c7dc2a897096ed71e96eb13a73380761b2caac2" and "ad9cea9378e95dd097cf63a29ebadb1eec7ae8da" have entirely different histories.
9c7dc2a897
...
ad9cea9378
1332
Cargo.lock
generated
1332
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -11,8 +11,8 @@ embedded-graphics = "0.8.1"
|
|||||||
linux-embedded-hal = "0.3.0"
|
linux-embedded-hal = "0.3.0"
|
||||||
embedded-hal = "0.2.5"
|
embedded-hal = "0.2.5"
|
||||||
libc = "0.2.98"
|
libc = "0.2.98"
|
||||||
rusqlite = "0.32.1"
|
rusqlite = "0.27.0"
|
||||||
time = { version = "0.3.9", features = ["parsing", "formatting"] }
|
time = { version = "0.3.9", features = ["parsing"] }
|
||||||
time-tz = "2"
|
time-tz = "2"
|
||||||
image = { version = "0.24.1", optional = true }
|
image = { version = "0.24.1", optional = true }
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
@ -21,20 +21,19 @@ serde = "1.0.136"
|
|||||||
rppal = { version = "0.14.1", features = ["hal"] }
|
rppal = { version = "0.14.1", features = ["hal"] }
|
||||||
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "3de5be50bd9a59391c669aec8357923a56d121f6" }
|
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "3de5be50bd9a59391c669aec8357923a56d121f6" }
|
||||||
display-interface-spi = "0.4.1"
|
display-interface-spi = "0.4.1"
|
||||||
ureq = { version = "=3.0.0-rc2", default-features = false, features = ["rustls"] }
|
ureq = { version = "2.4.0", default-features = false }
|
||||||
winit = { version = "0.28.7", optional = true }
|
winit = { version = "0.28.7", optional = true }
|
||||||
softbuffer = { version = "0.3.1", optional = true }
|
softbuffer = { version = "0.3.1", optional = true }
|
||||||
rand_xoshiro = "0.6.0"
|
rand_xoshiro = "0.6.0"
|
||||||
gpiocdev = "0.7.2"
|
gpiocdev = "0.6.0"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
andotp-import = "0.1.0"
|
andotp-import = "0.1.0"
|
||||||
totp-rs = "5.4.0"
|
totp-rs = "5.4.0"
|
||||||
color_space = "0.5.4"
|
|
||||||
#gpio-am2302-rs = { git = "https://github.com/FliegendeWurst/gpio-am2302-rs" }
|
#gpio-am2302-rs = { git = "https://github.com/FliegendeWurst/gpio-am2302-rs" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
pc = ["winit", "softbuffer", "image"]
|
pc = ["winit", "softbuffer", "image"]
|
||||||
default = ["pc"]
|
default = [ "pc" ]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
rustPlatform.buildRustPackage {
|
rustPlatform.buildRustPackage {
|
||||||
pname = "raspi-oled";
|
pname = "raspi-oled";
|
||||||
version = "unstable-infdev-7";
|
version = "unstable-infdev-4";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ rustPlatform.buildRustPackage {
|
|||||||
|
|
||||||
nativeBuildInputs = [ pkg-config ];
|
nativeBuildInputs = [ pkg-config ];
|
||||||
|
|
||||||
cargoBuildFlags = [ "--no-default-features" "--bin" "main_loop" ];
|
cargoBuildFlags = [ "--no-default-features" "--bin" "take_measurement" ];
|
||||||
|
|
||||||
buildInputs = [ sqlite ];
|
buildInputs = [ sqlite ];
|
||||||
|
|
||||||
|
@ -321,8 +321,8 @@ impl<D: DrawTarget<Color = Rgb565>> Draw<D> for Measurements {
|
|||||||
}
|
}
|
||||||
if time_until_first.is_none()
|
if time_until_first.is_none()
|
||||||
&& (i > 0
|
&& (i > 0
|
||||||
|| event.1 > time.hour() as i32
|
|| event.1 > time.hour() as i32 || (event.1 == time.hour() as i32
|
||||||
|| (event.1 == time.hour() as i32 && event.2 >= time.minute() as i32))
|
&& event.2 >= time.minute() as i32))
|
||||||
{
|
{
|
||||||
time_until_first = Some(
|
time_until_first = Some(
|
||||||
((i * 24 + event.1) * 60 + event.2) * 60
|
((i * 24 + event.1) * 60 + event.2) * 60
|
||||||
|
@ -22,8 +22,8 @@ use rppal::{
|
|||||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
};
|
};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use schedule::{github_notifications::GithubNotifications, Schedule};
|
use schedule::Schedule;
|
||||||
use screensaver::{BearReminder, Screensaver, TimeDisplay};
|
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};
|
||||||
@ -37,8 +37,6 @@ pub type Oled = Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>;
|
|||||||
pub type Rng = Xoroshiro128StarStar;
|
pub type Rng = Xoroshiro128StarStar;
|
||||||
|
|
||||||
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
|
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
|
||||||
/// Delay after drawing a frame in milliseconds.
|
|
||||||
const FRAME_INTERVAL: u64 = 66;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if rppal::system::DeviceInfo::new().is_ok() {
|
if rppal::system::DeviceInfo::new().is_ok() {
|
||||||
@ -48,9 +46,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Context<D: DrawTarget<Color = Rgb565>> {
|
pub trait Context {
|
||||||
fn do_draw(&self, drawable: Box<dyn Draw<D>>);
|
|
||||||
|
|
||||||
fn do_action(&self, action: Action);
|
fn do_action(&self, action: Action);
|
||||||
|
|
||||||
fn active_count(&self) -> usize;
|
fn active_count(&self) -> usize;
|
||||||
@ -62,7 +58,7 @@ pub trait Context<D: DrawTarget<Color = Rgb565>> {
|
|||||||
|
|
||||||
pub struct ContextDefault<D: DrawTarget<Color = Rgb565>> {
|
pub struct ContextDefault<D: DrawTarget<Color = Rgb565>> {
|
||||||
screensavers: Vec<Box<dyn Screensaver<D>>>,
|
screensavers: Vec<Box<dyn Screensaver<D>>>,
|
||||||
scheduled: Vec<Box<dyn Schedule<D>>>,
|
scheduled: Vec<Box<dyn Schedule>>,
|
||||||
active: RefCell<Vec<Box<dyn Draw<D>>>>,
|
active: RefCell<Vec<Box<dyn Draw<D>>>>,
|
||||||
database: Rc<RefCell<Connection>>,
|
database: Rc<RefCell<Connection>>,
|
||||||
}
|
}
|
||||||
@ -74,17 +70,10 @@ impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
|||||||
screensavers.push(Box::new(draw::Measurements::temps()));
|
screensavers.push(Box::new(draw::Measurements::temps()));
|
||||||
screensavers.push(Box::new(draw::Measurements::events()));
|
screensavers.push(Box::new(draw::Measurements::events()));
|
||||||
let database = Connection::open("sensors.db").expect("failed to open database");
|
let database = Connection::open("sensors.db").expect("failed to open database");
|
||||||
let mut scheduled = schedule::reminders();
|
|
||||||
scheduled.push(Box::new(GithubNotifications {
|
|
||||||
pat: env::var("GITHUB_PAT").expect("no env var GITHUB_PAT set"),
|
|
||||||
last_modified: RefCell::new(None),
|
|
||||||
last_call: RefCell::new(OffsetDateTime::now_utc().to_timezone(BERLIN) - time::Duration::seconds(50)),
|
|
||||||
}));
|
|
||||||
scheduled.push(Box::new(BearReminder::default()));
|
|
||||||
ContextDefault {
|
ContextDefault {
|
||||||
database: Rc::new(RefCell::new(database)),
|
database: Rc::new(RefCell::new(database)),
|
||||||
screensavers,
|
screensavers,
|
||||||
scheduled,
|
scheduled: schedule::reminders(),
|
||||||
active: RefCell::new(vec![Box::new(TimeDisplay::new())]),
|
active: RefCell::new(vec![Box::new(TimeDisplay::new())]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,11 +112,7 @@ impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Context<D> for ContextDefault<D> {
|
impl<D: DrawTarget<Color = Rgb565>> Context for ContextDefault<D> {
|
||||||
fn do_draw(&self, drawable: Box<dyn Draw<D>>) {
|
|
||||||
self.active.borrow_mut().push(drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_action(&self, action: Action) {
|
fn do_action(&self, action: Action) {
|
||||||
match action {
|
match action {
|
||||||
Action::Screensaver(id) => {
|
Action::Screensaver(id) => {
|
||||||
@ -241,7 +226,7 @@ fn pc_main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// redraw
|
// redraw
|
||||||
if Instant::now().duration_since(start) > Duration::from_millis(iters * FRAME_INTERVAL) {
|
if Instant::now().duration_since(start) > Duration::from_millis(iters * 66) {
|
||||||
iters += 1;
|
iters += 1;
|
||||||
buffer_dirty = ctx.loop_iter(&mut disp, &mut rng);
|
buffer_dirty = ctx.loop_iter(&mut disp, &mut rng);
|
||||||
}
|
}
|
||||||
@ -443,6 +428,6 @@ fn main_loop(mut disp: Oled, mut ctx: ContextDefault<Oled>) {
|
|||||||
if dirty {
|
if dirty {
|
||||||
let _ = disp.flush(); // ignore bus write errors, they are harmless
|
let _ = disp.flush(); // ignore bus write errors, they are harmless
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(FRAME_INTERVAL));
|
thread::sleep(Duration::from_millis(66));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,5 +23,5 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_json(url: &str) -> Result<String, Box<dyn Error>> {
|
fn get_json(url: &str) -> Result<String, Box<dyn Error>> {
|
||||||
Ok(ureq::get(url).call()?.into_body().read_to_string()?)
|
Ok(ureq::get(url).call()?.into_string()?)
|
||||||
}
|
}
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use color_space::{Hsv, ToRgb};
|
|
||||||
use embedded_graphics::{
|
|
||||||
mono_font::{iso_8859_10::FONT_8X13, MonoTextStyleBuilder},
|
|
||||||
pixelcolor::Rgb565,
|
|
||||||
prelude::{DrawTarget, Point, RgbColor},
|
|
||||||
text::Text,
|
|
||||||
Drawable,
|
|
||||||
};
|
|
||||||
use raspi_oled::github::get_new_notifications;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
screensaver::{SimpleScreensaver, GITHUB},
|
|
||||||
Draw,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::Schedule;
|
|
||||||
|
|
||||||
pub struct GithubNotifications {
|
|
||||||
pub pat: String,
|
|
||||||
pub last_modified: RefCell<Option<String>>,
|
|
||||||
pub last_call: RefCell<time::OffsetDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Schedule<D> for GithubNotifications {
|
|
||||||
fn check(&self, _ctx: &dyn crate::Context<D>, time: time::OffsetDateTime) -> bool {
|
|
||||||
let time_since_last = time - *self.last_call.borrow();
|
|
||||||
time_since_last.whole_minutes() >= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(&self, ctx: &dyn crate::Context<D>, time: time::OffsetDateTime) {
|
|
||||||
*self.last_call.borrow_mut() = time;
|
|
||||||
let last_modified = self.last_modified.borrow().clone();
|
|
||||||
if let Ok((notifications, last_modified)) = get_new_notifications(&self.pat, last_modified.as_deref()) {
|
|
||||||
*self.last_modified.borrow_mut() = last_modified;
|
|
||||||
let relevant: Vec<_> = notifications
|
|
||||||
.into_iter()
|
|
||||||
.filter(|x| x.reason != "state_change" && x.unread)
|
|
||||||
.collect();
|
|
||||||
if relevant.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let max_lines = 8;
|
|
||||||
let max_line_length = 16;
|
|
||||||
let mut lines = vec![];
|
|
||||||
let mut relevant = relevant.into_iter();
|
|
||||||
while lines.len() < max_lines {
|
|
||||||
if let Some(x) = relevant.next() {
|
|
||||||
let url = x.subject.url;
|
|
||||||
let Some(url) = url else {
|
|
||||||
lines.push("no url".to_owned());
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let parts: Vec<_> = url.split('/').collect();
|
|
||||||
if parts.len() < 8 {
|
|
||||||
lines.push("too few url parts".to_owned());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lines.push(format!("{} #{}", parts[5], parts[7]));
|
|
||||||
if lines.len() < max_lines {
|
|
||||||
let mut desc = format!(" {}", x.subject.title);
|
|
||||||
desc.truncate(desc.floor_char_boundary(max_line_length));
|
|
||||||
lines.push(desc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let remaining = relevant.count();
|
|
||||||
if remaining != 0 {
|
|
||||||
lines.push(format!("... {} more", remaining));
|
|
||||||
}
|
|
||||||
ctx.do_draw(Box::new(GithubNotificationsDraw {
|
|
||||||
calls: RefCell::new(0),
|
|
||||||
screen: &GITHUB,
|
|
||||||
lines,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GithubNotificationsDraw {
|
|
||||||
calls: RefCell<usize>,
|
|
||||||
screen: &'static SimpleScreensaver,
|
|
||||||
lines: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for GithubNotificationsDraw {
|
|
||||||
fn draw(&self, disp: &mut D, _rng: &mut crate::Rng) -> Result<bool, D::Error> {
|
|
||||||
let calls = *self.calls.borrow();
|
|
||||||
if calls < 40 {
|
|
||||||
let hue = calls as f64 / 40.0 * 360.0;
|
|
||||||
let hsv = Hsv::new(hue, 1.0, 1.0);
|
|
||||||
let rgb = hsv.to_rgb();
|
|
||||||
let r = rgb.r as u8 >> 3;
|
|
||||||
let g = rgb.g as u8 >> 2;
|
|
||||||
let b = rgb.b as u8 >> 3;
|
|
||||||
self.screen.draw_all_colored(disp, Rgb565::new(r, g, b))?;
|
|
||||||
} else {
|
|
||||||
disp.clear(Rgb565::BLACK)?;
|
|
||||||
// fit 9 lines
|
|
||||||
let text_style_clock = MonoTextStyleBuilder::new()
|
|
||||||
.font(&FONT_8X13)
|
|
||||||
.text_color(Rgb565::WHITE)
|
|
||||||
.build();
|
|
||||||
for (y, line) in self.lines.iter().enumerate() {
|
|
||||||
Text::new(line, Point::new(0, (12 + y * 14) as _), text_style_clock).draw(disp)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*self.calls.borrow_mut() += 1;
|
|
||||||
Ok(calls < 90)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expired(&self) -> bool {
|
|
||||||
*self.calls.borrow() > 90
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
&*self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
||||||
&mut *self
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +1,18 @@
|
|||||||
use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget};
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{action::Action, Context};
|
use crate::{action::Action, Context};
|
||||||
|
|
||||||
pub mod github_notifications;
|
|
||||||
|
|
||||||
/// 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.
|
||||||
pub trait Schedule<D>
|
pub trait Schedule {
|
||||||
where
|
fn check_and_do(&self, ctx: &dyn Context, time: OffsetDateTime) {
|
||||||
D: DrawTarget<Color = Rgb565>,
|
|
||||||
{
|
|
||||||
fn check_and_do(&self, ctx: &dyn Context<D>, time: OffsetDateTime) {
|
|
||||||
if self.check(ctx, time) {
|
if self.check(ctx, time) {
|
||||||
self.execute(ctx, time);
|
self.execute(ctx, time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self, ctx: &dyn Context<D>, time: OffsetDateTime) -> bool;
|
fn check(&self, ctx: &dyn Context, time: OffsetDateTime) -> bool;
|
||||||
fn execute(&self, ctx: &dyn Context<D>, time: OffsetDateTime);
|
fn execute(&self, ctx: &dyn Context, time: OffsetDateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@ -29,12 +23,12 @@ pub struct Reminder {
|
|||||||
should_beep: bool,
|
should_beep: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Schedule<D> for Reminder {
|
impl Schedule for Reminder {
|
||||||
fn check(&self, ctx: &dyn Context<D>, time: OffsetDateTime) -> bool {
|
fn check(&self, ctx: &dyn Context, time: OffsetDateTime) -> bool {
|
||||||
time.hour() == self.hour && time.minute() == self.minute && ctx.active_count() == 1
|
time.hour() == self.hour && time.minute() == self.minute && ctx.active_count() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(&self, ctx: &dyn Context<D>, _time: OffsetDateTime) {
|
fn execute(&self, ctx: &dyn Context, _time: OffsetDateTime) {
|
||||||
if self.should_beep {
|
if self.should_beep {
|
||||||
ctx.enable_pwm();
|
ctx.enable_pwm();
|
||||||
}
|
}
|
||||||
@ -57,6 +51,6 @@ static DUOLINGO: Reminder = Reminder::new(11, 40, Action::Screensaver("duolingo"
|
|||||||
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 40, Action::Screensaver("duolingo"), false);
|
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 40, Action::Screensaver("duolingo"), false);
|
||||||
static FOOD: Reminder = Reminder::new(13, 15, Action::Screensaver("plate"), false);
|
static FOOD: Reminder = Reminder::new(13, 15, Action::Screensaver("plate"), false);
|
||||||
|
|
||||||
pub fn reminders<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Schedule<D>>> {
|
pub fn reminders() -> Vec<Box<dyn Schedule>> {
|
||||||
vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT), Box::new(FOOD)]
|
vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT), Box::new(FOOD)]
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -3,7 +3,6 @@ use std::cell::RefCell;
|
|||||||
use std::sync::atomic::{AtomicU32, AtomicU64};
|
use std::sync::atomic::{AtomicU32, AtomicU64};
|
||||||
|
|
||||||
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
||||||
use embedded_graphics::prelude::RgbColor;
|
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
mono_font::MonoTextStyleBuilder,
|
mono_font::MonoTextStyleBuilder,
|
||||||
pixelcolor::Rgb565,
|
pixelcolor::Rgb565,
|
||||||
@ -16,7 +15,6 @@ use rand_xoshiro::rand_core::RngCore;
|
|||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
||||||
|
|
||||||
use crate::schedule::Schedule;
|
|
||||||
use crate::{Draw, Rng};
|
use crate::{Draw, Rng};
|
||||||
|
|
||||||
pub static SPEED: AtomicU64 = AtomicU64::new(32);
|
pub static SPEED: AtomicU64 = AtomicU64::new(32);
|
||||||
@ -104,44 +102,6 @@ impl SimpleScreensaver {
|
|||||||
iters: AtomicU32::new(0),
|
iters: AtomicU32::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_all_colored<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, color: Rgb565) -> Result<(), D::Error> {
|
|
||||||
disp.fill_contiguous(
|
|
||||||
&Rectangle::new((0, 0).into(), (128, 128).into()),
|
|
||||||
(0..128 * 128).map(|idx| {
|
|
||||||
let (red, green, blue) = (self.data[3 * idx], self.data[3 * idx + 1], self.data[3 * idx + 2]);
|
|
||||||
let r = red >> 3;
|
|
||||||
let g = green >> 2;
|
|
||||||
let b = blue >> 3;
|
|
||||||
if (r, g, b) != (0, 0, 0) {
|
|
||||||
color
|
|
||||||
} else {
|
|
||||||
Rgb565::BLACK
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_all<D: DrawTarget<Color = Rgb565>>(&self, disp: &mut D, flipped: bool) -> Result<(), D::Error> {
|
|
||||||
disp.fill_contiguous(
|
|
||||||
&Rectangle::new((0, 0).into(), (128, 128).into()),
|
|
||||||
(0..128 * 128).map(|idx| {
|
|
||||||
let (mut red, mut green, mut blue) =
|
|
||||||
(self.data[3 * idx], self.data[3 * idx + 1], self.data[3 * idx + 2]);
|
|
||||||
if flipped {
|
|
||||||
red = 255 - red;
|
|
||||||
green = 255 - green;
|
|
||||||
blue = 255 - blue;
|
|
||||||
}
|
|
||||||
let r = red >> 3;
|
|
||||||
let g = green >> 2;
|
|
||||||
let b = blue >> 3;
|
|
||||||
Rgb565::new(r, g, b)
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
||||||
@ -235,8 +195,6 @@ pub static RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!
|
|||||||
pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw"));
|
pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw"));
|
||||||
pub static SPAGHETTI: SimpleScreensaver = SimpleScreensaver::new("spaghetti", include_bytes!("./spaghetti.raw"));
|
pub static SPAGHETTI: SimpleScreensaver = SimpleScreensaver::new("spaghetti", include_bytes!("./spaghetti.raw"));
|
||||||
pub static PLATE: SimpleScreensaver = SimpleScreensaver::new("plate", include_bytes!("./plate.raw"));
|
pub static PLATE: SimpleScreensaver = SimpleScreensaver::new("plate", include_bytes!("./plate.raw"));
|
||||||
pub static GITHUB: SimpleScreensaver = SimpleScreensaver::new("github", include_bytes!("./github.raw"));
|
|
||||||
pub static TEDDY_BEAR: SimpleScreensaver = SimpleScreensaver::new("teddy_bear", include_bytes!("./teddy_bear.raw"));
|
|
||||||
|
|
||||||
pub fn screensavers<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Screensaver<D>>> {
|
pub fn screensavers<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Screensaver<D>>> {
|
||||||
vec![
|
vec![
|
||||||
@ -247,52 +205,3 @@ pub fn screensavers<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Screensaver<
|
|||||||
Box::new(PLATE.clone()),
|
Box::new(PLATE.clone()),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BearReminder;
|
|
||||||
|
|
||||||
impl Default for BearReminder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Schedule<D> for BearReminder {
|
|
||||||
fn check(&self, _ctx: &dyn crate::Context<D>, time: OffsetDateTime) -> bool {
|
|
||||||
time.hour() == 21 && time.minute() == 30 && time.to_julian_day() % 2 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(&self, ctx: &dyn crate::Context<D>, _time: OffsetDateTime) {
|
|
||||||
ctx.do_draw(Box::new(BearDraw { calls: RefCell::new(0) }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BearDraw {
|
|
||||||
calls: RefCell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for BearDraw {
|
|
||||||
fn draw(&self, disp: &mut D, _rng: &mut Rng) -> Result<bool, <D as DrawTarget>::Error> {
|
|
||||||
let mut calls = self.calls.borrow_mut();
|
|
||||||
*calls += 1;
|
|
||||||
|
|
||||||
if *calls > 73 {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEDDY_BEAR.draw_all(disp, *calls % 8 >= 4)?;
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expired(&self) -> bool {
|
|
||||||
*self.calls.borrow() > 110
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
&*self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
&mut *self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -64,7 +64,7 @@ fn main() {
|
|||||||
.success();
|
.success();
|
||||||
let sync_good = if nixos_up {
|
let sync_good = if nixos_up {
|
||||||
if let Ok(x) = ureq::get("http://nixos.fritz.box:12783/").call() {
|
if let Ok(x) = ureq::get("http://nixos.fritz.box:12783/").call() {
|
||||||
x.status().as_u16() < 400
|
x.status() < 400
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
222
src/github.rs
222
src/github.rs
@ -1,222 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
use time::{macros::format_description, PrimitiveDateTime};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Notification {
|
|
||||||
pub id: String,
|
|
||||||
pub repository: Repository,
|
|
||||||
pub subject: Subject,
|
|
||||||
pub reason: String,
|
|
||||||
pub unread: bool,
|
|
||||||
pub updated_at: String,
|
|
||||||
pub last_read_at: Option<String>,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Repository {
|
|
||||||
pub id: u64,
|
|
||||||
|
|
||||||
pub node_id: Option<String>,
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
pub full_name: Option<String>,
|
|
||||||
|
|
||||||
pub owner: Option<serde_json::Value>,
|
|
||||||
|
|
||||||
pub private: Option<bool>,
|
|
||||||
|
|
||||||
pub html_url: Option<String>,
|
|
||||||
|
|
||||||
pub description: Option<String>,
|
|
||||||
|
|
||||||
pub fork: Option<bool>,
|
|
||||||
pub url: String,
|
|
||||||
|
|
||||||
pub archive_url: Option<String>,
|
|
||||||
|
|
||||||
pub assignees_url: Option<String>,
|
|
||||||
|
|
||||||
pub blobs_url: Option<String>,
|
|
||||||
|
|
||||||
pub branches_url: Option<String>,
|
|
||||||
|
|
||||||
pub collaborators_url: Option<String>,
|
|
||||||
|
|
||||||
pub comments_url: Option<String>,
|
|
||||||
|
|
||||||
pub commits_url: Option<String>,
|
|
||||||
|
|
||||||
pub compare_url: Option<String>,
|
|
||||||
|
|
||||||
pub contents_url: Option<String>,
|
|
||||||
|
|
||||||
pub contributors_url: Option<String>,
|
|
||||||
|
|
||||||
pub deployments_url: Option<String>,
|
|
||||||
|
|
||||||
pub downloads_url: Option<String>,
|
|
||||||
|
|
||||||
pub events_url: Option<String>,
|
|
||||||
|
|
||||||
pub forks_url: Option<String>,
|
|
||||||
|
|
||||||
pub git_commits_url: Option<String>,
|
|
||||||
|
|
||||||
pub git_refs_url: Option<String>,
|
|
||||||
|
|
||||||
pub git_tags_url: Option<String>,
|
|
||||||
|
|
||||||
pub git_url: Option<String>,
|
|
||||||
|
|
||||||
pub issue_comment_url: Option<String>,
|
|
||||||
|
|
||||||
pub issue_events_url: Option<String>,
|
|
||||||
|
|
||||||
pub issues_url: Option<String>,
|
|
||||||
|
|
||||||
pub keys_url: Option<String>,
|
|
||||||
|
|
||||||
pub labels_url: Option<String>,
|
|
||||||
|
|
||||||
pub languages_url: Option<String>,
|
|
||||||
|
|
||||||
pub merges_url: Option<String>,
|
|
||||||
|
|
||||||
pub milestones_url: Option<String>,
|
|
||||||
|
|
||||||
pub notifications_url: Option<String>,
|
|
||||||
|
|
||||||
pub pulls_url: Option<String>,
|
|
||||||
|
|
||||||
pub releases_url: Option<String>,
|
|
||||||
|
|
||||||
pub ssh_url: Option<String>,
|
|
||||||
|
|
||||||
pub stargazers_url: Option<String>,
|
|
||||||
|
|
||||||
pub statuses_url: Option<String>,
|
|
||||||
|
|
||||||
pub subscribers_url: Option<String>,
|
|
||||||
|
|
||||||
pub subscription_url: Option<String>,
|
|
||||||
|
|
||||||
pub tags_url: Option<String>,
|
|
||||||
|
|
||||||
pub teams_url: Option<String>,
|
|
||||||
|
|
||||||
pub trees_url: Option<String>,
|
|
||||||
|
|
||||||
pub clone_url: Option<String>,
|
|
||||||
|
|
||||||
pub mirror_url: Option<String>,
|
|
||||||
|
|
||||||
pub hooks_url: Option<String>,
|
|
||||||
|
|
||||||
pub svn_url: Option<String>,
|
|
||||||
|
|
||||||
pub homepage: Option<String>,
|
|
||||||
|
|
||||||
pub language: Option<::serde_json::Value>,
|
|
||||||
|
|
||||||
pub forks_count: Option<u32>,
|
|
||||||
|
|
||||||
pub stargazers_count: Option<u32>,
|
|
||||||
|
|
||||||
pub watchers_count: Option<u32>,
|
|
||||||
|
|
||||||
pub size: Option<u32>,
|
|
||||||
|
|
||||||
pub default_branch: Option<String>,
|
|
||||||
|
|
||||||
pub open_issues_count: Option<u32>,
|
|
||||||
|
|
||||||
pub is_template: Option<bool>,
|
|
||||||
|
|
||||||
pub topics: Option<Vec<String>>,
|
|
||||||
|
|
||||||
pub has_issues: Option<bool>,
|
|
||||||
|
|
||||||
pub has_projects: Option<bool>,
|
|
||||||
|
|
||||||
pub has_wiki: Option<bool>,
|
|
||||||
|
|
||||||
pub has_pages: Option<bool>,
|
|
||||||
|
|
||||||
pub has_downloads: Option<bool>,
|
|
||||||
|
|
||||||
pub archived: Option<bool>,
|
|
||||||
|
|
||||||
pub disabled: Option<bool>,
|
|
||||||
|
|
||||||
pub visibility: Option<String>,
|
|
||||||
pub pushed_at: Option<String>,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
|
|
||||||
pub permissions: Option<serde_json::Value>,
|
|
||||||
|
|
||||||
pub allow_rebase_merge: Option<bool>,
|
|
||||||
|
|
||||||
pub template_repository: Option<Box<Repository>>,
|
|
||||||
|
|
||||||
pub allow_squash_merge: Option<bool>,
|
|
||||||
|
|
||||||
pub allow_merge_commit: Option<bool>,
|
|
||||||
|
|
||||||
pub allow_update_branch: Option<bool>,
|
|
||||||
|
|
||||||
pub allow_forking: Option<bool>,
|
|
||||||
|
|
||||||
pub subscribers_count: Option<i64>,
|
|
||||||
|
|
||||||
pub network_count: Option<i64>,
|
|
||||||
|
|
||||||
pub license: Option<serde_json::Value>,
|
|
||||||
|
|
||||||
pub allow_auto_merge: Option<bool>,
|
|
||||||
|
|
||||||
pub delete_branch_on_merge: Option<bool>,
|
|
||||||
|
|
||||||
pub parent: Option<Box<Repository>>,
|
|
||||||
|
|
||||||
pub source: Option<Box<Repository>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Subject {
|
|
||||||
pub title: String,
|
|
||||||
pub url: Option<String>,
|
|
||||||
pub latest_comment_url: Option<String>,
|
|
||||||
pub r#type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get new notifications.
|
|
||||||
/// Returns: notifications and new last-modified value.
|
|
||||||
pub fn get_new_notifications(
|
|
||||||
pat: &str,
|
|
||||||
last_modified: Option<&str>,
|
|
||||||
) -> Result<(Vec<Notification>, Option<String>), Box<dyn Error>> {
|
|
||||||
let mut resp = ureq::get("https://api.github.com/notifications").header("Authorization", &format!("Bearer {pat}"));
|
|
||||||
if let Some(val) = last_modified {
|
|
||||||
resp = resp.header("If-Modified-Since", val);
|
|
||||||
}
|
|
||||||
let json = resp.call()?.into_body().read_to_string()?;
|
|
||||||
let items: Vec<Notification> = serde_json::from_str(&json)?;
|
|
||||||
let new_last_modified = items.get(0).map(|x| x.updated_at.clone());
|
|
||||||
let last_modified = if let Some(lm) = new_last_modified {
|
|
||||||
// parse and increase by five seconds
|
|
||||||
let format = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z");
|
|
||||||
let mut dt = PrimitiveDateTime::parse(&lm, format)?;
|
|
||||||
dt += time::Duration::seconds(5);
|
|
||||||
Some(dt.format(&format)?)
|
|
||||||
} else {
|
|
||||||
last_modified.map(|x| x.to_owned())
|
|
||||||
};
|
|
||||||
Ok((items, last_modified))
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ use std::{
|
|||||||
time::{self, Duration},
|
time::{self, Duration},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "pc")]
|
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
draw_target::DrawTarget,
|
draw_target::DrawTarget,
|
||||||
pixelcolor::Rgb565,
|
pixelcolor::Rgb565,
|
||||||
@ -18,8 +17,6 @@ use gpiocdev::{
|
|||||||
#[cfg(feature = "pc")]
|
#[cfg(feature = "pc")]
|
||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
|
|
||||||
pub mod github;
|
|
||||||
|
|
||||||
#[cfg(feature = "pc")]
|
#[cfg(feature = "pc")]
|
||||||
pub struct FrameOutput {
|
pub struct FrameOutput {
|
||||||
pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||||
@ -46,8 +43,7 @@ impl DrawTarget for FrameOutput {
|
|||||||
{
|
{
|
||||||
for pos in pixels {
|
for pos in pixels {
|
||||||
if pos.0.x < 0
|
if pos.0.x < 0
|
||||||
|| pos.0.y < 0
|
|| pos.0.y < 0 || pos.0.x as u32 >= self.buffer.width()
|
||||||
|| pos.0.x as u32 >= self.buffer.width()
|
|
||||||
|| pos.0.y as u32 >= self.buffer.height()
|
|| pos.0.y as u32 >= self.buffer.height()
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
Loading…
Reference in New Issue
Block a user