Drawing CO2 graph
This commit is contained in:
parent
aee648bfa5
commit
d85e1b5807
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -107,6 +107,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "circular-buffer"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dacb91f972298e70fc507a2ffcaf1545807f1a36da586fb846646030adc542f"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@ -697,6 +703,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.19.12"
|
||||
@ -708,7 +723,7 @@ dependencies = [
|
||||
"diff",
|
||||
"ena",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
@ -880,6 +895,7 @@ dependencies = [
|
||||
name = "pico-enviro-sensor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"circular-buffer",
|
||||
"cortex-m-rt",
|
||||
"display-interface-spi",
|
||||
"embassy-embedded-hal",
|
||||
@ -890,6 +906,7 @@ dependencies = [
|
||||
"embedded-graphics",
|
||||
"embedded-graphics-framebuf",
|
||||
"heapless",
|
||||
"itertools 0.14.0",
|
||||
"rtt-target",
|
||||
"scd4x",
|
||||
"ssd1351",
|
||||
|
@ -43,6 +43,8 @@ display-interface-spi = "0.5.0"
|
||||
|
||||
# Extra
|
||||
heapless = "0.8.0"
|
||||
circular-buffer = { version = "1.0.0", default-features = false }
|
||||
itertools = { version = "0.14.0", default-features = false }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "s"
|
||||
|
@ -17,14 +17,25 @@ use embedded_graphics::{
|
||||
draw_target::DrawTarget,
|
||||
mono_font::{ascii::FONT_6X10, MonoTextStyle},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{Point, RgbColor, Size, WebColors},
|
||||
primitives::Rectangle,
|
||||
prelude::{Point, Primitive, RgbColor, Size, WebColors},
|
||||
primitives::{Line, PrimitiveStyle, Rectangle},
|
||||
text::{Alignment, Text},
|
||||
Drawable,
|
||||
};
|
||||
|
||||
// Containers
|
||||
use circular_buffer::CircularBuffer;
|
||||
use heapless::String;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{Spi0BusMutex, SENSOR_DATA_SIGNAL};
|
||||
use core::fmt::Write;
|
||||
use core::{
|
||||
fmt::Write,
|
||||
ops::{Add, Div, Sub},
|
||||
};
|
||||
|
||||
const DISPLAY_WIDTH: usize = 128;
|
||||
const DISPLAY_HEIGHT: usize = 128;
|
||||
|
||||
/// Output to the SSD1351 display
|
||||
#[embassy_executor::task]
|
||||
@ -50,16 +61,23 @@ pub async fn display_output_task(
|
||||
display.init().unwrap();
|
||||
|
||||
// Framebuffer, prevents flickering on redraw
|
||||
let buf = [Rgb565::BLACK; 128 * 128];
|
||||
let mut framebuf = FrameBuf::new(buf, 128, 128);
|
||||
let buf = [Rgb565::BLACK; DISPLAY_WIDTH * DISPLAY_HEIGHT];
|
||||
let mut framebuf = FrameBuf::new(buf, DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||
|
||||
// Text styles
|
||||
let co2_text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::CSS_DARK_GREEN);
|
||||
let temp_text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::CSS_ORANGE);
|
||||
let humidity_text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::CSS_AQUA);
|
||||
|
||||
let mut co2_text_buf = heapless::String::<16>::new();
|
||||
let mut temp_text_buf = heapless::String::<16>::new();
|
||||
let mut humidity_text_buf = heapless::String::<16>::new();
|
||||
// Format string buffers
|
||||
let mut co2_text_buf = String::<16>::new();
|
||||
let mut temp_text_buf = String::<16>::new();
|
||||
let mut humidity_text_buf = String::<16>::new();
|
||||
|
||||
// Ring buffer for storing past measurement data
|
||||
let mut co2_samples = CircularBuffer::<60, u16>::new();
|
||||
let mut temp_samples = CircularBuffer::<60, f32>::new();
|
||||
let mut humidity_samples = CircularBuffer::<60, f32>::new();
|
||||
|
||||
loop {
|
||||
// Clear the framebuffer
|
||||
@ -71,10 +89,67 @@ pub async fn display_output_task(
|
||||
humidity_text_buf.clear();
|
||||
|
||||
// Wait on sensor data & format into the buffers
|
||||
let data = SENSOR_DATA_SIGNAL.wait().await;
|
||||
write!(&mut co2_text_buf, "CO2: {} ppm", data.co2).unwrap();
|
||||
write!(&mut temp_text_buf, "Temp: {:.1} C", data.temperature).unwrap();
|
||||
write!(&mut humidity_text_buf, "RH: {:.1} %", data.humidity).unwrap();
|
||||
let sensor_data = SENSOR_DATA_SIGNAL.wait().await;
|
||||
write!(&mut co2_text_buf, "CO2: {} ppm", sensor_data.co2).unwrap();
|
||||
write!(&mut temp_text_buf, "Temp: {:.1} C", sensor_data.temperature).unwrap();
|
||||
write!(&mut humidity_text_buf, "RH: {:.1} %", sensor_data.humidity).unwrap();
|
||||
|
||||
// Record samples
|
||||
co2_samples.push_back(sensor_data.co2);
|
||||
temp_samples.push_back(sensor_data.temperature);
|
||||
humidity_samples.push_back(sensor_data.humidity);
|
||||
|
||||
let co2_min = *co2_samples.iter().min().unwrap();
|
||||
let co2_max = *co2_samples.iter().max().unwrap();
|
||||
let _temp_min = *temp_samples
|
||||
.iter()
|
||||
.reduce(|a: &f32, b: &f32| if a.le(b) { a } else { b })
|
||||
.unwrap();
|
||||
let _temp_max = *temp_samples
|
||||
.iter()
|
||||
.reduce(|a: &f32, b: &f32| if a.ge(b) { a } else { b })
|
||||
.unwrap();
|
||||
let _humid_min = *humidity_samples
|
||||
.iter()
|
||||
.reduce(|a: &f32, b: &f32| if a.le(b) { a } else { b })
|
||||
.unwrap();
|
||||
let _humid_max = *humidity_samples
|
||||
.iter()
|
||||
.reduce(|a: &f32, b: &f32| if a.ge(b) { a } else { b })
|
||||
.unwrap();
|
||||
|
||||
/*
|
||||
Note about drawing positions:
|
||||
The embedded-graphics library follows the OpenGL convention of the
|
||||
top-left of the image being (0, 0) with X increasing to the right
|
||||
and Y increasing downwards.
|
||||
*/
|
||||
|
||||
// Draw line graphs
|
||||
|
||||
if co2_samples.len() >= 2 {
|
||||
// CO2 line graph happens 4 pixels from the top, with 4 pixels either side
|
||||
|
||||
let mut x_pos: i32 = (DISPLAY_WIDTH - 4) as i32;
|
||||
|
||||
for (a, b) in co2_samples.iter().rev().tuple_windows::<(_, _)>() {
|
||||
let co2_range = if (co2_max - co2_min) == 0 {
|
||||
1
|
||||
} else {
|
||||
co2_max - co2_min
|
||||
};
|
||||
|
||||
let a_y_pos: i32 = (4 + (((co2_max - a) * 30) / co2_range)) as i32;
|
||||
let b_y_pos: i32 = (4 + (((co2_max - b) * 30) / co2_range)) as i32;
|
||||
|
||||
Line::new(Point::new(x_pos, a_y_pos), Point::new(x_pos - 2, b_y_pos))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Rgb565::CSS_DARK_GREEN, 1))
|
||||
.draw(&mut framebuf)
|
||||
.unwrap();
|
||||
|
||||
x_pos -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text to the screen
|
||||
Text::with_alignment(
|
||||
@ -105,7 +180,10 @@ pub async fn display_output_task(
|
||||
.unwrap();
|
||||
|
||||
// Draw the entire framebuffer to the display
|
||||
let area: Rectangle = Rectangle::new(Point::new(0, 0), Size::new(128, 128));
|
||||
let area: Rectangle = Rectangle::new(
|
||||
Point::new(0, 0),
|
||||
Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32),
|
||||
);
|
||||
display.fill_contiguous(&area, framebuf.data).unwrap();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user