Faster fill
This commit is contained in:
293
src/main.rs
293
src/main.rs
@ -6,23 +6,36 @@
|
||||
holding buffers for the duration of a data transfer."
|
||||
)]
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::hint;
|
||||
|
||||
use buoyant::primitives::ProposedDimension;
|
||||
use buoyant::view::AsDrawable;
|
||||
use critical_section::Mutex;
|
||||
use defmt::info;
|
||||
use defmt::trace;
|
||||
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::Duration;
|
||||
use embassy_time::Timer;
|
||||
use embedded_graphics::Drawable;
|
||||
use embedded_graphics::draw_target::DrawTarget;
|
||||
use embedded_graphics::framebuffer::Framebuffer;
|
||||
use embedded_graphics::framebuffer::buffer_size;
|
||||
use embedded_graphics::geometry::Dimensions;
|
||||
use embedded_graphics::image::GetPixel;
|
||||
use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_graphics::pixelcolor::raw::LittleEndian;
|
||||
use embedded_graphics::prelude::PixelColor;
|
||||
use embedded_graphics::prelude::Primitive;
|
||||
use embedded_graphics::prelude::RgbColor;
|
||||
use embedded_graphics::primitives::PrimitiveStyle;
|
||||
use embedded_graphics_framebuf::FrameBuf;
|
||||
use embedded_graphics_framebuf::backends::FrameBufferBackend;
|
||||
use esp_hal::clock::CpuClock;
|
||||
use esp_hal::gpio::Input;
|
||||
use esp_hal::gpio::InputConfig;
|
||||
use esp_hal::gpio::Level;
|
||||
use esp_hal::gpio::Output;
|
||||
use esp_hal::gpio::OutputConfig;
|
||||
use esp_hal::gpio::Pull;
|
||||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
@ -31,18 +44,25 @@ use esp_rtos::main;
|
||||
extern crate alloc;
|
||||
extern crate esp_alloc;
|
||||
|
||||
// mod colors;
|
||||
// mod graph;
|
||||
mod colors;
|
||||
mod display;
|
||||
mod graph;
|
||||
mod images;
|
||||
mod sampler;
|
||||
//mod views;
|
||||
mod views;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
use esp_backtrace as _;
|
||||
use esp_println as _;
|
||||
use heapless::HistoryBuf;
|
||||
use heapless::format;
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::display::MainDisplay;
|
||||
use crate::images::StaticImage;
|
||||
use crate::sampler::History;
|
||||
use crate::sampler::Sample;
|
||||
use crate::sampler::Sampler;
|
||||
|
||||
pub enum ApplicationEvent {
|
||||
ButtonPress,
|
||||
@ -51,14 +71,16 @@ pub enum ApplicationEvent {
|
||||
}
|
||||
|
||||
static EVENT_CHANNEL: Channel<CriticalSectionRawMutex, ApplicationEvent, 8> = Channel::new();
|
||||
static MAIN_DISPLAY: Mutex<RefCell<Option<MainDisplay>>> = Mutex::new(RefCell::new(None));
|
||||
static SAMPLER: StaticCell<Sampler> = StaticCell::new();
|
||||
|
||||
#[main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
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.");
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
@ -70,7 +92,6 @@ async fn main(spawner: Spawner) -> ! {
|
||||
info!("Init done.");
|
||||
info!("Setting up display");
|
||||
let mut timer = esp_hal::delay::Delay::new();
|
||||
let mut buffer = [0_u8; 512];
|
||||
let mut display = display::setup_display(
|
||||
peripherals.SPI2,
|
||||
peripherals.GPIO4,
|
||||
@ -78,7 +99,6 @@ async fn main(spawner: Spawner) -> ! {
|
||||
peripherals.GPIO0,
|
||||
peripherals.GPIO1,
|
||||
&mut timer,
|
||||
&mut buffer,
|
||||
);
|
||||
|
||||
info!("Clearing screen");
|
||||
@ -86,20 +106,32 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let fbuf_data = [Rgb565::new(0, 0, 0); 240 * 240];
|
||||
let _ = display.fill_contiguous(&display.bounding_box(), fbuf_data);
|
||||
}
|
||||
critical_section::with(|cs| {
|
||||
MAIN_DISPLAY.borrow_ref_mut(cs).replace(display);
|
||||
});
|
||||
|
||||
// Setup button
|
||||
let btn = Input::new(
|
||||
peripherals.GPIO10,
|
||||
InputConfig::default().with_pull(Pull::Down),
|
||||
);
|
||||
|
||||
spawner.spawn(button_listener(btn));
|
||||
spawner.spawn(event_handler());
|
||||
// Setup sampler
|
||||
let sampler = SAMPLER.init(Sampler::new(
|
||||
peripherals.I2C0,
|
||||
peripherals.GPIO8,
|
||||
peripherals.GPIO9,
|
||||
&mut timer,
|
||||
));
|
||||
|
||||
loop {}
|
||||
spawner.spawn(button_listener(btn)).unwrap();
|
||||
spawner.spawn(event_handler()).unwrap();
|
||||
spawner.spawn(sampler_task(sampler)).unwrap();
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn button_listener(mut btn: Input<'static>) {
|
||||
info!("Button listner task launched");
|
||||
let sender = EVENT_CHANNEL.sender();
|
||||
loop {
|
||||
btn.wait_for_rising_edge().await;
|
||||
@ -111,17 +143,242 @@ async fn button_listener(mut btn: Input<'static>) {
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn event_handler() {
|
||||
info!("Event handler task launched");
|
||||
let receiver = EVENT_CHANNEL.receiver();
|
||||
let mut state = false;
|
||||
let mut display = critical_section::with(|cs| MAIN_DISPLAY.borrow_ref_mut(cs).take().unwrap());
|
||||
let mut fbuf =
|
||||
Framebuffer::<Rgb565, _, LittleEndian, 240, 240, { buffer_size::<Rgb565>(240, 240) }>::new(
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
loop {
|
||||
if redraw {
|
||||
redraw = false;
|
||||
let mut draw_graph = None;
|
||||
let tendencies = Tendencies::from_history(&last_5_mins);
|
||||
let _ = fbuf
|
||||
.bounding_box()
|
||||
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
|
||||
.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);
|
||||
}
|
||||
ViewState::TemperatureGraph => draw_graph = Some(MeasurementType::Temperature),
|
||||
ViewState::HumidityGraph => draw_graph = Some(MeasurementType::Humidity),
|
||||
ViewState::ECo2Graph => draw_graph = Some(MeasurementType::ECo2),
|
||||
ViewState::TVocGraph => draw_graph = Some(MeasurementType::TVoc),
|
||||
}
|
||||
|
||||
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(
|
||||
&fbuf.bounding_box(),
|
||||
fbuf.data()
|
||||
.chunks(2)
|
||||
.map(|x| unsafe { core::mem::transmute::<_, Rgb565>([x[0], x[1]]) }),
|
||||
);
|
||||
}
|
||||
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());
|
||||
current_view = current_view.next();
|
||||
redraw = true;
|
||||
}
|
||||
ApplicationEvent::NewSample(x) => {
|
||||
last_5_mins.write(x);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ViewState {
|
||||
Main,
|
||||
TemperatureGraph,
|
||||
HumidityGraph,
|
||||
ECo2Graph,
|
||||
TVocGraph,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn next(&self) -> Self {
|
||||
match self {
|
||||
ViewState::Main => ViewState::TemperatureGraph,
|
||||
ViewState::TemperatureGraph => ViewState::HumidityGraph,
|
||||
ViewState::HumidityGraph => ViewState::ECo2Graph,
|
||||
ViewState::ECo2Graph => ViewState::TVocGraph,
|
||||
ViewState::TVocGraph => ViewState::Main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LOW_PASS_LENGTH: usize = 10;
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn sampler_task(sampler: &'static mut Sampler<'static>) {
|
||||
info!("Sampler task launched");
|
||||
let sender = EVENT_CHANNEL.sender();
|
||||
sender
|
||||
.send(ApplicationEvent::NewSample(Sample::zero()))
|
||||
.await;
|
||||
let mut low_pass: HistoryBuf<Sample, LOW_PASS_LENGTH> = HistoryBuf::new();
|
||||
let mut delay = esp_hal::delay::Delay::new();
|
||||
let mut count = 0;
|
||||
|
||||
loop {
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
let sample = sampler.sample(&mut delay);
|
||||
low_pass.write(sample);
|
||||
count += 1;
|
||||
|
||||
if count >= 2 {
|
||||
sender.send(ApplicationEvent::NewSample(sample)).await;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Tendencies {
|
||||
temperature: Tendency,
|
||||
humidity: Tendency,
|
||||
eco2: Tendency,
|
||||
tvoc: Tendency,
|
||||
}
|
||||
|
||||
impl Tendencies {
|
||||
pub fn from_history<const N: usize>(history: &HistoryBuf<Sample, N>) -> Self {
|
||||
let mut iter = history.oldest_ordered().rev().copied().take(5);
|
||||
let len = history.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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum MeasurementType {
|
||||
Temperature,
|
||||
Humidity,
|
||||
ECo2,
|
||||
TVoc,
|
||||
}
|
||||
|
||||
impl MeasurementType {
|
||||
pub fn get_corresponding_icon(&self) -> &'static StaticImage {
|
||||
match self {
|
||||
MeasurementType::Temperature => &images::TEMPERATURE_ICON,
|
||||
MeasurementType::Humidity => &images::HUMIDITY_ICON,
|
||||
MeasurementType::ECo2 => &images::CO2_ICON,
|
||||
MeasurementType::TVoc => &images::VOC_ICON,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_corresponding_unit_string(&self) -> &'static str {
|
||||
match self {
|
||||
MeasurementType::Temperature => "C",
|
||||
MeasurementType::Humidity => "%",
|
||||
MeasurementType::ECo2 => "ppm",
|
||||
MeasurementType::TVoc => "ppb",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value_str(&self, sample: Sample) -> heapless::String<16> {
|
||||
match self {
|
||||
MeasurementType::Temperature => format!(16; "{:.1}", sample.temperature).unwrap(),
|
||||
MeasurementType::Humidity => format!(16; "{:.1}", sample.humidity).unwrap(),
|
||||
MeasurementType::ECo2 => format!(16; "{}", sample.eco2 as u32).unwrap(),
|
||||
MeasurementType::TVoc => format!(16; "{}", sample.tvoc as u32).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tendency(&self, tendencies: Tendencies) -> Tendency {
|
||||
match self {
|
||||
MeasurementType::Temperature => tendencies.temperature,
|
||||
MeasurementType::Humidity => tendencies.humidity,
|
||||
MeasurementType::ECo2 => tendencies.eco2,
|
||||
MeasurementType::TVoc => tendencies.tvoc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user