comparison 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
comparison
equal deleted inserted replaced
619:aecd0a15133c 620:8fda564cc46f
1 extern crate futures; 1 extern crate futures;
2 extern crate tokio_core; 2 extern crate tokio_core;
3 extern crate sysfs_gpio;
3 4
4 use std; 5 use std;
5 use std::io; 6 use std::io;
6 use std::mem; 7 use std::mem;
7 use std::error::Error; 8 use std::error::Error;
8 use std::time::{Duration,Instant}; 9 use std::time::{Duration,Instant};
9 10
10 use futures::{Future,future,Sink,Stream}; 11 use futures::{Future,future,Sink,Stream};
11 use tokio_core::reactor::{Timeout,Handle}; 12 use tokio_core::reactor::{Timeout,Handle};
12 use futures::sync::{mpsc}; 13 use futures::sync::{mpsc};
14 use sysfs_gpio::{Direction, Pin};
13 15
14 use config::Config; 16 use config::Config;
15 use params::Params; 17 use params::Params;
16 use types::*; 18 use types::*;
17 19
23 } 25 }
24 26
25 pub struct Fridge { 27 pub struct Fridge {
26 params: Params, 28 params: Params,
27 config: Config, 29 config: Config,
30
31 on: bool,
28 temp_wort: Option<f32>, 32 temp_wort: Option<f32>,
29 temp_fridge: Option<f32>, 33 temp_fridge: Option<f32>,
34 last_off_time: Instant,
35 wort_valid_time: Instant,
36 integrator: StepIntegrator,
37 gpio: Pin,
30 38
31 // Timeouts to wake ourselves up again 39 // Timeouts to wake ourselves up again
32 handle: Handle, 40 handle: Handle,
33 timeout_s: mpsc::Sender<u64>, 41 timeout_s: mpsc::Sender<u64>,
34 timeout_r: Option<mpsc::Receiver<u64>>, 42 timeout_r: Option<mpsc::Receiver<u64>>,
35 ticker: u64, 43 ticker: u64,
36 last_off_time: Instant,
37 wort_valid_time: Instant,
38 } 44 }
39 45
40 impl Sink for Fridge { 46 impl Sink for Fridge {
41 47
42 type SinkItem = Message; 48 type SinkItem = Message;
48 Ok(futures::AsyncSink::Ready) 54 Ok(futures::AsyncSink::Ready)
49 } 55 }
50 56
51 fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> { 57 fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> {
52 Ok(futures::Async::Ready(())) 58 Ok(futures::Async::Ready(()))
59 }
60 }
61
62 impl Drop for Fridge {
63 fn drop(&mut self) {
64 // safety fridge off
65 self.gpio.set_state(0);
53 } 66 }
54 } 67 }
55 68
56 impl Fridge { 69 impl Fridge {
57 pub fn new(config: &Config, nowait: bool, p: Params, handle: &Handle) -> Fridge { 70 pub fn new(config: &Config, nowait: bool, p: Params, handle: &Handle) -> Fridge {
58 let (s, r) = mpsc::channel(1); 71 let (s, r) = mpsc::channel(1);
59 let mut f = Fridge { 72 let mut f = Fridge {
60 config: config.clone(), 73 config: config.clone(),
61 params: p.clone(), 74 params: p.clone(),
75 on: false,
62 temp_wort: None, 76 temp_wort: None,
63 temp_fridge: None, 77 temp_fridge: None,
78 last_off_time: Instant::now(),
79 wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100),
80 integrator: StepIntegrator::new(p.overshoot_delay),
81 gpio: Pin::new(config.FRIDGE_GPIO_PIN),
64 82
65 handle: handle.clone(), 83 handle: handle.clone(),
66 timeout_s: s, 84 timeout_s: s,
67 timeout_r: Some(r), 85 timeout_r: Some(r),
68 ticker: 0, 86 ticker: 0,
69 last_off_time: Instant::now(),
70 wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100),
71 }; 87 };
88
72 if nowait { 89 if nowait {
73 f.last_off_time -= Duration::new(config.FRIDGE_DELAY, 1); 90 f.last_off_time -= Duration::new(config.FRIDGE_DELAY, 1);
74 } 91 }
92
93 // XXX better error handling?
94 f.gpio.export().expect("Exporting fridge gpio failed");
95 f.gpio.set_direction(Direction::Low).expect("Fridge gpio direction failed");
96
75 f.tick(); 97 f.tick();
98
76 f 99 f
77 } 100 }
78 101
79 fn next_wakeup(&self) -> Duration { 102 fn next_wakeup(&self) -> Duration {
80 let millis = 400; 103 let millis = 400;
97 .map(|v| Message::Tick(v)) 120 .map(|v| Message::Tick(v))
98 .map_err(|e| TemplogError::new("wakeups() receive failed")) 121 .map_err(|e| TemplogError::new("wakeups() receive failed"))
99 .boxed() 122 .boxed()
100 } 123 }
101 124
125 fn turn_off(&mut self) {
126 log!("Turning fridge off");
127 self.turn(false);
128 }
129
130 fn turn_on(&mut self) {
131 log!("Turning fridge on");
132 self.turn(true);
133 }
134
135 fn turn(&mut self, on: bool) {
136 self.on = on;
137 self.gpio.set_value(on as u8)
138 }
139
140 /// Turns the fridge off and on
141 fn compare_temperatures(&mut self) {
142 let fridge_min = self.params.fridge_setpoint - self.params.fridge_range_lower;
143 let fridge_max = self.params.fridge_setpoint - self.params.fridge_range_upper;
144 let wort_max = self.params.fridge_setpoint + self.params.fridge_difference;
145 let off_time = Instant::now() - self.last_off_time;
146
147 // Or elsewhere?
148 self.integrator.set_limit(self.params.overshoot_delay);
149
150 // Safety to avoid bad things happening to the fridge motor (?)
151 // When it turns off don't start up again for at least FRIDGE_DELAY
152 if !self.on && off_time < self.config.FRIDGE_DELAY {
153 log!("fridge skipping, too early");
154 return;
155 }
156
157 if self.params.disabled {
158 if self.on {
159 log!("Disabled, turning fridge off");
160 self.turn_off();
161 }
162 return;
163 }
164
165 // handle broken wort sensor
166 if self.temp_wort.is_none() {
167 let invalid_time = Instant::now() - self.wort_valid_time;
168 warn!("Invalid wort sensor for {:?} secs", invalid_time);
169 if invalid_time < self.config.FRIDGE_WORT_INVALID_TIME {
170 warn!("Has only been invalid for {:?}, waiting", invalid_time);
171 return;
172 }
173 }
174
175 if self.temp_fridge.is_none() {
176 warn!("Invalid fridge sensor");
177 }
178
179 if self.on {
180 debug!("fridge is on");
181 let on_time = self.integrator.integrate().as_secs() as f32;
182 let on_ratio = on_time / params.overshoot_delay as f32;
183
184 overshoot = params.overshoot_factor as f32 * on_ratio;
185 debug!("on_time {}, overshoot {}", on_percent, overshoot);
186
187 let mut turn_off = false;
188 if let Some(t) = self.temp_wort && !params.nowort {
189 // use the wort temperature
190 if t - overshoot < params.fridge_setpoint {
191 log!("wort has cooled enough, {temp}º (overshoot {overshoot}º = {factor} × {percent}%)",
192 temp = t, overshoot = overshoot,
193 factor = params.overshoot_factor,
194 percent = on_ratio*100);
195 turn_off = true;
196 }
197 } else let if Some(t) = self.temp_fridge {
198 // use the fridge temperature
199 if t < fridge_min {
200 warn!("fridge off fallback, fridge {}, min {}", t, fridge_min);
201 if self.temp_wort.is_none() {
202 W("wort has been invalid for {:?}", Instant::now() - self.wort_valid_time);
203 }
204 turn_off = true;
205 }
206 }
207 if turn_off {
208 self.turn_off();
209 }
210 } else {
211 debug!("fridge is off. fridge {:?} max {:?}. wort {:?} max {:?}",
212 self.temp_fridge, fridge_max, self.temp_wort, wort_max);
213 let mut turn_on = false;
214 if let Some(t) = self.temp_wort && !params.nowort {
215 // use the wort temperature
216 if t >= wort_max {
217 log!("Wort is too hot {}°, max {}°", t, wort_max);
218 turn_on = true;
219 }
220 }
221
222 if let Some(t) = self.temp_fridge {
223 if t >= fridge_max {
224 warn!("fridge too hot fallback, fridge {}°, max {}°", t, fridge_max)
225 turn_on = true;
226 }
227 }
228
229 if turn_on {
230 self.turn_on()
231 }
232 }
233 }
234
102 /// Must be called after every state change. Turns the fridge on/off as required and 235 /// Must be called after every state change. Turns the fridge on/off as required and
103 /// schedules any future wakeups based on the present (new) state 236 /// schedules any future wakeups based on the present (new) state
104 fn tick(&mut self) { 237 fn tick(&mut self) {
105 debug!("tick"); 238 debug!("tick");
106 239
240 self.compare_temperatures()
107 self.send_next_timeout(); 241 self.send_next_timeout();
108 } 242 }
109 243
110 /// Sets the next self-wakeup timeout 244 /// Sets the next self-wakeup timeout
111 fn send_next_timeout(&mut self) { 245 fn send_next_timeout(&mut self) {
145 279
146 pub fn update_sensor(&mut self, wort: Option<f32>, fridge: Option<f32>) { 280 pub fn update_sensor(&mut self, wort: Option<f32>, fridge: Option<f32>) {
147 self.temp_wort = wort; 281 self.temp_wort = wort;
148 self.temp_fridge = fridge; 282 self.temp_fridge = fridge;
149 283
150 if self.temp_wort.is_some() { 284 if self.temp_wort.is_some() {
151 self.wort_valid_time = Instant::now(); 285 self.wort_valid_time = Instant::now();
152 } 286 }
153 287
154 self.tick(); 288 self.tick();
155 } 289 }
156 290
157 } 291 }