mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-22 02:14:58 +00:00
Implement measurements view
This commit is contained in:
parent
3e38684c17
commit
6335eb818e
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -493,9 +493,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-graphics"
|
name = "embedded-graphics"
|
||||||
version = "0.7.1"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "750082c65094fbcc4baf9ba31583ce9a8bb7f52cadfb96f6164b1bc7f922f32b"
|
checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@ -506,9 +506,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-graphics-core"
|
name = "embedded-graphics-core"
|
||||||
version = "0.3.3"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa"
|
checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@ -526,10 +526,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-hal"
|
name = "embedded-hal"
|
||||||
version = "1.0.0-alpha.5"
|
version = "1.0.0-alpha.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a554c04648665230499563ccdfd1fcd719ef2d5a0af54bdc81c5d877fb556db4"
|
checksum = "129b101ddfee640565f7c07b301a31d95aa21e5acef21a491c307139f5fa4c91"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal-nb"
|
||||||
|
version = "1.0.0-alpha.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e0760ec0a3bf76859d5e33f39542af103f157d5b2ecfb00ace56dd461472e3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"embedded-hal 1.0.0-alpha.9",
|
||||||
"nb 1.1.0",
|
"nb 1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -594,9 +601,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "float-cmp"
|
name = "float-cmp"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
@ -1062,9 +1069,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "micromath"
|
name = "micromath"
|
||||||
version = "1.1.1"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94"
|
checksum = "39617bc909d64b068dcffd0e3e31679195b5576d0c83fadc52690268cc2b2b55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
@ -1502,7 +1509,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"linux-embedded-hal",
|
"linux-embedded-hal",
|
||||||
"rand_xoshiro",
|
"rand_xoshiro",
|
||||||
"rppal 0.13.1",
|
"rppal",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@ -1581,26 +1588,13 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rppal"
|
name = "rppal"
|
||||||
version = "0.12.0"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb26758c881b4837b2f4aef569e4251f75388e36b37204e1804ef429c220121c"
|
checksum = "612e1a22e21f08a246657c6433fe52b773ae43d07c9ef88ccfc433cc8683caba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-hal 0.2.7",
|
"embedded-hal 0.2.7",
|
||||||
"lazy_static",
|
"embedded-hal 1.0.0-alpha.9",
|
||||||
"libc",
|
"embedded-hal-nb",
|
||||||
"nb 0.1.3",
|
|
||||||
"void",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rppal"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c88c9c6248de4d337747b619d8f671055ef48a87dc21b97998833f189a0bbd4f"
|
|
||||||
dependencies = [
|
|
||||||
"embedded-hal 0.2.7",
|
|
||||||
"embedded-hal 1.0.0-alpha.5",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
"libc",
|
||||||
"nb 0.1.3",
|
"nb 0.1.3",
|
||||||
"spin_sleep",
|
"spin_sleep",
|
||||||
@ -1838,7 +1832,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ssd1351"
|
name = "ssd1351"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "git+https://github.com/FliegendeWurst/ssd1351-rust?rev=ce3194474355f4433ffe2a63484f263071a0d54e#ce3194474355f4433ffe2a63484f263071a0d54e"
|
source = "git+https://github.com/FliegendeWurst/ssd1351-rust?rev=3de5be50bd9a59391c669aec8357923a56d121f6#3de5be50bd9a59391c669aec8357923a56d121f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"display-interface",
|
"display-interface",
|
||||||
@ -1846,7 +1840,7 @@ dependencies = [
|
|||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
"embedded-hal 0.2.7",
|
"embedded-hal 0.2.7",
|
||||||
"linux-embedded-hal",
|
"linux-embedded-hal",
|
||||||
"rppal 0.12.0",
|
"rppal",
|
||||||
"simple-signal",
|
"simple-signal",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embedded-graphics = "0.7.1"
|
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"
|
||||||
@ -18,8 +18,8 @@ image = { version = "0.24.1", optional = true }
|
|||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
serde_derive = "1.0.136"
|
serde_derive = "1.0.136"
|
||||||
serde = "1.0.136"
|
serde = "1.0.136"
|
||||||
rppal = { version = "0.13.1", features = ["hal"] }
|
rppal = { version = "0.14.1", features = ["hal"] }
|
||||||
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "ce3194474355f4433ffe2a63484f263071a0d54e" }
|
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "3de5be50bd9a59391c669aec8357923a56d121f6" }
|
||||||
display-interface-spi = "0.4.1"
|
display-interface-spi = "0.4.1"
|
||||||
ureq = { version = "2.4.0", default-features = false }
|
ureq = { version = "2.4.0", default-features = false }
|
||||||
winit = { version = "0.28.7", optional = true }
|
winit = { version = "0.28.7", optional = true }
|
||||||
|
@ -9,7 +9,7 @@ rustPlatform.buildRustPackage {
|
|||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./Cargo.lock;
|
lockFile = ./Cargo.lock;
|
||||||
outputHashes = {
|
outputHashes = {
|
||||||
"ssd1351-0.3.0" = "sha256-D0gnYbZfSsG/K8BN5N8pmMGtGcqWt7/0gN3UXLRsOlc=";
|
"ssd1351-0.3.0" = "sha256-DD7+NhYwUwD/xC+7ZUNKdhcfsSCOQ9NVEy9lcS47Q5E=";
|
||||||
# "gpio-am2302-rs-1.1.0" = "sha256-tyA/R80LtWIXoVEoxHhkmzy0IsMdMH1Oi3FTQ56XjyQ=";
|
# "gpio-am2302-rs-1.1.0" = "sha256-tyA/R80LtWIXoVEoxHhkmzy0IsMdMH1Oi3FTQ56XjyQ=";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -16,39 +16,15 @@ use embedded_graphics::{
|
|||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use raspi_oled::Events;
|
||||||
use rppal::{
|
use rppal::{
|
||||||
gpio::Gpio,
|
gpio::Gpio,
|
||||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
};
|
};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde_derive::Deserialize;
|
|
||||||
//use ssd1306::{I2CDisplayInterface, Ssd1306, size::DisplaySize128x64, rotation::DisplayRotation, mode::DisplayConfig};
|
|
||||||
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
|
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
|
||||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Events {
|
|
||||||
events: Vec<Event>,
|
|
||||||
weekly: Vec<Weekly>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Event {
|
|
||||||
name: String,
|
|
||||||
start_time: String,
|
|
||||||
end_time: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Weekly {
|
|
||||||
name: String,
|
|
||||||
day: i32,
|
|
||||||
hour: i32,
|
|
||||||
minute: i32,
|
|
||||||
duration: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum Status {
|
enum Status {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
BIN
src/bin/draw/font_15x30.png
Normal file
BIN
src/bin/draw/font_15x30.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 781 B |
BIN
src/bin/draw/font_15x30.raw
Normal file
BIN
src/bin/draw/font_15x30.raw
Normal file
Binary file not shown.
422
src/bin/draw/measurements.rs
Normal file
422
src/bin/draw/measurements.rs
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
use std::{fs, ops::Sub, sync::atomic::AtomicBool, time::Duration};
|
||||||
|
|
||||||
|
use embedded_graphics::{
|
||||||
|
image::ImageRaw,
|
||||||
|
mono_font::{
|
||||||
|
ascii::{FONT_10X20, FONT_4X6, FONT_5X8, FONT_6X9, FONT_9X15},
|
||||||
|
mapping::StrGlyphMapping,
|
||||||
|
DecorationDimensions, MonoFont, MonoTextStyleBuilder,
|
||||||
|
},
|
||||||
|
pixelcolor::Rgb565,
|
||||||
|
prelude::*,
|
||||||
|
primitives::{Primitive, PrimitiveStyleBuilder, Rectangle},
|
||||||
|
text::{renderer::CharacterStyle, Text},
|
||||||
|
Drawable,
|
||||||
|
};
|
||||||
|
use raspi_oled::Events;
|
||||||
|
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
|
||||||
|
|
||||||
|
use crate::{screensaver::Screensaver, Context, ContextDefault, Draw};
|
||||||
|
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||||
|
|
||||||
|
static CLOCK_FONT: MonoFont = MonoFont {
|
||||||
|
image: ImageRaw::new(include_bytes!("font_15x30.raw"), 165),
|
||||||
|
glyph_mapping: &StrGlyphMapping::new("0123456789:", 0),
|
||||||
|
character_size: Size::new(15, 30),
|
||||||
|
character_spacing: 0,
|
||||||
|
baseline: 22,
|
||||||
|
underline: DecorationDimensions::default_underline(30),
|
||||||
|
strikethrough: DecorationDimensions::default_strikethrough(30),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Measurements {
|
||||||
|
drawn: AtomicBool,
|
||||||
|
mode: MeasurementsMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum MeasurementsMode {
|
||||||
|
Default,
|
||||||
|
Temps,
|
||||||
|
Events,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Measurements {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
drawn: AtomicBool::new(false),
|
||||||
|
mode: MeasurementsMode::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Measurements {
|
||||||
|
pub fn temps() -> Self {
|
||||||
|
Self {
|
||||||
|
drawn: AtomicBool::new(false),
|
||||||
|
mode: MeasurementsMode::Temps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events() -> Self {
|
||||||
|
Self {
|
||||||
|
drawn: AtomicBool::new(false),
|
||||||
|
mode: MeasurementsMode::Events,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for Measurements {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
match self.mode {
|
||||||
|
MeasurementsMode::Default => "measurements",
|
||||||
|
MeasurementsMode::Temps => "measurements_temps",
|
||||||
|
MeasurementsMode::Events => "measurements_events",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_draw(&self) -> Box<dyn Draw<D>> {
|
||||||
|
Box::new(Measurements {
|
||||||
|
drawn: AtomicBool::new(false),
|
||||||
|
mode: self.mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for Measurements {
|
||||||
|
fn draw_with_ctx(&self, ctx: &ContextDefault<D>, disp: &mut D, rng: &mut crate::Rng) -> Result<bool, D::Error> {
|
||||||
|
if self.drawn.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let events = fs::read_to_string("events.json").expect("failed to read events.json");
|
||||||
|
let events: Events = serde_json::from_str(&events).unwrap();
|
||||||
|
let database = ctx.database();
|
||||||
|
let database = database.borrow_mut();
|
||||||
|
|
||||||
|
let (rh, temp): (i64, i64) = database
|
||||||
|
.query_row(
|
||||||
|
"SELECT humidity, celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 1",
|
||||||
|
[],
|
||||||
|
|row| Ok((row.get(0).unwrap(), row.get(1).unwrap())),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||||
|
|
||||||
|
let mut query = database
|
||||||
|
.prepare("SELECT celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 288")
|
||||||
|
.unwrap();
|
||||||
|
let mut temps: Vec<i32> = query
|
||||||
|
.query_map([], |r| Ok(r.get(0)))
|
||||||
|
.unwrap()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect();
|
||||||
|
let mut global_min = 1000;
|
||||||
|
let mut global_max = 0;
|
||||||
|
let mut vals: Vec<(i32, i32)> = vec![];
|
||||||
|
for hour in temps.chunks_mut(6) {
|
||||||
|
hour.sort();
|
||||||
|
let mut min = hour[1];
|
||||||
|
let mut max = hour[hour.len() - 2];
|
||||||
|
//println!("min {} max {}", min, max);
|
||||||
|
// sanity check value
|
||||||
|
if max > 400 {
|
||||||
|
if vals.is_empty() {
|
||||||
|
max = min;
|
||||||
|
} else {
|
||||||
|
max = vals.last().unwrap().1;
|
||||||
|
}
|
||||||
|
min = min.min(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
global_min = min.min(global_min);
|
||||||
|
global_max = max.max(global_max);
|
||||||
|
vals.push((min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hour = time.hour();
|
||||||
|
let minute = time.minute();
|
||||||
|
|
||||||
|
let text_style_clock = MonoTextStyleBuilder::new()
|
||||||
|
.font(&CLOCK_FONT)
|
||||||
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
let text_style2 = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_9X15)
|
||||||
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
let mut text_style_6x9 = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_6X9)
|
||||||
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
let text_style_4x6 = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_4X6)
|
||||||
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
let text_style4 = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_5X8)
|
||||||
|
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
let rect_style = PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//let text = format!("{}.{}% {}.{}°C", rh / 10, rh % 10, temp / 10, temp % 10);
|
||||||
|
//Text::new(&text, Point::new(0, 10), text_style).draw(disp).unwrap();
|
||||||
|
let hour = format!("{:02}", hour);
|
||||||
|
Text::new(&hour, Point::new(64 - 2, 6 + 20), text_style_clock).draw(disp)?;
|
||||||
|
let minute = format!("{:02}", minute);
|
||||||
|
Text::new(&minute, Point::new(64 + 30 + 4, 6 + 20), text_style_clock).draw(disp)?;
|
||||||
|
Rectangle::new((93, 14).into(), (4, 4).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
Rectangle::new((93, 22).into(), (4, 4).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
|
||||||
|
let rh = format!("{:02}", rh / 10);
|
||||||
|
Text::new(&rh, Point::new(64 + 3, 64 - 4), text_style2).draw(disp)?;
|
||||||
|
Text::new("%", Point::new(64 + 3 + 18, 64 - 4), text_style_6x9).draw(disp)?;
|
||||||
|
let temp_int = format!("{:02}", temp / 10);
|
||||||
|
Text::new(&temp_int, Point::new(64 + 32 + 3, 64 - 4), text_style2).draw(disp)?;
|
||||||
|
Rectangle::new((64 + 32 + 3 + 18, 64 - 4).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
let temp_fract = format!("{}", temp % 10);
|
||||||
|
Text::new(&temp_fract, Point::new(64 + 32 + 3 + 18 + 2, 64 - 4), text_style_6x9).draw(disp)?;
|
||||||
|
|
||||||
|
for (x, y) in [
|
||||||
|
(118, 49),
|
||||||
|
(119, 49),
|
||||||
|
(117, 50),
|
||||||
|
(117, 51),
|
||||||
|
(120, 50),
|
||||||
|
(120, 51),
|
||||||
|
(118, 52),
|
||||||
|
(119, 52),
|
||||||
|
(122, 50),
|
||||||
|
(122, 51),
|
||||||
|
(122, 52),
|
||||||
|
(123, 49),
|
||||||
|
(124, 49),
|
||||||
|
(123, 53),
|
||||||
|
(124, 53),
|
||||||
|
] {
|
||||||
|
Rectangle::new((x, y).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
Rectangle::new((x + 2, y + 8).into(), (1, 24).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
Rectangle::new((x + 1, y + 16).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
Rectangle::new((x + 1, y + 28).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(disp)?;
|
||||||
|
let mut day = time.weekday().number_days_from_monday() as usize;
|
||||||
|
let days = [
|
||||||
|
("M", "o"),
|
||||||
|
("D", "i"),
|
||||||
|
("M", "i"),
|
||||||
|
("D", "o"),
|
||||||
|
("F", "r"),
|
||||||
|
("S", "a"),
|
||||||
|
("S", "o"),
|
||||||
|
];
|
||||||
|
for i in 0..5 {
|
||||||
|
Text::new(days[day].0, (x + 12 * i + 4, y + 6).into(), text_style_6x9).draw(disp)?;
|
||||||
|
Text::new(days[day].1, (x + 12 * i + 10, y + 6).into(), text_style4).draw(disp)?;
|
||||||
|
day += 1;
|
||||||
|
day %= days.len();
|
||||||
|
}
|
||||||
|
let mut bits = vec![];
|
||||||
|
// events
|
||||||
|
let mut all_events = vec![];
|
||||||
|
for event in events.weekly {
|
||||||
|
let mut event_time = time.clone();
|
||||||
|
while event_time.weekday().number_days_from_monday() as i32 != event.day {
|
||||||
|
event_time += Duration::from_secs(24 * 60 * 60);
|
||||||
|
}
|
||||||
|
all_events.push((
|
||||||
|
event.day,
|
||||||
|
event.hour,
|
||||||
|
event.minute,
|
||||||
|
event.duration,
|
||||||
|
event.name,
|
||||||
|
event_time.to_julian_day(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let format = format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]").unwrap();
|
||||||
|
for event in events.events {
|
||||||
|
let dt = PrimitiveDateTime::parse(&event.start_time, &format)
|
||||||
|
.unwrap()
|
||||||
|
.assume_timezone(BERLIN)
|
||||||
|
.unwrap();
|
||||||
|
let julian_day = dt.to_julian_day();
|
||||||
|
if dt < time {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let duration = if let Some(end_time) = event.end_time.as_ref() {
|
||||||
|
let dt2 = PrimitiveDateTime::parse(end_time, &format)
|
||||||
|
.unwrap()
|
||||||
|
.assume_timezone(BERLIN)
|
||||||
|
.unwrap();
|
||||||
|
(dt2.sub(dt).as_seconds_f32() / 60.0) as i32
|
||||||
|
} else {
|
||||||
|
30
|
||||||
|
};
|
||||||
|
all_events.push((
|
||||||
|
dt.weekday().number_days_from_monday() as _,
|
||||||
|
dt.hour() as _,
|
||||||
|
dt.minute() as _,
|
||||||
|
duration,
|
||||||
|
event.name,
|
||||||
|
julian_day,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let today = time.date().to_julian_day();
|
||||||
|
let weekday = time.weekday().number_days_from_monday() as i32;
|
||||||
|
all_events.sort_by_key(|x| (x.5, ((x.0 + 7) - weekday) % 7, x.1, x.2));
|
||||||
|
//println!("{:?}", all_events);
|
||||||
|
let mut time_until_first = None;
|
||||||
|
let colors = vec![
|
||||||
|
Rgb565::new(0xff >> 3, 0xff >> 2, 0x00 >> 3),
|
||||||
|
Rgb565::new(0xff >> 3, 0x00 >> 2, 0xff >> 3),
|
||||||
|
Rgb565::new(0x00 >> 3, 0xff >> 2, 0xff >> 3),
|
||||||
|
Rgb565::new(0xff >> 3, 0x00 >> 2, 0x00 >> 3),
|
||||||
|
Rgb565::new(0x00 >> 3, 0xff >> 2, 0x00 >> 3),
|
||||||
|
Rgb565::new(0x00 >> 3, 0x00 >> 2, 0xff >> 3),
|
||||||
|
Rgb565::new(0xff >> 3, 0xff >> 2, 0xff >> 3),
|
||||||
|
];
|
||||||
|
for i in 0..5 {
|
||||||
|
let day = (weekday + i) % 7;
|
||||||
|
for hour in 0..24 {
|
||||||
|
for minute in 0..60 {
|
||||||
|
if minute % 6 != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 && hour == time.hour() as i32 && minute == (time.minute() as i32 / 6) * 6 {
|
||||||
|
bits.push((i, hour, minute / 6, Some(Rgb565::new(0xff, 0x00, 0xff))));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event_idx, event) in all_events.iter().enumerate() {
|
||||||
|
if event.0 != day || event.5 < today || event.5 - today > 4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let event_start = event.1 * 60 + event.2;
|
||||||
|
let event_end = event_start + event.3;
|
||||||
|
let now = hour * 60 + minute;
|
||||||
|
let now2 = hour * 60 + minute + 6;
|
||||||
|
if now2 > event_start && now < event_end {
|
||||||
|
bits.push((i, hour, minute / 6, colors.get(event_idx).copied()));
|
||||||
|
}
|
||||||
|
if time_until_first.is_none()
|
||||||
|
&& (i > 0
|
||||||
|
|| event.1 > time.hour() as i32 || (event.1 == time.hour() as i32
|
||||||
|
&& event.2 >= time.minute() as i32))
|
||||||
|
{
|
||||||
|
time_until_first = Some(
|
||||||
|
((i * 24 + event.1) * 60 + event.2) * 60
|
||||||
|
- (time.hour() as i32 * 60 + time.minute() as i32) * 60,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (d, h, m, color) in bits {
|
||||||
|
// calculate position
|
||||||
|
let x = x + 4 + d * 12 + m;
|
||||||
|
let y = y + 8 + h;
|
||||||
|
disp.fill_solid(
|
||||||
|
&Rectangle::new((x, y).into(), (1, 1).into()),
|
||||||
|
color.unwrap_or(Rgb565::new(0xff, 0xff, 0x10)),
|
||||||
|
)?;
|
||||||
|
//Rectangle::new((x, y).into(), (1, 1).into()).into_styled(rect_style).draw(disp).unwrap();
|
||||||
|
}
|
||||||
|
if self.mode == MeasurementsMode::Events {
|
||||||
|
for (i, event) in all_events.iter().take(7).enumerate() {
|
||||||
|
let text = if event.4.len() > 19 {
|
||||||
|
&event.4[0..event.4.floor_char_boundary(19)]
|
||||||
|
} else {
|
||||||
|
&event.4
|
||||||
|
};
|
||||||
|
let day = event.0 as usize;
|
||||||
|
let y = y + 64 + 9 * i as i32 + 5;
|
||||||
|
if event.5 > today && event.5 - today > 7 {
|
||||||
|
let dt = Date::from_julian_day(event.5).unwrap();
|
||||||
|
Text::new(
|
||||||
|
&format!("{}.{}.", dt.day(), dt.month() as u8),
|
||||||
|
(0, y).into(),
|
||||||
|
text_style_4x6,
|
||||||
|
)
|
||||||
|
.draw(disp)?;
|
||||||
|
} else {
|
||||||
|
text_style_6x9.set_text_color(Some(Rgb565::new(0xff, 0xff, 0xff)));
|
||||||
|
Text::new(days[day].0, (x, y).into(), text_style_6x9).draw(disp)?;
|
||||||
|
Text::new(days[day].1, (x + 6, y).into(), text_style4).draw(disp)?;
|
||||||
|
}
|
||||||
|
text_style_6x9.set_text_color(Some(colors[i]));
|
||||||
|
Text::new(text, (x + 14, y).into(), text_style_6x9).draw(disp)?;
|
||||||
|
}
|
||||||
|
} else if self.mode == MeasurementsMode::Temps {
|
||||||
|
let diff = global_max - global_min;
|
||||||
|
let x = 0;
|
||||||
|
let y = 64;
|
||||||
|
let scaley = 63;
|
||||||
|
let scalex = 2;
|
||||||
|
vals.reverse();
|
||||||
|
for (i, (a, b)) in vals.into_iter().enumerate() {
|
||||||
|
let x = x + i as i32 * scalex;
|
||||||
|
let y1 = y + (global_max - b) * scaley / diff;
|
||||||
|
let y2 = y + (global_max - a) * scaley / diff;
|
||||||
|
let height = y2 - y1 + 1;
|
||||||
|
let rect = Rectangle::new((x, y1).into(), (scalex as u32, height as u32).into());
|
||||||
|
disp.fill_solid(&rect, Rgb565::new(0xff, 0xff, 0xff))?;
|
||||||
|
}
|
||||||
|
Text::new(
|
||||||
|
&format!("{}", global_max as f32 / 10.0),
|
||||||
|
(100, 64 + 10).into(),
|
||||||
|
text_style_6x9,
|
||||||
|
)
|
||||||
|
.draw(disp)?;
|
||||||
|
Text::new(
|
||||||
|
&format!("{}", global_min as f32 / 10.0),
|
||||||
|
(100, 64 + 50).into(),
|
||||||
|
text_style_6x9,
|
||||||
|
)
|
||||||
|
.draw(disp)?;
|
||||||
|
}
|
||||||
|
if let Some(secs) = time_until_first {
|
||||||
|
let days = secs / (24 * 60 * 60);
|
||||||
|
let hours = secs / (60 * 60) % 24;
|
||||||
|
let minutes = secs / 60 % 60;
|
||||||
|
let text = if days > 0 {
|
||||||
|
String::new()
|
||||||
|
} else if hours > 0 {
|
||||||
|
format!("{}h{}m", hours, minutes)
|
||||||
|
} else if minutes > 0 {
|
||||||
|
format!("{}m", minutes)
|
||||||
|
} else {
|
||||||
|
"?".into()
|
||||||
|
};
|
||||||
|
Text::new(&text, (x + 2, y + 60).into(), text_style2).draw(disp)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.drawn.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, disp: &mut D, rng: &mut crate::Rng) -> Result<bool, <D as DrawTarget>::Error> {
|
||||||
|
panic!("draw without ctx");
|
||||||
|
}
|
||||||
|
}
|
2
src/bin/draw/mod.rs
Normal file
2
src/bin/draw/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod measurements;
|
||||||
|
pub use measurements::Measurements;
|
@ -1,21 +1,24 @@
|
|||||||
#![feature(array_windows)]
|
#![feature(array_windows, round_char_boundary)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
rc::Rc,
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use action::Action;
|
use action::Action;
|
||||||
use display_interface_spi::SPIInterfaceNoCS;
|
use display_interface_spi::SPIInterfaceNoCS;
|
||||||
use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget, Drawable};
|
use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget};
|
||||||
use gpiocdev::line::{Bias, EdgeDetection, Value};
|
use gpiocdev::line::{Bias, EdgeDetection, Value};
|
||||||
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
|
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
|
||||||
|
use raspi_oled::{disable_pwm, enable_pwm, PWM_ON};
|
||||||
use rppal::{
|
use rppal::{
|
||||||
gpio::{Gpio, OutputPin},
|
gpio::{Gpio, OutputPin},
|
||||||
hal::Delay,
|
hal::Delay,
|
||||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
};
|
};
|
||||||
|
use rusqlite::Connection;
|
||||||
use schedule::Schedule;
|
use schedule::Schedule;
|
||||||
use screensaver::{Screensaver, TimeDisplay};
|
use screensaver::{Screensaver, TimeDisplay};
|
||||||
use ssd1351::display::display::Ssd1351;
|
use ssd1351::display::display::Ssd1351;
|
||||||
@ -23,6 +26,7 @@ use time::OffsetDateTime;
|
|||||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
|
mod draw;
|
||||||
mod schedule;
|
mod schedule;
|
||||||
mod screensaver;
|
mod screensaver;
|
||||||
|
|
||||||
@ -43,18 +47,29 @@ pub trait Context {
|
|||||||
fn do_action(&self, action: Action);
|
fn do_action(&self, action: Action);
|
||||||
|
|
||||||
fn active_count(&self) -> usize;
|
fn active_count(&self) -> usize;
|
||||||
|
|
||||||
|
fn database(&self) -> Rc<RefCell<Connection>>;
|
||||||
|
|
||||||
|
fn enable_pwm(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>,
|
scheduled: Vec<Box<dyn Schedule>>,
|
||||||
active: RefCell<Vec<Box<dyn Draw<D>>>>,
|
active: RefCell<Vec<Box<dyn Draw<D>>>>,
|
||||||
|
database: Rc<RefCell<Connection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
let mut screensavers = screensaver::screensavers();
|
||||||
|
screensavers.push(Box::new(draw::Measurements::default()));
|
||||||
|
screensavers.push(Box::new(draw::Measurements::temps()));
|
||||||
|
screensavers.push(Box::new(draw::Measurements::events()));
|
||||||
|
let database = Connection::open("sensors.db").expect("failed to open database");
|
||||||
ContextDefault {
|
ContextDefault {
|
||||||
screensavers: screensaver::screensavers(),
|
database: Rc::new(RefCell::new(database)),
|
||||||
|
screensavers,
|
||||||
scheduled: schedule::reminders(),
|
scheduled: schedule::reminders(),
|
||||||
active: RefCell::new(vec![Box::new(TimeDisplay::new())]),
|
active: RefCell::new(vec![Box::new(TimeDisplay::new())]),
|
||||||
}
|
}
|
||||||
@ -72,10 +87,11 @@ impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
|||||||
}
|
}
|
||||||
let a = active.last().unwrap();
|
let a = active.last().unwrap();
|
||||||
if !a.expired() {
|
if !a.expired() {
|
||||||
return a.draw(disp, rng).unwrap_or(true);
|
return a.draw_with_ctx(self, disp, rng).unwrap_or(true);
|
||||||
}
|
}
|
||||||
drop(active);
|
drop(active);
|
||||||
self.active.borrow_mut().pop();
|
self.active.borrow_mut().pop();
|
||||||
|
disable_pwm().unwrap();
|
||||||
self.loop_iter(disp, rng)
|
self.loop_iter(disp, rng)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +123,14 @@ impl<D: DrawTarget<Color = Rgb565>> Context for ContextDefault<D> {
|
|||||||
fn active_count(&self) -> usize {
|
fn active_count(&self) -> usize {
|
||||||
self.active.borrow().len()
|
self.active.borrow().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn database(&self) -> Rc<RefCell<Connection>> {
|
||||||
|
self.database.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_pwm(&self) {
|
||||||
|
enable_pwm().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "pc"))]
|
#[cfg(not(feature = "pc"))]
|
||||||
@ -149,7 +173,7 @@ fn pc_main() {
|
|||||||
let mut buffer_dirty = true;
|
let mut buffer_dirty = true;
|
||||||
|
|
||||||
let mut ctx = ContextDefault::new();
|
let mut ctx = ContextDefault::new();
|
||||||
ctx.do_action(Action::Screensaver("plate"));
|
ctx.do_action(Action::Screensaver("measurements"));
|
||||||
let mut rng = Xoroshiro128StarStar::seed_from_u64(17381);
|
let mut rng = Xoroshiro128StarStar::seed_from_u64(17381);
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
@ -209,6 +233,7 @@ fn pc_main() {
|
|||||||
}
|
}
|
||||||
buffer.present().unwrap();
|
buffer.present().unwrap();
|
||||||
buffer_dirty = false;
|
buffer_dirty = false;
|
||||||
|
let _ = disp.buffer.save(format!("/tmp/iter{}.png", iters));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -217,6 +242,9 @@ fn pc_main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Draw<D: DrawTarget<Color = Rgb565>> {
|
pub trait Draw<D: DrawTarget<Color = Rgb565>> {
|
||||||
|
fn draw_with_ctx(&self, ctx: &ContextDefault<D>, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error> {
|
||||||
|
self.draw(disp, rng)
|
||||||
|
}
|
||||||
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error>;
|
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error>;
|
||||||
fn expired(&self) -> bool {
|
fn expired(&self) -> bool {
|
||||||
false
|
false
|
||||||
@ -237,7 +265,35 @@ fn rpi_main() {
|
|||||||
disp.reset(&mut rst, &mut Delay).unwrap();
|
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
disp.turn_on().unwrap();
|
disp.turn_on().unwrap();
|
||||||
|
|
||||||
|
// Init PWM handling
|
||||||
|
let pwm = thread::spawn(handle_pwm);
|
||||||
|
|
||||||
main_loop(disp);
|
main_loop(disp);
|
||||||
|
|
||||||
|
let _ = pwm.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pwm() {
|
||||||
|
let pwm = gpiocdev::Request::builder()
|
||||||
|
.on_chip("/dev/gpiochip0")
|
||||||
|
.with_line(12)
|
||||||
|
.as_output(Value::Inactive)
|
||||||
|
.request()
|
||||||
|
.unwrap();
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
let on = PWM_ON.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if !on {
|
||||||
|
let _ = pwm.set_value(12, Value::Inactive);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for _ in 0..100 {
|
||||||
|
let _ = pwm.set_value(12, Value::Active);
|
||||||
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
let _ = pwm.set_value(12, Value::Inactive);
|
||||||
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(mut disp: Oled) {
|
fn main_loop(mut disp: Oled) {
|
||||||
@ -278,29 +334,63 @@ fn main_loop(mut disp: Oled) {
|
|||||||
let e = lines.read_edge_event().unwrap();
|
let e = lines.read_edge_event().unwrap();
|
||||||
last_button = Instant::now();
|
last_button = Instant::now();
|
||||||
match e.offset {
|
match e.offset {
|
||||||
19 => {
|
5 => {
|
||||||
menu.push(1);
|
menu.push(1);
|
||||||
},
|
},
|
||||||
6 => {
|
6 => {
|
||||||
menu.push(2);
|
menu.push(2);
|
||||||
},
|
},
|
||||||
5 => {
|
19 => {
|
||||||
menu.push(3);
|
menu.push(3);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
println!("unknown offset: {}", e.offset);
|
println!("unknown offset: {}", e.offset);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
let mut pop_last = false;
|
||||||
|
let mut clear = false;
|
||||||
match &*menu {
|
match &*menu {
|
||||||
[2] => {
|
[1] => {
|
||||||
let _ = ctx.pop_action_and_clear(&mut disp);
|
ctx.do_action(Action::Screensaver("measurements"));
|
||||||
},
|
},
|
||||||
[3] => {
|
[1, 2] => {
|
||||||
|
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||||
|
ctx.do_action(Action::Screensaver("measurements_temps"));
|
||||||
|
pop_last = true;
|
||||||
|
},
|
||||||
|
[1, 3] => {
|
||||||
|
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||||
|
ctx.do_action(Action::Screensaver("measurements_events"));
|
||||||
|
pop_last = true;
|
||||||
|
},
|
||||||
|
[2] => {
|
||||||
|
if ctx.active_count() > 1 {
|
||||||
|
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||||
|
disable_pwm().unwrap();
|
||||||
|
let _ = disp.flush();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[3] => {},
|
||||||
|
[3, 1] => {
|
||||||
|
enable_pwm().unwrap();
|
||||||
|
pop_last = true;
|
||||||
|
},
|
||||||
|
[3, 2] => {
|
||||||
|
disable_pwm().unwrap();
|
||||||
|
pop_last = true;
|
||||||
|
},
|
||||||
|
[3, 3] => {
|
||||||
ctx.do_action(Action::Screensaver("rpi"));
|
ctx.do_action(Action::Screensaver("rpi"));
|
||||||
|
clear = true;
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
//println!("menu: {menu:?}");
|
if pop_last {
|
||||||
|
menu.pop();
|
||||||
|
}
|
||||||
|
if clear {
|
||||||
|
menu.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// clean up stale menu selection
|
// clean up stale menu selection
|
||||||
if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 {
|
if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 {
|
||||||
|
@ -20,6 +20,7 @@ pub struct Reminder {
|
|||||||
hour: u8,
|
hour: u8,
|
||||||
minute: u8,
|
minute: u8,
|
||||||
action: Action,
|
action: Action,
|
||||||
|
should_beep: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Schedule for Reminder {
|
impl Schedule for Reminder {
|
||||||
@ -28,19 +29,27 @@ impl Schedule for Reminder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn execute(&self, ctx: &dyn Context, _time: OffsetDateTime) {
|
fn execute(&self, ctx: &dyn Context, _time: OffsetDateTime) {
|
||||||
|
if self.should_beep {
|
||||||
|
ctx.enable_pwm();
|
||||||
|
}
|
||||||
ctx.do_action(self.action);
|
ctx.do_action(self.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Reminder {
|
impl Reminder {
|
||||||
const fn new(hour: u8, minute: u8, action: Action) -> Self {
|
const fn new(hour: u8, minute: u8, action: Action, should_beep: bool) -> Self {
|
||||||
Reminder { hour, minute, action }
|
Reminder {
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
action,
|
||||||
|
should_beep,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DUOLINGO: Reminder = Reminder::new(11, 30, Action::Screensaver("duolingo"));
|
static DUOLINGO: Reminder = Reminder::new(11, 40, Action::Screensaver("duolingo"), false);
|
||||||
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 30, Action::Screensaver("duolingo"));
|
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 40, Action::Screensaver("duolingo"), false);
|
||||||
static FOOD: Reminder = Reminder::new(13, 15, Action::Screensaver("plate"));
|
static FOOD: Reminder = Reminder::new(13, 15, Action::Screensaver("plate"), false);
|
||||||
|
|
||||||
pub fn reminders() -> Vec<Box<dyn Schedule>> {
|
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)]
|
||||||
|
79
src/lib.rs
79
src/lib.rs
@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
sync::atomic::AtomicBool,
|
||||||
thread::sleep,
|
thread::sleep,
|
||||||
time::{self, Duration},
|
time::{self, Duration},
|
||||||
};
|
};
|
||||||
@ -8,7 +9,7 @@ use embedded_graphics::{
|
|||||||
pixelcolor::Rgb565,
|
pixelcolor::Rgb565,
|
||||||
prelude::{OriginDimensions, RgbColor, Size},
|
prelude::{OriginDimensions, RgbColor, Size},
|
||||||
};
|
};
|
||||||
use gpiocdev::line::{Bias, EdgeDetection, EdgeKind, Value};
|
use gpiocdev::line::{Bias, EdgeKind, Value};
|
||||||
#[cfg(feature = "pc")]
|
#[cfg(feature = "pc")]
|
||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
|
|
||||||
@ -65,21 +66,34 @@ fn read_events(timeout: std::time::Duration) -> Result<Vec<(u64, EdgeKind)>, Sen
|
|||||||
.on_chip("/dev/gpiochip0")
|
.on_chip("/dev/gpiochip0")
|
||||||
.with_line(26)
|
.with_line(26)
|
||||||
.as_input()
|
.as_input()
|
||||||
.with_edge_detection(EdgeDetection::BothEdges)
|
//.with_edge_detection(EdgeDetection::BothEdges)
|
||||||
|
//.with_debounce_period(Duration::ZERO)
|
||||||
|
.with_kernel_event_buffer_size(1024)
|
||||||
.with_bias(Bias::PullDown)
|
.with_bias(Bias::PullDown)
|
||||||
.request()?;
|
.request()?;
|
||||||
|
|
||||||
let start = time::Instant::now();
|
let start = time::Instant::now();
|
||||||
|
let mut last_value = Value::Active;
|
||||||
|
|
||||||
let mut events = Vec::with_capacity(81);
|
let mut events = Vec::with_capacity(81);
|
||||||
while start.elapsed() < timeout && events.len() < 81 {
|
while start.elapsed() < timeout && events.len() < 81 {
|
||||||
|
let new_value = input.value(26)?;
|
||||||
|
if new_value != last_value {
|
||||||
|
match new_value {
|
||||||
|
Value::Inactive => events.push((start.elapsed().as_micros() as u64, EdgeKind::Falling)),
|
||||||
|
Value::Active => events.push((start.elapsed().as_micros() as u64, EdgeKind::Rising)),
|
||||||
|
}
|
||||||
|
last_value = new_value;
|
||||||
|
}
|
||||||
|
/*
|
||||||
if input.wait_edge_event(timeout)? {
|
if input.wait_edge_event(timeout)? {
|
||||||
let event = input.read_edge_event()?;
|
let event = input.read_edge_event()?;
|
||||||
events.push((start.elapsed().as_micros() as u64, event.kind));
|
events.push((start.elapsed().as_micros() as u64, event.kind));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
if events.len() < 81 {
|
if events.len() < 81 {
|
||||||
println!("error: only got {} events", events.len());
|
println!("error: only got {} events: {:?}", events.len(), events);
|
||||||
return Err(SensorError::Timeout);
|
return Err(SensorError::Timeout);
|
||||||
}
|
}
|
||||||
Ok(events)
|
Ok(events)
|
||||||
@ -174,10 +188,15 @@ pub fn am2302_reading() -> Result<(u16, u16), SensorError> {
|
|||||||
out.set_value(26, Value::Active)?;
|
out.set_value(26, Value::Active)?;
|
||||||
sleep(Duration::from_millis(500));
|
sleep(Duration::from_millis(500));
|
||||||
set_max_priority();
|
set_max_priority();
|
||||||
|
out.set_value(26, Value::Inactive)?;
|
||||||
|
sleep(Duration::from_millis(4));
|
||||||
|
drop(out);
|
||||||
|
/*
|
||||||
// set low for 20 ms
|
// set low for 20 ms
|
||||||
out.set_value(26, Value::Inactive)?;
|
out.set_value(26, Value::Inactive)?;
|
||||||
sleep(Duration::from_millis(3));
|
sleep(Duration::from_millis(3));
|
||||||
drop(out);
|
drop(out);
|
||||||
|
*/
|
||||||
|
|
||||||
let events = read_events(Duration::from_secs(1));
|
let events = read_events(Duration::from_secs(1));
|
||||||
println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
|
println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
|
||||||
@ -201,3 +220,57 @@ fn set_normal_priority() {
|
|||||||
libc::sched_setscheduler(0, libc::SCHED_OTHER, (&sched_para) as *const libc::sched_param);
|
libc::sched_setscheduler(0, libc::SCHED_OTHER, (&sched_para) as *const libc::sched_param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn disable_pwm() -> Result<(), rppal::pwm::Error> {
|
||||||
|
/*
|
||||||
|
let pwm = Pwm::new(rppal::pwm::Channel::Pwm0)?;
|
||||||
|
if pwm.is_enabled()? {
|
||||||
|
pwm.disable()?;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
PWM_ON.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_pwm() -> Result<(), rppal::pwm::Error> {
|
||||||
|
PWM_ON.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
/*
|
||||||
|
let mut pwm = Pwm::with_period(
|
||||||
|
rppal::pwm::Channel::Pwm0,
|
||||||
|
Duration::from_micros(500),
|
||||||
|
Duration::from_micros(250),
|
||||||
|
rppal::pwm::Polarity::Normal,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
assert!(pwm.is_enabled()?);
|
||||||
|
pwm.set_reset_on_drop(false);
|
||||||
|
*/
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static PWM_ON: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Events {
|
||||||
|
pub events: Vec<Event>,
|
||||||
|
pub weekly: Vec<Weekly>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Event {
|
||||||
|
pub name: String,
|
||||||
|
pub start_time: String,
|
||||||
|
pub end_time: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Weekly {
|
||||||
|
pub name: String,
|
||||||
|
pub day: i32,
|
||||||
|
pub hour: i32,
|
||||||
|
pub minute: i32,
|
||||||
|
pub duration: i32,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user