mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-28 04:55:53 +00:00
Compare commits
No commits in common. "adfc7925c70917d89eb943f4b624fc1a3101579b" and "9cc84258eb3cd4ed9ea8328f1aa53130a154b6e5" have entirely different histories.
adfc7925c7
...
9cc84258eb
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
events_weekly.json
|
||||
events.json
|
||||
sensors.db
|
||||
|
1107
Cargo.lock
generated
1107
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "raspi-oled"
|
||||
version = "0.1.0"
|
||||
authors = ["FliegendeWurst <2012gdwu@posteo.de>"]
|
||||
authors = ["chux0519 <chuxdesign@hotmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# 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"
|
||||
linux-embedded-hal = "0.3.0"
|
||||
embedded-hal = "0.2.5"
|
||||
machine-ip = "0.2.1"
|
||||
ssd1306 = "0.6.0"
|
||||
libc = "0.2.98"
|
||||
gpio-cdev = "0.4"
|
||||
dht-hal = "0.0.1"
|
||||
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]
|
||||
codegen-units = 1
|
||||
|
42
LICENSE
42
LICENSE
@ -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
|
||||
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:
|
||||
Copyright (c) 2020, Yongsheng Xu
|
||||
All rights reserved.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
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.
|
||||
|
21
README.md
21
README.md
@ -1,24 +1,17 @@
|
||||
# raspi demo for OLED ssd1351 display
|
||||
|
||||
https://www.waveshare.com/wiki/1.5inch_RGB_OLED_Module
|
||||
# raspi demo for oled ssd1306 display
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
> nix-shell
|
||||
>
|
||||
> rustup target add 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
|
||||
> patchelf --set-interpreter /lib/ld-linux-armhf.so.3 display_all
|
||||
> ./display_off on
|
||||
> ./display_all sensors.db events.json temps
|
||||
```
|
||||
|
||||
Then scp the release file to your raspi.
|
||||
|
||||
## Example
|
||||
|
||||
![temperature graph](./images/temps.png)
|
||||
![picture](./images/01.jpg)
|
||||
|
||||
![events](./images/events.png)
|
||||
|
||||
(the second blue text is brighter on the OLED)
|
||||
![primitive](./images/02.jpg)
|
||||
|
@ -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
BIN
images/01.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
BIN
images/02.jpg
Normal file
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 |
BIN
images/temps.png
BIN
images/temps.png
Binary file not shown.
Before Width: | Height: | Size: 843 B |
@ -1,3 +0,0 @@
|
||||
hard_tabs = true
|
||||
match_block_trailing_comma = true
|
||||
max_width = 120
|
@ -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
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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()?)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
210
src/lib.rs
210
src/lib.rs
@ -1,140 +1,94 @@
|
||||
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 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> {
|
||||
let input = line.request(LineRequestFlags::INPUT, 0, "read-data")?;
|
||||
let input = line.request(
|
||||
LineRequestFlags::INPUT,
|
||||
0,
|
||||
"read-data")?;
|
||||
|
||||
let mut last_state = 1;
|
||||
let start = time::Instant::now();
|
||||
let mut last_state = 1;
|
||||
let start = time::Instant::now();
|
||||
|
||||
let mut events = Vec::with_capacity(81);
|
||||
while start.elapsed() < timeout && events.len() < 81 {
|
||||
let new_state = input.get_value()?;
|
||||
if new_state != last_state {
|
||||
let timestamp = start.elapsed();
|
||||
let event_type = if last_state < new_state {
|
||||
EventType::RisingEdge
|
||||
} else {
|
||||
EventType::FallingEdge
|
||||
};
|
||||
events.push((timestamp.as_micros() as u64, event_type));
|
||||
last_state = new_state;
|
||||
}
|
||||
}
|
||||
let mut events = Vec::with_capacity(81);
|
||||
while start.elapsed() < timeout && events.len() < 81 {
|
||||
let new_state = input.get_value()?;
|
||||
if new_state != last_state {
|
||||
let timestamp = start.elapsed();
|
||||
let event_type = if last_state < new_state {
|
||||
EventType::RisingEdge
|
||||
} else {
|
||||
EventType::FallingEdge
|
||||
};
|
||||
events.push((timestamp.as_micros() as u64, event_type));
|
||||
last_state = new_state;
|
||||
}
|
||||
}
|
||||
if events.len() < 81 {
|
||||
return Err(SensorError::Timeout);
|
||||
}
|
||||
Ok(events)
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
fn events_to_data(events: Vec<(u64, EventType)>) -> Vec<u8> {
|
||||
events[1..]
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
let prev = pair.get(0).unwrap();
|
||||
let next = pair.get(1).unwrap();
|
||||
match next.1 {
|
||||
EventType::FallingEdge => Some(next.0 - prev.0),
|
||||
EventType::RisingEdge => None,
|
||||
}
|
||||
})
|
||||
.filter(|&d| d.is_some())
|
||||
.map(|elapsed| if elapsed.unwrap() > 35 { 1 } else { 0 })
|
||||
.collect()
|
||||
events[1..]
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
let prev = pair.get(0).unwrap();
|
||||
let next = pair.get(1).unwrap();
|
||||
match next.1 {
|
||||
EventType::FallingEdge => Some(next.0 - prev.0),
|
||||
EventType::RisingEdge => None,
|
||||
}
|
||||
})
|
||||
.filter(|&d| d.is_some())
|
||||
.map(|elapsed| {
|
||||
if elapsed.unwrap() > 35 { 1 } else { 0 }
|
||||
}).collect()
|
||||
}
|
||||
|
||||
const MAX_HUMIDITY: u16 = 1000;
|
||||
|
||||
fn process_data(mut bits: &[u8]) -> Result<(u16, u16), SensorError> {
|
||||
if bits[0] == 1 {
|
||||
// definitely incorrect first bit
|
||||
if bits[0] == 1 {
|
||||
// definitely incorrect first bit
|
||||
// (the humidity can't be this big..)
|
||||
bits = &bits[1..];
|
||||
}
|
||||
let bytes: Vec<u8> = bits
|
||||
.chunks(8)
|
||||
.map(|chunk| {
|
||||
chunk
|
||||
.iter()
|
||||
.enumerate()
|
||||
// 8 bits, starting with the MSB
|
||||
.map(|(bit_idx, &x)| x << (7 - bit_idx))
|
||||
.sum()
|
||||
})
|
||||
.collect();
|
||||
let rh = (bytes[0] as u16) << 8 | bytes[1] as u16;
|
||||
bits = &bits[1..];
|
||||
}
|
||||
let bytes: Vec<u8> = bits
|
||||
.chunks(8)
|
||||
.map(|chunk| chunk.iter()
|
||||
.enumerate()
|
||||
// 8 bits, starting with the MSB
|
||||
.map(|(bit_idx, &x)| x << (7 - bit_idx))
|
||||
.sum()
|
||||
).collect();
|
||||
let rh = (bytes[0] as u16) << 8 | bytes[1] as u16;
|
||||
if rh > MAX_HUMIDITY {
|
||||
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 {
|
||||
let cksum: u8 = bits[32..40].iter().enumerate().map(|(idx, &x)| x << (7 - idx)).sum();
|
||||
let actual_sum = (bytes[0]
|
||||
.wrapping_add(bytes[1])
|
||||
.wrapping_add(bytes[2])
|
||||
.wrapping_add(bytes[3]))
|
||||
& 0xff;
|
||||
if actual_sum != cksum {
|
||||
if bits.len() >= 40 {
|
||||
let cksum: u8 = bits[32..40]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, &x)| x << (7 - idx))
|
||||
.sum();
|
||||
let actual_sum = (bytes[0].wrapping_add(bytes[1]).wrapping_add(bytes[2]).wrapping_add(bytes[3])) & 0xff;
|
||||
if actual_sum != cksum {
|
||||
return Err(SensorError::ChecksumMismatch);
|
||||
}
|
||||
}
|
||||
Ok((rh, celsius))
|
||||
}
|
||||
Ok((rh, celsius))
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
assert_eq!(471, x.0);
|
||||
assert_eq!(268, x.1);
|
||||
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();
|
||||
assert_eq!(471, x.0);
|
||||
assert_eq!(268, x.1);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -142,45 +96,45 @@ pub enum SensorError {
|
||||
Io(gpio_cdev::Error),
|
||||
ChecksumMismatch,
|
||||
HumidityTooHigh,
|
||||
Timeout,
|
||||
Timeout
|
||||
}
|
||||
|
||||
impl From<gpio_cdev::Error> for SensorError {
|
||||
fn from(error: gpio_cdev::Error) -> Self {
|
||||
SensorError::Io(error)
|
||||
}
|
||||
fn from(error: gpio_cdev::Error) -> Self {
|
||||
SensorError::Io(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn am2302_reading(line: &Line) -> Result<(u16, u16), SensorError> {
|
||||
line.request(LineRequestFlags::OUTPUT, 1, "rust-am2302").unwrap();
|
||||
sleep(Duration::from_millis(500));
|
||||
set_max_priority();
|
||||
// set low for 20 ms
|
||||
if let Err(e) = line.request(LineRequestFlags::OUTPUT, 0, "rust-am2302") {
|
||||
sleep(Duration::from_millis(500));
|
||||
set_max_priority();
|
||||
// set low for 20 ms
|
||||
if let Err(e) = line.request(LineRequestFlags::OUTPUT, 0, "rust-am2302") {
|
||||
set_normal_priority();
|
||||
return Err(SensorError::Io(e));
|
||||
}
|
||||
sleep(Duration::from_millis(3));
|
||||
sleep(Duration::from_millis(3));
|
||||
|
||||
let events = read_events(&line, Duration::from_secs(1));
|
||||
println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
|
||||
set_normal_priority();
|
||||
let events = events?;
|
||||
let data = events_to_data(events);
|
||||
process_data(&data)
|
||||
process_data(&data)
|
||||
}
|
||||
|
||||
fn set_max_priority() {
|
||||
unsafe {
|
||||
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);
|
||||
libc::sched_setscheduler(0, libc::SCHED_FIFO, (&sched_para) as *const libc::sched_param);
|
||||
}
|
||||
unsafe {
|
||||
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);
|
||||
libc::sched_setscheduler(0, libc::SCHED_FIFO, (&sched_para) as *const libc::sched_param);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_normal_priority() {
|
||||
unsafe {
|
||||
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);
|
||||
}
|
||||
unsafe {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
481
src/main.rs
481
src/main.rs
@ -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::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
|
||||
|
||||
@ -21,317 +45,209 @@ const CCS811_APP_START: u8 = 0xF4;
|
||||
const CCS811_SW_RESET: u8 = 0xFF;
|
||||
|
||||
struct CCS811 {
|
||||
i2c: I2cdev,
|
||||
addr: u8,
|
||||
i2c: I2cdev,
|
||||
addr: u8
|
||||
}
|
||||
|
||||
impl CCS811 {
|
||||
fn new(mut i2c: I2cdev, addr: u8) -> Self {
|
||||
i2c.set_slave_address(addr as u16).unwrap();
|
||||
Self { i2c, addr }
|
||||
}
|
||||
fn new(mut i2c: I2cdev, addr: u8) -> Self {
|
||||
i2c.set_slave_address(addr as u16).unwrap();
|
||||
Self {
|
||||
i2c,
|
||||
addr
|
||||
}
|
||||
}
|
||||
|
||||
fn check_for_error(&mut self) -> Option<u8> {
|
||||
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
|
||||
if (x & 1) != 0 {
|
||||
let err_code = self.i2c.smbus_read_byte_data(CCS811_ERROR_ID).unwrap();
|
||||
Some(err_code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn check_for_error(&mut self) -> Option<u8> {
|
||||
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
|
||||
if (x & 1) != 0 {
|
||||
let err_code = self.i2c.smbus_read_byte_data(CCS811_ERROR_ID).unwrap();
|
||||
Some(err_code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn hardware_id(&mut self) -> u8 {
|
||||
self.i2c.smbus_read_byte_data(CCS811_HW_ID).unwrap()
|
||||
}
|
||||
fn hardware_id(&mut self) -> u8 {
|
||||
self.i2c.smbus_read_byte_data(CCS811_HW_ID).unwrap()
|
||||
}
|
||||
|
||||
fn app_valid(&mut self) -> bool {
|
||||
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
|
||||
x & 1 << 4 != 0
|
||||
}
|
||||
fn app_valid(&mut self) -> bool {
|
||||
let x = self.i2c.smbus_read_byte_data(CCS811_STATUS).unwrap();
|
||||
x & 1 << 4 != 0
|
||||
}
|
||||
|
||||
fn set_drive_mode(&mut self, mode: CCS811DriveMode) -> Result<(), Option<LinuxI2CError>> {
|
||||
self.i2c.smbus_write_byte(CCS811_APP_START).map_err(Some)?;
|
||||
if let Some(x) = self.check_for_error() {
|
||||
println!("error ignored {:b}", x);
|
||||
}
|
||||
let mut setting = self.i2c.smbus_read_byte_data(CCS811_MEAS_MODE).map_err(Some)?;
|
||||
setting &= !(0b00000111 << 4);
|
||||
setting |= (mode as u8) << 4;
|
||||
self.i2c
|
||||
.smbus_write_byte_data(CCS811_MEAS_MODE, setting)
|
||||
.map_err(Some)?;
|
||||
Ok(())
|
||||
}
|
||||
fn set_drive_mode(&mut self, mode: CCS811DriveMode) -> Result<(), Option<LinuxI2CError>> {
|
||||
self.i2c.smbus_write_byte(CCS811_APP_START).map_err(Some)?;
|
||||
if let Some(x) = self.check_for_error() {
|
||||
println!("error ignored {:b}", x);
|
||||
}
|
||||
let mut setting = self.i2c.smbus_read_byte_data(CCS811_MEAS_MODE).map_err(Some)?;
|
||||
setting &= !(0b00000111 << 4);
|
||||
setting |= (mode as u8) << 4;
|
||||
self.i2c.smbus_write_byte_data(CCS811_MEAS_MODE, setting).map_err(Some)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_baseline(&mut self) -> u16 {
|
||||
let x = self.i2c.smbus_read_i2c_block_data(CCS811_BASELINE, 2).unwrap();
|
||||
((x[0] as u16) << 8) | (x[1] as u16)
|
||||
}
|
||||
fn get_baseline(&mut self) -> u16 {
|
||||
let x = self.i2c.smbus_read_i2c_block_data(CCS811_BASELINE, 2).unwrap();
|
||||
((x[0] as u16) << 8) | (x[1] as u16)
|
||||
}
|
||||
|
||||
/// Returns (eCO2, tVOC)
|
||||
fn get_reading(&mut self) -> (u16, u16) {
|
||||
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),
|
||||
)
|
||||
}
|
||||
/// Returns (eCO2, tVOC)
|
||||
fn get_reading(&mut self) -> (u16, u16) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
enum CCS811DriveMode {
|
||||
Idle = 0,
|
||||
EverySecond = 1,
|
||||
Every10Seconds = 2,
|
||||
Every60Seconds = 3,
|
||||
/// Note the English manual states this is calculated every 10 ms!
|
||||
Every250Milliseconds = 4,
|
||||
Idle = 0,
|
||||
EverySecond = 1,
|
||||
Every10Seconds = 2,
|
||||
Every60Seconds = 3,
|
||||
/// Note the English manual states this is calculated every 10 ms!
|
||||
Every250Milliseconds = 4,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let i2c = I2cdev::new("/dev/i2c-1").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 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 args = std::env::args().collect::<Vec<_>>();
|
||||
if args.len() < 2 {
|
||||
panic!("missing argument: database path");
|
||||
}
|
||||
|
||||
let mut disp = FrameOutput {
|
||||
buffer: ImageBuffer::new(128, 64),
|
||||
};
|
||||
|
||||
let database = Connection::open(&args[1]).expect("failed to open database");
|
||||
|
||||
let mut query = database
|
||||
.prepare("SELECT celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 288")
|
||||
.unwrap();
|
||||
let mut temps: Vec<i32> = query
|
||||
.query_map([], |r| Ok(r.get(0)))
|
||||
.unwrap()
|
||||
.map(Result::unwrap)
|
||||
.map(Result::unwrap)
|
||||
.collect();
|
||||
let mut global_min = 1000;
|
||||
let mut global_max = 0;
|
||||
let mut vals: Vec<(i32, i32)> = vec![];
|
||||
for hour in temps.chunks_mut(6) {
|
||||
hour.sort();
|
||||
let min = hour[1];
|
||||
let mut max = hour[hour.len() - 2];
|
||||
println!("min {} max {}", min, max);
|
||||
// sanity check value
|
||||
if max > 300 {
|
||||
if vals.is_empty() {
|
||||
max = min;
|
||||
} else {
|
||||
max = vals.last().unwrap().1;
|
||||
}
|
||||
}
|
||||
|
||||
global_min = min.min(global_min);
|
||||
global_max = max.max(global_max);
|
||||
vals.push((min, max));
|
||||
}
|
||||
println!("global {} | {}", global_min, global_max);
|
||||
let diff = global_max - global_min;
|
||||
let x = 1;
|
||||
let y = 1;
|
||||
let scaley = 64;
|
||||
let scalex = 2;
|
||||
vals.reverse();
|
||||
for (i, (a, b)) in vals.into_iter().enumerate() {
|
||||
let x = x + i as i32 * scalex;
|
||||
let y1 = y + (global_max - b) * scaley / diff;
|
||||
let y2 = y + (global_max - a) * scaley / diff;
|
||||
let height = y2 - y1 + 1;
|
||||
disp.fill_solid(
|
||||
&Rectangle::new((x, y1).into(), (scalex as u32, height as u32).into()),
|
||||
BinaryColor::On,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
disp.buffer.save("/tmp/frame.png").unwrap();
|
||||
*/
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
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) {
|
||||
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();
|
||||
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();
|
||||
disp.init().unwrap();
|
||||
|
||||
let text_style = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_9X18)
|
||||
.text_color(BinaryColor::On)
|
||||
.build();
|
||||
let text_style = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_9X18)
|
||||
.text_color(BinaryColor::On)
|
||||
.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 secs = time.as_secs();
|
||||
let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60);
|
||||
Text::new(&time, Point::new(0, 32), text_style).draw(&mut disp).unwrap();
|
||||
disp.flush().unwrap();
|
||||
/*
|
||||
sleep(Duration::from_secs(2));
|
||||
disp.clear();
|
||||
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 secs = time.as_secs();
|
||||
let time = format!("{:02}:{:02} Uhr", (secs / 3600 + 2) % 24, secs / 60 % 60);
|
||||
Text::new(&time, Point::new(0, 32), text_style)
|
||||
.draw(&mut disp)
|
||||
.unwrap();
|
||||
disp.flush().unwrap();
|
||||
/*
|
||||
sleep(Duration::from_secs(2));
|
||||
disp.clear();
|
||||
|
||||
let base_y = 0.0;
|
||||
let max_dy = 32.0;
|
||||
let mut tick = 0;
|
||||
loop {
|
||||
let y = if tick % 32 < 16 {
|
||||
base_y + (tick % 16) as f32 / 16.0 * max_dy
|
||||
} else {
|
||||
base_y + max_dy - (tick % 16) as f32 / 16.0 * max_dy
|
||||
} as i32;
|
||||
tick += 1;
|
||||
Line::new(Point::new(8, y + 16), Point::new(8 + 16, y + 16))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
Line::new(Point::new(8, y + 16), Point::new(8 + 8, y))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
let base_y = 0.0;
|
||||
let max_dy = 32.0;
|
||||
let mut tick = 0;
|
||||
loop {
|
||||
let y = if tick % 32 < 16 {
|
||||
base_y + (tick % 16) as f32 / 16.0 * max_dy
|
||||
} else {
|
||||
base_y + max_dy - (tick % 16) as f32 / 16.0 * max_dy
|
||||
} as i32;
|
||||
tick += 1;
|
||||
Line::new(Point::new(8, y + 16), Point::new(8 + 16, y + 16))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
Line::new(Point::new(8, y + 16), Point::new(8 + 8, y))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
|
||||
Line::new(Point::new(8 + 16, y + 16), Point::new(8 + 8, y))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
Line::new(Point::new(8 + 16, y + 16), Point::new(8 + 8, y))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
|
||||
Rectangle::new(Point::new(48, y), Size::new(16, 16))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
Rectangle::new(Point::new(48, y), Size::new(16, 16))
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
|
||||
|
||||
Circle::new(Point::new(88, y), 16)
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
Circle::new(Point::new(88, y), 16)
|
||||
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
|
||||
.draw(&mut disp).unwrap();
|
||||
|
||||
/*
|
||||
Text::new(&format!("Hello from frame {}", tick), Point::new(0, 56), text_style)
|
||||
.draw(&mut disp)
|
||||
.unwrap();
|
||||
*/
|
||||
disp.flush().unwrap();
|
||||
/*
|
||||
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));
|
||||
sleep(Duration::from_millis(10));
|
||||
|
||||
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();
|
||||
/*
|
||||
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();
|
||||
*/
|
||||
}
|
||||
*/
|
||||
sleep(Duration::from_secs(2));
|
||||
disp.clear();
|
||||
*/
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
struct LineWrapper(gpio_cdev::Line);
|
||||
|
||||
impl InputPin for LineWrapper {
|
||||
type Error = gpio_cdev::Error;
|
||||
type Error = gpio_cdev::Error;
|
||||
|
||||
fn is_high(&self) -> Result<bool, Self::Error> {
|
||||
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
|
||||
Ok(handle.get_value()? == 1)
|
||||
}
|
||||
fn is_high(&self) -> Result<bool, Self::Error> {
|
||||
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
|
||||
Ok(handle.get_value()? == 1)
|
||||
}
|
||||
|
||||
fn is_low(&self) -> Result<bool, Self::Error> {
|
||||
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
|
||||
Ok(handle.get_value()? == 0)
|
||||
}
|
||||
fn is_low(&self) -> Result<bool, Self::Error> {
|
||||
let handle = self.0.request(LineRequestFlags::INPUT, 0, "rust-line-wrapper")?;
|
||||
Ok(handle.get_value()? == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputPin for LineWrapper {
|
||||
<<<<<<< HEAD
|
||||
type Error = gpio_cdev::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)
|
||||
}
|
||||
}
|
||||
||||||| 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)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user