Mercurial > templog
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 } |