comparison rust/src/fridge.rs @ 632:bde302def78e rust

moving to riker, nowhere near yet
author Matt Johnston <matt@ucc.asn.au>
date Thu, 22 Aug 2019 23:59:50 +0800
parents c57821a60e51
children 490e9e15b98c
comparison
equal deleted inserted replaced
631:c57821a60e51 632:bde302def78e
1 #[cfg(target_os = "linux")]
2 extern crate sysfs_gpio;
3
4 use std; 1 use std;
5 use std::io; 2
6 use std::mem;
7 use std::error::Error;
8 use std::time::{Duration,Instant}; 3 use std::time::{Duration,Instant};
9 4 use riker::actors::*;
10 use futures::{Future,Stream};
11 use futures::sync::{mpsc};
12 5
13 #[cfg(target_os = "linux")] 6 #[cfg(target_os = "linux")]
14 use self::sysfs_gpio::{Direction, Pin}; 7 use self::sysfs_gpio::{Direction, Pin};
15 8
16 use config::Config; 9 use super::config::Config;
17 use params::Params; 10 use super::params::Params;
18 use types::*; 11 use super::types::*;
19 12
20 #[derive(Debug)] 13 #[derive(Debug)]
21 pub enum Message { 14 pub struct Reading {
22 Sensor {wort: Option<f32>, fridge: Option<f32>}, 15 wort: Option<f32>,
23 Params (Params), 16 fridge: Option<f32>,
24 Tick(u64), 17 }
25 } 18
26 19 #[derive(Debug)]
20 pub struct Tick;
21
22
23 #[actor(Params, Tick, Reading)]
27 pub struct Fridge { 24 pub struct Fridge {
28 params: Params, 25 params: Params,
29 config: Config, 26 config: Config,
30 27
31 on: bool, 28 on: bool,
33 temp_fridge: Option<f32>, 30 temp_fridge: Option<f32>,
34 last_off_time: Instant, 31 last_off_time: Instant,
35 wort_valid_time: Instant, 32 wort_valid_time: Instant,
36 integrator: StepIntegrator, 33 integrator: StepIntegrator,
37 control: FridgeControl, 34 control: FridgeControl,
38 35 }
39 // Timeouts to wake ourselves up again 36
40 handle: Handle, 37 impl Actor for Fridge {
41 timeout_s: mpsc::Sender<u64>, 38 type Msg = FridgeMsg;
42 timeout_r: Option<mpsc::Receiver<u64>>, 39 fn recv(&mut self,
43 ticker: u64, 40 ctx: &Context<Self::Msg>,
44 } 41 msg: Self::Msg,
45 42 sender: Sender) {
46 impl Sink for Fridge { 43 self.receive(ctx, msg, sender);
47 44 // TODO: should we do self.tick(ctx) here instead?
48 type SinkItem = Message; 45 }
49 type SinkError = TemplogError; 46
50 47 fn post_start(&mut self, ctx: &Context<Self::Msg>) {
51 fn start_send(&mut self, msg: Message) 48 self.tick(ctx);
52 -> Result<(), Self::SinkError> { 49 }
53 self.process_msg(msg); 50 }
54 Ok() 51
55 } 52 impl Receive<Reading> for Fridge {
56 53 type Msg = FridgeMsg;
57 fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> { 54 fn receive(&mut self,
58 Ok(futures::Async::Ready(())) 55 ctx: &Context<Self::Msg>,
56 r: Reading,
57 _sender: Sender) {
58 self.temp_wort = r.wort;
59 self.temp_fridge = r.fridge;
60
61 if self.temp_wort.is_some() {
62 self.wort_valid_time = Instant::now();
63 }
64
65 self.tick(ctx);
66 }
67 }
68
69 impl Receive<Params> for Fridge {
70 type Msg = FridgeMsg;
71 fn receive(&mut self,
72 ctx: &Context<Self::Msg>,
73 p: Params,
74 _sender: Sender) {
75 self.params = p;
76 println!("fridge set_params {:?}", self.params);
77
78 self.tick(ctx);
79 }
80 }
81
82 impl Receive<Tick> for Fridge {
83 type Msg = FridgeMsg;
84 fn receive(&mut self,
85 ctx: &Context<Self::Msg>,
86 _tick: Tick,
87 _sender: Sender) {
88 self.tick(ctx);
59 } 89 }
60 } 90 }
61 91
62 enum FridgeControl { 92 enum FridgeControl {
63 #[cfg(target_os = "linux")] 93 #[cfg(target_os = "linux")]
64 Gpio(Pin), 94 Gpio(Pin),
65 Fake, 95 Fake,
66 } 96 }
67
68 struct TestControl {}
69 97
70 impl Drop for Fridge { 98 impl Drop for Fridge {
71 fn drop(&mut self) { 99 fn drop(&mut self) {
72 // safety fridge off 100 // safety fridge off
73 self.turn(false); 101 self.turn(false);
74 } 102 }
75 } 103 }
76 104
77 impl Fridge { 105 impl Fridge {
78 pub fn new(config: &Config, nowait: bool, p: Params, handle: &Handle) -> Fridge { 106 pub fn new(config: &Config, nowait: bool, p: Params) -> Fridge {
79 let (s, r) = mpsc::channel(1);
80 let mut f = Fridge { 107 let mut f = Fridge {
81 config: config.clone(), 108 config: config.clone(),
82 params: p.clone(), 109 params: p.clone(),
83 on: false, 110 on: false,
84 temp_wort: None, 111 temp_wort: None,
85 temp_fridge: None, 112 temp_fridge: None,
86 last_off_time: Instant::now(), 113 last_off_time: Instant::now(),
87 wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100), 114 wort_valid_time: Instant::now() - Duration::new(config.FRIDGE_WORT_INVALID_TIME, 100),
88 integrator: StepIntegrator::new(Duration::new(p.overshoot_delay, 0)), 115 integrator: StepIntegrator::new(Duration::new(p.overshoot_delay, 0)),
89 control: Self::make_control(config), 116 control: Self::make_control(config),
90
91 handle: handle.clone(),
92 timeout_s: s,
93 timeout_r: Some(r),
94 ticker: 0,
95 }; 117 };
96 118
97 if nowait { 119 if nowait {
98 f.last_off_time -= Duration::new(config.FRIDGE_DELAY, 1); 120 f.last_off_time -= Duration::new(config.FRIDGE_DELAY, 1);
99 } 121 }
115 #[cfg(not(target_os = "linux"))] 137 #[cfg(not(target_os = "linux"))]
116 fn make_control(config: &Config) -> FridgeControl { 138 fn make_control(config: &Config) -> FridgeControl {
117 FridgeControl::Fake 139 FridgeControl::Fake
118 } 140 }
119 141
120
121
122 fn next_wakeup(&self) -> Duration { 142 fn next_wakeup(&self) -> Duration {
123 let millis = 400; 143 let millis = 400;
124 let dur = Duration::from_millis(millis); 144 let dur = Duration::from_millis(millis);
125 dur 145 dur
126 } 146 }
127 147
128 /// The fridge needs to periodically wake itself up, the returned
129 /// stream of Tick messages does so.
130 /// Examples of wakeups events are
131 ///
132 /// * overshoot calculation
133 /// * minimum fridge-off time
134 /// * invalid wort timeout
135 /// All specified in next_wakeup()
136 pub fn wakeups(&mut self)
137 -> Box<Stream<Item=Message, Error=TemplogError>> {
138 Box::new(mem::replace(&mut self.timeout_r, None)
139 .expect("Fridge::wakeups() can only be called once")
140 .map(|v| Message::Tick(v))
141 .map_err(|e| TemplogError::new("wakeups() receive failed")))
142 }
143 148
144 fn turn_off(&mut self) { 149 fn turn_off(&mut self) {
145 info!("Turning fridge off"); 150 info!("Turning fridge off");
146 self.turn(false); 151 self.turn(false);
147 } 152 }
158 FridgeControl::Fake => debug!("fridge turns {}", if on {"on"} else {"off"}), 163 FridgeControl::Fake => debug!("fridge turns {}", if on {"on"} else {"off"}),
159 } 164 }
160 self.on = on; 165 self.on = on;
161 } 166 }
162 167
163 /// Turns the fridge off and on 168 // Turns the fridge off and on
164 fn compare_temperatures(&mut self) { 169 fn compare_temperatures(&mut self) {
165 let fridge_min = self.params.fridge_setpoint - self.params.fridge_range_lower; 170 let fridge_min = self.params.fridge_setpoint - self.params.fridge_range_lower;
166 let fridge_max = self.params.fridge_setpoint - self.params.fridge_range_upper; 171 let fridge_max = self.params.fridge_setpoint - self.params.fridge_range_upper;
167 let wort_max = self.params.fridge_setpoint + self.params.fridge_difference; 172 let wort_max = self.params.fridge_setpoint + self.params.fridge_difference;
168 let off_time = Instant::now() - self.last_off_time; 173 let off_time = Instant::now() - self.last_off_time;
257 } 262 }
258 } 263 }
259 264
260 /// Must be called after every state change. Turns the fridge on/off as required and 265 /// Must be called after every state change. Turns the fridge on/off as required and
261 /// schedules any future wakeups based on the present (new) state 266 /// schedules any future wakeups based on the present (new) state
262 fn tick(&mut self) { 267 /// Examples of wakeups events are
268 ///
269 /// * overshoot calculation
270 /// * minimum fridge-off time
271 /// * invalid wort timeout
272 /// All specified in next_wakeup()
273 fn tick(&mut self,
274 ctx: &Context<Self::Msg>) {
263 debug!("tick"); 275 debug!("tick");
264 276
265 self.compare_temperatures(); 277 self.compare_temperatures();
266 self.send_next_timeout(); 278
267 } 279 // Sets the next self-wakeup timeout
268
269 /// Sets the next self-wakeup timeout
270 fn send_next_timeout(&mut self) {
271 let waker = self.timeout_s.clone();
272 let dur = self.next_wakeup(); 280 let dur = self.next_wakeup();
273 debug!("fridge next wakeup {:?}", dur); 281 ctx.schedule_once(dur, self, None, Tick);
274 self.ticker += 1; 282 }
275 let v = self.ticker; 283 }
276 let t = Timeout::new(dur, &self.handle).unwrap()
277 .map_err(|_| ())
278 .and_then(move |_| {
279 waker.send(v)
280 .map_err(|e| {
281 warn!("Send error in tick(): {}", e.to_string());
282 ()
283 })
284 })
285 .map(|_| ());
286 self.handle.spawn(t);
287 }
288
289 fn process_msg(&mut self, msg: Message) {
290 debug!("process_msg {:?}", msg);
291 match msg {
292 Message::Sensor{wort, fridge} => self.update_sensor(wort, fridge),
293 Message::Params(p) => self.update_params(p),
294 Message::Tick(v) => if v == self.ticker {self.tick()}, // schedule a timeout if there are none pending
295 };
296 }
297
298 pub fn update_params(&mut self, p: Params) {
299 self.params = p;
300 println!("fridge set_params {:?}", self.params);
301
302 self.tick();
303 }
304
305 pub fn update_sensor(&mut self, wort: Option<f32>, fridge: Option<f32>) {
306 self.temp_wort = wort;
307 self.temp_fridge = fridge;
308
309 if self.temp_wort.is_some() {
310 self.wort_valid_time = Instant::now();
311 }
312
313 self.tick();
314 }
315
316 }