Compare commits

...

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

19 changed files with 389 additions and 2119 deletions

3
.gitignore vendored
View File

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

1107
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 = ["FliegendeWurst <2012gdwu@posteo.de>"] authors = ["chux0519 <chuxdesign@hotmail.com>"]
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,19 +10,12 @@ 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,19 +1,29 @@
Copyright (c) 2022 Arne Keller BSD 3-Clause License
Permission is hereby granted, free of charge, to any person obtaining a copy Copyright (c) 2020, Yongsheng Xu
of this software and associated documentation files (the "Software"), to deal All rights reserved.
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:
The above copyright notice and this permission notice shall be included in all Redistribution and use in source and binary forms, with or without
copies or substantial portions of the Software. modification, are permitted provided that the following conditions are met:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1. Redistributions of source code must retain the above copyright notice, this
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, list of conditions and the following disclaimer.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 2. Redistributions in binary form must reproduce the above copyright notice,
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, this list of conditions and the following disclaimer in the documentation
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE and/or other materials provided with the distribution.
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,24 +1,17 @@
# raspi demo for OLED ssd1351 display # raspi demo for oled ssd1306 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:~'
> # on the Pi, create sensors.db and events.json Then scp the release file to your raspi.
> patchelf --set-interpreter /lib/ld-linux-armhf.so.3 display_all
> ./display_off on
> ./display_all sensors.db events.json temps
```
## Example ## Example
![temperature graph](./images/temps.png) ![picture](./images/01.jpg)
![events](./images/events.png) ![primitive](./images/02.jpg)
(the second blue text is brighter on the OLED)

View File

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

BIN
images/01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

BIN
images/02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

BIN
rust.raw Normal file

Binary file not shown.

View File

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

View File

@ -1,392 +0,0 @@
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
}

View File

@ -1,33 +0,0 @@
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();
}
}

View File

@ -1,22 +0,0 @@
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()?)
}

View File

@ -1,102 +0,0 @@
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

@ -1,53 +0,0 @@
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,140 +1,94 @@
use std::{ use std::{thread::sleep, time::{self, Duration}};
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(LineRequestFlags::INPUT, 0, "read-data")?; let input = line.request(
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| if elapsed.unwrap() > 35 { 1 } else { 0 }) .map(|elapsed| {
.collect() if elapsed.unwrap() > 35 { 1 } else { 0 }
}).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| { .map(|chunk| chunk.iter()
chunk .enumerate()
.iter() // 8 bits, starting with the MSB
.enumerate() .map(|(bit_idx, &x)| x << (7 - bit_idx))
// 8 bits, starting with the MSB .sum()
.map(|(bit_idx, &x)| x << (7 - bit_idx)) ).collect();
.sum() let rh = (bytes[0] as u16) << 8 | bytes[1] as u16;
})
.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].iter().enumerate().map(|(idx, &x)| x << (7 - idx)).sum(); let cksum: u8 = bits[32..40]
let actual_sum = (bytes[0] .iter()
.wrapping_add(bytes[1]) .enumerate()
.wrapping_add(bytes[2]) .map(|(idx, &x)| x << (7 - idx))
.wrapping_add(bytes[3])) .sum();
& 0xff; let actual_sum = (bytes[0].wrapping_add(bytes[1]).wrapping_add(bytes[2]).wrapping_add(bytes[3])) & 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(&[ 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();
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!(471, x.0);
0, 1, 1, assert_eq!(268, x.1);
])
.unwrap();
assert_eq!(471, x.0);
assert_eq!(268, x.1);
} }
#[derive(Debug)] #[derive(Debug)]
@ -142,45 +96,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,6 +1,30 @@
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 linux_embedded_hal::I2cdev; use rusqlite::{Connection, params};
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
@ -21,317 +45,209 @@ 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 { i2c, addr } Self {
} 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 self.i2c.smbus_write_byte_data(CCS811_MEAS_MODE, setting).map_err(Some)?;
.smbus_write_byte_data(CCS811_MEAS_MODE, setting) Ok(())
.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 i2c = I2cdev::new("/dev/i2c-1").unwrap(); let args = env::args().collect::<Vec<_>>();
let mut ccs = CCS811::new(i2c, CCS811_ADDR); if args.len() < 2 {
println!("HW ID, should be 0x81 {:x}", ccs.hardware_id()); panic!("missing argument: database path");
println!("Error code, should be None: {:?}", ccs.check_for_error()); }
println!("app valid = {:?}", ccs.app_valid()); let database = Connection::open(&args[1]).expect("failed to open database");
println!("baseline = {:x}", ccs.get_baseline()); database.execute("
println!("reading {:?}", ccs.get_reading()); CREATE TABLE IF NOT EXISTS sensor_readings(
println!("setting drive mode to 1: {:?}", ccs.set_drive_mode(CCS811DriveMode::EverySecond)); time INTEGER PRIMARY KEY,
humidity INTEGER NOT NULL,
celsius INTEGER NOT NULL
)", []).unwrap();
/* /*
let args = std::env::args().collect::<Vec<_>>(); let mut ccs = CCS811::new(i2c, CCS811_ADDR);
if args.len() < 2 { println!("HW ID, should be 0x81 {:x}", ccs.hardware_id());
panic!("missing argument: database path"); println!("Error code, should be None: {:?}", ccs.check_for_error());
} println!("app valid = {:?}", ccs.app_valid());
println!("baseline = {:x}", ccs.get_baseline());
let mut disp = FrameOutput { println!("reading {:?}", ccs.get_reading());
buffer: ImageBuffer::new(128, 64), println!("setting drive mode to 1: {:?}", ccs.set_drive_mode(CCS811DriveMode::EverySecond));
}; */
let mut chip = Chip::new("/dev/gpiochip0").unwrap();
let database = Connection::open(&args[1]).expect("failed to open database"); let line = chip.get_line(26).unwrap();
for _attempt in 0..5 {
let mut query = database let time = std::time::SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
.prepare("SELECT celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 288") if let Ok((rh, temp)) = raspi_oled::am2302_reading(&line) {
.unwrap(); if rh > 0 && temp < 500 {
let mut temps: Vec<i32> = query database.execute("INSERT INTO sensor_readings (time, humidity, celsius) VALUES (?1, ?2, ?3)", params![time.as_secs(), rh, temp]).unwrap();
.query_map([], |r| Ok(r.get(0))) display_on_ssd1306(rh, temp, time);
.unwrap() break;
.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(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_buffered_graphics_mode(); let mut disp = Ssd1306::new(
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).draw(&mut disp).unwrap(); Text::new(&text, Point::new(0, 10), text_style)
let secs = time.as_secs(); .draw(&mut disp)
let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60); .unwrap();
Text::new(&time, Point::new(0, 32), text_style).draw(&mut disp).unwrap(); let secs = time.as_secs();
disp.flush().unwrap(); let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60);
/* Text::new(&time, Point::new(0, 32), text_style)
sleep(Duration::from_secs(2)); .draw(&mut disp)
disp.clear(); .unwrap();
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)) 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();
Rectangle::new(Point::new(48, y), Size::new(16, 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();
Circle::new(Point::new(88, y), 16) Circle::new(Point::new(88, 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();
/* /*
Text::new(&format!("Hello from frame {}", tick), Point::new(0, 56), text_style) Text::new(&format!("Hello from frame {}", tick), Point::new(0, 56), text_style)
.draw(&mut disp) .draw(&mut disp)
.unwrap(); .unwrap();
*/ */
disp.flush().unwrap(); disp.flush().unwrap();
sleep(Duration::from_millis(10)); sleep(Duration::from_millis(10));
disp.clear(); disp.clear();
/* /*
let im: ImageRaw<BinaryColor> = ImageRaw::new(IMG_DATA, 64); let im: ImageRaw<BinaryColor> = ImageRaw::new(IMG_DATA, 64);
let img = Image::new(&im, Point::new(32, 0)); let img = Image::new(&im, Point::new(32, 0));
img.draw(&mut disp).unwrap(); img.draw(&mut disp).unwrap();
disp.flush().unwrap(); disp.flush().unwrap();
sleep(Duration::from_secs(2)); sleep(Duration::from_secs(2));
disp.clear(); 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> {
@ -342,30 +258,3 @@ impl OutputPin for LineWrapper {
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)
}
}
*/