diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1d36631 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "aht20-driver"] + path = aht20-driver + url = git@github.com:chalbin73/aht20-driver.git diff --git a/Cargo.lock b/Cargo.lock index 336a041..1c798c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,8 +5,6 @@ version = 4 [[package]] name = "aht20-driver" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c920c092aef3156141a32df93663d683f06c733187f924b09d917efa53f08fb" dependencies = [ "crc-any", "embedded-hal 1.0.0", @@ -74,6 +72,19 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "buoyant" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc36827a22794f33600a539fc2f16e6c535f9de5cf26509f8ce7ddb666cda1b" +dependencies = [ + "embedded-graphics", + "embedded-graphics-core", + "heapless 0.8.0", + "paste", + "u8g2-fonts", +] + [[package]] name = "bytemuck" version = "1.24.0" @@ -97,6 +108,7 @@ name = "co2-meter" version = "0.1.0" dependencies = [ "aht20-driver", + "buoyant", "critical-section", "defmt 1.0.1", "embedded-graphics", @@ -107,9 +119,11 @@ dependencies = [ "esp-bootloader-esp-idf", "esp-hal", "esp-println", - "lcd-async", + "heapless 0.9.2", + "libm", "mipidsi", "profont", + "tinybmp", ] [[package]] @@ -950,24 +964,18 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "lcd-async" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362f088c5c176ceedd4d0e817d17ae3b79480495deac605158a7cd86450450c5" -dependencies = [ - "embedded-graphics", - "embedded-graphics-core", - "embedded-hal 1.0.0", - "embedded-hal-async", -] - [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linked_list_allocator" version = "0.10.5" @@ -1441,6 +1449,15 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tinybmp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6" +dependencies = [ + "embedded-graphics", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -1477,6 +1494,16 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "u8g2-fonts" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825f57be1429fd60f335a4aade11e128a7ae4f89d75ca3a003cb8410a91093f7" +dependencies = [ + "embedded-graphics", + "embedded-graphics-core", +] + [[package]] name = "ufmt-write" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 49bac7d..54b5827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,14 +23,19 @@ esp-backtrace = { version = "0.18.1", features = [ "panic-handler", ] } esp-println = { version = "0.16.1", features = ["defmt-espflash", "esp32c3"] } -aht20-driver = { version = "2.0", default-features = false } +aht20-driver = { path = "aht20-driver" , default-features = false } ens160 = { version = "0.6", default-features = false} -embedded-hal-bus = "0.3.0" +embedded-hal-bus = {version = "0.3.0", features = ["alloc"]} mipidsi = "0.9.0" embedded-graphics = "0.8.1" -lcd-async = "0.1.1" + profont = "0.7.0" +buoyant = "0.5.3" +heapless = "0.9.2" +tinybmp = "0.6.0" +libm = "0.2.15" + [profile.dev] diff --git a/aht20-driver b/aht20-driver new file mode 160000 index 0000000..1bb4b02 --- /dev/null +++ b/aht20-driver @@ -0,0 +1 @@ +Subproject commit 1bb4b02145d266c0cdef91d3e2064dee4203d8fa diff --git a/assets/Makefile b/assets/Makefile new file mode 100644 index 0000000..c4b9027 --- /dev/null +++ b/assets/Makefile @@ -0,0 +1,18 @@ +SVGS = $(wildcard ./*.svg) +TARGET_BMPS = $(SVGS:.svg=.bmp) +BACKCOLOR = 080808 + +.PHONY: all + +all: $(TARGET_BMPS) + @echo $(SVGS) + +clean: + rm $(TARGET_BMPS) + +%.bmp: %.png + @convert $< -background "#$(BACKCOLOR)" -alpha remove -define bmp:subtype=RGB565 $@ + +%.png: %.svg + inkscape $< -o $@ + diff --git a/assets/a.bmp b/assets/a.bmp new file mode 100644 index 0000000..3141fb5 Binary files /dev/null and b/assets/a.bmp differ diff --git a/assets/a.svg b/assets/a.svg new file mode 100644 index 0000000..7d84e84 --- /dev/null +++ b/assets/a.svg @@ -0,0 +1,52 @@ + + + + + + + diff --git a/assets/co2-icon.bmp b/assets/co2-icon.bmp new file mode 100644 index 0000000..a74e213 Binary files /dev/null and b/assets/co2-icon.bmp differ diff --git a/assets/co2-icon.svg b/assets/co2-icon.svg new file mode 100644 index 0000000..391b8ec --- /dev/null +++ b/assets/co2-icon.svg @@ -0,0 +1,89 @@ + + + + + + + + + 2 + C + O + + diff --git a/assets/humidity-icon.bmp b/assets/humidity-icon.bmp new file mode 100644 index 0000000..1b72f13 Binary files /dev/null and b/assets/humidity-icon.bmp differ diff --git a/assets/humidity-icon.svg b/assets/humidity-icon.svg new file mode 100644 index 0000000..6ac5f37 --- /dev/null +++ b/assets/humidity-icon.svg @@ -0,0 +1,60 @@ + + + + + + + + diff --git a/assets/indic-falling.bmp b/assets/indic-falling.bmp new file mode 100644 index 0000000..859d6ad Binary files /dev/null and b/assets/indic-falling.bmp differ diff --git a/assets/indic-falling.svg b/assets/indic-falling.svg new file mode 100644 index 0000000..8d10fc3 --- /dev/null +++ b/assets/indic-falling.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + diff --git a/assets/indic-rising.bmp b/assets/indic-rising.bmp new file mode 100644 index 0000000..bc67411 Binary files /dev/null and b/assets/indic-rising.bmp differ diff --git a/assets/indic-rising.svg b/assets/indic-rising.svg new file mode 100644 index 0000000..6a6818a --- /dev/null +++ b/assets/indic-rising.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + diff --git a/assets/indic-steady.bmp b/assets/indic-steady.bmp new file mode 100644 index 0000000..9f35aee Binary files /dev/null and b/assets/indic-steady.bmp differ diff --git a/assets/indic-steady.svg b/assets/indic-steady.svg new file mode 100644 index 0000000..4ac30f3 --- /dev/null +++ b/assets/indic-steady.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + diff --git a/assets/temperature-icon.bmp b/assets/temperature-icon.bmp new file mode 100644 index 0000000..67e6285 Binary files /dev/null and b/assets/temperature-icon.bmp differ diff --git a/assets/temperature-icon.svg b/assets/temperature-icon.svg new file mode 100644 index 0000000..2ada07b --- /dev/null +++ b/assets/temperature-icon.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + diff --git a/assets/voc-icon.bmp b/assets/voc-icon.bmp new file mode 100644 index 0000000..7ffb243 Binary files /dev/null and b/assets/voc-icon.bmp differ diff --git a/assets/voc-icon.svg b/assets/voc-icon.svg new file mode 100644 index 0000000..8aec3ee --- /dev/null +++ b/assets/voc-icon.svg @@ -0,0 +1,46 @@ + + + + + + + diff --git a/src/bin/colors.rs b/src/bin/colors.rs new file mode 100644 index 0000000..f37e25d --- /dev/null +++ b/src/bin/colors.rs @@ -0,0 +1,13 @@ +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::RgbColor; +use embedded_graphics::prelude::WebColors; + +pub const BACKGROUND_COLOR: Rgb565 = Rgb565::BLACK; +//pub const FRAME_BACKGROUD_COLOR: Rgb565 = Rgb565::new(1, 2, 1); +pub const FRAME_BACKGROUD_COLOR: Rgb565 = Rgb565::new(0, 0, 0); +pub const FRAME_STROKE_COLOR: Rgb565 = Rgb565::new(4, 9, 4); + +pub const MAIN_TEXT_COLOR: Rgb565 = Rgb565::WHITE; +pub const SUB_TEXT_COLOR: Rgb565 = Rgb565::CSS_DARK_GRAY; + +pub const FRAME_STROKE: u32 = 3; diff --git a/src/bin/graph.rs b/src/bin/graph.rs new file mode 100644 index 0000000..4af5b3a --- /dev/null +++ b/src/bin/graph.rs @@ -0,0 +1,135 @@ +use core::iter::Iterator; +use embedded_graphics::Pixel; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::DrawTarget; +use embedded_graphics::prelude::Drawable; +use embedded_graphics::prelude::Point; +use embedded_graphics::prelude::Primitive; +use embedded_graphics::prelude::RgbColor; +use embedded_graphics::prelude::Size; +use embedded_graphics::primitives::Line; +use embedded_graphics::primitives::PrimitiveStyle; + +fn map_float(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 { + ((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min +} + +const DEFAULT_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), +]; + +fn rgb565_interpolate(a: Rgb565, b: Rgb565, x: f32) -> 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, + ) +} + +fn color_lut(mut x: f32, colors: &[Rgb565]) -> Rgb565 { + if x == 1. { + return colors[colors.len() - 1]; + } + + if x == 0. { + return colors[0]; + } + + x *= (colors.len() - 1) as f32; + + let index = libm::floorf(x); + let interp = x - index; + + rgb565_interpolate(colors[index as usize], colors[index as usize + 1], interp) +} + +pub fn graph_data>(data: &[f32], target: &mut T) { + let min = data.iter().copied().reduce(f32::min).unwrap_or(0.); + let max = data.iter().copied().reduce(f32::max).unwrap_or(0.); + data.iter().map(|x| x); + let size = Size::new( + target.bounding_box().size.width, + target.bounding_box().size.height, + ); + + let mut start = Point::new( + 0, + map_float(data[0], min, max, size.height as f32, 0.) as i32, + ); + let point_count = size.width / 2; + + for y in 0..size.height { + let value = map_float(y as f32, size.height as f32, 0., min, max); + let lut_color = color_lut( + map_float(y as f32, size.height as f32, 0., 0., 1.), + &DEFAULT_LUT, + ); + + let color = rgb565_interpolate(Rgb565::BLACK, lut_color, 0.3); + for x in 0..size.width { + let pos = map_float( + x as f32, + 0., + size.width as f32 - 1., + 0., + (data.len() - 1) as f32, + ); + + // Sample + let index = libm::floorf(pos).min((data.len() - 2) as f32); + let interpolation = pos - index; + let curve_value = data[index as usize] * (1. - interpolation) + + data[index as usize + 1] * interpolation; + // if value <= curve_value && (x + y) % 2 == 0 { + // let _ = Pixel(Point::new(x as i32, y as i32), color).draw(target); + // } + if (x as i32 - y as i32) % 6 == 0 { + if value <= curve_value { + let mut pixel_color = Rgb565::new(1, 2, 1); + pixel_color = color; + let _ = Pixel(Point::new(x as i32 - 2, y as i32), pixel_color).draw(target); + } + } + } + } + + for (i, x) in data.iter().skip(1).enumerate() { + let point = Point::new( + map_float(i as f32, 0., data.len() as f32 - 1., 0., size.width as f32) as i32, + map_float(*x, min, max, size.height as f32, 0.) as i32, + ); + let factor = map_float(*x, min, max, 0., 1.); + let _ = Line::new(start, point) + .into_styled(PrimitiveStyle::with_stroke( + color_lut(factor, &DEFAULT_LUT), + 2, + )) + .draw(target); + start = point; + } + + // for i in 0..point_count { + // // Sample data + // let index = map_float( + // i as f32, + // 0., + // point_count as f32, + // 0 as f32, + // data.len() as f32, + // ) as usize; + // let value = data[index]; + // let point = Point::new( + // map_float(i as f32, 0., point_count as f32, 0., size.width as f32) as i32, + // map_float(value, min, max, 0., size.height as f32) as i32, + // ); + // + // let _ = Line::new(start, point) + // .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 2)) + // .draw(target); + // start = point; + // } +} diff --git a/src/bin/images.rs b/src/bin/images.rs new file mode 100644 index 0000000..4e0785e --- /dev/null +++ b/src/bin/images.rs @@ -0,0 +1,55 @@ +use core::cell::UnsafeCell; +use core::include_bytes; +use core::mem::MaybeUninit; + +use critical_section::Mutex; +use embedded_graphics::pixelcolor::Rgb565; +use tinybmp::Bmp; + +pub type StaticImage = Mutex>>>; + +#[rustfmt::skip] +pub static HUMIDITY_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); +pub static TEMPERATURE_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); +pub static VOC_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); +pub static CO2_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); + +// Tendency indicators +pub static TENDENCY_RISING: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); +pub static TENDENCY_STEADY: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); +pub static TENDENCY_FALLING: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed())); + +macro_rules! load_image { + ($stat:expr, $path:expr) => { + critical_section::with(|cs| { + *$stat.borrow(cs).get() = + MaybeUninit::new(Bmp::from_slice(include_bytes!($path)).unwrap()); + }) + }; +} + +#[macro_export] +macro_rules! get_image { + ($image:expr) => { + unsafe { + use core::mem::MaybeUninit; + use tinybmp::Bmp; + &*core::mem::transmute::<*mut MaybeUninit>, *mut Bmp>( + critical_section::with(|cs| $image.borrow(cs).get()), + ) + } + }; +} + +pub fn prepare_images() { + unsafe { + load_image!(HUMIDITY_ICON, "../../assets/humidity-icon.bmp"); + load_image!(TEMPERATURE_ICON, "../../assets/temperature-icon.bmp"); + load_image!(VOC_ICON, "../../assets/voc-icon.bmp"); + load_image!(CO2_ICON, "../../assets/co2-icon.bmp"); + + load_image!(TENDENCY_RISING, "../../assets/indic-rising.bmp"); + load_image!(TENDENCY_STEADY, "../../assets/indic-steady.bmp"); + load_image!(TENDENCY_FALLING, "../../assets/indic-falling.bmp"); + } +} diff --git a/src/bin/main.rs b/src/bin/main.rs index 6695ffe..d756fc2 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -7,30 +7,51 @@ )] #![allow(unreachable_code)] +mod colors; +mod graph; +mod images; +mod sampler; +mod views; + +use buoyant::primitives::Size; +use buoyant::view::AsDrawable; +use buoyant::view::Image; +use buoyant::view::ViewExt; +use core::default::Default; +use core::iter::Iterator; use defmt::info; use embedded_graphics::Drawable; -use embedded_graphics::framebuffer::{Framebuffer, buffer_size}; -use embedded_graphics::image::{Image, ImageRaw}; -use embedded_graphics::pixelcolor::Rgb565; -use embedded_graphics::pixelcolor::raw::{BigEndian, LittleEndian}; -use embedded_graphics::prelude::{Angle, DrawTarget, Point, Primitive, RgbColor, WebColors}; -use embedded_graphics::primitives::{ - Circle, PrimitiveStyle, PrimitiveStyleBuilder, Sector, StyledDrawable, Triangle, -}; -use embedded_graphics::text::renderer::TextRenderer; +use embedded_graphics::framebuffer::Framebuffer; +use embedded_graphics::framebuffer::buffer_size; +use embedded_graphics::image::ImageRaw; +use embedded_graphics::pixelcolor::raw::LittleEndian; +use embedded_graphics::prelude::Point; +use embedded_graphics::prelude::RgbColor; use embedded_hal_bus::spi::ExclusiveDevice; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; -use esp_hal::gpio::{Level, Output, OutputConfig}; -use esp_hal::main; -use esp_hal::riscv::asm::delay; -use esp_hal::rtc_cntl::Rtc; -use esp_hal::time::{Duration, Instant, Rate}; -use esp_hal::timer::Timer; -use esp_hal::timer::timg::TimerGroup; -use mipidsi::interface::{Interface, SpiInterface}; +use esp_hal::gpio::Level; +use esp_hal::gpio::Output; +use esp_hal::gpio::OutputConfig; +use esp_hal::time::Rate; +use heapless::format; +use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; -use {esp_backtrace as _, esp_println as _}; +use mipidsi::options::Orientation; +use mipidsi::options::Rotation; + +use buoyant::view::HStack; +use buoyant::view::Spacer; +use buoyant::view::View; +use core::env; +use embedded_graphics::pixelcolor::Rgb565; +use esp_backtrace as _; +use esp_hal::main; +use esp_println as _; +use tinybmp::Bmp; + +use crate::graph::graph_data; +use crate::images::StaticImage; extern crate alloc; @@ -41,6 +62,7 @@ esp_bootloader_esp_idf::esp_app_desc!(); #[main] fn main() -> ! { // generator version: 1.0.1 + images::prepare_images(); esp_alloc::heap_allocator!(size: 32 * 1024); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); @@ -81,125 +103,97 @@ fn main() -> ! { .init(&mut timer) .unwrap(); - display - .set_tearing_effect(mipidsi::options::TearingEffect::Vertical) - .unwrap(); + let mut fb = + Framebuffer::(240, 240) }>::new( + ); + //fb.clear(Rgb565::BLACK).unwrap(); - // Create styles used by the drawing operations. - let sector_style = PrimitiveStyleBuilder::new() - .stroke_color(Rgb565::BLACK) - .stroke_width(2) - .fill_color(Rgb565::YELLOW) - .build(); - let eye_style = PrimitiveStyleBuilder::new() - .stroke_color(Rgb565::BLACK) - .stroke_width(1) - .fill_color(Rgb565::BLACK) - .build(); - - //let output_settings = ::new().scale(4).build(); - //let mut window = Window::new("Pacman", &output_settings); - - // The current progress of the animation - const STEPS: i32 = 10; - let mut progress: i32 = 0; - let delay = Delay::new(); - - loop { - let mut fb = Framebuffer::< - Rgb565, - _, - LittleEndian, - 320, - 240, - { buffer_size::(320, 240) }, - >::new(); - fb.clear(Rgb565::WHITE).unwrap(); - - let p = (progress - STEPS).abs(); - - // Draw a Sector as the main Pacman feature. - let _ = Sector::new( - Point::new(2, 2), - 61, - Angle::from_degrees((p * 30 / STEPS) as f32), - Angle::from_degrees((360 - 2 * p * 30 / STEPS) as f32), - ) - .draw_styled(§or_style, &mut fb); - - // Draw a Circle as the eye. - let _ = Circle::new(Point::new(36, 16), 5).draw_styled(&eye_style, &mut fb); - - delay.delay_millis(50); - - progress = (progress + 1) % (2 * STEPS + 1); - - let img_raw = ImageRaw::::new(fb.data(), 320); - let image = Image::new(&img_raw, Point::zero()); - image.draw(&mut display).unwrap(); - } - - // display - // .set_pixels(0, 0, 50, 50, core::iter::repeat(Rgb565::BLUE)) + // views::menu::menu_view() + // .as_drawable(Size::new(240, 240), Rgb565::WHITE) + // .draw(&mut fb) // .unwrap(); - // //display.set_pixel(10, 10, Rgb565::WHITE).unwrap(); - // let i2c = I2c::new(peripherals.I2C0, Config::default()) - // .unwrap() - // .with_sda(peripherals.GPIO8) - // .with_scl(peripherals.GPIO9); - // - // esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 66320); - // - // let rc = RefCell::new(i2c); - // - // let mut device = Ens160::new(embedded_hal_bus::i2c::RefCellDevice::new(&rc), 0x53); - // timer.delay_millis(500); - // device.reset().unwrap(); - // info!("Reset device"); - // timer.delay_millis(500); - // device.operational().unwrap(); - // info!("Device operational"); - // - // // Configure the AHT20 temperature and humidity sensor. - // let mut aht20_uninit = aht20_driver::AHT20::new( - // embedded_hal_bus::i2c::RefCellDevice::new(&rc), - // aht20_driver::SENSOR_ADDRESS, - // ); - // let mut aht20 = aht20_uninit.init(&mut timer).unwrap(); - // - // // Take the temperature and humidity measurement. - // - // loop { - // //timer.delay_millis(5000); - // if let Ok(status) = device.status() { - // if status.data_is_ready() { - // let aht20_measurement = aht20.measure(&mut timer).unwrap(); - // //println!("temperature (aht20): {}C", aht20_measurement.temperature); - // //println!("humidity (aht20): {}%", aht20_measurement.humidity); - // - // let tvoc = device.tvoc().unwrap(); - // let eco2 = device.eco2().unwrap(); - // //info!("eco2: {}, tvoc: {}", *eco2, tvoc); - // print!( - // "{}, {}, {}, {}\n", - // *eco2, tvoc, aht20_measurement.temperature, aht20_measurement.humidity - // ); - // // from eCO2 - // // directly - // //let air_quality_index = device.air_quality_index().unwrap(); - // } - // } - // } - // - // + let mut x = [0.; 100]; + for (i, x) in x.iter_mut().enumerate() { + *x = libm::sinf(i as f32 / 10.); + } + graph_data(&x, &mut fb); + + let img_raw = ImageRaw::::new(fb.data(), 240); + let image = embedded_graphics::image::Image::new(&img_raw, Point::zero()); + image.draw(&mut display).unwrap(); + // embedded_graphics::image::Image::new(get_image!(images::HUMIDITY_ICON), Point::zero()) + // .draw(&mut display) + // .unwrap(); + + info!("Finished !"); loop { - info!("Hello world!"); - let delay_start = Instant::now(); - while delay_start.elapsed() < Duration::from_millis(500) {} + core::hint::spin_loop(); } // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin } -fn draw_interface(target: &mut T) {} +pub enum MenuIndicatorType { + Temperature(f32), + Humidity(f32), + Co2(u32), + Voc(u32), +} + +impl MenuIndicatorType { + pub fn get_corresponding_icon(&self) -> &'static StaticImage { + match self { + MenuIndicatorType::Temperature(_) => &images::TEMPERATURE_ICON, + MenuIndicatorType::Humidity(_) => &images::HUMIDITY_ICON, + MenuIndicatorType::Co2(_) => &images::CO2_ICON, + MenuIndicatorType::Voc(_) => &images::VOC_ICON, + } + } + + pub fn get_corresponding_unit_string(&self) -> &'static str { + match self { + MenuIndicatorType::Temperature(_) => "C", + MenuIndicatorType::Humidity(_) => "%", + MenuIndicatorType::Co2(_) => "ppm", + MenuIndicatorType::Voc(_) => "ppb", + } + } + + pub fn get_value_str(&self) -> heapless::String<16> { + match self { + MenuIndicatorType::Temperature(temp) => format!(16; "{:.1}", temp).unwrap(), + MenuIndicatorType::Humidity(hum) => format!(16; "{:.1}", hum).unwrap(), + MenuIndicatorType::Co2(co2) => format!(16; "{}", co2).unwrap(), + MenuIndicatorType::Voc(voc) => format!(16; "{}", voc).unwrap(), + } + } +} + +pub enum Tendency { + Rising, + Steady, + Falling, +} + +impl Tendency { + pub fn get_corresponding_icon(&self) -> &'static StaticImage { + match self { + Self::Rising => &images::TENDENCY_RISING, + Self::Steady => &images::TENDENCY_STEADY, + Self::Falling => &images::TENDENCY_FALLING, + } + } +} + +fn tendency_indicator(tendency: Tendency) -> impl View { + HStack::new(( + Image::new(get_image!(tendency.get_corresponding_icon())) + .flex_frame() + .with_min_size(10, 20) + .with_max_size(10, 20), + Spacer::default(), + )) + .flex_frame() + .with_max_width(15) +} diff --git a/src/bin/sampler.rs b/src/bin/sampler.rs new file mode 100644 index 0000000..f4426bb --- /dev/null +++ b/src/bin/sampler.rs @@ -0,0 +1,53 @@ +use core::cell::RefCell; + +use aht20_driver::AHT20; +use alloc::rc::Rc; +use alloc::vec::Vec; +use core::default::Default; +use embedded_hal_bus::i2c::RcDevice; +use ens160::Ens160; +use esp_hal::Blocking; +use esp_hal::DriverMode; +use esp_hal::delay::Delay; +use esp_hal::gpio::interconnect::PeripheralOutput; +use esp_hal::i2c; +use esp_hal::i2c::master::I2c; +use esp_hal::i2c::master::Instance; +use esp_hal::peripherals; +use esp_hal::peripherals::Peripherals; + +pub struct Sampler<'a> { + ens160: Ens160>>, + aht20: aht20_driver::AHT20Initialized>>, +} + +impl<'a> Sampler<'a> { + pub fn new( + i2c: impl Instance + 'a, + sda: impl PeripheralOutput<'a>, + scl: impl PeripheralOutput<'a>, + mut timer: Delay, + ) -> Self { + let i2c = I2c::new(i2c, Default::default()) + .unwrap() + .with_sda(sda) + .with_scl(scl); + + let i2c = Rc::new(RefCell::new(i2c)); + + let mut ens160 = Ens160::new(embedded_hal_bus::i2c::RcDevice::new(i2c.clone()), 0x53); + timer.delay_millis(500); + ens160.reset().unwrap(); + timer.delay_millis(500); + ens160.operational().unwrap(); + + let aht20_uninit = AHT20::new( + embedded_hal_bus::i2c::RcDevice::new(i2c.clone()), + aht20_driver::SENSOR_ADDRESS, + ); + + let aht20 = aht20_uninit.init(&mut timer).unwrap(); + + Sampler { ens160, aht20 } + } +} diff --git a/src/bin/views.rs b/src/bin/views.rs new file mode 100644 index 0000000..38dde75 --- /dev/null +++ b/src/bin/views.rs @@ -0,0 +1,3 @@ +pub mod detail; +pub mod icon; +pub mod menu; diff --git a/src/bin/views/detail.rs b/src/bin/views/detail.rs new file mode 100644 index 0000000..0bcf0ff --- /dev/null +++ b/src/bin/views/detail.rs @@ -0,0 +1,52 @@ +use buoyant::layout::HorizontalAlignment; +use buoyant::layout::VerticalAlignment; +use buoyant::view::HStack; +use buoyant::view::Spacer; +use buoyant::view::Text; +use buoyant::view::VStack; +use buoyant::view::View; +use buoyant::view::ViewExt; +use embedded_graphics::pixelcolor::Rgb565; +use profont::PROFONT_18_POINT; +use profont::PROFONT_24_POINT; + +use crate::MenuIndicatorType; +use crate::Tendency; +use crate::colors::FRAME_STROKE_COLOR; +use crate::colors::MAIN_TEXT_COLOR; +use crate::colors::SUB_TEXT_COLOR; +use crate::tendency_indicator; +use crate::views::icon::icon_box_view; + +pub fn detailed_view(indicator: MenuIndicatorType, tendency: Tendency) -> impl View { + VStack::new(( + // Header + HStack::new(( + icon_box_view(FRAME_STROKE_COLOR, indicator.get_corresponding_icon()), + Spacer::default().flex_frame().with_max_width(10), + tendency_indicator(tendency), + Text::new(indicator.get_value_str(), &PROFONT_24_POINT) + .foreground_color(MAIN_TEXT_COLOR), + Text::new(indicator.get_corresponding_unit_string(), &PROFONT_18_POINT) + .foreground_color(SUB_TEXT_COLOR) + .flex_frame() + .with_infinite_max_height() + .with_vertical_alignment(VerticalAlignment::Bottom) + .with_max_height(25), + Spacer::default(), + Text::new("Temperature", &PROFONT_18_POINT) + .foreground_color(SUB_TEXT_COLOR) + .flex_frame() + .with_infinite_max_height() + .with_vertical_alignment(VerticalAlignment::Bottom) + .with_max_height(25), + Spacer::default().flex_frame().with_max_width(10), + )), + // Window + Spacer::default() + .flex_frame() + .with_infinite_max_width() + .with_infinite_max_height(), + )) + .with_alignment(HorizontalAlignment::Leading) +} diff --git a/src/bin/views/icon.rs b/src/bin/views/icon.rs new file mode 100644 index 0000000..8d0f5c1 --- /dev/null +++ b/src/bin/views/icon.rs @@ -0,0 +1,18 @@ +use buoyant::view::View; +use buoyant::view::ViewExt; +use buoyant::view::ZStack; +use buoyant::view::shape::Rectangle; +use embedded_graphics::pixelcolor::Rgb565; + +use crate::get_image; +use crate::images::StaticImage; + +pub fn icon_box_view(box_color: Rgb565, icon: &'static StaticImage) -> impl View { + ZStack::new(( + Rectangle.corner_radius(10).foreground_color(box_color), + buoyant::view::Image::new(get_image!(icon)), + )) + .flex_frame() + .with_max_size(53, 53) + .with_min_size(53, 53) +} diff --git a/src/bin/views/menu.rs b/src/bin/views/menu.rs new file mode 100644 index 0000000..23d7a7a --- /dev/null +++ b/src/bin/views/menu.rs @@ -0,0 +1,85 @@ +use buoyant::view::VStack; +use buoyant::view::View; + +use buoyant::view::prelude::*; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use profont::PROFONT_18_POINT; +use profont::PROFONT_24_POINT; + +use crate::MenuIndicatorType; +use crate::Tendency; +use crate::colors::FRAME_BACKGROUD_COLOR; +use crate::colors::FRAME_STROKE; +use crate::colors::FRAME_STROKE_COLOR; +use crate::colors::MAIN_TEXT_COLOR; +use crate::colors::SUB_TEXT_COLOR; +use crate::get_image; +use crate::views::icon::icon_box_view; + +pub fn menu_view() -> impl View { + VStack::new(( + HStack::new(( + main_menu_indicator(MenuIndicatorType::Temperature(31.5), Tendency::Falling), + main_menu_indicator(MenuIndicatorType::Humidity(36.2), Tendency::Steady), + )) + .with_spacing(5), + HStack::new(( + main_menu_indicator(MenuIndicatorType::Co2(1329), Tendency::Rising), + main_menu_indicator(MenuIndicatorType::Voc(29), Tendency::Falling), + )) + .with_spacing(5), + )) + .with_spacing(5) +} + +fn main_menu_indicator(indicator_type: MenuIndicatorType, tendency: Tendency) -> impl View { + Rectangle + .corner_radius(10) + .stroked(FRAME_STROKE) + .foreground_color(FRAME_STROKE_COLOR) + .background(Alignment::Center, || { + ZStack::new(( + Rectangle + .corner_radius(15) + .foreground_color(FRAME_BACKGROUD_COLOR), + VStack::new(( + HStack::new(( + Spacer::default(), + icon_box_view(FRAME_STROKE_COLOR, indicator_type.get_corresponding_icon()), + Spacer::default(), + )), + HStack::new(( + Spacer::default(), + tendency_indicator(tendency), + Text::new(indicator_type.get_value_str(), &PROFONT_24_POINT) + .foreground_color(MAIN_TEXT_COLOR), + Text::new( + indicator_type.get_corresponding_unit_string(), + &PROFONT_18_POINT, + ) + .foreground_color(SUB_TEXT_COLOR) + .flex_frame() + .with_infinite_max_height() + .with_vertical_alignment(VerticalAlignment::Bottom) + .with_max_height(25), + Spacer::default(), + )), + )) + .with_alignment(HorizontalAlignment::Center) + .flex_frame(), + )) + }) +} + +fn tendency_indicator(tendency: Tendency) -> impl View { + HStack::new(( + Image::new(get_image!(tendency.get_corresponding_icon())) + .flex_frame() + .with_min_size(10, 20) + .with_max_size(10, 20), + Spacer::default(), + )) + .flex_frame() + .with_max_width(15) +}