Fast graphs
This commit is contained in:
110
Cargo.lock
generated
110
Cargo.lock
generated
@ -143,6 +143,8 @@ dependencies = [
|
||||
"esp-hal",
|
||||
"esp-println",
|
||||
"esp-rtos",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"heapless 0.9.2",
|
||||
"libm",
|
||||
"mipidsi",
|
||||
@ -175,6 +177,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@ -961,6 +969,53 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0c48af8cb14e02868f449f8a2187bd78af7a08da201fdc78d518ecb1675bc"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"fixed-macro-impl",
|
||||
"fixed-macro-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro-impl"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93086f471c0a1b9c5e300ea92f5cd990ac6d3f8edf27616ef624b8fa6402d4b"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"paste",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro-types"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "044a61b034a2264a7f65aa0c3cd112a01b4d4ee58baace51fead3f21b993c7e4"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"fixed-macro-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
@ -1032,6 +1087,17 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.3.1"
|
||||
@ -1309,6 +1375,30 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
@ -1922,3 +2012,23 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
@ -39,6 +39,8 @@ embedded-graphics-framebuf = "0.5.0"
|
||||
static_cell = "2.1.1"
|
||||
ens160-aq = {version = "0.2.11"}
|
||||
aht20-async = "1.0.0"
|
||||
fixed = "1.29.0"
|
||||
fixed-macro = "1.2.0"
|
||||
|
||||
|
||||
|
||||
|
||||
462
src/graph.rs
462
src/graph.rs
@ -8,57 +8,108 @@ use embedded_graphics::prelude::Point;
|
||||
use embedded_graphics::prelude::Primitive;
|
||||
use embedded_graphics::prelude::RgbColor;
|
||||
use embedded_graphics::prelude::Size;
|
||||
use embedded_graphics::prelude::WebColors;
|
||||
use embedded_graphics::primitives::Line;
|
||||
use embedded_graphics::primitives::PrimitiveStyle;
|
||||
use embedded_graphics::primitives::line::StyledPixelsIterator;
|
||||
use embedded_graphics::text::Text;
|
||||
use fixed::traits::Fixed;
|
||||
use fixed::types::I16F16;
|
||||
use fixed_macro::fixed;
|
||||
use heapless::format;
|
||||
use profont::PROFONT_10_POINT;
|
||||
|
||||
fn map_float(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 {
|
||||
use crate::colors::BACKGROUND_COLOR;
|
||||
|
||||
pub type FixedType = I16F16;
|
||||
fn map_float(
|
||||
x: FixedType,
|
||||
x_min: FixedType,
|
||||
x_max: FixedType,
|
||||
y_min: FixedType,
|
||||
y_max: FixedType,
|
||||
) -> FixedType {
|
||||
((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min
|
||||
}
|
||||
|
||||
const DEFAULT_LUT: [Rgb565; 5] = [
|
||||
pub const TEMPERATURE_LUT: [Rgb565; 5] = [
|
||||
Rgb565::new(0, 16, 11),
|
||||
Rgb565::new(11, 20, 17),
|
||||
Rgb565::new(23, 20, 18),
|
||||
Rgb565::new(31, 24, 12),
|
||||
Rgb565::new(31, 41, 0),
|
||||
];
|
||||
// const DEFAULT_LUT: [Rgb565; 4] = [Rgb565::GREEN, Rgb565::YELLOW, Rgb565::RED, Rgb565::RED];
|
||||
pub const HUMIDITY_LUT: [Rgb565; 2] = [Rgb565::CSS_DIM_GRAY, Rgb565::new(0, 14, 29)];
|
||||
pub const ECO2_LUT: [Rgb565; 3] = [Rgb565::GREEN, Rgb565::CSS_ORANGE, Rgb565::RED];
|
||||
pub const TVOC_LUT: [Rgb565; 3] = [Rgb565::GREEN, Rgb565::CSS_ORANGE, Rgb565::RED];
|
||||
|
||||
fn rgb565_interpolate(a: Rgb565, b: Rgb565, x: f32) -> Rgb565 {
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Lut {
|
||||
HeightLut(&'static [Rgb565]),
|
||||
MapLut(&'static [Rgb565], FixedType, FixedType),
|
||||
}
|
||||
|
||||
impl Lut {
|
||||
pub fn get_color(&self, height_factor: FixedType, value: FixedType) -> Rgb565 {
|
||||
match self {
|
||||
Lut::HeightLut(lut) => color_lut(height_factor, lut),
|
||||
Lut::MapLut(lut, min, max) => color_lut(
|
||||
map_float(
|
||||
value.clamp(*min, *max),
|
||||
*min,
|
||||
*max,
|
||||
FixedType::ZERO,
|
||||
FixedType::ONE,
|
||||
),
|
||||
lut,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rgb565_interpolate(a: Rgb565, b: Rgb565, x: FixedType) -> Rgb565 {
|
||||
Rgb565::new(
|
||||
(a.r() as f32 * (1. - x)) as u8 + (b.r() as f32 * x) as u8,
|
||||
(a.g() as f32 * (1. - x)) as u8 + (b.g() as f32 * x) as u8,
|
||||
(a.b() as f32 * (1. - x)) as u8 + (b.b() as f32 * x) as u8,
|
||||
(FixedType::from_num(a.r()) * (FixedType::from_num(1) - x)).to_num::<u8>()
|
||||
+ (FixedType::from_num(b.r()) * x).to_num::<u8>(),
|
||||
(FixedType::from_num(a.g()) * (FixedType::from_num(1) - x)).to_num::<u8>()
|
||||
+ (FixedType::from_num(b.g()) * x).to_num::<u8>(),
|
||||
(FixedType::from_num(a.b()) * (FixedType::from_num(1) - x)).to_num::<u8>()
|
||||
+ (FixedType::from_num(b.b()) * x).to_num::<u8>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn color_lut(mut x: f32, colors: &[Rgb565]) -> Rgb565 {
|
||||
if x == 1. {
|
||||
fn color_lut(mut x: FixedType, colors: &[Rgb565]) -> Rgb565 {
|
||||
if x == FixedType::from_num(1.) {
|
||||
return colors[colors.len() - 1];
|
||||
}
|
||||
|
||||
if x == 0. {
|
||||
if x == FixedType::from_num(0.) {
|
||||
return colors[0];
|
||||
}
|
||||
|
||||
x *= (colors.len() - 1) as f32;
|
||||
x *= FixedType::from_num(colors.len() - 1);
|
||||
|
||||
let index = libm::floorf(x);
|
||||
let index = x.floor();
|
||||
let interp = x - index;
|
||||
|
||||
rgb565_interpolate(colors[index as usize], colors[index as usize + 1], interp)
|
||||
rgb565_interpolate(
|
||||
colors[index.to_num::<usize>()],
|
||||
colors[index.to_num::<usize>() + 1],
|
||||
interp.to_num(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn graph_data<I, T>(data: I, data_count: usize, target: &mut T)
|
||||
where
|
||||
I: Iterator<Item = f32> + Clone,
|
||||
pub fn graph_data<I, T>(
|
||||
data: I,
|
||||
min: FixedType,
|
||||
max: FixedType,
|
||||
lut: Lut,
|
||||
data_count: usize,
|
||||
target: &mut T,
|
||||
) where
|
||||
I: Iterator<Item = FixedType> + Clone,
|
||||
T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>,
|
||||
{
|
||||
let min = data.clone().reduce(f32::min).unwrap_or(0.);
|
||||
let max = data.clone().reduce(f32::max).unwrap_or(0.);
|
||||
let size = Size::new(
|
||||
target.bounding_box().size.width,
|
||||
target.bounding_box().size.height,
|
||||
@ -69,16 +120,32 @@ where
|
||||
0,
|
||||
map_float(
|
||||
data.clone().next().unwrap(),
|
||||
min,
|
||||
max,
|
||||
size.height as f32,
|
||||
0.,
|
||||
) as i32,
|
||||
FixedType::from_num(min),
|
||||
FixedType::from_num(max),
|
||||
FixedType::from_num(size.height as f32),
|
||||
FixedType::from_num(0.),
|
||||
)
|
||||
.to_num::<i32>(),
|
||||
);
|
||||
for (i, x) in data.clone().skip(1).enumerate() {
|
||||
|
||||
for (i, x) in data.enumerate().skip(1) {
|
||||
let point = Point::new(
|
||||
map_float(i as f32, 0., data_count as f32 - 2., 0., size.width as f32) as i32,
|
||||
map_float(x, min, max, size.height as f32, 0.) as i32,
|
||||
map_float(
|
||||
FixedType::from_num(i),
|
||||
FixedType::from_num(0),
|
||||
FixedType::from_num(data_count - 1),
|
||||
FixedType::from_num(0),
|
||||
FixedType::from_num(size.width),
|
||||
)
|
||||
.to_num::<i32>(),
|
||||
map_float(
|
||||
x,
|
||||
min,
|
||||
max,
|
||||
FixedType::from_num(size.height),
|
||||
FixedType::from_num(0),
|
||||
)
|
||||
.to_num::<i32>(),
|
||||
);
|
||||
let _ = Line::new(start, point)
|
||||
.into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 2))
|
||||
@ -89,180 +156,197 @@ where
|
||||
for x in 0..size.width {
|
||||
// Start coloring from up to bottom
|
||||
let mut met_curve = false;
|
||||
let mut cross_y = x % 7;
|
||||
|
||||
let mut y = 0;
|
||||
while y < size.height {
|
||||
for y in 0..size.height {
|
||||
let position = Point::new(x as i32, y as i32);
|
||||
let height_factor = map_float(y as f32, 0., size.height as f32, 1., 0.);
|
||||
let height_color = color_lut(height_factor, &DEFAULT_LUT);
|
||||
let pixel = target.pixel(position).unwrap();
|
||||
|
||||
if target.pixel(position).unwrap() == Rgb565::WHITE {
|
||||
let height_factor = map_float(
|
||||
FixedType::from_num(y),
|
||||
FixedType::from_num(0),
|
||||
FixedType::from_num(size.height),
|
||||
FixedType::from_num(1),
|
||||
FixedType::from_num(0.),
|
||||
);
|
||||
|
||||
//let height_color = color_lut(height_factor, &DEFAULT_LUT);
|
||||
let height_color = lut.get_color(
|
||||
height_factor,
|
||||
map_float(
|
||||
FixedType::from_num(y),
|
||||
FixedType::from_num(size.height),
|
||||
FixedType::from_num(0),
|
||||
min,
|
||||
max,
|
||||
),
|
||||
);
|
||||
|
||||
if pixel == Rgb565::WHITE {
|
||||
let _ = Pixel(position, height_color).draw(target);
|
||||
met_curve = true;
|
||||
y += 1;
|
||||
} else if met_curve {
|
||||
} else if met_curve && (x as i32 - 2 * y as i32) % 7 == 0 {
|
||||
let _ = Pixel(
|
||||
position,
|
||||
rgb565_interpolate(height_color, Rgb565::BLACK, 1. - height_factor),
|
||||
rgb565_interpolate(
|
||||
height_color,
|
||||
Rgb565::BLACK,
|
||||
FixedType::from_num(1) - FixedType::from_num(height_factor),
|
||||
),
|
||||
)
|
||||
.draw(target);
|
||||
y += 7 - (y % 7);
|
||||
} else {
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_indicator<
|
||||
I: Iterator<Item = f32> + Clone,
|
||||
T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>,
|
||||
>(
|
||||
data: I,
|
||||
data_count: usize,
|
||||
target: &mut T,
|
||||
) {
|
||||
let size = target.bounding_box().size;
|
||||
let (min_index, min) = data
|
||||
.clone()
|
||||
.enumerate()
|
||||
.reduce(|a, b| if a.1 < b.1 { a } else { b })
|
||||
.unwrap_or((0, 0.));
|
||||
|
||||
let min_x = map_float(
|
||||
min_index as f32,
|
||||
0.,
|
||||
data_count as f32,
|
||||
0.,
|
||||
size.width as f32,
|
||||
) as i32;
|
||||
|
||||
// let _ = Line::new(Point::new(min_x, 0), Point::new(min_x, size.height as i32))
|
||||
// .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1))
|
||||
// .draw(target);
|
||||
for y in 0..size.height {
|
||||
if (y / 2) % 2 == 0 {
|
||||
let position = Point::new(min_x, y as i32);
|
||||
let _ = Pixel(
|
||||
position,
|
||||
rgb565_interpolate(Rgb565::RED, Rgb565::BLACK, 0.6),
|
||||
)
|
||||
.draw(target);
|
||||
|
||||
// let position = Point::new(min_x + 1, y as i32);
|
||||
// let _ = Pixel(
|
||||
// position,
|
||||
// rgb565_interpolate(Rgb565::RED, Rgb565::BLACK, 0.6),
|
||||
// )
|
||||
// .draw(target);
|
||||
}
|
||||
}
|
||||
|
||||
let minimum_text = "Minimum";
|
||||
let font = &PROFONT_10_POINT;
|
||||
|
||||
let text_start = if min_x < (size.width / 2) as i32 {
|
||||
5
|
||||
} else {
|
||||
-((minimum_text.len() + 1) as i32 * font.character_size.width as i32 + 3)
|
||||
};
|
||||
|
||||
let style = MonoTextStyle::new(
|
||||
&PROFONT_10_POINT,
|
||||
rgb565_interpolate(Rgb565::WHITE, Rgb565::BLACK, 0.8),
|
||||
);
|
||||
let _ = Text::new(
|
||||
minimum_text,
|
||||
Point::new(min_x + text_start as i32, 10),
|
||||
style,
|
||||
)
|
||||
.draw(target);
|
||||
|
||||
let value = format!(16; "{:.1}", min).unwrap();
|
||||
let _ = Text::new(
|
||||
value.as_str(),
|
||||
Point::new(
|
||||
min_x + text_start as i32,
|
||||
10 + font.character_size.height as i32,
|
||||
),
|
||||
style,
|
||||
)
|
||||
.draw(target);
|
||||
}
|
||||
|
||||
pub fn max_indicator<
|
||||
T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>,
|
||||
I: Iterator<Item = f32> + Clone,
|
||||
>(
|
||||
data: I,
|
||||
data_count: usize,
|
||||
target: &mut T,
|
||||
) {
|
||||
let size = target.bounding_box().size;
|
||||
let (max_index, max) = data
|
||||
.clone()
|
||||
.enumerate()
|
||||
.reduce(|a, b| if a.1 > b.1 { a } else { b })
|
||||
.unwrap_or((0, 0.));
|
||||
|
||||
let max_x = map_float(
|
||||
max_index as f32,
|
||||
0.,
|
||||
data_count as f32,
|
||||
0.,
|
||||
size.width as f32,
|
||||
) as i32;
|
||||
|
||||
for y in 0..size.height {
|
||||
if (y / 2) % 2 == 0 {
|
||||
let position = Point::new(max_x, y as i32);
|
||||
let _ = Pixel(
|
||||
position,
|
||||
rgb565_interpolate(Rgb565::GREEN, Rgb565::BLACK, 0.6),
|
||||
)
|
||||
.draw(target);
|
||||
|
||||
// let position = Point::new(max_x + 1, y as i32);
|
||||
// let _ = Pixel(
|
||||
// position,
|
||||
// rgb565_interpolate(Rgb565::GREEN, Rgb565::BLACK, 0.6),
|
||||
// )
|
||||
// .draw(target);
|
||||
}
|
||||
}
|
||||
|
||||
let maximum_text = "Maximum";
|
||||
let font = &PROFONT_10_POINT;
|
||||
|
||||
let text_start = if max_x < (size.width / 2) as i32 {
|
||||
5
|
||||
} else {
|
||||
-((maximum_text.len() + 1) as i32 * font.character_size.width as i32 + 3)
|
||||
};
|
||||
|
||||
let style = MonoTextStyle::new(
|
||||
&PROFONT_10_POINT,
|
||||
rgb565_interpolate(Rgb565::WHITE, Rgb565::BLACK, 0.8),
|
||||
);
|
||||
let _ = Text::new(
|
||||
maximum_text,
|
||||
Point::new(
|
||||
max_x + text_start as i32,
|
||||
size.height as i32 - font.character_size.height as i32 * 2,
|
||||
),
|
||||
style,
|
||||
)
|
||||
.draw(target);
|
||||
|
||||
let value = format!(16; "{:.1}", max).unwrap();
|
||||
let _ = Text::new(
|
||||
value.as_str(),
|
||||
Point::new(
|
||||
max_x + text_start as i32,
|
||||
size.height as i32 - font.character_size.height as i32,
|
||||
),
|
||||
style,
|
||||
)
|
||||
.draw(target);
|
||||
}
|
||||
// pub fn min_indicator<
|
||||
// I: Iterator<Item = f32> + Clone,
|
||||
// T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>,
|
||||
// >(
|
||||
// data: I,
|
||||
// data_count: usize,
|
||||
// target: &mut T,
|
||||
// ) {
|
||||
// let size = target.bounding_box().size;
|
||||
// let (min_index, min) = data
|
||||
// .clone()
|
||||
// .enumerate()
|
||||
// .reduce(|a, b| if a.1 < b.1 { a } else { b })
|
||||
// .unwrap_or((0, 0.));
|
||||
//
|
||||
// let min_x = map_float(
|
||||
// min_index as f32,
|
||||
// 0.,
|
||||
// data_count as f32,
|
||||
// 0.,
|
||||
// size.width as f32,
|
||||
// ) as i32;
|
||||
//
|
||||
// // let _ = Line::new(Point::new(min_x, 0), Point::new(min_x, size.height as i32))
|
||||
// // .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1))
|
||||
// // .draw(target);
|
||||
// for y in 0..size.height {
|
||||
// if (y / 2) % 2 == 0 {
|
||||
// let position = Point::new(min_x, y as i32);
|
||||
// let _ = Pixel(
|
||||
// position,
|
||||
// rgb565_interpolate(Rgb565::RED, Rgb565::BLACK, 0.6),
|
||||
// )
|
||||
// .draw(target);
|
||||
//
|
||||
// // let position = Point::new(min_x + 1, y as i32);
|
||||
// // let _ = Pixel(
|
||||
// // position,
|
||||
// // rgb565_interpolate(Rgb565::RED, Rgb565::BLACK, 0.6),
|
||||
// // )
|
||||
// // .draw(target);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let minimum_text = "Minimum";
|
||||
// let font = &PROFONT_10_POINT;
|
||||
//
|
||||
// let text_start = if min_x < (size.width / 2) as i32 {
|
||||
// 5
|
||||
// } else {
|
||||
// -((minimum_text.len() + 1) as i32 * font.character_size.width as i32 + 3)
|
||||
// };
|
||||
//
|
||||
// let style = MonoTextStyle::new(
|
||||
// &PROFONT_10_POINT,
|
||||
// rgb565_interpolate(Rgb565::WHITE, Rgb565::BLACK, 0.8),
|
||||
// );
|
||||
// let _ = Text::new(
|
||||
// minimum_text,
|
||||
// Point::new(min_x + text_start as i32, 10),
|
||||
// style,
|
||||
// )
|
||||
// .draw(target);
|
||||
//
|
||||
// let value = format!(16; "{:.1}", min).unwrap();
|
||||
// let _ = Text::new(
|
||||
// value.as_str(),
|
||||
// Point::new(
|
||||
// min_x + text_start as i32,
|
||||
// 10 + font.character_size.height as i32,
|
||||
// ),
|
||||
// style,
|
||||
// )
|
||||
// .draw(target);
|
||||
// }
|
||||
//
|
||||
// pub fn max_indicator<
|
||||
// T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>,
|
||||
// I: Iterator<Item = f32> + Clone,
|
||||
// >(
|
||||
// data: I,
|
||||
// data_count: usize,
|
||||
// target: &mut T,
|
||||
// ) {
|
||||
// let size = target.bounding_box().size;
|
||||
// let (max_index, max) = data
|
||||
// .clone()
|
||||
// .enumerate()
|
||||
// .reduce(|a, b| if a.1 > b.1 { a } else { b })
|
||||
// .unwrap_or((0, 0.));
|
||||
//
|
||||
// let max_x = map_float(
|
||||
// max_index as f32,
|
||||
// 0.,
|
||||
// data_count as f32,
|
||||
// 0.,
|
||||
// size.width as f32,
|
||||
// ) as i32;
|
||||
//
|
||||
// for y in 0..size.height {
|
||||
// if (y / 2) % 2 == 0 {
|
||||
// let position = Point::new(max_x, y as i32);
|
||||
// let _ = Pixel(
|
||||
// position,
|
||||
// rgb565_interpolate(Rgb565::GREEN, Rgb565::BLACK, 0.6),
|
||||
// )
|
||||
// .draw(target);
|
||||
//
|
||||
// // let position = Point::new(max_x + 1, y as i32);
|
||||
// // let _ = Pixel(
|
||||
// // position,
|
||||
// // rgb565_interpolate(Rgb565::GREEN, Rgb565::BLACK, 0.6),
|
||||
// // )
|
||||
// // .draw(target);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let maximum_text = "Maximum";
|
||||
// let font = &PROFONT_10_POINT;
|
||||
//
|
||||
// let text_start = if max_x < (size.width / 2) as i32 {
|
||||
// 5
|
||||
// } else {
|
||||
// -((maximum_text.len() + 1) as i32 * font.character_size.width as i32 + 3)
|
||||
// };
|
||||
//
|
||||
// let style = MonoTextStyle::new(
|
||||
// &PROFONT_10_POINT,
|
||||
// rgb565_interpolate(Rgb565::WHITE, Rgb565::BLACK, 0.8),
|
||||
// );
|
||||
// let _ = Text::new(
|
||||
// maximum_text,
|
||||
// Point::new(
|
||||
// max_x + text_start as i32,
|
||||
// size.height as i32 - font.character_size.height as i32 * 2,
|
||||
// ),
|
||||
// style,
|
||||
// )
|
||||
// .draw(target);
|
||||
//
|
||||
// let value = format!(16; "{:.1}", max).unwrap();
|
||||
// let _ = Text::new(
|
||||
// value.as_str(),
|
||||
// Point::new(
|
||||
// max_x + text_start as i32,
|
||||
// size.height as i32 - font.character_size.height as i32,
|
||||
// ),
|
||||
// style,
|
||||
// )
|
||||
// .draw(target);
|
||||
// }
|
||||
|
||||
48
src/main.rs
48
src/main.rs
@ -8,6 +8,7 @@
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::hint;
|
||||
use core::ops::Add;
|
||||
|
||||
use buoyant::primitives::ProposedDimension;
|
||||
use buoyant::view::AsDrawable;
|
||||
@ -19,6 +20,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::channel::Channel;
|
||||
use embassy_time::Duration;
|
||||
use embassy_time::Timer;
|
||||
use embassy_time::with_timeout;
|
||||
use embedded_graphics::Drawable;
|
||||
use embedded_graphics::draw_target::DrawTarget;
|
||||
use embedded_graphics::framebuffer::Framebuffer;
|
||||
@ -123,6 +125,9 @@ async fn main(spawner: Spawner) {
|
||||
peripherals.GPIO9,
|
||||
&mut timer,
|
||||
));
|
||||
sampler.sample(&mut timer);
|
||||
Timer::after_secs(2).await;
|
||||
sampler.sample(&mut timer);
|
||||
|
||||
spawner.spawn(button_listener(btn)).unwrap();
|
||||
spawner.spawn(event_handler()).unwrap();
|
||||
@ -135,8 +140,18 @@ async fn button_listener(mut btn: Input<'static>) {
|
||||
let sender = EVENT_CHANNEL.sender();
|
||||
loop {
|
||||
btn.wait_for_rising_edge().await;
|
||||
sender.send(ApplicationEvent::ButtonPress).await;
|
||||
btn.wait_for_low().await;
|
||||
|
||||
match with_timeout(Duration::from_millis(750), btn.wait_for_low()).await {
|
||||
Ok(()) => {
|
||||
info!("Short press");
|
||||
sender.send(ApplicationEvent::ButtonPress).await
|
||||
}
|
||||
Err(_) => {
|
||||
info!("Long press");
|
||||
sender.send(ApplicationEvent::LongButtonPress).await;
|
||||
btn.wait_for_low().await
|
||||
}
|
||||
};
|
||||
Timer::after_millis(20).await;
|
||||
}
|
||||
}
|
||||
@ -151,7 +166,6 @@ async fn event_handler() {
|
||||
);
|
||||
|
||||
let mut last_5_mins: HistoryBuf<Sample, { 60 * 5 }> = HistoryBuf::new();
|
||||
last_5_mins.write(Sample::zero());
|
||||
let mut redraw = true;
|
||||
let mut current_view = ViewState::Main;
|
||||
|
||||
@ -166,9 +180,12 @@ async fn event_handler() {
|
||||
.draw(&mut fbuf);
|
||||
match current_view {
|
||||
ViewState::Main => {
|
||||
let _ = views::menu::menu_view(*last_5_mins.last().unwrap(), tendencies)
|
||||
.as_drawable(fbuf.bounding_box().size, Rgb565::WHITE)
|
||||
.draw(&mut fbuf);
|
||||
let _ = views::menu::menu_view(
|
||||
*last_5_mins.last().unwrap_or(&Sample::zero()),
|
||||
tendencies,
|
||||
)
|
||||
.as_drawable(fbuf.bounding_box().size, Rgb565::WHITE)
|
||||
.draw(&mut fbuf);
|
||||
}
|
||||
ViewState::TemperatureGraph => draw_graph = Some(MeasurementType::Temperature),
|
||||
ViewState::HumidityGraph => draw_graph = Some(MeasurementType::Humidity),
|
||||
@ -177,9 +194,7 @@ async fn event_handler() {
|
||||
}
|
||||
|
||||
if let Some(graph_type) = draw_graph {
|
||||
info!("Drawing graph");
|
||||
views::detail::detailed(&last_5_mins, tendencies, graph_type, &mut fbuf);
|
||||
info!("Drew graph");
|
||||
}
|
||||
|
||||
let _ = display.fill_contiguous(
|
||||
@ -200,7 +215,9 @@ async fn event_handler() {
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
ApplicationEvent::LongButtonPress => {
|
||||
redraw = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -226,7 +243,7 @@ impl ViewState {
|
||||
}
|
||||
}
|
||||
|
||||
const LOW_PASS_LENGTH: usize = 10;
|
||||
const LOW_PASS_LENGTH: usize = 5;
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn sampler_task(sampler: &'static mut Sampler<'static>) {
|
||||
@ -246,7 +263,16 @@ async fn sampler_task(sampler: &'static mut Sampler<'static>) {
|
||||
count += 1;
|
||||
|
||||
if count >= 2 {
|
||||
sender.send(ApplicationEvent::NewSample(sample)).await;
|
||||
sender
|
||||
.send(ApplicationEvent::NewSample(
|
||||
low_pass
|
||||
.oldest_ordered()
|
||||
.copied()
|
||||
.reduce(Sample::add)
|
||||
.unwrap_or(Sample::zero())
|
||||
* (1. / LOW_PASS_LENGTH as f32),
|
||||
))
|
||||
.await;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_graphics::pixelcolor::raw::LittleEndian;
|
||||
use embedded_graphics::prelude::DrawTarget;
|
||||
use embedded_graphics::prelude::RgbColor;
|
||||
use fixed::types::I16F16;
|
||||
use heapless::HistoryBuf;
|
||||
use profont::PROFONT_18_POINT;
|
||||
use profont::PROFONT_24_POINT;
|
||||
@ -26,9 +27,14 @@ use crate::Tendencies;
|
||||
use crate::colors::FRAME_STROKE_COLOR;
|
||||
use crate::colors::MAIN_TEXT_COLOR;
|
||||
use crate::colors::SUB_TEXT_COLOR;
|
||||
use crate::graph::ECO2_LUT;
|
||||
use crate::graph::FixedType;
|
||||
use crate::graph::HUMIDITY_LUT;
|
||||
use crate::graph::Lut;
|
||||
use crate::graph::TEMPERATURE_LUT;
|
||||
use crate::graph::graph_data;
|
||||
use crate::graph::max_indicator;
|
||||
use crate::graph::min_indicator;
|
||||
//use crate::graph::max_indicator;
|
||||
//use crate::graph::min_indicator;
|
||||
use crate::sampler::Sample;
|
||||
use crate::views::icon::icon_box_view;
|
||||
use crate::views::menu::tendency_indicator;
|
||||
@ -51,15 +57,55 @@ pub fn detailed<T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>, const
|
||||
{ 240 - 53 },
|
||||
{ buffer_size::<Rgb565>(240, 240) },
|
||||
>::new();
|
||||
let iter = history.oldest_ordered().map(|x| match indicator {
|
||||
MeasurementType::Temperature => x.temperature,
|
||||
MeasurementType::Humidity => x.humidity,
|
||||
MeasurementType::ECo2 => x.eco2,
|
||||
MeasurementType::TVoc => x.tvoc,
|
||||
let iter = history
|
||||
.oldest_ordered()
|
||||
.map(|x| match indicator {
|
||||
MeasurementType::Temperature => x.temperature,
|
||||
MeasurementType::Humidity => x.humidity,
|
||||
MeasurementType::ECo2 => x.eco2,
|
||||
MeasurementType::TVoc => x.tvoc,
|
||||
})
|
||||
.map(I16F16::from_num);
|
||||
|
||||
let min = iter
|
||||
.clone()
|
||||
.reduce(Ord::min)
|
||||
.unwrap_or(FixedType::from_num(0.));
|
||||
let max = iter
|
||||
.clone()
|
||||
.reduce(Ord::max)
|
||||
.unwrap_or(FixedType::from_num(0.));
|
||||
|
||||
let min_spacing = FixedType::from_num(match indicator {
|
||||
MeasurementType::Temperature => 5,
|
||||
MeasurementType::Humidity => 10,
|
||||
MeasurementType::ECo2 => 100,
|
||||
MeasurementType::TVoc => 100,
|
||||
});
|
||||
graph_data(iter.clone(), history.len(), &mut graph_fb);
|
||||
min_indicator(iter.clone(), history.len(), &mut graph_fb);
|
||||
max_indicator(iter.clone(), history.len(), &mut graph_fb);
|
||||
|
||||
let middle = (max + min) / FixedType::from_num(2.);
|
||||
|
||||
let min = middle - min_spacing.max(max - min) / FixedType::from_num(2.);
|
||||
let max = middle + min_spacing.max(max - min) / FixedType::from_num(2.);
|
||||
|
||||
let lut = match indicator {
|
||||
MeasurementType::Temperature => Lut::HeightLut(&TEMPERATURE_LUT),
|
||||
MeasurementType::Humidity => Lut::HeightLut(&HUMIDITY_LUT),
|
||||
MeasurementType::ECo2 => Lut::MapLut(
|
||||
&ECO2_LUT,
|
||||
FixedType::from_num(500),
|
||||
FixedType::from_num(1500),
|
||||
),
|
||||
MeasurementType::TVoc => Lut::MapLut(
|
||||
&ECO2_LUT,
|
||||
FixedType::from_num(200),
|
||||
FixedType::from_num(1000),
|
||||
),
|
||||
};
|
||||
|
||||
graph_data(iter.clone(), min, max, lut, history.len(), &mut graph_fb);
|
||||
//min_indicator(iter.clone(), history.len(), &mut graph_fb);
|
||||
//max_indicator(iter.clone(), history.len(), &mut graph_fb);
|
||||
|
||||
let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(graph_fb.data(), 240);
|
||||
let image = embedded_graphics::image::Image::new(
|
||||
|
||||
Reference in New Issue
Block a user