# HG changeset patch # User Matt Johnston # Date 1354204240 -28800 # Node ID 6517ddee31874838d4e46fa56f043c3a4ad258f3 # Parent 482d7852b511cd6c285d743212148f9a26eca5a3 few more bits diff -r 482d7852b511 -r 6517ddee3187 py/config.py --- a/py/config.py Mon Nov 26 23:21:03 2012 +0800 +++ b/py/config.py Thu Nov 29 23:50:40 2012 +0800 @@ -4,3 +4,5 @@ SENSOR_SLEEP = 120 UPLOAD_SLEEP = 300 + +PARAMS_FILE='./tempserver.conf' diff -r 482d7852b511 -r 6517ddee3187 py/fridge.py --- a/py/fridge.py Mon Nov 26 23:21:03 2012 +0800 +++ b/py/fridge.py Thu Nov 29 23:50:40 2012 +0800 @@ -19,7 +19,7 @@ def setup_gpio(self): dir_fn = '%s/direction' % config.FRIDGE_GPIO - with f = open(dir_fn, 'w'): + with open(dir_fn, 'w') as f: f.write('low') val_fn = '%s/value' % config.FRIDGE_GPIO self.value_file = f.open(val_fn, 'r+') @@ -54,10 +54,12 @@ self.do() gevent.sleep(config.FRIDGE_SLEEP) - def do(self) + def do(self): """ this is the main fridge control logic """ wort, fridge = self.server.current_temps() + params = self.server.params + fridge_min = params.fridge_setpoint - self.FRIDGE_AIR_MIN_RANGE fridge_max = params.fridge_setpoint + self.FRIDGE_AIR_MAX_RANGE diff -r 482d7852b511 -r 6517ddee3187 py/params.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/params.py Thu Nov 29 23:50:40 2012 +0800 @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +import collections +import json +import config + +_FIELD_DEFAULTS = { + 'fridge_setpoint': 16, + 'fridge_difference': 0.2, + 'overshoot_delay': 720, # 12 minutes + 'overshoot_factor': 1, # ÂșC + } + +class Params(dict): + class Error(Exception): + pass + + def __init__(self): + self.update(_FIELD_DEFAULTS) + + def __getattr__(self, k): + return self[k] + + def __setattr__(self, k, v): + # fail if we set a bad value + self[k] + self[k] = v + + def load(self, f = None): + if not f: + f = file(config.PARAMS_FILE, 'r') + u = json.load(f) + for k in u: + if k not in self: + raise self.Error("Unknown parameter %s=%s in file '%s'" % (str(k), str(u[k]), getattr(f, 'name', '???'))) + self.update(u) + + def save(self, f = None): + if not f: + f = file(config.PARAMS_FILE, 'w') + json.dump(self, f, sort_keys=True, indent=4) + f.flush() diff -r 482d7852b511 -r 6517ddee3187 py/tempserver.py --- a/py/tempserver.py Mon Nov 26 23:21:03 2012 +0800 +++ b/py/tempserver.py Thu Nov 29 23:50:40 2012 +0800 @@ -3,28 +3,47 @@ import sys import os import gevent +import gevent.monkey import utils +import fridge +import config +import sensor_ds18b20 +import params + class Tempserver(object): def __init__(self): self.readings = [] - self.current = (None, None, None) + self.current = (None, None) + + # don't patch os, fork() is used by daemonize + gevent.monkey.patch_all(os=False, thread=False) - self.start_time = utils.monotonic_time() + self.start_time = self.now() + + self.params = params.Params() + self.params.load() self.fridge = fridge.Fridge(self) self.fridge.start() self.set_sensors(sensor_ds18b20.DS18B20s(self)) + def run(self): + # won't return. + while True: + gevent.sleep(60) + + def now(self): + return utils.monotonic_time() + def set_sensors(self, sensors): if self.hasattr(self, 'sensors'): self.sensors.kill() self.sensors = sensors self.wort_name = sensors.wort_name() self.fridge_name = sensors.fridge_name() - self.sensor_names = sensors.sensor_names() def take_readings(self): ret = self.readings @@ -39,10 +58,26 @@ # are float degrees def add_reading(self, reading): """ adds a reading at the current time """ - self.readings.append( (reading, utils.monotonic_time())) + self.readings.append( (reading, self.now())) self.current = (reading.get(self.wort_name, None), reading.get(self.fridge_name, None)) def current_temps(self): """ returns (wort_temp, fridge_temp) tuple """ return current + +def setup_logging(): + logging.basicConfig(format='%(asctime)s %(message)s', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO) + +def main(): + server = Tempserver() + + if '--daemon' in sys.argv: + utils.cheap_daemon() + + server.run() + +if __name__ == '__main__': + main() diff -r 482d7852b511 -r 6517ddee3187 py/test.py --- a/py/test.py Mon Nov 26 23:21:03 2012 +0800 +++ b/py/test.py Thu Nov 29 23:50:40 2012 +0800 @@ -1,11 +1,15 @@ +#!/usr/bin/env python2.7 +import StringIO + import unittest import sensor_ds18b20 +import params class TestSensors(unittest.TestCase): def setUp(self): self.sensors = sensor_ds18b20.DS18B20s(None) - def test_re(self): + def test_sensors_regex(self): f1 = """6e 01 4b 46 7f ff 02 10 71 : crc=71 YES 6e 01 4b 46 7f ff 02 10 71 t=22875 """ @@ -24,4 +28,47 @@ val = self.sensors.do_sensor_name('blank', f3) self.assertEqual(val, -0.001) +class TestParams(unittest.TestCase): + def setUp(self): + self.params = params.Params() + + def test_params_basic(self): + defparams = params.Params() + self.assertEqual(defparams.overshoot_factor, + params._FIELD_DEFAULTS['overshoot_factor']) + + # fetching a bad parameter fails + with self.assertRaises(KeyError): + x = self.params.param_that_doesnt_exist + + # setting a parameter + defparams.overshoot_factor = 8877 + self.assertEqual(defparams.overshoot_factor, 8877) + + # setting a bad parameter fails + with self.assertRaises(KeyError): + self.params.somewrongthing = 5 + + def test_params_load(self): + jsbuf = StringIO.StringIO('{"fridge_setpoint": 999}') + + self.params.load(f=jsbuf) + self.assertEqual(self.params.fridge_setpoint, 999) + + with self.assertRaises(params.Params.Error): + jsbuf = StringIO.StringIO('{"something_else": 999}') + self.params.load(f=jsbuf) + + with self.assertRaises(KeyError): + x = self.params.something_else + + def test_params_save(self): + jsbuf = StringIO.StringIO() + + self.params.overshoot_delay = 123 + self.params.save(f=jsbuf) + + s = jsbuf.getvalue() + self.assertTrue('"overshoot_delay": 123' in s, msg=s) + unittest.main() diff -r 482d7852b511 -r 6517ddee3187 py/uploader.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/uploader.py Thu Nov 29 23:50:40 2012 +0800 @@ -0,0 +1,55 @@ +import config + +import json +import hmac +import zlib + +class Uploader(gevent.Greenlet): + def __init__(self, server): + gevent.Greenlet.__init__(self) + self.server = server + + def _run(self): + while True: + self.do() + gevent.sleep(config.UPLOAD_SLEEP) + + def get_tosend(self, readings): + tosend = {} + + tosend['fridge_on'] = self.server.fridge.is_on() + + tosend['now'] = self.server.now() + tosend['readings'] = readings + + tosend['wort_name'] = self.server.wort_name + tosend['fridge_name'] = self.server.wort_fridge_name + + tosend.update(dict(self.server.params)) + + tosend['start_time'] = self.server.start_time + tosend['uptime'] = utils.uptime() + + return tosend + + def send(self, tosend): + js = json.dumps(tosend) + js_enc = binascii.b2a_base64(zlib.compress(js)) + mac = hmac.new(config.HMAC_KEY, js_enc).hexdigest() + url_data = urllib.urlencode( {'data': js_enc, 'hmac': mac} ) + con = urllib2.urlopen(config.UPDATE_URL, url_data) + result = con.read(100) + if result != 'OK': + raise Exception("Server returned %s" % result) + + def do(): + readings = self.server.take_readings() + try: + tosend = self.get_to_send(readings) + readings = None + self.send(tosend) + except Exception, e: + EX"Error in uploader: %s" % str(e)) + finally: + if readings is not None: + self.server.pushfront(readings) diff -r 482d7852b511 -r 6517ddee3187 py/utils.py --- a/py/utils.py Mon Nov 26 23:21:03 2012 +0800 +++ b/py/utils.py Thu Nov 29 23:50:40 2012 +0800 @@ -126,4 +126,9 @@ E("Bad fork()") sys.exit(1) +def uptime(): + try: + return float(open('/proc/uptime', 'r').read().split(' ', 1)[0]) + except Exception, e: + return -1