# HG changeset patch # User Matt Johnston # Date 1447770414 -28800 # Node ID fd9c6517225eb8475984ad5b40ef40b125867d84 # Parent 0a1c02160e66eb8ec334b43df5d967e329343a56# Parent c0f903cf8268825fe196b6421f20649c52a65dd4 merge diff -r c0f903cf8268 -r fd9c6517225e py/fridge.py --- a/py/fridge.py Tue Nov 17 22:25:02 2015 +0800 +++ b/py/fridge.py Tue Nov 17 22:26:54 2015 +0800 @@ -5,6 +5,7 @@ import config import gpio +import utils class Fridge(object): @@ -13,6 +14,7 @@ def __init__(self, server, nowait = False): self.server = server self.gpio = gpio.Gpio(config.FRIDGE_GPIO_PIN, "fridge") + self.integrator = utils.StepIntegrator(self.server.now, self.server.params.overshoot_delay) self.wort_valid_clock = 0 self.fridge_on_clock = 0 self.off() @@ -21,10 +23,10 @@ def turn(self, value): self.gpio.turn(value) + self.integrator.turn(value) def on(self): self.turn(True) - pass def off(self): self.turn(False) @@ -61,6 +63,8 @@ if wort is not None: self.wort_valid_clock = self.server.now() + self.integrator.set_limit(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 not self.is_on() and off_time < config.FRIDGE_DELAY: @@ -88,14 +92,10 @@ if self.is_on(): turn_off = False - on_time = self.server.now() - self.fridge_on_clock + on_percent = self.integrator.integrate() / params.overshoot_delay - overshoot = 0 - if on_time > params.overshoot_delay: - overshoot = params.overshoot_factor \ - * min(self.OVERSHOOT_MAX_DIV, on_time) \ - / self.OVERSHOOT_MAX_DIV - D("on_time %(on_time)f, overshoot %(overshoot)f" % locals()) + overshoot = params.overshoot_factor * on_percent + D("on_time %(on_percent)f, overshoot %(overshoot)f" % locals()) if not params.nowort and wort is not None: if wort - overshoot < params.fridge_setpoint: diff -r c0f903cf8268 -r fd9c6517225e py/test.py --- a/py/test.py Tue Nov 17 22:25:02 2015 +0800 +++ b/py/test.py Tue Nov 17 22:26:54 2015 +0800 @@ -1,4 +1,3 @@ -#!/usr/bin/env python2.7 import io import unittest @@ -7,7 +6,7 @@ class TestSensors(unittest.TestCase): def setUp(self): - self.sensors = sensor_ds18b20.DS18B20s(None) + self.sensors = sensor_ds18b20.SensorDS18B20(None) def test_sensors_regex(self): f1 = """6e 01 4b 46 7f ff 02 10 71 : crc=71 YES @@ -66,9 +65,7 @@ jsbuf = io.StringIO() self.params.overshoot_delay = 123 - self.params.save(f=jsbuf) - - s = jsbuf.getvalue() + s = self.params.save_string() self.assertTrue('"overshoot_delay": 123' in s, msg=s) unittest.main() diff -r c0f903cf8268 -r fd9c6517225e py/utils.py --- a/py/utils.py Tue Nov 17 22:25:02 2015 +0800 +++ b/py/utils.py Tue Nov 17 22:26:54 2015 +0800 @@ -7,6 +7,7 @@ import binascii import json import datetime +import collections D = logging.debug L = logging.info @@ -171,3 +172,103 @@ L(msg + " (log interval %s)" % str(self.limit)) else: D(msg) + +Period = collections.namedtuple('Period', 'start end') +class StepIntegrator(object): + """ + Takes on/off events and a monotonically increasing timefn. Returns the integral + of (now-limittime, now) over those events. + + >>> s = StepIntegrator(lambda: t, 40) + >>> t = 1 + >>> s.turn(1) + >>> t = 10 + >>> s.turn(0) + >>> t = 20 + >>> s.turn(1) + >>> t = 30 + >>> print(s.integrate()) + 19 + >>> s.turn(0) + >>> print(s.integrate()) + 19 + >>> t = 35 + >>> print(s.integrate()) + 19 + >>> t = 42 + >>> print(s.integrate()) + 18 + >>> t = 52 + >>> print(s.integrate()) + 10 + >>> t = 69 + >>> print(s.integrate()) + 1 + >>> t = 70 + >>> print(s.integrate()) + 0 + >>> t = 170 + >>> print(s.integrate()) + 0 + """ + def __init__(self, timefn, limittime): + # _on_periods is a list of [period]. End is None if still on + self._on_periods = [] + self._timefn = timefn + self._limittime = limittime + + def set_limit(self, limittime): + if self._limittime == limittime: + return + self._limittime = limittime + self._trim() + + def turn(self, value): + if not self._on_periods: + if value: + self._on_periods.append(Period(self._timefn(), None)) + return + + # state hasn't changed + on_now = (self._on_periods[-1].end is None) + if value == on_now: + return + + if value: + self._on_periods.append(Period(self._timefn(), None)) + else: + self._on_periods[-1] = self._on_periods[-1]._replace(end = self._timefn()) + + def _trim(self): + begin = self._timefn() - self._limittime + # shortcut, first start is after begin + if not self._on_periods or self._on_periods[0].start >= begin: + return + + new_periods = [] + for s, e in self._on_periods: + if s == e: + continue + elif s >= begin: + new_periods.append(Period(s,e)) + elif e is not None and e < begin: + continue + else: + new_periods.append(Period(begin, e)) + self._on_periods = new_periods + + def integrate(self): + self._trim() + tot = 0 + for s, e in self._on_periods: + if e is None: + e = self._timefn() + tot += (e-s) + return tot + + + + + + + diff -r c0f903cf8268 -r fd9c6517225e web/config.py --- a/web/config.py Tue Nov 17 22:25:02 2015 +0800 +++ b/web/config.py Tue Nov 17 22:26:54 2015 +0800 @@ -31,8 +31,8 @@ 'sensor_fridge_setpoint': 'Setpoint', 'sensor_fridge_on': 'Cool', 'sensor_28-0000042cf4dd': "Wort", - 'sensor_28-0000042cccc4': "Fridge", - 'sensor_28-0000042c6dbb': "Ambient", + 'sensor_28-0000042cccc4': "OldFridge", + 'sensor_28-0000042c6dbb': "Fridge", 'sensor_internal': "Processor", } diff -r c0f903cf8268 -r fd9c6517225e web/templog.py --- a/web/templog.py Tue Nov 17 22:25:02 2015 +0800 +++ b/web/templog.py Tue Nov 17 22:26:54 2015 +0800 @@ -69,6 +69,12 @@ @route('/set/update', method='post') def set_update(): + if not secure.check_user_hash(config.ALLOWED_USERS): + # the "Save" button should be disabled if the cert wasn't + # good + response.status = 403 + return "No cert, dodginess" + post_json = json.loads(request.forms.data) csrf_blob = post_json['csrf_blob'] @@ -79,7 +85,7 @@ ret = log.update_params(post_json['params']) if not ret is True: - response.status = 403 + response.status = 409 # Conflict return ret return "Good" @@ -157,6 +163,11 @@ #var_lookup = environ['mod_ssl.var_lookup'] #return var_lookup("SSL_SERVER_I_DN_O") +@route('/h') +def headers(): + response.set_header('Content-Type', 'text/plain') + return '\n'.join("%s: %s" % x for x in request.headers.items()) + @route('/get_settings') def get_settings(): response.set_header('Cache-Control', 'no-cache')