Nice graphs

This commit is contained in:
2025-11-30 22:11:47 +01:00
parent 3cb8a04a77
commit ea9852d5bc
28 changed files with 918 additions and 426 deletions

209
src/sampler.rs Normal file
View File

@ -0,0 +1,209 @@
use core::cell::RefCell;
use core::ops::Add;
use core::ops::Mul;
use core::ops::Sub;
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;
use esp_hal::time::Duration;
use esp_hal::time::Instant;
use heapless::Deque;
use heapless::HistoryBuf;
use crate::sampler;
pub struct Sampler<'a> {
ens160: Ens160<RcDevice<I2c<'a, Blocking>>>,
aht20: aht20_driver::AHT20Initialized<RcDevice<I2c<'a, Blocking>>>,
}
#[derive(Clone, Copy, Debug)]
pub struct Sample {
pub temperature: f32,
pub humidity: f32,
pub eco2: f32,
pub tvoc: f32,
}
impl Sample {
pub fn zero() -> Self {
Sample {
temperature: 0.,
humidity: 0.,
eco2: 0.,
tvoc: 0.,
}
}
}
impl Add<Sample> for Sample {
type Output = Sample;
fn add(self, rhs: Sample) -> Self::Output {
Sample {
temperature: self.temperature + rhs.temperature,
humidity: self.humidity + rhs.humidity,
eco2: self.eco2 + rhs.eco2,
tvoc: self.tvoc + rhs.tvoc,
}
}
}
impl Sub<Sample> for Sample {
type Output = Sample;
fn sub(self, rhs: Sample) -> Self::Output {
Sample {
temperature: self.temperature - rhs.temperature,
humidity: self.humidity - rhs.humidity,
eco2: self.eco2 - rhs.eco2,
tvoc: self.tvoc - rhs.tvoc,
}
}
}
impl Mul<Sample> for Sample {
type Output = Sample;
fn mul(self, rhs: Sample) -> Self::Output {
Sample {
temperature: self.temperature * rhs.temperature,
humidity: self.humidity * rhs.humidity,
eco2: self.eco2 * rhs.eco2,
tvoc: self.tvoc * rhs.tvoc,
}
}
}
impl Mul<f32> for Sample {
type Output = Sample;
fn mul(self, rhs: f32) -> Self::Output {
Sample {
temperature: self.temperature * rhs,
humidity: self.humidity * rhs,
eco2: self.eco2 * rhs,
tvoc: self.tvoc * rhs,
}
}
}
impl<'a> Sampler<'a> {
pub fn new(
i2c: impl Instance + 'a,
sda: impl PeripheralOutput<'a>,
scl: impl PeripheralOutput<'a>,
timer: &mut 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(timer).unwrap();
Sampler { ens160, aht20 }
}
pub fn sample(&mut self, timer: &mut Delay) -> Sample {
let aht20_measurement = self.aht20.measure(timer).unwrap();
Sample {
temperature: aht20_measurement.temperature,
humidity: aht20_measurement.humidity,
eco2: *self.ens160.eco2().unwrap() as f32,
tvoc: self.ens160.tvoc().unwrap() as f32,
}
}
}
pub const SECONDS_PER_SAMPLES: usize = 1;
pub const MIN_5_LENGTH: usize = (5 * 60) / SECONDS_PER_SAMPLES;
pub struct History {
// 5 minutes, every 5 seconds
pub min5: heapless::history_buf::HistoryBuf<Sample, MIN_5_LENGTH>,
// 2 hours every 5 seconds
pub hour2: heapless::history_buf::HistoryBuf<Sample, { (2 * 60) / SECONDS_PER_SAMPLES }>,
// 24 hours every 5 minutes
pub day: heapless::history_buf::HistoryBuf<Sample, { (24 * 60) / 5 }>,
samples_since_day: u32,
last_sample: Instant,
}
impl History {
pub fn new(sampler: &mut Sampler, timer: &mut Delay) -> Self {
let mut min5 = HistoryBuf::new();
let mut hour2 = HistoryBuf::new();
let mut day = HistoryBuf::new();
// First sampler
let sample = sampler.sample(timer);
min5.write(sample);
hour2.write(sample);
day.write(sample);
History {
min5,
hour2,
day,
samples_since_day: 0,
last_sample: Instant::now(),
}
}
pub fn update(&mut self, sampler: &mut Sampler, timer: &mut Delay) -> bool {
let now = Instant::now();
if now - self.last_sample > Duration::from_secs(SECONDS_PER_SAMPLES as u64) {
let sample = sampler.sample(timer);
self.last_sample = Instant::now();
self.samples_since_day += 1;
if self.samples_since_day as usize == MIN_5_LENGTH {
// Compute average
let avg = self.min5.iter().fold(Sample::zero(), |a, b| a + *b)
* (1. / self.min5.len() as f32);
self.day.write(avg);
self.samples_since_day = 0;
}
self.min5.write(sample);
self.hour2.write(sample);
true
} else {
false
}
}
}