changeset 620:8fda564cc46f rust

fridge work
author Matt Johnston <matt@ucc.asn.au>
date Thu, 23 Mar 2017 00:20:22 +0800
parents aecd0a15133c
children 8136a6b99866
files rust/Cargo.lock rust/Cargo.toml rust/src/fridge.rs rust/src/main.rs
diffstat 4 files changed, 199 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- 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"
--- 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"
--- 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();
     }
--- 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();
 }