From 43fead021c52c4e4cdfe6d605d66ef92da3b9370 Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Thu, 4 Dec 2025 13:27:55 +0100 Subject: [PATCH] Fast graphs --- Cargo.lock | 110 +++++++++++ Cargo.toml | 2 + src/graph.rs | 462 ++++++++++++++++++++++++++------------------ src/main.rs | 48 +++-- src/views/detail.rs | 66 ++++++- 5 files changed, 478 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b041392..a73af28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml index 8d757b4..063ff29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/graph.rs b/src/graph.rs index 7023287..917bace 100644 --- a/src/graph.rs +++ b/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::() + + (FixedType::from_num(b.r()) * x).to_num::(), + (FixedType::from_num(a.g()) * (FixedType::from_num(1) - x)).to_num::() + + (FixedType::from_num(b.g()) * x).to_num::(), + (FixedType::from_num(a.b()) * (FixedType::from_num(1) - x)).to_num::() + + (FixedType::from_num(b.b()) * x).to_num::(), ) } -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::()], + colors[index.to_num::() + 1], + interp.to_num(), + ) } -pub fn graph_data(data: I, data_count: usize, target: &mut T) -where - I: Iterator + Clone, +pub fn graph_data( + data: I, + min: FixedType, + max: FixedType, + lut: Lut, + data_count: usize, + target: &mut T, +) where + I: Iterator + Clone, T: DrawTarget + GetPixel, { - 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::(), ); - 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::(), + map_float( + x, + min, + max, + FixedType::from_num(size.height), + FixedType::from_num(0), + ) + .to_num::(), ); 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 + Clone, - T: DrawTarget + GetPixel, ->( - 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 + GetPixel, - I: Iterator + 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 + Clone, +// T: DrawTarget + GetPixel, +// >( +// 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 + GetPixel, +// I: Iterator + 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); +// } diff --git a/src/main.rs b/src/main.rs index 38d5b64..5789bc6 100644 --- a/src/main.rs +++ b/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 = 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; } } diff --git a/src/views/detail.rs b/src/views/detail.rs index 6cb4f94..74f4656 100644 --- a/src/views/detail.rs +++ b/src/views/detail.rs @@ -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 + GetPixel, const { 240 - 53 }, { buffer_size::(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::::new(graph_fb.data(), 240); let image = embedded_graphics::image::Image::new(