Nice graphs
This commit is contained in:
209
src/sampler.rs
Normal file
209
src/sampler.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user