Fast graphs

This commit is contained in:
2025-12-04 13:27:55 +01:00
parent 56f9c423e7
commit 43fead021c
5 changed files with 478 additions and 210 deletions

110
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -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);
// }

View File

@ -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;
}
}

View File

@ -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(