changeset 145:6517ddee3187

few more bits
author Matt Johnston <matt@ucc.asn.au>
date Thu, 29 Nov 2012 23:50:40 +0800
parents 482d7852b511
children 3b4277aaed3c
files py/config.py py/fridge.py py/params.py py/tempserver.py py/test.py py/uploader.py py/utils.py
diffstat 7 files changed, 194 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- 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'
--- 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
 
--- /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()
--- 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()
--- 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()
--- /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)
--- 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