# HG changeset patch # User Matt Johnston # Date 1490199622 -28800 # Node ID 8fda564cc46f2d7965acbe5d67d329e0aade4eea # Parent aecd0a15133ca5bfb08ae08ff5c6b29a45dbb51b fridge work diff -r aecd0a15133c -r 8fda564cc46f rust/Cargo.lock --- a/rust/Cargo.lock Tue Mar 21 22:41:29 2017 +0800 +++ b/rust/Cargo.lock Thu Mar 23 00:20:22 2017 +0800 @@ -16,6 +16,7 @@ "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", + "sysfs_gpio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-curl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -47,6 +48,11 @@ ] [[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "byteorder" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -258,6 +264,19 @@ ] [[package]] +name = "nix" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "num-traits" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -350,11 +369,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "scoped-tls" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "serde" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -417,6 +449,15 @@ ] [[package]] +name = "sysfs_gpio" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "thread-id" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -572,6 +613,7 @@ "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" "checksum base64 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "065a0ce220ab84d0b6d5ae3e7bb77232209519c366f51f946fe28c19e84989d0" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "46112a0060ae15e3a3f9a445428a53e082b91215b744fa27a1948842f4a64b96" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" @@ -598,6 +640,7 @@ "checksum mio 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aa30e3753079b08ce3d75cf3b44783e36fe0e1f64065f65c1d894d1688fb2580" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)" = "18b9642ad6222faf5ce46f6966f59b71b9775ad5758c9e09fcf0a6c8061972b4" +"checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" "checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" "checksum num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18c392466409c50b87369414a2680c93e739aedeb498eb2bff7d7eb569744e2" "checksum openssl-probe 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "756d49c8424483a3df3b5d735112b4da22109ced9a8294f1f5cdf80fb3810919" @@ -611,7 +654,9 @@ "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a702319c807c016e51f672e5c77d6f0b46afddd744b5e437d6b8436b888b458f" "checksum serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d52006899f910528a10631e5b727973fe668f3228109d1707ccf5bad5490b6e" "checksum serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f15ea24bd037b2d64646b4d934fa99c649be66e3f7b29fb595a5543b212b1452" @@ -620,6 +665,7 @@ "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum sysfs_gpio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90fc056b3e1a0f1cd10de67b14b62b85dfe450134e87bd9af2f066981c6ebb75" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" diff -r aecd0a15133c -r 8fda564cc46f rust/Cargo.toml --- a/rust/Cargo.toml Tue Mar 21 22:41:29 2017 +0800 +++ b/rust/Cargo.toml Thu Mar 23 00:20:22 2017 +0800 @@ -21,4 +21,7 @@ toml = "0.3" curl = "0.4" serde_json = "0.9" -base64 = "0.4.0" +base64 = "0.4" + +# linux only +sysfs_gpio = "0.5" diff -r aecd0a15133c -r 8fda564cc46f rust/src/fridge.rs --- 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, temp_fridge: Option, + last_off_time: Instant, + wort_valid_time: Instant, + integrator: StepIntegrator, + gpio: Pin, // Timeouts to wake ourselves up again handle: Handle, timeout_s: mpsc::Sender, timeout_r: Option>, 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(); } diff -r aecd0a15133c -r 8fda564cc46f rust/src/main.rs --- a/rust/src/main.rs Tue Mar 21 22:41:29 2017 +0800 +++ b/rust/src/main.rs Thu Mar 23 00:20:22 2017 +0800 @@ -44,9 +44,6 @@ let params = params::Params::load(&config); let mut fridge = fridge::Fridge::new(&config, nowait, params, &handle); - let (fridge_reading_s, fridge_reading_r) = mpsc::channel(1); - let fridge_reading_r = fridge_reading_r.map_err(|e| TemplogError::new("Problem with fridge_reading_r channel")); - let sensor_stream = if testmode { sensor::TestSensor::new(config).stream(&handle) } else { @@ -55,7 +52,9 @@ // Send the sensors of interest to the fridge (fridge_reading_s), // while streaming them all to the web sender. - let s = sensor_stream.map(|r| { + let (fridge_reading_s, fridge_reading_r) = mpsc::channel(1); + let fridge_reading_r = fridge_reading_r.map_err(|e| TemplogError::new("Problem with fridge_reading_r channel")); + let sensor_stream = sensor_stream.map(|r| { debug!("sensors {:?}", r); let msg = fridge::Message::Sensor { wort: r.get_temp(&config.WORT_NAME), @@ -72,18 +71,19 @@ }); let param_stream = params::ParamWaiter::stream(config, &handle); - let p = param_stream.map(|p| { + let param_stream = param_stream.map(|p| { fridge::Message::Params(p) }); let timeouts = fridge.wakeups(); - let all_readings = s.for_each(|_| Ok(())); - let all_fridge = p.select(timeouts).select(fridge_reading_r).forward(fridge) - .map(|_| () ); + // forward all the different types of messages to the fridge + let all_fridge = param_stream.select(timeouts).select(fridge_reading_r).forward(fridge) .map(|_| () ); + let all_readings = sensor_stream.for_each(|_| Ok(())); + + // run forever let all = all_fridge.select(all_readings); - core.run(all).ok(); }