Embassy
This commit is contained in:
109
Cargo.lock
generated
109
Cargo.lock
generated
@ -111,7 +111,11 @@ dependencies = [
|
||||
"buoyant",
|
||||
"critical-section",
|
||||
"defmt 1.0.1",
|
||||
"embassy-executor",
|
||||
"embassy-sync 0.7.2",
|
||||
"embassy-time",
|
||||
"embedded-graphics",
|
||||
"embedded-graphics-framebuf",
|
||||
"embedded-hal-bus",
|
||||
"ens160",
|
||||
"esp-alloc",
|
||||
@ -119,6 +123,7 @@ dependencies = [
|
||||
"esp-bootloader-esp-idf",
|
||||
"esp-hal",
|
||||
"esp-println",
|
||||
"esp-rtos",
|
||||
"heapless 0.9.2",
|
||||
"libm",
|
||||
"mipidsi",
|
||||
@ -310,6 +315,36 @@ dependencies = [
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-executor"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"document-features",
|
||||
"embassy-executor-macros",
|
||||
"embassy-executor-timer-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-executor-macros"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472"
|
||||
dependencies = [
|
||||
"darling 0.20.11",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-executor-timer-queue"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c"
|
||||
|
||||
[[package]]
|
||||
name = "embassy-futures"
|
||||
version = "0.1.2"
|
||||
@ -357,6 +392,41 @@ dependencies = [
|
||||
"heapless 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-time"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"critical-section",
|
||||
"document-features",
|
||||
"embassy-time-driver",
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-hal 1.0.0",
|
||||
"embedded-hal-async",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-time-driver"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
|
||||
dependencies = [
|
||||
"document-features",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-time-queue-utils"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454"
|
||||
dependencies = [
|
||||
"embassy-executor-timer-queue",
|
||||
"heapless 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-can"
|
||||
version = "0.4.1"
|
||||
@ -366,6 +436,15 @@ dependencies = [
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-dma"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-graphics"
|
||||
version = "0.8.1"
|
||||
@ -389,6 +468,16 @@ dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-graphics-framebuf"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22354420f68727fa24d1e2741dae1e9a041065e80fb63b35a8d19c647a85be76"
|
||||
dependencies = [
|
||||
"embedded-dma",
|
||||
"embedded-graphics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "0.2.7"
|
||||
@ -691,6 +780,26 @@ dependencies = [
|
||||
"esp-metadata-generated",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "esp-rtos"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ec711c8d06e79c67b75d01595539e86b0aac209643af98ca87a12250428b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"document-features",
|
||||
"embassy-executor",
|
||||
"embassy-sync 0.7.2",
|
||||
"embassy-time-driver",
|
||||
"embassy-time-queue-utils",
|
||||
"esp-config",
|
||||
"esp-hal",
|
||||
"esp-hal-procmacros",
|
||||
"esp-metadata-generated",
|
||||
"esp-sync",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "esp-sync"
|
||||
version = "0.1.1"
|
||||
|
||||
@ -6,6 +6,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
esp-hal = { version = "1.0.0", features = ["defmt", "esp32c3", "unstable"] }
|
||||
esp-rtos = {version = "0.2.0", features = ["esp32c3", "embassy"]}
|
||||
|
||||
|
||||
defmt = "1.0.1"
|
||||
@ -31,6 +32,10 @@ buoyant = "0.5.3"
|
||||
heapless = "0.9.2"
|
||||
tinybmp = "0.6.0"
|
||||
libm = "0.2.15"
|
||||
embassy-executor = {version = "0.9.1", features = ["arch-riscv32"]}
|
||||
embassy-time = "0.5.0"
|
||||
embassy-sync = "0.7.2"
|
||||
embedded-graphics-framebuf = "0.5.0"
|
||||
|
||||
|
||||
|
||||
|
||||
429
main.rs
Normal file
429
main.rs
Normal file
@ -0,0 +1,429 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(
|
||||
clippy::mem_forget,
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
#![allow(unreachable_code)]
|
||||
|
||||
mod colors;
|
||||
mod graph;
|
||||
mod images;
|
||||
mod sampler;
|
||||
mod views;
|
||||
|
||||
use buoyant::primitives::Pixel;
|
||||
use buoyant::primitives::Size;
|
||||
use buoyant::primitives::geometry::Rectangle;
|
||||
use buoyant::view::AsDrawable;
|
||||
use buoyant::view::Image;
|
||||
use buoyant::view::ViewExt;
|
||||
use core::cell::RefCell;
|
||||
use core::default::Default;
|
||||
use core::iter::Iterator;
|
||||
use core::ops::Sub;
|
||||
use critical_section::Mutex;
|
||||
use defmt::info;
|
||||
use embedded_graphics::Drawable;
|
||||
use embedded_graphics::framebuffer::Framebuffer;
|
||||
use embedded_graphics::framebuffer::buffer_size;
|
||||
use embedded_graphics::image::GetPixel;
|
||||
use embedded_graphics::image::ImageRaw;
|
||||
use embedded_graphics::pixelcolor::raw::LittleEndian;
|
||||
use embedded_graphics::prelude::Dimensions;
|
||||
use embedded_graphics::prelude::DrawTarget;
|
||||
use embedded_graphics::prelude::Point;
|
||||
use embedded_graphics::prelude::Primitive;
|
||||
use embedded_graphics::prelude::RgbColor;
|
||||
use embedded_graphics::primitives::PrimitiveStyle;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::delay::Delay;
|
||||
use esp_hal::gpio::Input;
|
||||
use esp_hal::gpio::InputConfig;
|
||||
use esp_hal::gpio::Io;
|
||||
use esp_hal::gpio::Level;
|
||||
use esp_hal::gpio::Output;
|
||||
use esp_hal::gpio::OutputConfig;
|
||||
use esp_hal::gpio::Pull;
|
||||
use esp_hal::handler;
|
||||
use esp_hal::peripherals;
|
||||
use esp_hal::time::Rate;
|
||||
use heapless::format;
|
||||
use mipidsi::interface::SpiInterface;
|
||||
use mipidsi::models::ST7789;
|
||||
|
||||
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 crate::colors::BACKGROUND_COLOR;
|
||||
use crate::graph::graph_data;
|
||||
use crate::graph::max_indicator;
|
||||
use crate::graph::min_indicator;
|
||||
use crate::images::StaticImage;
|
||||
use crate::sampler::History;
|
||||
use crate::sampler::Sample;
|
||||
use crate::sampler::Sampler;
|
||||
use crate::views::detail;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
static INPUT_BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
|
||||
static BUTTON_PRESSED: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
|
||||
|
||||
#[main]
|
||||
fn main() -> ! {
|
||||
// generator version: 1.0.1
|
||||
info!("Starting up.");
|
||||
images::prepare_images();
|
||||
info!("Prepared images.");
|
||||
|
||||
esp_alloc::heap_allocator!(size: 32 * 1024);
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
let mut timer = Delay::new();
|
||||
let mut io = Io::new(peripherals.IO_MUX);
|
||||
info!("Init done.");
|
||||
|
||||
let spi = esp_hal::spi::master::Spi::new(
|
||||
peripherals.SPI2,
|
||||
esp_hal::spi::master::Config::default()
|
||||
.with_mode(esp_hal::spi::Mode::_0)
|
||||
.with_frequency(Rate::from_mhz(80)),
|
||||
)
|
||||
.unwrap()
|
||||
.with_sck(peripherals.GPIO4)
|
||||
.with_mosi(peripherals.GPIO6);
|
||||
|
||||
let mut cs_output = Output::new(peripherals.GPIO0, Level::High, OutputConfig::default());
|
||||
cs_output.set_high();
|
||||
let spi_device = ExclusiveDevice::new_no_delay(spi, cs_output).unwrap();
|
||||
|
||||
let mut buffer = [0_u8; 512];
|
||||
|
||||
// Define the display interface with no chip select
|
||||
let di = SpiInterface::new(
|
||||
spi_device,
|
||||
Output::new(peripherals.GPIO1, Level::Low, OutputConfig::default()),
|
||||
&mut buffer,
|
||||
);
|
||||
info!("Display creating, initializing ...");
|
||||
|
||||
let mut display = mipidsi::Builder::new(ST7789, di)
|
||||
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
||||
.init(&mut timer)
|
||||
.unwrap();
|
||||
info!("Initialized");
|
||||
|
||||
let mut fb =
|
||||
Framebuffer::<Rgb565, _, LittleEndian, 240, 240, { buffer_size::<Rgb565>(240, 240) }>::new(
|
||||
);
|
||||
|
||||
// views::menu::menu_view()
|
||||
// .as_drawable(Size::new(240, 240), Rgb565::WHITE)
|
||||
// .draw(&mut fb)
|
||||
// .unwrap();
|
||||
|
||||
// embedded_graphics::image::Image::new(get_image!(images::HUMIDITY_ICON), Point::zero())
|
||||
// .draw(&mut display)
|
||||
// .unwrap();
|
||||
|
||||
info!("Creating sampler");
|
||||
let mut sampler = Sampler::new(
|
||||
peripherals.I2C0,
|
||||
peripherals.GPIO8,
|
||||
peripherals.GPIO9,
|
||||
&mut timer,
|
||||
);
|
||||
let _ = sampler.sample(&mut timer);
|
||||
info!("Sensor initialized");
|
||||
|
||||
// Input button interrupt
|
||||
{
|
||||
esp_hal::interrupt::enable(
|
||||
peripherals::Interrupt::GPIO,
|
||||
esp_hal::interrupt::Priority::Priority1,
|
||||
)
|
||||
.unwrap();
|
||||
let mut input_button = Input::new(
|
||||
peripherals.GPIO10,
|
||||
InputConfig::default().with_pull(Pull::Up),
|
||||
);
|
||||
|
||||
io.set_interrupt_handler(interrupt_handler);
|
||||
critical_section::with(|cs| {
|
||||
input_button.listen(esp_hal::gpio::Event::FallingEdge);
|
||||
INPUT_BUTTON.borrow_ref_mut(cs).replace(input_button);
|
||||
});
|
||||
}
|
||||
info!("Setup interrupts");
|
||||
|
||||
let mut history = History::new(&mut sampler, &mut timer);
|
||||
let mut view_state = ViewState::Main;
|
||||
let mut x = 0;
|
||||
|
||||
loop {
|
||||
// input state
|
||||
let button_pressed = critical_section::with(|cs| {
|
||||
let val = *BUTTON_PRESSED.borrow(cs).borrow();
|
||||
*BUTTON_PRESSED.borrow_ref_mut(cs) = false;
|
||||
val
|
||||
});
|
||||
|
||||
if button_pressed {
|
||||
view_state = view_state.circulate();
|
||||
info!("Btn pressed");
|
||||
}
|
||||
|
||||
if false && (history.update(&mut sampler, &mut timer) || button_pressed) {
|
||||
// let iter = history.min5.oldest_ordered().map(|x| x.eco2);
|
||||
// embedded_graphics::primitives::Rectangle::new(Point::zero(), fb.bounding_box().size)
|
||||
// .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
|
||||
// .draw(&mut fb)
|
||||
// .unwrap();
|
||||
// graph_data(iter.clone(), history.min5.len(), &mut fb);
|
||||
// min_indicator(iter.clone(), history.min5.len(), &mut fb);
|
||||
// max_indicator(iter.clone(), history.min5.len(), &mut fb);
|
||||
//
|
||||
// let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 240);
|
||||
// let image = embedded_graphics::image::Image::new(&img_raw, Point::zero());
|
||||
// image.draw(&mut display).unwrap();
|
||||
|
||||
let app_state = AppState {
|
||||
sample: *history.min5.last().unwrap(),
|
||||
history: &history,
|
||||
tendencies: Tendencies::from_history(&history),
|
||||
};
|
||||
display
|
||||
.bounding_box()
|
||||
.into_styled(PrimitiveStyle::with_fill(BACKGROUND_COLOR))
|
||||
.draw(&mut fb)
|
||||
.unwrap();
|
||||
view_state.draw_view(&mut fb, app_state);
|
||||
let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 240);
|
||||
let image = embedded_graphics::image::Image::new(&img_raw, Point::zero());
|
||||
image.draw(&mut display).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Tendencies {
|
||||
temperature: Tendency,
|
||||
humidity: Tendency,
|
||||
eco2: Tendency,
|
||||
tvoc: Tendency,
|
||||
}
|
||||
|
||||
impl Tendencies {
|
||||
pub fn from_history(history: &History) -> Self {
|
||||
let mut iter = history.min5.oldest_ordered().rev().copied().take(5);
|
||||
let len = history.min5.len().min(5);
|
||||
|
||||
if len <= 1 {
|
||||
return Tendencies {
|
||||
temperature: Tendency::Steady,
|
||||
humidity: Tendency::Steady,
|
||||
eco2: Tendency::Steady,
|
||||
tvoc: Tendency::Steady,
|
||||
};
|
||||
}
|
||||
|
||||
let mut last = iter.next().unwrap();
|
||||
let mut avg_slope = Sample::zero();
|
||||
for x in iter {
|
||||
avg_slope = avg_slope + (last - x);
|
||||
last = x;
|
||||
}
|
||||
|
||||
avg_slope = avg_slope * (1. / len as f32);
|
||||
|
||||
const TEMPERATURE_TENDENCY_TRESHOLD: f32 = 0.3;
|
||||
const HUMIDITY_TENDENCY_TRESHOLD: f32 = 0.3;
|
||||
const ECO2_TENDENCY_TRESHOLD: f32 = 50.;
|
||||
const TVOC_TENDENCY_TRESHOLD: f32 = 50.;
|
||||
Tendencies {
|
||||
temperature: if avg_slope.temperature > TEMPERATURE_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.temperature < -TEMPERATURE_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
humidity: if avg_slope.humidity > HUMIDITY_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.humidity < -HUMIDITY_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
eco2: if avg_slope.eco2 > ECO2_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.eco2 < -ECO2_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
tvoc: if avg_slope.tvoc > TVOC_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.tvoc < -TVOC_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AppState<'a> {
|
||||
sample: Sample,
|
||||
tendencies: Tendencies,
|
||||
history: &'a History,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ViewState {
|
||||
Main,
|
||||
GraphTemperature,
|
||||
GraphHumidity,
|
||||
GraphECo2,
|
||||
GraphTvoc,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn circulate(&self) -> Self {
|
||||
match self {
|
||||
ViewState::Main => ViewState::GraphTemperature,
|
||||
ViewState::GraphTemperature => ViewState::GraphHumidity,
|
||||
ViewState::GraphHumidity => ViewState::GraphECo2,
|
||||
ViewState::GraphECo2 => ViewState::GraphTvoc,
|
||||
ViewState::GraphTvoc => ViewState::Main,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_view<T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>>(
|
||||
&self,
|
||||
target: &mut T,
|
||||
app_state: AppState<'_>,
|
||||
) {
|
||||
match self {
|
||||
ViewState::Main => {
|
||||
let _ = views::menu::menu_view(app_state)
|
||||
.as_drawable(Size::new(240, 240), Rgb565::WHITE)
|
||||
.draw(target);
|
||||
}
|
||||
ViewState::GraphTemperature => {
|
||||
detail::detailed(app_state, MenuIndicatorType::Temperature, target)
|
||||
}
|
||||
ViewState::GraphHumidity => {
|
||||
detail::detailed(app_state, MenuIndicatorType::Humidity, target)
|
||||
}
|
||||
ViewState::GraphECo2 => detail::detailed(app_state, MenuIndicatorType::Co2, target),
|
||||
ViewState::GraphTvoc => detail::detailed(app_state, MenuIndicatorType::Voc, target),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum MenuIndicatorType {
|
||||
Temperature,
|
||||
Humidity,
|
||||
Co2,
|
||||
Voc,
|
||||
}
|
||||
|
||||
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, app_state: &AppState) -> heapless::String<16> {
|
||||
match self {
|
||||
MenuIndicatorType::Temperature => {
|
||||
format!(16; "{:.1}", app_state.sample.temperature).unwrap()
|
||||
}
|
||||
MenuIndicatorType::Humidity => format!(16; "{:.1}", app_state.sample.humidity).unwrap(),
|
||||
MenuIndicatorType::Co2 => format!(16; "{}", app_state.sample.eco2 as u32).unwrap(),
|
||||
MenuIndicatorType::Voc => format!(16; "{}", app_state.sample.tvoc as u32).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tendency(&self, app_state: &AppState) -> Tendency {
|
||||
match self {
|
||||
MenuIndicatorType::Temperature => app_state.tendencies.temperature,
|
||||
MenuIndicatorType::Humidity => app_state.tendencies.humidity,
|
||||
MenuIndicatorType::Co2 => app_state.tendencies.eco2,
|
||||
MenuIndicatorType::Voc => app_state.tendencies.tvoc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
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<Rgb565> {
|
||||
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)
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn interrupt_handler() {
|
||||
critical_section::with(|cs| {
|
||||
let mut button = INPUT_BUTTON.borrow_ref_mut(cs);
|
||||
let Some(button) = button.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
*BUTTON_PRESSED.borrow_ref_mut(cs) = true;
|
||||
button.clear_interrupt();
|
||||
});
|
||||
}
|
||||
52
src/display.rs
Normal file
52
src/display.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use defmt::info;
|
||||
use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_graphics::prelude::DrawTarget;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use esp_hal::delay::Delay;
|
||||
use esp_hal::gpio::Level;
|
||||
use esp_hal::gpio::Output;
|
||||
use esp_hal::gpio::OutputConfig;
|
||||
use esp_hal::gpio::OutputPin;
|
||||
use esp_hal::spi::master::Instance;
|
||||
use esp_hal::time::Rate;
|
||||
use mipidsi::interface::SpiInterface;
|
||||
use mipidsi::models::ST7789;
|
||||
|
||||
pub fn setup_display<'a>(
|
||||
spi: impl Instance + 'static,
|
||||
sck: impl OutputPin + 'static,
|
||||
mosi: impl OutputPin + 'static,
|
||||
cs: impl OutputPin + 'static,
|
||||
dc: impl OutputPin + 'static,
|
||||
timer: &mut Delay,
|
||||
buffer: &mut [u8],
|
||||
) -> impl DrawTarget<Color = Rgb565> {
|
||||
let spi = esp_hal::spi::master::Spi::new(
|
||||
spi,
|
||||
esp_hal::spi::master::Config::default()
|
||||
.with_mode(esp_hal::spi::Mode::_0)
|
||||
.with_frequency(Rate::from_mhz(80)),
|
||||
)
|
||||
.unwrap()
|
||||
.with_sck(sck)
|
||||
.with_mosi(mosi);
|
||||
|
||||
let mut cs_output = Output::new(cs, Level::High, OutputConfig::default());
|
||||
cs_output.set_high();
|
||||
let spi_device = ExclusiveDevice::new_no_delay(spi, cs_output).unwrap();
|
||||
|
||||
// Define the display interface with no chip select
|
||||
let di = SpiInterface::new(
|
||||
spi_device,
|
||||
Output::new(dc, Level::Low, OutputConfig::default()),
|
||||
buffer,
|
||||
);
|
||||
info!("Display creating, initializing ...");
|
||||
|
||||
let display = mipidsi::Builder::new(ST7789, di)
|
||||
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
||||
.init(timer)
|
||||
.unwrap();
|
||||
info!("Initialized");
|
||||
display
|
||||
}
|
||||
461
src/main.rs
461
src/main.rs
@ -5,424 +5,123 @@
|
||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
#![allow(unreachable_code)]
|
||||
|
||||
mod colors;
|
||||
mod graph;
|
||||
mod images;
|
||||
mod sampler;
|
||||
mod views;
|
||||
|
||||
use buoyant::primitives::Pixel;
|
||||
use buoyant::primitives::Size;
|
||||
use buoyant::primitives::geometry::Rectangle;
|
||||
use buoyant::view::AsDrawable;
|
||||
use buoyant::view::Image;
|
||||
use buoyant::view::ViewExt;
|
||||
use core::cell::RefCell;
|
||||
use core::default::Default;
|
||||
use core::iter::Iterator;
|
||||
use core::ops::Sub;
|
||||
use critical_section::Mutex;
|
||||
use defmt::info;
|
||||
use embedded_graphics::Drawable;
|
||||
use embedded_graphics::framebuffer::Framebuffer;
|
||||
use embedded_graphics::framebuffer::buffer_size;
|
||||
use embedded_graphics::image::GetPixel;
|
||||
use embedded_graphics::image::ImageRaw;
|
||||
use embedded_graphics::pixelcolor::raw::LittleEndian;
|
||||
use embedded_graphics::prelude::Dimensions;
|
||||
use embedded_graphics::prelude::DrawTarget;
|
||||
use embedded_graphics::prelude::Point;
|
||||
use embedded_graphics::prelude::Primitive;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::channel::Channel;
|
||||
use embassy_time::Timer;
|
||||
use embedded_graphics::draw_target::DrawTarget;
|
||||
use embedded_graphics::geometry::Dimensions;
|
||||
use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_graphics::prelude::RgbColor;
|
||||
use embedded_graphics::primitives::PrimitiveStyle;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use embedded_graphics_framebuf::FrameBuf;
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::delay::Delay;
|
||||
use esp_hal::gpio::Input;
|
||||
use esp_hal::gpio::InputConfig;
|
||||
use esp_hal::gpio::Io;
|
||||
use esp_hal::gpio::Level;
|
||||
use esp_hal::gpio::Output;
|
||||
use esp_hal::gpio::OutputConfig;
|
||||
use esp_hal::gpio::Pull;
|
||||
use esp_hal::handler;
|
||||
use esp_hal::peripherals;
|
||||
use esp_hal::time::Rate;
|
||||
use heapless::format;
|
||||
use mipidsi::interface::SpiInterface;
|
||||
use mipidsi::models::ST7789;
|
||||
|
||||
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 crate::colors::BACKGROUND_COLOR;
|
||||
use crate::graph::graph_data;
|
||||
use crate::graph::max_indicator;
|
||||
use crate::graph::min_indicator;
|
||||
use crate::images::StaticImage;
|
||||
use crate::sampler::History;
|
||||
use crate::sampler::Sample;
|
||||
use crate::sampler::Sampler;
|
||||
use crate::views::detail;
|
||||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_rtos::main;
|
||||
|
||||
extern crate alloc;
|
||||
extern crate esp_alloc;
|
||||
|
||||
// mod colors;
|
||||
// mod graph;
|
||||
mod display;
|
||||
mod images;
|
||||
mod sampler;
|
||||
//mod views;
|
||||
|
||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
use esp_backtrace as _;
|
||||
use esp_println as _;
|
||||
|
||||
static INPUT_BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
|
||||
static BUTTON_PRESSED: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
|
||||
use crate::sampler::Sample;
|
||||
|
||||
pub enum ApplicationEvent {
|
||||
ButtonPress,
|
||||
LongButtonPress,
|
||||
NewSample(Sample),
|
||||
}
|
||||
|
||||
static EVENT_CHANNEL: Channel<CriticalSectionRawMutex, ApplicationEvent, 8> = Channel::new();
|
||||
|
||||
#[main]
|
||||
fn main() -> ! {
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
esp_alloc::heap_allocator!(size: 32 * 1024);
|
||||
|
||||
// generator version: 1.0.1
|
||||
info!("Starting up.");
|
||||
images::prepare_images();
|
||||
//images::prepare_images();
|
||||
info!("Prepared images.");
|
||||
|
||||
esp_alloc::heap_allocator!(size: 32 * 1024);
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
let mut timer = Delay::new();
|
||||
let mut io = Io::new(peripherals.IO_MUX);
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
||||
esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);
|
||||
|
||||
info!("Init done.");
|
||||
|
||||
let spi = esp_hal::spi::master::Spi::new(
|
||||
peripherals.SPI2,
|
||||
esp_hal::spi::master::Config::default()
|
||||
.with_mode(esp_hal::spi::Mode::_0)
|
||||
.with_frequency(Rate::from_mhz(80)),
|
||||
)
|
||||
.unwrap()
|
||||
.with_sck(peripherals.GPIO4)
|
||||
.with_mosi(peripherals.GPIO6);
|
||||
|
||||
let mut cs_output = Output::new(peripherals.GPIO0, Level::High, OutputConfig::default());
|
||||
cs_output.set_high();
|
||||
let spi_device = ExclusiveDevice::new_no_delay(spi, cs_output).unwrap();
|
||||
|
||||
info!("Setting up display");
|
||||
let mut timer = esp_hal::delay::Delay::new();
|
||||
let mut buffer = [0_u8; 512];
|
||||
|
||||
// Define the display interface with no chip select
|
||||
let di = SpiInterface::new(
|
||||
spi_device,
|
||||
Output::new(peripherals.GPIO1, Level::Low, OutputConfig::default()),
|
||||
let mut display = display::setup_display(
|
||||
peripherals.SPI2,
|
||||
peripherals.GPIO4,
|
||||
peripherals.GPIO6,
|
||||
peripherals.GPIO0,
|
||||
peripherals.GPIO1,
|
||||
&mut timer,
|
||||
&mut buffer,
|
||||
);
|
||||
info!("Display creating, initializing ...");
|
||||
|
||||
let mut display = mipidsi::Builder::new(ST7789, di)
|
||||
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
||||
.init(&mut timer)
|
||||
.unwrap();
|
||||
info!("Initialized");
|
||||
|
||||
let mut fb =
|
||||
Framebuffer::<Rgb565, _, LittleEndian, 240, 240, { buffer_size::<Rgb565>(240, 240) }>::new(
|
||||
);
|
||||
|
||||
// views::menu::menu_view()
|
||||
// .as_drawable(Size::new(240, 240), Rgb565::WHITE)
|
||||
// .draw(&mut fb)
|
||||
// .unwrap();
|
||||
|
||||
// embedded_graphics::image::Image::new(get_image!(images::HUMIDITY_ICON), Point::zero())
|
||||
// .draw(&mut display)
|
||||
// .unwrap();
|
||||
|
||||
info!("Creating sampler");
|
||||
let mut sampler = Sampler::new(
|
||||
peripherals.I2C0,
|
||||
peripherals.GPIO8,
|
||||
peripherals.GPIO9,
|
||||
&mut timer,
|
||||
);
|
||||
let _ = sampler.sample(&mut timer);
|
||||
info!("Sensor initialized");
|
||||
|
||||
// Input button interrupt
|
||||
info!("Clearing screen");
|
||||
{
|
||||
esp_hal::interrupt::enable(
|
||||
peripherals::Interrupt::GPIO,
|
||||
esp_hal::interrupt::Priority::Priority1,
|
||||
)
|
||||
.unwrap();
|
||||
let mut input_button = Input::new(
|
||||
peripherals.GPIO10,
|
||||
InputConfig::default().with_pull(Pull::Up),
|
||||
);
|
||||
|
||||
io.set_interrupt_handler(interrupt_handler);
|
||||
critical_section::with(|cs| {
|
||||
input_button.listen(esp_hal::gpio::Event::FallingEdge);
|
||||
INPUT_BUTTON.borrow_ref_mut(cs).replace(input_button);
|
||||
});
|
||||
let fbuf_data = [Rgb565::new(0, 0, 0); 240 * 240];
|
||||
let _ = display.fill_contiguous(&display.bounding_box(), fbuf_data);
|
||||
}
|
||||
info!("Setup interrupts");
|
||||
|
||||
let mut history = History::new(&mut sampler, &mut timer);
|
||||
let mut view_state = ViewState::Main;
|
||||
let mut x = 0;
|
||||
let btn = Input::new(
|
||||
peripherals.GPIO10,
|
||||
InputConfig::default().with_pull(Pull::Down),
|
||||
);
|
||||
|
||||
spawner.spawn(button_listener(btn));
|
||||
spawner.spawn(event_handler());
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn button_listener(mut btn: Input<'static>) {
|
||||
let sender = EVENT_CHANNEL.sender();
|
||||
loop {
|
||||
// input state
|
||||
let button_pressed = critical_section::with(|cs| {
|
||||
let val = *BUTTON_PRESSED.borrow(cs).borrow();
|
||||
*BUTTON_PRESSED.borrow_ref_mut(cs) = false;
|
||||
val
|
||||
});
|
||||
|
||||
if button_pressed {
|
||||
view_state = view_state.circulate();
|
||||
}
|
||||
|
||||
if false && (history.update(&mut sampler, &mut timer) || button_pressed) {
|
||||
// let iter = history.min5.oldest_ordered().map(|x| x.eco2);
|
||||
// embedded_graphics::primitives::Rectangle::new(Point::zero(), fb.bounding_box().size)
|
||||
// .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
|
||||
// .draw(&mut fb)
|
||||
// .unwrap();
|
||||
// graph_data(iter.clone(), history.min5.len(), &mut fb);
|
||||
// min_indicator(iter.clone(), history.min5.len(), &mut fb);
|
||||
// max_indicator(iter.clone(), history.min5.len(), &mut fb);
|
||||
//
|
||||
// let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 240);
|
||||
// let image = embedded_graphics::image::Image::new(&img_raw, Point::zero());
|
||||
// image.draw(&mut display).unwrap();
|
||||
|
||||
let app_state = AppState {
|
||||
sample: *history.min5.last().unwrap(),
|
||||
history: &history,
|
||||
tendencies: Tendencies::from_history(&history),
|
||||
};
|
||||
display
|
||||
.bounding_box()
|
||||
.into_styled(PrimitiveStyle::with_fill(BACKGROUND_COLOR))
|
||||
.draw(&mut fb)
|
||||
.unwrap();
|
||||
view_state.draw_view(&mut fb, app_state);
|
||||
let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 240);
|
||||
let image = embedded_graphics::image::Image::new(&img_raw, Point::zero());
|
||||
image.draw(&mut display).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Tendencies {
|
||||
temperature: Tendency,
|
||||
humidity: Tendency,
|
||||
eco2: Tendency,
|
||||
tvoc: Tendency,
|
||||
}
|
||||
|
||||
impl Tendencies {
|
||||
pub fn from_history(history: &History) -> Self {
|
||||
let mut iter = history.min5.oldest_ordered().rev().copied().take(5);
|
||||
let len = history.min5.len().min(5);
|
||||
|
||||
if len <= 1 {
|
||||
return Tendencies {
|
||||
temperature: Tendency::Steady,
|
||||
humidity: Tendency::Steady,
|
||||
eco2: Tendency::Steady,
|
||||
tvoc: Tendency::Steady,
|
||||
};
|
||||
}
|
||||
|
||||
let mut last = iter.next().unwrap();
|
||||
let mut avg_slope = Sample::zero();
|
||||
for x in iter {
|
||||
avg_slope = avg_slope + (last - x);
|
||||
last = x;
|
||||
}
|
||||
|
||||
avg_slope = avg_slope * (1. / len as f32);
|
||||
|
||||
const TEMPERATURE_TENDENCY_TRESHOLD: f32 = 0.3;
|
||||
const HUMIDITY_TENDENCY_TRESHOLD: f32 = 0.3;
|
||||
const ECO2_TENDENCY_TRESHOLD: f32 = 50.;
|
||||
const TVOC_TENDENCY_TRESHOLD: f32 = 50.;
|
||||
Tendencies {
|
||||
temperature: if avg_slope.temperature > TEMPERATURE_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.temperature < -TEMPERATURE_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
humidity: if avg_slope.humidity > HUMIDITY_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.humidity < -HUMIDITY_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
eco2: if avg_slope.eco2 > ECO2_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.eco2 < -ECO2_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
tvoc: if avg_slope.tvoc > TVOC_TENDENCY_TRESHOLD {
|
||||
Tendency::Rising
|
||||
} else if avg_slope.tvoc < -TVOC_TENDENCY_TRESHOLD {
|
||||
Tendency::Falling
|
||||
} else {
|
||||
Tendency::Steady
|
||||
},
|
||||
}
|
||||
btn.wait_for_rising_edge().await;
|
||||
sender.send(ApplicationEvent::ButtonPress).await;
|
||||
btn.wait_for_low().await;
|
||||
Timer::after_millis(20).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AppState<'a> {
|
||||
sample: Sample,
|
||||
tendencies: Tendencies,
|
||||
history: &'a History,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ViewState {
|
||||
Main,
|
||||
GraphTemperature,
|
||||
GraphHumidity,
|
||||
GraphECo2,
|
||||
GraphTvoc,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn circulate(&self) -> Self {
|
||||
match self {
|
||||
ViewState::Main => ViewState::GraphTemperature,
|
||||
ViewState::GraphTemperature => ViewState::GraphHumidity,
|
||||
ViewState::GraphHumidity => ViewState::GraphECo2,
|
||||
ViewState::GraphECo2 => ViewState::GraphTvoc,
|
||||
ViewState::GraphTvoc => ViewState::Main,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_view<T: DrawTarget<Color = Rgb565> + GetPixel<Color = Rgb565>>(
|
||||
&self,
|
||||
target: &mut T,
|
||||
app_state: AppState<'_>,
|
||||
) {
|
||||
match self {
|
||||
ViewState::Main => {
|
||||
let _ = views::menu::menu_view(app_state)
|
||||
.as_drawable(Size::new(240, 240), Rgb565::WHITE)
|
||||
.draw(target);
|
||||
#[embassy_executor::task]
|
||||
async fn event_handler() {
|
||||
let receiver = EVENT_CHANNEL.receiver();
|
||||
let mut state = false;
|
||||
loop {
|
||||
let event = receiver.receive().await;
|
||||
match event {
|
||||
ApplicationEvent::ButtonPress => {
|
||||
state = !state;
|
||||
let _ = fbuf.clear(if state { Rgb565::WHITE } else { Rgb565::BLACK });
|
||||
let _ = display.fill_contiguous(&display.bounding_box(), fbuf.data.iter().copied());
|
||||
}
|
||||
ViewState::GraphTemperature => {
|
||||
detail::detailed(app_state, MenuIndicatorType::Temperature, target)
|
||||
}
|
||||
ViewState::GraphHumidity => {
|
||||
detail::detailed(app_state, MenuIndicatorType::Humidity, target)
|
||||
}
|
||||
ViewState::GraphECo2 => detail::detailed(app_state, MenuIndicatorType::Co2, target),
|
||||
ViewState::GraphTvoc => detail::detailed(app_state, MenuIndicatorType::Voc, target),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum MenuIndicatorType {
|
||||
Temperature,
|
||||
Humidity,
|
||||
Co2,
|
||||
Voc,
|
||||
}
|
||||
|
||||
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, app_state: &AppState) -> heapless::String<16> {
|
||||
match self {
|
||||
MenuIndicatorType::Temperature => {
|
||||
format!(16; "{:.1}", app_state.sample.temperature).unwrap()
|
||||
}
|
||||
MenuIndicatorType::Humidity => format!(16; "{:.1}", app_state.sample.humidity).unwrap(),
|
||||
MenuIndicatorType::Co2 => format!(16; "{}", app_state.sample.eco2 as u32).unwrap(),
|
||||
MenuIndicatorType::Voc => format!(16; "{}", app_state.sample.tvoc as u32).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tendency(&self, app_state: &AppState) -> Tendency {
|
||||
match self {
|
||||
MenuIndicatorType::Temperature => app_state.tendencies.temperature,
|
||||
MenuIndicatorType::Humidity => app_state.tendencies.humidity,
|
||||
MenuIndicatorType::Co2 => app_state.tendencies.eco2,
|
||||
MenuIndicatorType::Voc => app_state.tendencies.tvoc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
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<Rgb565> {
|
||||
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)
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn interrupt_handler() {
|
||||
critical_section::with(|cs| {
|
||||
let mut button = INPUT_BUTTON.borrow_ref_mut(cs);
|
||||
let Some(button) = button.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
*BUTTON_PRESSED.borrow_ref_mut(cs) = true;
|
||||
button.clear_interrupt();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user