Mercurial > templog
diff rust/src/fridge.rs @ 620:8fda564cc46f rust
fridge work
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Thu, 23 Mar 2017 00:20:22 +0800 |
parents | f153aec221be |
children | 8136a6b99866 |
line wrap: on
line diff
--- a/rust/src/fridge.rs Tue Mar 21 22:41:29 2017 +0800 +++ b/rust/src/fridge.rs Thu Mar 23 00:20:22 2017 +0800 @@ -1,5 +1,6 @@ extern crate futures; extern crate tokio_core; +extern crate sysfs_gpio; use std; use std::io; @@ -10,6 +11,7 @@ use futures::{Future,future,Sink,Stream}; use tokio_core::reactor::{Timeout,Handle}; use futures::sync::{mpsc}; +use sysfs_gpio::{Direction, Pin}; use config::Config; use params::Params; @@ -25,16 +27,20 @@ pub struct Fridge { params: Params, config: Config, + + on: bool, temp_wort: Option<f32>, temp_fridge: Option<f32>, + last_off_time: Instant, + wort_valid_time: Instant, + integrator: StepIntegrator, + gpio: Pin, // Timeouts to wake ourselves up again handle: Handle, timeout_s: mpsc::Sender<u64>, timeout_r: Option<mpsc::Receiver<u64>>, ticker: u64, - last_off_time: Instant, - wort_valid_time: Instant, } impl Sink for Fridge { @@ -53,26 +59,43 @@ } } +impl Drop for Fridge { + fn drop(&mut self) { + // safety fridge off + self.gpio.set_state(0); + } +} + impl Fridge { pub fn new(config: &Config, nowait: bool, p: Params, handle: &Handle) -> Fridge { let (s, r) = mpsc::channel(1); let mut f = Fridge { config: config.clone(), params: p.clone(), + on: false, temp_wort: None, temp_fridge: None, + last_off_time: Instant::now(), + wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100), + integrator: StepIntegrator::new(p.overshoot_delay), + gpio: Pin::new(config.FRIDGE_GPIO_PIN), handle: handle.clone(), timeout_s: s, timeout_r: Some(r), ticker: 0, - last_off_time: Instant::now(), - wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100), }; + if nowait { f.last_off_time -= Duration::new(config.FRIDGE_DELAY, 1); } + + // XXX better error handling? + f.gpio.export().expect("Exporting fridge gpio failed"); + f.gpio.set_direction(Direction::Low).expect("Fridge gpio direction failed"); + f.tick(); + f } @@ -99,11 +122,122 @@ .boxed() } + fn turn_off(&mut self) { + log!("Turning fridge off"); + self.turn(false); + } + + fn turn_on(&mut self) { + log!("Turning fridge on"); + self.turn(true); + } + + fn turn(&mut self, on: bool) { + self.on = on; + self.gpio.set_value(on as u8) + } + + /// Turns the fridge off and on + fn compare_temperatures(&mut self) { + let fridge_min = self.params.fridge_setpoint - self.params.fridge_range_lower; + let fridge_max = self.params.fridge_setpoint - self.params.fridge_range_upper; + let wort_max = self.params.fridge_setpoint + self.params.fridge_difference; + let off_time = Instant::now() - self.last_off_time; + + // Or elsewhere? + self.integrator.set_limit(self.params.overshoot_delay); + + // Safety to avoid bad things happening to the fridge motor (?) + // When it turns off don't start up again for at least FRIDGE_DELAY + if !self.on && off_time < self.config.FRIDGE_DELAY { + log!("fridge skipping, too early"); + return; + } + + if self.params.disabled { + if self.on { + log!("Disabled, turning fridge off"); + self.turn_off(); + } + return; + } + + // handle broken wort sensor + if self.temp_wort.is_none() { + let invalid_time = Instant::now() - self.wort_valid_time; + warn!("Invalid wort sensor for {:?} secs", invalid_time); + if invalid_time < self.config.FRIDGE_WORT_INVALID_TIME { + warn!("Has only been invalid for {:?}, waiting", invalid_time); + return; + } + } + + if self.temp_fridge.is_none() { + warn!("Invalid fridge sensor"); + } + + if self.on { + debug!("fridge is on"); + let on_time = self.integrator.integrate().as_secs() as f32; + let on_ratio = on_time / params.overshoot_delay as f32; + + overshoot = params.overshoot_factor as f32 * on_ratio; + debug!("on_time {}, overshoot {}", on_percent, overshoot); + + let mut turn_off = false; + if let Some(t) = self.temp_wort && !params.nowort { + // use the wort temperature + if t - overshoot < params.fridge_setpoint { + log!("wort has cooled enough, {temp}º (overshoot {overshoot}º = {factor} × {percent}%)", + temp = t, overshoot = overshoot, + factor = params.overshoot_factor, + percent = on_ratio*100); + turn_off = true; + } + } else let if Some(t) = self.temp_fridge { + // use the fridge temperature + if t < fridge_min { + warn!("fridge off fallback, fridge {}, min {}", t, fridge_min); + if self.temp_wort.is_none() { + W("wort has been invalid for {:?}", Instant::now() - self.wort_valid_time); + } + turn_off = true; + } + } + if turn_off { + self.turn_off(); + } + } else { + debug!("fridge is off. fridge {:?} max {:?}. wort {:?} max {:?}", + self.temp_fridge, fridge_max, self.temp_wort, wort_max); + let mut turn_on = false; + if let Some(t) = self.temp_wort && !params.nowort { + // use the wort temperature + if t >= wort_max { + log!("Wort is too hot {}°, max {}°", t, wort_max); + turn_on = true; + } + } + + if let Some(t) = self.temp_fridge { + if t >= fridge_max { + warn!("fridge too hot fallback, fridge {}°, max {}°", t, fridge_max) + turn_on = true; + } + } + + if turn_on { + self.turn_on() + } + } + } + /// Must be called after every state change. Turns the fridge on/off as required and /// schedules any future wakeups based on the present (new) state fn tick(&mut self) { debug!("tick"); + self.compare_temperatures() self.send_next_timeout(); } @@ -147,9 +281,9 @@ self.temp_wort = wort; self.temp_fridge = fridge; - if self.temp_wort.is_some() { + if self.temp_wort.is_some() { self.wort_valid_time = Instant::now(); - } + } self.tick(); }