mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-22 02:14:58 +00:00
Refactor everything + RGB
This commit is contained in:
parent
106e4050b0
commit
422221d97e
1028
Cargo.lock
generated
1028
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -11,11 +11,21 @@ embedded-graphics = "0.7.1"
|
|||||||
linux-embedded-hal = "0.3.0"
|
linux-embedded-hal = "0.3.0"
|
||||||
embedded-hal = "0.2.5"
|
embedded-hal = "0.2.5"
|
||||||
machine-ip = "0.2.1"
|
machine-ip = "0.2.1"
|
||||||
ssd1306 = "0.6.0"
|
#ssd1306 = "0.6.0"
|
||||||
libc = "0.2.98"
|
libc = "0.2.98"
|
||||||
gpio-cdev = "0.4"
|
gpio-cdev = "0.4"
|
||||||
dht-hal = "0.0.1"
|
dht-hal = "0.0.1"
|
||||||
rusqlite = "0.25.3"
|
rusqlite = "0.25.3"
|
||||||
|
time = { version = "0.3.9", features = ["parsing"] }
|
||||||
|
time-tz = "1.0.1"
|
||||||
|
image = "0.24.1"
|
||||||
|
serde_json = "1.0.79"
|
||||||
|
serde_derive = "1.0.136"
|
||||||
|
serde = "1.0.136"
|
||||||
|
rppal = { version = "0.13.1", features = ["hal"] }
|
||||||
|
#ssd1351 = "0.4.0"
|
||||||
|
ssd1351 = { path = "../ssd1351-rust" }
|
||||||
|
display-interface-spi = "0.4.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
422
src/bin/display_all.rs
Normal file
422
src/bin/display_all.rs
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
use std::fs;
|
||||||
|
|
||||||
|
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},
|
||||||
|
text::Text,
|
||||||
|
Drawable,
|
||||||
|
};
|
||||||
|
use image::{ImageBuffer, Rgb};
|
||||||
|
use linux_embedded_hal::I2cdev;
|
||||||
|
use rppal::{
|
||||||
|
gpio::Gpio,
|
||||||
|
hal::Delay,
|
||||||
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
|
};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
//use ssd1306::{I2CDisplayInterface, Ssd1306, size::DisplaySize128x64, rotation::DisplayRotation, mode::DisplayConfig};
|
||||||
|
use time::{format_description, OffsetDateTime, PrimitiveDateTime};
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Weekly {
|
||||||
|
name: String,
|
||||||
|
day: i32,
|
||||||
|
hour: i32,
|
||||||
|
minute: i32,
|
||||||
|
duration: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
if args.len() < 4 {
|
||||||
|
panic!("missing argument: database path, event JSON data file, events / temps");
|
||||||
|
}
|
||||||
|
let database = Connection::open(&args[1]).expect("failed to open database");
|
||||||
|
let events = fs::read_to_string(&args[2]).expect("failed to read events.json");
|
||||||
|
let events: Events = serde_json::from_str(&events).unwrap();
|
||||||
|
|
||||||
|
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 > 300 {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
println!("global {} | {}", global_min, global_max);
|
||||||
|
|
||||||
|
let hour = time.hour();
|
||||||
|
let minute = time.minute();
|
||||||
|
//let i2c = I2cdev::new("/dev/i2c-1").unwrap();
|
||||||
|
//let interface = I2CDisplayInterface::new(i2c);
|
||||||
|
//let mut disp = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_buffered_graphics_mode();
|
||||||
|
//disp.init().unwrap();
|
||||||
|
//let mut disp = FrameOutput { buffer: ImageBuffer::new(128, 64) };
|
||||||
|
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
|
||||||
|
let gpio = Gpio::new().unwrap();
|
||||||
|
let dc = gpio.get(25).unwrap().into_output();
|
||||||
|
let mut rst = gpio.get(27).unwrap().into_output();
|
||||||
|
|
||||||
|
// Init SPI
|
||||||
|
let spii = SPIInterfaceNoCS::new(spi, dc);
|
||||||
|
let mut disp = ssd1351::display::display::Ssd1351::new(spii);
|
||||||
|
|
||||||
|
// Reset & init
|
||||||
|
//disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
|
//disp.turn_on().unwrap();
|
||||||
|
|
||||||
|
let text_style_clock = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_10X20)
|
||||||
|
.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 text_style3 = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_6X9)
|
||||||
|
.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(&mut disp).unwrap();
|
||||||
|
let hour = format!("{:02}", hour);
|
||||||
|
Text::new(&hour, Point::new(64 + 10, 6 + 20), text_style_clock)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
let minute = format!("{:02}", minute);
|
||||||
|
Text::new(&minute, Point::new(64 + 10 + 20 + 4, 6 + 20), text_style_clock)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Rectangle::new((95, 17).into(), (2, 2).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Rectangle::new((95, 22).into(), (2, 2).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let rh = format!("{:02}", rh / 10);
|
||||||
|
Text::new(&rh, Point::new(64 + 3, 64 - 4), text_style2)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Text::new("%", Point::new(64 + 3 + 18, 64 - 4), text_style3)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
let temp_int = format!("{:02}", temp / 10);
|
||||||
|
Text::new(&temp_int, Point::new(64 + 32 + 3, 64 - 4), text_style2)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Rectangle::new((64 + 32 + 3 + 18, 64 - 4).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
let temp_fract = format!("{}", temp % 10);
|
||||||
|
Text::new(&temp_fract, Point::new(64 + 32 + 3 + 18 + 2, 64 - 4), text_style3)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
Rectangle::new((x + 2, y + 8).into(), (1, 24).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Rectangle::new((x + 1, y + 16).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Rectangle::new((x + 1, y + 28).into(), (1, 1).into())
|
||||||
|
.into_styled(rect_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
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_style3)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Text::new(days[day].1, (x + 12 * i + 10, y + 6).into(), text_style4)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
day += 1;
|
||||||
|
day %= days.len();
|
||||||
|
}
|
||||||
|
let mut bits = vec![];
|
||||||
|
// events
|
||||||
|
let mut all_events = vec![];
|
||||||
|
for event in events.weekly {
|
||||||
|
all_events.push((event.day, event.hour, event.minute, event.duration, event.name));
|
||||||
|
}
|
||||||
|
let today = time.date().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 || julian_day - today > 4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
all_events.push((
|
||||||
|
dt.weekday().number_days_from_monday() as _,
|
||||||
|
dt.hour() as _,
|
||||||
|
dt.minute() as _,
|
||||||
|
30,
|
||||||
|
event.name,
|
||||||
|
)); // TODO length
|
||||||
|
}
|
||||||
|
let weekday = time.weekday().number_days_from_monday() as i32;
|
||||||
|
all_events.sort_by_key(|x| (((x.0 + 7) - weekday) % 7, x.1, x.2));
|
||||||
|
println!("{:?}", all_events);
|
||||||
|
let mut time_until_first = None;
|
||||||
|
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, Rgb565::new(0xff, 0x00, 0xff)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in &all_events {
|
||||||
|
if event.0 != day {
|
||||||
|
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, Rgb565::new(0xff, 0xff, 0x10)));
|
||||||
|
}
|
||||||
|
if time_until_first.is_none() {
|
||||||
|
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();
|
||||||
|
//Rectangle::new((x, y).into(), (1, 1).into()).into_styled(rect_style).draw(&mut disp).unwrap();
|
||||||
|
}
|
||||||
|
if args[3] == "events" {
|
||||||
|
for (i, event) in all_events.iter().take(7).enumerate() {
|
||||||
|
let text = if event.4.len() > 10 { &event.4[0..10] } else { &event.4 };
|
||||||
|
Text::new(text, (x + 2, y + 64 + 9 * i as i32 + 5).into(), text_style3)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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)).unwrap();
|
||||||
|
}
|
||||||
|
Text::new(
|
||||||
|
&format!("{}", global_max as f32 / 10.0),
|
||||||
|
(100, 64 + 10).into(),
|
||||||
|
text_style3,
|
||||||
|
)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
Text::new(
|
||||||
|
&format!("{}", global_min as f32 / 10.0),
|
||||||
|
(100, 64 + 50).into(),
|
||||||
|
text_style3,
|
||||||
|
)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
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(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
sleep(Duration::from_secs(2));
|
||||||
|
disp.clear();
|
||||||
|
|
||||||
|
let base_y = 0.0;
|
||||||
|
let max_dy = 32.0;
|
||||||
|
let mut tick = 0;
|
||||||
|
loop {
|
||||||
|
let y = if tick % 32 < 16 {
|
||||||
|
base_y + (tick % 16) as f32 / 16.0 * max_dy
|
||||||
|
} else {
|
||||||
|
base_y + max_dy - (tick % 16) as f32 / 16.0 * max_dy
|
||||||
|
} as i32;
|
||||||
|
tick += 1;
|
||||||
|
Line::new(Point::new(8, y + 16), Point::new(8 + 16, y + 16))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||||
|
.draw(&mut disp).unwrap();
|
||||||
|
Line::new(Point::new(8, y + 16), Point::new(8 + 8, y))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||||
|
.draw(&mut disp).unwrap();
|
||||||
|
|
||||||
|
Line::new(Point::new(8 + 16, y + 16), Point::new(8 + 8, y))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||||
|
.draw(&mut disp).unwrap();
|
||||||
|
|
||||||
|
Rectangle::new(Point::new(48, y), Size::new(16, 16))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||||
|
.draw(&mut disp).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
Circle::new(Point::new(88, y), 16)
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||||
|
.draw(&mut disp).unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Text::new(&format!("Hello from frame {}", tick), Point::new(0, 56), text_style)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
*/
|
||||||
|
disp.flush().unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(10));
|
||||||
|
|
||||||
|
disp.clear();
|
||||||
|
|
||||||
|
/*
|
||||||
|
let im: ImageRaw<BinaryColor> = ImageRaw::new(IMG_DATA, 64);
|
||||||
|
let img = Image::new(&im, Point::new(32, 0));
|
||||||
|
img.draw(&mut disp).unwrap();
|
||||||
|
disp.flush().unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(2));
|
||||||
|
disp.clear();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
let _ = disp.flush();
|
||||||
|
//disp.buffer.save("/tmp/frame.png").unwrap();
|
||||||
|
}
|
33
src/bin/display_off.rs
Normal file
33
src/bin/display_off.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use display_interface_spi::SPIInterfaceNoCS;
|
||||||
|
use rppal::{
|
||||||
|
gpio::Gpio,
|
||||||
|
hal::Delay,
|
||||||
|
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
if args.len() < 2 {
|
||||||
|
panic!("missing argument: on/off");
|
||||||
|
}
|
||||||
|
display_on_ssd1306(args[1] == "on");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_on_ssd1306(on: bool) {
|
||||||
|
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
|
||||||
|
let gpio = Gpio::new().unwrap();
|
||||||
|
let dc = gpio.get(25).unwrap().into_output();
|
||||||
|
let mut rst = gpio.get(27).unwrap().into_output();
|
||||||
|
|
||||||
|
// Init SPI
|
||||||
|
let spii = SPIInterfaceNoCS::new(spi, dc);
|
||||||
|
let mut disp = ssd1351::display::display::Ssd1351::new(spii);
|
||||||
|
|
||||||
|
// Reset & init
|
||||||
|
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
|
if on {
|
||||||
|
disp.turn_on().unwrap();
|
||||||
|
} else {
|
||||||
|
disp.turn_off().unwrap();
|
||||||
|
}
|
||||||
|
}
|
102
src/bin/rgb_test.rs
Normal file
102
src/bin/rgb_test.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use std::{
|
||||||
|
fs, 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},
|
||||||
|
text::Text,
|
||||||
|
Drawable,
|
||||||
|
};
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
use gpio_cdev::{Chip, LineHandle, LineRequestFlags};
|
||||||
|
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() {
|
||||||
|
//let i2c = I2cdev::new("/dev/i2c-1").unwrap();
|
||||||
|
//let interface = I2CDisplayInterface::new(i2c);
|
||||||
|
//let mut disp = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_buffered_graphics_mode();
|
||||||
|
//disp.init().unwrap();
|
||||||
|
// Configure gpio
|
||||||
|
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
|
||||||
|
let gpio = Gpio::new().unwrap();
|
||||||
|
let dc = gpio.get(25).unwrap().into_output();
|
||||||
|
let mut rst = gpio.get(27).unwrap().into_output();
|
||||||
|
|
||||||
|
// Init SPI
|
||||||
|
let spii = SPIInterfaceNoCS::new(spi, dc);
|
||||||
|
let mut disp = ssd1351::display::display::Ssd1351::new(spii);
|
||||||
|
|
||||||
|
// Reset & init
|
||||||
|
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
|
disp.turn_on().unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
|
disp.turn_off().unwrap();
|
||||||
|
panic!("done!");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Clear the display
|
||||||
|
disp.clear(Rgb565::new(0x00, 0x00, 0x00)).unwrap();
|
||||||
|
|
||||||
|
//disp.flush().unwrap();
|
||||||
|
//disp.flush().unwrap();
|
||||||
|
|
||||||
|
let text_style_clock = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_10X20)
|
||||||
|
.text_color(Rgb565::new(0xff, 0x00, 0x00))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//let text = format!("{}.{}% {}.{}°C", rh / 10, rh % 10, temp / 10, temp % 10);
|
||||||
|
//Text::new(&text, Point::new(0, 10), text_style).draw(&mut disp).unwrap();
|
||||||
|
Text::new("Abc", (0, 30).into(), text_style_clock)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _ = disp.flush();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(15));
|
||||||
|
Text::new("5 seconds to off!", (0, 60).into(), text_style_clock)
|
||||||
|
.draw(&mut disp)
|
||||||
|
.unwrap();
|
||||||
|
let start = Instant::now();
|
||||||
|
let _ = disp.flush();
|
||||||
|
println!("{:?} ms", start.elapsed().as_millis());
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||||
|
disp.turn_off().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LineHandleWrapper(LineHandle);
|
||||||
|
|
||||||
|
impl OutputPin for LineHandleWrapper {
|
||||||
|
type Error = gpio_cdev::Error;
|
||||||
|
|
||||||
|
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.set_value(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.set_value(1)
|
||||||
|
}
|
||||||
|
}
|
53
src/bin/take_measurement.rs
Normal file
53
src/bin/take_measurement.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use gpio_cdev::Chip;
|
||||||
|
use rusqlite::{params, 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");
|
||||||
|
database
|
||||||
|
.execute(
|
||||||
|
"
|
||||||
|
CREATE TABLE IF NOT EXISTS sensor_readings(
|
||||||
|
time INTEGER PRIMARY KEY,
|
||||||
|
humidity INTEGER NOT NULL,
|
||||||
|
celsius INTEGER NOT NULL
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut chip = Chip::new("/dev/gpiochip0").unwrap();
|
||||||
|
let line = chip.get_line(26).unwrap();
|
||||||
|
let mut attempts = 0;
|
||||||
|
let mut temps = vec![];
|
||||||
|
let mut rhs = vec![];
|
||||||
|
let time = std::time::SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap();
|
||||||
|
while temps.len() < 5 && attempts < 10 {
|
||||||
|
if let Ok((rh, temp)) = raspi_oled::am2302_reading(&line) {
|
||||||
|
if rh > 0 && temp < 500 {
|
||||||
|
rhs.push(rh);
|
||||||
|
temps.push(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_secs(5));
|
||||||
|
attempts += 1;
|
||||||
|
}
|
||||||
|
if !temps.is_empty() {
|
||||||
|
// median = hopefully no faulty readings
|
||||||
|
temps.sort();
|
||||||
|
rhs.sort();
|
||||||
|
database
|
||||||
|
.execute(
|
||||||
|
"INSERT INTO sensor_readings (time, humidity, celsius) VALUES (?1, ?2, ?3)",
|
||||||
|
params![time.as_secs(), rhs[rhs.len() / 2], temps[temps.len() / 2]],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
108
src/main.rs
108
src/main.rs
@ -1,3 +1,110 @@
|
|||||||
|
use embedded_graphics::{
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
pixelcolor::BinaryColor,
|
||||||
|
prelude::{OriginDimensions, Size},
|
||||||
|
primitives::Rectangle,
|
||||||
|
};
|
||||||
|
use image::{ImageBuffer, Rgb};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
|
||||||
|
struct FrameOutput {
|
||||||
|
buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawTarget for FrameOutput {
|
||||||
|
type Color = BinaryColor;
|
||||||
|
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for pos in pixels {
|
||||||
|
if pos.0.x < 0 || pos.0.y < 0 || pos.0.x >= 128 || pos.0.y >= 64 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let color = if pos.1 == BinaryColor::On {
|
||||||
|
Rgb([0, 255, 255])
|
||||||
|
} else {
|
||||||
|
Rgb([0, 0, 0])
|
||||||
|
};
|
||||||
|
self.buffer.put_pixel(pos.0.x as u32, pos.0.y as u32, color);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginDimensions for FrameOutput {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
Size::new(self.buffer.width(), self.buffer.height())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
if args.len() < 2 {
|
||||||
|
panic!("missing argument: database path");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut disp = FrameOutput {
|
||||||
|
buffer: ImageBuffer::new(128, 64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let database = Connection::open(&args[1]).expect("failed to open database");
|
||||||
|
|
||||||
|
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 min = hour[1];
|
||||||
|
let mut max = hour[hour.len() - 2];
|
||||||
|
println!("min {} max {}", min, max);
|
||||||
|
// sanity check value
|
||||||
|
if max > 300 {
|
||||||
|
if vals.is_empty() {
|
||||||
|
max = min;
|
||||||
|
} else {
|
||||||
|
max = vals.last().unwrap().1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_min = min.min(global_min);
|
||||||
|
global_max = max.max(global_max);
|
||||||
|
vals.push((min, max));
|
||||||
|
}
|
||||||
|
println!("global {} | {}", global_min, global_max);
|
||||||
|
let diff = global_max - global_min;
|
||||||
|
let x = 1;
|
||||||
|
let y = 1;
|
||||||
|
let scaley = 64;
|
||||||
|
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;
|
||||||
|
disp.fill_solid(
|
||||||
|
&Rectangle::new((x, y1).into(), (scalex as u32, height as u32).into()),
|
||||||
|
BinaryColor::On,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
disp.buffer.save("/tmp/frame.png").unwrap();
|
||||||
|
}
|
||||||
|
/*
|
||||||
use dht_hal::{Dht22, Reading};
|
use dht_hal::{Dht22, Reading};
|
||||||
use embedded_graphics::image::{Image, ImageRaw};
|
use embedded_graphics::image::{Image, ImageRaw};
|
||||||
use embedded_graphics::mono_font::iso_8859_7::FONT_9X18;
|
use embedded_graphics::mono_font::iso_8859_7::FONT_9X18;
|
||||||
@ -289,3 +396,4 @@ impl OutputPin for LineWrapper {
|
|||||||
.set_value(1)
|
.set_value(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user