Compare commits

..

No commits in common. "9cc84258eb3cd4ed9ea8328f1aa53130a154b6e5" and "adfc7925c70917d89eb943f4b624fc1a3101579b" have entirely different histories.

19 changed files with 2130 additions and 400 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
/target /target
**/*.rs.bk **/*.rs.bk
events_weekly.json
events.json
sensors.db

1129
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
[package] [package]
name = "raspi-oled" name = "raspi-oled"
version = "0.1.0" version = "0.1.0"
authors = ["chux0519 <chuxdesign@hotmail.com>"] authors = ["FliegendeWurst <2012gdwu@posteo.de>"]
edition = "2018" 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
@ -10,12 +10,19 @@ edition = "2018"
embedded-graphics = "0.7.1" 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"
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"
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 = { git = "https://github.com/FliegendeWurst/ssd1351-rust" }
display-interface-spi = "0.4.1"
ureq = { version = "2.4.0", default-features = false }
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1

42
LICENSE
View File

@ -1,29 +1,19 @@
BSD 3-Clause License Copyright (c) 2022 Arne Keller
Copyright (c) 2020, Yongsheng Xu Permission is hereby granted, free of charge, to any person obtaining a copy
All rights reserved. of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without The above copyright notice and this permission notice shall be included in all
modification, are permitted provided that the following conditions are met: copies or substantial portions of the Software.
1. Redistributions of source code must retain the above copyright notice, this THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
list of conditions and the following disclaimer. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2. Redistributions in binary form must reproduce the above copyright notice, AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
this list of conditions and the following disclaimer in the documentation LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
and/or other materials provided with the distribution. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,17 +1,24 @@
# raspi demo for oled ssd1306 display # raspi demo for OLED ssd1351 display
https://www.waveshare.com/wiki/1.5inch_RGB_OLED_Module
## Quick start ## Quick start
```bash
> nix-shell > nix-shell
>
> rustup target add arm-unknown-linux-musleabihf > rustup target add arm-unknown-linux-musleabihf
>
> cargo build --release --target arm-unknown-linux-musleabihf > cargo build --release --target arm-unknown-linux-musleabihf
> scp target/arm-unknown-linux-musleabihf/release/{display_all,display_off,refresh_json,take_measurement} 'pi@raspberrypi:~'
Then scp the release file to your raspi. > # on the Pi, create sensors.db and events.json
> patchelf --set-interpreter /lib/ld-linux-armhf.so.3 display_all
> ./display_off on
> ./display_all sensors.db events.json temps
```
## Example ## Example
![picture](./images/01.jpg) ![temperature graph](./images/temps.png)
![primitive](./images/02.jpg) ![events](./images/events.png)
(the second blue text is brighter on the OLED)

16
events_weekly.json Normal file
View File

@ -0,0 +1,16 @@
"weekly": [
{
"name": "Example 1",
"day": 0,
"hour": 9,
"minute": 45,
"duration": 90
},
{
"name": "Example 2",
"day": 3,
"hour": 11,
"minute": 15,
"duration": 45
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

BIN
images/events.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/temps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

BIN
rust.raw

Binary file not shown.

3
rustfmt.toml Normal file
View File

@ -0,0 +1,3 @@
hard_tabs = true
match_block_trailing_comma = true
max_width = 120

392
src/bin/display_all.rs Normal file
View File

@ -0,0 +1,392 @@
use std::{fs, ops::Sub, time::Duration, fmt::Debug};
use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{
draw_target::DrawTarget,
mono_font::{
ascii::{FONT_10X20, FONT_5X8, FONT_6X9, FONT_9X15},
MonoTextStyleBuilder,
},
pixelcolor::{Rgb565},
prelude::{Point, Primitive},
primitives::{PrimitiveStyleBuilder, Rectangle},
text::{Text, renderer::CharacterStyle},
Drawable,
};
use raspi_oled::FrameOutput;
use rppal::{
gpio::Gpio,
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,
end_time: Option<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 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 spii = SPIInterfaceNoCS::new(spi, dc);
let disp = ssd1351::display::display::Ssd1351::new(spii);
//let mut disp = FrameOutput::new(128, 128);
let mut disp = draw(disp, time, rh, temp, events, &args, global_min, global_max, vals);
let _ = disp.flush();
//disp.buffer.save("/tmp/x.png");
}
fn draw<D: DrawTarget<Color = Rgb565>>(mut disp: D, time: OffsetDateTime, rh: i64, temp: i64, events: Events, args: &[String], global_min: i32, global_max: i32, mut vals: Vec<(i32, i32)>) -> D where <D as DrawTarget>::Error: Debug {
let hour = time.hour();
let minute = time.minute();
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 mut text_style_6x9 = 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_style_6x9)
.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_style_6x9)
.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_style_6x9)
.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 {
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)))
.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() > 19 { &event.4[0..19] } else { &event.4 };
let day = event.0 as usize;
let y = y + 64 + 9 * i as i32 + 5;
text_style_6x9.set_text_color(Some(Rgb565::new(0xff, 0xff, 0xff)));
Text::new(days[day].0, (x, y).into(), text_style_6x9)
.draw(&mut disp)
.unwrap();
Text::new(days[day].1, (x + 6, y).into(), text_style4)
.draw(&mut disp)
.unwrap();
text_style_6x9.set_text_color(Some(colors[i]));
Text::new(text, (x + 14, y).into(), text_style_6x9)
.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_style_6x9,
)
.draw(&mut disp)
.unwrap();
Text::new(
&format!("{}", global_min as f32 / 10.0),
(100, 64 + 50).into(),
text_style_6x9,
)
.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();
}
disp
}

33
src/bin/display_off.rs Normal file
View 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();
}
}

22
src/bin/refresh_json.rs Normal file
View File

@ -0,0 +1,22 @@
use std::{error::Error, fs};
static WEEKLY: &'static str = include_str!("../../events_weekly.json");
fn main() {
let url = "http://nixos.fritz.box:12783/custom/event_alerts";
if let Ok(json) = get_json(url) {
let mut buf = String::new();
buf += "{";
buf += "\"events\": ";
buf += &json;
buf += ",";
buf += WEEKLY;
buf += "}";
fs::write("events.json", buf.as_bytes()).unwrap();
}
}
fn get_json(url: &str) -> Result<String, Box<dyn Error>> {
Ok(ureq::get(url).call()?.into_string()?)
}

102
src/bin/rgb_test.rs Normal file
View 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)
}
}

View 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();
}
}

View File

@ -1,94 +1,140 @@
use std::{thread::sleep, time::{self, Duration}}; use std::{
thread::sleep,
time::{self, Duration},
};
use embedded_graphics::{pixelcolor::Rgb565, draw_target::DrawTarget, prelude::{OriginDimensions, Size, RgbColor}};
use gpio_cdev::{EventType, Line, LineRequestFlags}; use gpio_cdev::{EventType, Line, LineRequestFlags};
use image::{ImageBuffer, Rgb};
pub struct FrameOutput {
pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
}
impl FrameOutput {
pub fn new(width: u32, height: u32) -> Self {
FrameOutput {
buffer: ImageBuffer::new(width, height)
}
}
}
impl DrawTarget for FrameOutput {
type Color = Rgb565;
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 as u32 >= self.buffer.width() || pos.0.y as u32 >= self.buffer.height() {
continue;
}
self.buffer.put_pixel(pos.0.x as u32, pos.0.y as u32, Rgb([pos.1.r() << 3, pos.1.g() << 2, pos.1.b() << 3]));
}
Ok(())
}
}
impl OriginDimensions for FrameOutput {
fn size(&self) -> Size {
Size::new(self.buffer.width(), self.buffer.height())
}
}
fn read_events(line: &gpio_cdev::Line, timeout: std::time::Duration) -> Result<Vec<(u64, EventType)>, SensorError> { fn read_events(line: &gpio_cdev::Line, timeout: std::time::Duration) -> Result<Vec<(u64, EventType)>, SensorError> {
let input = line.request( let input = line.request(LineRequestFlags::INPUT, 0, "read-data")?;
LineRequestFlags::INPUT,
0,
"read-data")?;
let mut last_state = 1; let mut last_state = 1;
let start = time::Instant::now(); let start = time::Instant::now();
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_state = input.get_value()?; let new_state = input.get_value()?;
if new_state != last_state { if new_state != last_state {
let timestamp = start.elapsed(); let timestamp = start.elapsed();
let event_type = if last_state < new_state { let event_type = if last_state < new_state {
EventType::RisingEdge EventType::RisingEdge
} else { } else {
EventType::FallingEdge EventType::FallingEdge
}; };
events.push((timestamp.as_micros() as u64, event_type)); events.push((timestamp.as_micros() as u64, event_type));
last_state = new_state; last_state = new_state;
} }
} }
if events.len() < 81 { if events.len() < 81 {
return Err(SensorError::Timeout); return Err(SensorError::Timeout);
} }
Ok(events) Ok(events)
} }
fn events_to_data(events: Vec<(u64, EventType)>) -> Vec<u8> { fn events_to_data(events: Vec<(u64, EventType)>) -> Vec<u8> {
events[1..] events[1..]
.windows(2) .windows(2)
.map(|pair| { .map(|pair| {
let prev = pair.get(0).unwrap(); let prev = pair.get(0).unwrap();
let next = pair.get(1).unwrap(); let next = pair.get(1).unwrap();
match next.1 { match next.1 {
EventType::FallingEdge => Some(next.0 - prev.0), EventType::FallingEdge => Some(next.0 - prev.0),
EventType::RisingEdge => None, EventType::RisingEdge => None,
} }
}) })
.filter(|&d| d.is_some()) .filter(|&d| d.is_some())
.map(|elapsed| { .map(|elapsed| if elapsed.unwrap() > 35 { 1 } else { 0 })
if elapsed.unwrap() > 35 { 1 } else { 0 } .collect()
}).collect()
} }
const MAX_HUMIDITY: u16 = 1000; const MAX_HUMIDITY: u16 = 1000;
fn process_data(mut bits: &[u8]) -> Result<(u16, u16), SensorError> { fn process_data(mut bits: &[u8]) -> Result<(u16, u16), SensorError> {
if bits[0] == 1 { if bits[0] == 1 {
// definitely incorrect first bit // definitely incorrect first bit
// (the humidity can't be this big..) // (the humidity can't be this big..)
bits = &bits[1..]; bits = &bits[1..];
} }
let bytes: Vec<u8> = bits let bytes: Vec<u8> = bits
.chunks(8) .chunks(8)
.map(|chunk| chunk.iter() .map(|chunk| {
.enumerate() chunk
// 8 bits, starting with the MSB .iter()
.map(|(bit_idx, &x)| x << (7 - bit_idx)) .enumerate()
.sum() // 8 bits, starting with the MSB
).collect(); .map(|(bit_idx, &x)| x << (7 - bit_idx))
let rh = (bytes[0] as u16) << 8 | bytes[1] as u16; .sum()
})
.collect();
let rh = (bytes[0] as u16) << 8 | bytes[1] as u16;
if rh > MAX_HUMIDITY { if rh > MAX_HUMIDITY {
return Err(SensorError::HumidityTooHigh); return Err(SensorError::HumidityTooHigh);
} }
let celsius = (bytes[2] as u16) << 8 | bytes[3] as u16; let celsius = (bytes[2] as u16) << 8 | bytes[3] as u16;
if bits.len() >= 40 { if bits.len() >= 40 {
let cksum: u8 = bits[32..40] let cksum: u8 = bits[32..40].iter().enumerate().map(|(idx, &x)| x << (7 - idx)).sum();
.iter() let actual_sum = (bytes[0]
.enumerate() .wrapping_add(bytes[1])
.map(|(idx, &x)| x << (7 - idx)) .wrapping_add(bytes[2])
.sum(); .wrapping_add(bytes[3]))
let actual_sum = (bytes[0].wrapping_add(bytes[1]).wrapping_add(bytes[2]).wrapping_add(bytes[3])) & 0xff; & 0xff;
if actual_sum != cksum { if actual_sum != cksum {
return Err(SensorError::ChecksumMismatch); return Err(SensorError::ChecksumMismatch);
} }
} }
Ok((rh, celsius)) Ok((rh, celsius))
} }
#[test] #[test]
fn test_process_data() { fn test_process_data() {
let x = process_data(&[1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1]).unwrap(); let x = process_data(&[
assert_eq!(471, x.0); 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0,
assert_eq!(268, x.1); 0, 1, 1,
])
.unwrap();
assert_eq!(471, x.0);
assert_eq!(268, x.1);
} }
#[derive(Debug)] #[derive(Debug)]
@ -96,45 +142,45 @@ pub enum SensorError {
Io(gpio_cdev::Error), Io(gpio_cdev::Error),
ChecksumMismatch, ChecksumMismatch,
HumidityTooHigh, HumidityTooHigh,
Timeout Timeout,
} }
impl From<gpio_cdev::Error> for SensorError { impl From<gpio_cdev::Error> for SensorError {
fn from(error: gpio_cdev::Error) -> Self { fn from(error: gpio_cdev::Error) -> Self {
SensorError::Io(error) SensorError::Io(error)
} }
} }
pub fn am2302_reading(line: &Line) -> Result<(u16, u16), SensorError> { pub fn am2302_reading(line: &Line) -> Result<(u16, u16), SensorError> {
line.request(LineRequestFlags::OUTPUT, 1, "rust-am2302").unwrap(); line.request(LineRequestFlags::OUTPUT, 1, "rust-am2302").unwrap();
sleep(Duration::from_millis(500)); sleep(Duration::from_millis(500));
set_max_priority(); set_max_priority();
// set low for 20 ms // set low for 20 ms
if let Err(e) = line.request(LineRequestFlags::OUTPUT, 0, "rust-am2302") { if let Err(e) = line.request(LineRequestFlags::OUTPUT, 0, "rust-am2302") {
set_normal_priority(); set_normal_priority();
return Err(SensorError::Io(e)); return Err(SensorError::Io(e));
} }
sleep(Duration::from_millis(3)); sleep(Duration::from_millis(3));
let events = read_events(&line, Duration::from_secs(1)); let events = read_events(&line, Duration::from_secs(1));
println!("{:?} {:?}", events, events.as_ref().map(|x| x.len())); println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
set_normal_priority(); set_normal_priority();
let events = events?; let events = events?;
let data = events_to_data(events); let data = events_to_data(events);
process_data(&data) process_data(&data)
} }
fn set_max_priority() { fn set_max_priority() {
unsafe { unsafe {
let mut sched_para: libc::sched_param = std::mem::transmute([0u8; std::mem::size_of::<libc::sched_param>()]); let mut sched_para: libc::sched_param = std::mem::transmute([0u8; std::mem::size_of::<libc::sched_param>()]);
sched_para.sched_priority = libc::sched_get_priority_max(libc::SCHED_FIFO); sched_para.sched_priority = libc::sched_get_priority_max(libc::SCHED_FIFO);
libc::sched_setscheduler(0, libc::SCHED_FIFO, (&sched_para) as *const libc::sched_param); libc::sched_setscheduler(0, libc::SCHED_FIFO, (&sched_para) as *const libc::sched_param);
} }
} }
fn set_normal_priority() { fn set_normal_priority() {
unsafe { unsafe {
let sched_para: libc::sched_param = std::mem::transmute([0u8; std::mem::size_of::<libc::sched_param>()]); let sched_para: libc::sched_param = std::mem::transmute([0u8; std::mem::size_of::<libc::sched_param>()]);
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);
} }
} }

View File

@ -1,30 +1,6 @@
use dht_hal::{Dht22, Reading};
use embedded_graphics::image::{Image, ImageRaw};
use embedded_graphics::mono_font::iso_8859_7::FONT_9X18;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle, Rectangle};
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder},
text::Text,
};
use embedded_hal::digital::v2::{InputPin, OutputPin};
use embedded_hal::prelude::_embedded_hal_blocking_i2c_Write;
use gpio_cdev::{Chip, EventRequestFlags, EventType, LineRequestFlags};
use linux_embedded_hal::i2cdev::core::I2CDevice; use linux_embedded_hal::i2cdev::core::I2CDevice;
use linux_embedded_hal::i2cdev::linux::LinuxI2CError; use linux_embedded_hal::i2cdev::linux::LinuxI2CError;
use rusqlite::{Connection, params}; use linux_embedded_hal::I2cdev;
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
use linux_embedded_hal::{Delay, I2cdev};
use machine_ip;
use std::intrinsics::transmute;
use std::{env, mem, time};
use std::thread::sleep;
use std::time::{Duration, SystemTime};
static IMG_DATA: &[u8; 512] = include_bytes!("../rust.raw");
const CCS811_ADDR: u8 = 0x5A; // or 0x5B const CCS811_ADDR: u8 = 0x5A; // or 0x5B
@ -45,209 +21,317 @@ const CCS811_APP_START: u8 = 0xF4;
const CCS811_SW_RESET: u8 = 0xFF; const CCS811_SW_RESET: u8 = 0xFF;
struct CCS811 { struct CCS811 {
i2c: I2cdev, i2c: I2cdev,
addr: u8 addr: u8,
} }
impl CCS811 { impl CCS811 {
fn new(mut i2c: I2cdev, addr: u8) -> Self { fn new(mut i2c: I2cdev, addr: u8) -> Self {
i2c.set_slave_address(addr as u16).unwrap(); i2c.set_slave_address(addr as u16).unwrap();
Self { Self { i2c, addr }
i2c, }
addr
}
}
fn check_for_error(&mut self) -> Option<u8> { fn check_for_error(&mut self) -> Option<u8> {
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap(); let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
if (x & 1) != 0 { if (x & 1) != 0 {
let err_code = self.i2c.smbus_read_byte_data(CCS811_ERROR_ID).unwrap(); let err_code = self.i2c.smbus_read_byte_data(CCS811_ERROR_ID).unwrap();
Some(err_code) Some(err_code)
} else { } else {
None None
} }
} }
fn hardware_id(&mut self) -> u8 { fn hardware_id(&mut self) -> u8 {
self.i2c.smbus_read_byte_data(CCS811_HW_ID).unwrap() self.i2c.smbus_read_byte_data(CCS811_HW_ID).unwrap()
} }
fn app_valid(&mut self) -> bool { fn app_valid(&mut self) -> bool {
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap(); let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
x & 1 << 4 != 0 x & 1 << 4 != 0
} }
fn set_drive_mode(&mut self, mode: CCS811DriveMode) -> Result<(), Option<LinuxI2CError>> { fn set_drive_mode(&mut self, mode: CCS811DriveMode) -> Result<(), Option<LinuxI2CError>> {
self.i2c.smbus_write_byte(CCS811_APP_START).map_err(Some)?; self.i2c.smbus_write_byte(CCS811_APP_START).map_err(Some)?;
if let Some(x) = self.check_for_error() { if let Some(x) = self.check_for_error() {
println!("error ignored {:b}", x); println!("error ignored {:b}", x);
} }
let mut setting = self.i2c.smbus_read_byte_data(CCS811_MEAS_MODE).map_err(Some)?; let mut setting = self.i2c.smbus_read_byte_data(CCS811_MEAS_MODE).map_err(Some)?;
setting &= !(0b00000111 << 4); setting &= !(0b00000111 << 4);
setting |= (mode as u8) << 4; setting |= (mode as u8) << 4;
self.i2c.smbus_write_byte_data(CCS811_MEAS_MODE, setting).map_err(Some)?; self.i2c
Ok(()) .smbus_write_byte_data(CCS811_MEAS_MODE, setting)
} .map_err(Some)?;
Ok(())
}
fn get_baseline(&mut self) -> u16 { fn get_baseline(&mut self) -> u16 {
let x = self.i2c.smbus_read_i2c_block_data(CCS811_BASELINE, 2).unwrap(); let x = self.i2c.smbus_read_i2c_block_data(CCS811_BASELINE, 2).unwrap();
((x[0] as u16) << 8) | (x[1] as u16) ((x[0] as u16) << 8) | (x[1] as u16)
} }
/// Returns (eCO2, tVOC) /// Returns (eCO2, tVOC)
fn get_reading(&mut self) -> (u16, u16) { fn get_reading(&mut self) -> (u16, u16) {
let x = self.i2c.smbus_read_i2c_block_data(CCS811_ALG_RESULT_DATA, 4).unwrap(); let x = self.i2c.smbus_read_i2c_block_data(CCS811_ALG_RESULT_DATA, 4).unwrap();
(((x[0] as u16) << 8) | (x[1] as u16), ((x[2] as u16) << 8) | (x[3] as u16)) (
} ((x[0] as u16) << 8) | (x[1] as u16),
((x[2] as u16) << 8) | (x[3] as u16),
)
}
} }
enum CCS811DriveMode { enum CCS811DriveMode {
Idle = 0, Idle = 0,
EverySecond = 1, EverySecond = 1,
Every10Seconds = 2, Every10Seconds = 2,
Every60Seconds = 3, Every60Seconds = 3,
/// Note the English manual states this is calculated every 10 ms! /// Note the English manual states this is calculated every 10 ms!
Every250Milliseconds = 4, Every250Milliseconds = 4,
} }
fn main() { fn main() {
let args = env::args().collect::<Vec<_>>(); let i2c = I2cdev::new("/dev/i2c-1").unwrap();
if args.len() < 2 { let mut ccs = CCS811::new(i2c, CCS811_ADDR);
panic!("missing argument: database path"); println!("HW ID, should be 0x81 {:x}", ccs.hardware_id());
} println!("Error code, should be None: {:?}", ccs.check_for_error());
let database = Connection::open(&args[1]).expect("failed to open database"); println!("app valid = {:?}", ccs.app_valid());
database.execute(" println!("baseline = {:x}", ccs.get_baseline());
CREATE TABLE IF NOT EXISTS sensor_readings( println!("reading {:?}", ccs.get_reading());
time INTEGER PRIMARY KEY, println!("setting drive mode to 1: {:?}", ccs.set_drive_mode(CCS811DriveMode::EverySecond));
humidity INTEGER NOT NULL,
celsius INTEGER NOT NULL
)", []).unwrap();
/* /*
let mut ccs = CCS811::new(i2c, CCS811_ADDR); let args = std::env::args().collect::<Vec<_>>();
println!("HW ID, should be 0x81 {:x}", ccs.hardware_id()); if args.len() < 2 {
println!("Error code, should be None: {:?}", ccs.check_for_error()); panic!("missing argument: database path");
println!("app valid = {:?}", ccs.app_valid()); }
println!("baseline = {:x}", ccs.get_baseline());
println!("reading {:?}", ccs.get_reading()); let mut disp = FrameOutput {
println!("setting drive mode to 1: {:?}", ccs.set_drive_mode(CCS811DriveMode::EverySecond)); buffer: ImageBuffer::new(128, 64),
*/ };
let mut chip = Chip::new("/dev/gpiochip0").unwrap();
let line = chip.get_line(26).unwrap(); let database = Connection::open(&args[1]).expect("failed to open database");
for _attempt in 0..5 {
let time = std::time::SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let mut query = database
if let Ok((rh, temp)) = raspi_oled::am2302_reading(&line) { .prepare("SELECT celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 288")
if rh > 0 && temp < 500 { .unwrap();
database.execute("INSERT INTO sensor_readings (time, humidity, celsius) VALUES (?1, ?2, ?3)", params![time.as_secs(), rh, temp]).unwrap(); let mut temps: Vec<i32> = query
display_on_ssd1306(rh, temp, time); .query_map([], |r| Ok(r.get(0)))
break; .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 embedded_graphics::image::{Image, ImageRaw};
use embedded_graphics::mono_font::iso_8859_7::FONT_9X18;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle, Rectangle};
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder},
text::Text,
};
use embedded_hal::digital::v2::{InputPin, OutputPin};
use embedded_hal::prelude::_embedded_hal_blocking_i2c_Write;
use gpio_cdev::{Chip, EventRequestFlags, EventType, LineRequestFlags};
use linux_embedded_hal::i2cdev::core::I2CDevice;
use linux_embedded_hal::i2cdev::linux::LinuxI2CError;
use linux_embedded_hal::{Delay, I2cdev};
use machine_ip;
use rusqlite::{params, Connection};
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
use std::intrinsics::transmute;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use std::{env, mem, time};
static IMG_DATA: &[u8; 512] = include_bytes!("../rust.raw");
fn main() {
let args = 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 ccs = CCS811::new(i2c, CCS811_ADDR);
println!("HW ID, should be 0x81 {:x}", ccs.hardware_id());
println!("Error code, should be None: {:?}", ccs.check_for_error());
println!("app valid = {:?}", ccs.app_valid());
println!("baseline = {:x}", ccs.get_baseline());
println!("reading {:?}", ccs.get_reading());
println!("setting drive mode to 1: {:?}", ccs.set_drive_mode(CCS811DriveMode::EverySecond));
*/
let mut chip = Chip::new("/dev/gpiochip0").unwrap();
let line = chip.get_line(26).unwrap();
for _attempt in 0..5 {
let time = std::time::SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
if let Ok((rh, temp)) = raspi_oled::am2302_reading(&line) {
if rh > 0 && temp < 500 {
database
.execute(
"INSERT INTO sensor_readings (time, humidity, celsius) VALUES (?1, ?2, ?3)",
params![time.as_secs(), rh, temp],
)
.unwrap();
display_on_ssd1306(rh, temp, time);
break;
}
}
}
} }
fn display_on_ssd1306(rh: u16, temp: u16, time: Duration) { fn display_on_ssd1306(rh: u16, temp: u16, time: Duration) {
let i2c = I2cdev::new("/dev/i2c-1").unwrap(); let i2c = I2cdev::new("/dev/i2c-1").unwrap();
let interface = I2CDisplayInterface::new(i2c); let interface = I2CDisplayInterface::new(i2c);
let mut disp = Ssd1306::new( let mut disp = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_buffered_graphics_mode();
interface,
DisplaySize128x64,
DisplayRotation::Rotate0,
).into_buffered_graphics_mode();
disp.init().unwrap(); disp.init().unwrap();
let text_style = MonoTextStyleBuilder::new() let text_style = MonoTextStyleBuilder::new()
.font(&FONT_9X18) .font(&FONT_9X18)
.text_color(BinaryColor::On) .text_color(BinaryColor::On)
.build(); .build();
let text = format!("{}.{}% {}.{}°C", rh / 10, rh % 10, temp / 10, temp % 10); let text = format!("{}.{}% {}.{}°C", rh / 10, rh % 10, temp / 10, temp % 10);
Text::new(&text, Point::new(0, 10), text_style) Text::new(&text, Point::new(0, 10), text_style).draw(&mut disp).unwrap();
.draw(&mut disp) let secs = time.as_secs();
.unwrap(); let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60);
let secs = time.as_secs(); Text::new(&time, Point::new(0, 32), text_style).draw(&mut disp).unwrap();
let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60); disp.flush().unwrap();
Text::new(&time, Point::new(0, 32), text_style) /*
.draw(&mut disp) sleep(Duration::from_secs(2));
.unwrap(); disp.clear();
disp.flush().unwrap();
/*
sleep(Duration::from_secs(2));
disp.clear();
let base_y = 0.0; let base_y = 0.0;
let max_dy = 32.0; let max_dy = 32.0;
let mut tick = 0; let mut tick = 0;
loop { loop {
let y = if tick % 32 < 16 { let y = if tick % 32 < 16 {
base_y + (tick % 16) as f32 / 16.0 * max_dy base_y + (tick % 16) as f32 / 16.0 * max_dy
} else { } else {
base_y + max_dy - (tick % 16) as f32 / 16.0 * max_dy base_y + max_dy - (tick % 16) as f32 / 16.0 * max_dy
} as i32; } as i32;
tick += 1; tick += 1;
Line::new(Point::new(8, y + 16), Point::new(8 + 16, y + 16)) Line::new(Point::new(8, y + 16), Point::new(8 + 16, y + 16))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut disp).unwrap(); .draw(&mut disp).unwrap();
Line::new(Point::new(8, y + 16), Point::new(8 + 8, y)) Line::new(Point::new(8, y + 16), Point::new(8 + 8, y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut disp).unwrap(); .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)) Line::new(Point::new(8 + 16, y + 16), Point::new(8 + 8, y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut disp).unwrap(); .draw(&mut disp).unwrap();
Circle::new(Point::new(88, y), 16) Rectangle::new(Point::new(48, y), Size::new(16, 16))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut disp).unwrap(); .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)); Circle::new(Point::new(88, y), 16)
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut disp).unwrap();
disp.clear(); /*
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));
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();
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();
*/
}
*/
} }
struct LineWrapper(gpio_cdev::Line); struct LineWrapper(gpio_cdev::Line);
impl InputPin for LineWrapper { impl InputPin for LineWrapper {
type Error = gpio_cdev::Error; type Error = gpio_cdev::Error;
fn is_high(&self) -> Result<bool, Self::Error> { fn is_high(&self) -> Result<bool, Self::Error> {
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?; let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
Ok(handle.get_value()? == 1) Ok(handle.get_value()? == 1)
} }
fn is_low(&self) -> Result<bool, Self::Error> { fn is_low(&self) -> Result<bool, Self::Error> {
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?; let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
Ok(handle.get_value()? == 0) Ok(handle.get_value()? == 0)
} }
} }
impl OutputPin for LineWrapper { impl OutputPin for LineWrapper {
<<<<<<< HEAD
type Error = gpio_cdev::Error; type Error = gpio_cdev::Error;
fn set_low(&mut self) -> Result<(), Self::Error> { fn set_low(&mut self) -> Result<(), Self::Error> {
@ -257,4 +341,31 @@ impl OutputPin for LineWrapper {
fn set_high(&mut self) -> Result<(), Self::Error> { fn set_high(&mut self) -> Result<(), Self::Error> {
self.0.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?.set_value(1) self.0.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?.set_value(1)
} }
} }
||||||| parent of 683458d (Reformat code)
type Error = gpio_cdev::Error;
fn set_low(&mut self) -> Result<(), Self::Error> {
self.0.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?.set_value(0)
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.0.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?.set_value(1)
}
}
=======
type Error = gpio_cdev::Error;
fn set_low(&mut self) -> Result<(), Self::Error> {
self.0
.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?
.set_value(0)
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.0
.request(LineRequestFlags::OUTPUT, 1, "rust-line-wrapper")?
.set_value(1)
}
}
*/