# HG changeset patch # User Matt Johnston # Date 1353943263 -28800 # Node ID 482d7852b511cd6c285d743212148f9a26eca5a3 # Parent 0895f5ad7731bbb64e3ba7763a21acaf3be2a25b a bit more, and some tests diff -r 0895f5ad7731 -r 482d7852b511 py/config.py --- a/py/config.py Tue Nov 20 22:03:10 2012 +0800 +++ b/py/config.py Mon Nov 26 23:21:03 2012 +0800 @@ -1,2 +1,6 @@ FRIDGE_GPIO = '/sys/devices/virtual/gpio/gpio5' FRIDGE_SLEEP = 60 + +SENSOR_SLEEP = 120 + +UPLOAD_SLEEP = 300 diff -r 0895f5ad7731 -r 482d7852b511 py/fridge.py --- a/py/fridge.py Tue Nov 20 22:03:10 2012 +0800 +++ b/py/fridge.py Mon Nov 26 23:21:03 2012 +0800 @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- -from utils import L,W,E +from utils import L,W,E,EX import config +import gevent -class Fridge(object): +class Fridge(gevent.Greenlet): OVERSHOOT_MAX_DIV = 1800.0 # 30 mins FRIDGE_AIR_MIN_RANGE = 4 # ÂșC FRIDGE_AIR_MAX_RANGE = 4 - def __init__(self): + def __init__(self, server): + gevent.Greenlet.__init__(self) + self.server = server self.setup_gpio() self.wort_valid_clock = 0 self.fridge_on_clock = 0 @@ -45,22 +48,22 @@ % (self.value_file.name, buf)) return True - def run(self, server): - + # greenlet subclassed + def _run(self): while True: - self.do(server) + self.do() gevent.sleep(config.FRIDGE_SLEEP) - def do(self, server): + def do(self) """ this is the main fridge control logic """ - wort, fridge, ambient = server.current_temps() + wort, fridge = self.server.current_temps() fridge_min = params.fridge_setpoint - self.FRIDGE_AIR_MIN_RANGE fridge_max = params.fridge_setpoint + self.FRIDGE_AIR_MAX_RANGE wort_max = params.fridge_setpoint + params.fridge_difference - off_time = server.now() - self.fridge_off_clock + off_time = self.server.now() - self.fridge_off_clock if off_time < config.FRIDGE_DELAY: L("fridge skipping, too early") @@ -68,10 +71,10 @@ # handle broken wort sensor if wort is not None: - self.wort_valid_clock = server.now() + self.wort_valid_clock = self.server.now() else: W("Invalid wort sensor") - invalid_time = server.now() - self.wort_valid_clock + invalid_time = self.server.now() - self.wort_valid_clock if invalid_time < config.FRIDGE_WORT_INVALID_TIME: W("Has only been invalid for %d, waiting" % invalid_time) return @@ -81,7 +84,7 @@ if self.is_on(): turn_off = False - on_time = server.now() - self.fridge_on_clock + on_time = self.server.now() - self.fridge_on_clock overshoot = 0 if on_time > params.overshoot_delay: @@ -96,14 +99,14 @@ turn_off = True else: # wort sensor is broken - if fridge is not None and last_fridge < fridge_min: + if fridge is not None and fridge < fridge_min: W("fridge off fallback") turn_off = True if turn_off: L("Turning fridge off") self.off() - self.fridge_off_clock = server.now() + self.fridge_off_clock = self.server.now() else: # fridge is off @@ -121,4 +124,4 @@ if turn_on: L("Turning fridge on") self.on() - fridge_on_clock = server.now() + fridge_on_clock = self.server.now() diff -r 0895f5ad7731 -r 482d7852b511 py/sensor_ds18b20.py --- a/py/sensor_ds18b20.py Tue Nov 20 22:03:10 2012 +0800 +++ b/py/sensor_ds18b20.py Mon Nov 26 23:21:03 2012 +0800 @@ -1,21 +1,59 @@ #!/usr/bin/env python2.7 -class DS18B20s(object): +import gevent +import config +import re +from utils import L,W,E,EX + +class DS18B20s(gevent.Greenlet): + + THERM_RE = re.compile('.* YES\n.*t=(.*)\n', re.MULTILINE) - def __init__(self): - # query the bus - pass + def __init__(self, server): + gevent.Greenlet.__init__(self) + self.server = server + # XXX set up paths + # XXX set up drain etc + + def _run(self): + while True: + self.do() + gevent.sleep(config.SENSOR_SLEEP) + + def sensor_path(self, s): + return os.path.join(self.master_dir, s) - def get_sensors(self): - """ Returns a sequence of sensorname """ - pass + def do_sensor_name(self, s, contents = None): + try: + if contents is None: + fn = os.path.join(self.sensor_path(s), 'w1_slave') + f = open(fn, 'r') + contents = f.read() + match = self.THERM_RE.match(contents) + if match is None: + return None + temp = int(match.groups(1)[0]) + return temp / 1000.0 + except Exception, e: + EX("Problem reading sensor '%s': %s" % (s, str(e))) + return None - def read(self): - """ Returns a map of sensorname->temperature """ - pass + def do(self): + vals = {} + for n in self.sensor_names(): + value = do_sensor(n) + if value is not None: + vals[n] = value + + self.server.add_reading(vals) + + def sensor_names(self): + """ Returns a sequence of sensorname """ + return [d for d in os.listdir(self.master_dir) if + os.stat(sensor_path(d)).st_mode & stat.S_ISDIR] def wort_name(self): - pass + return config.WORT_NAME def fridge_name(self): - pass + return config.FRIDGE_NAME diff -r 0895f5ad7731 -r 482d7852b511 py/tempserver.py --- a/py/tempserver.py Tue Nov 20 22:03:10 2012 +0800 +++ b/py/tempserver.py Mon Nov 26 23:21:03 2012 +0800 @@ -13,9 +13,18 @@ self.start_time = utils.monotonic_time() - self.fridge = fridge.Fridge() - self.fridge.run(self) + self.fridge = fridge.Fridge(self) + self.fridge.start() + + self.set_sensors(sensor_ds18b20.DS18B20s(self)) + 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 @@ -26,40 +35,14 @@ """ used if a caller of take_readings() fails """ self.readings = pushback + self.readings + # a reading is a map of {sensorname: value}. temperatures + # are float degrees def add_reading(self, reading): """ adds a reading at the current time """ self.readings.append( (reading, utils.monotonic_time())) + self.current = (reading.get(self.wort_name, None), + reading.get(self.fridge_name, None)) def current_temps(self): - """ returns (wort_temp, fridge_temp, ambient_temp) tuple """ + """ returns (wort_temp, fridge_temp) tuple """ return current - - def set_current(self, wort, fridge, ambient): - current = (wort, fridge, ambient) - - def now(self): - return utils.monotonic_time() - -def spawn_timer(seconds, fn, *args, **kwargs): - def loop(): - while True: - fn(*args, **kwargs) - gevent.sleep(seconds) - return gevent.spawn(loop) - -def setup(): - pass - -def main(): - setup() - - def p(x): - print "hello %s" % x - - spawn_timer(2, p, 'one') - - gevent.sleep(20) - - -if __name__ == '__main__': - main() diff -r 0895f5ad7731 -r 482d7852b511 py/test.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/test.py Mon Nov 26 23:21:03 2012 +0800 @@ -0,0 +1,27 @@ +import unittest +import sensor_ds18b20 + +class TestSensors(unittest.TestCase): + def setUp(self): + self.sensors = sensor_ds18b20.DS18B20s(None) + + def test_re(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 +""" + val = self.sensors.do_sensor_name('blank', f1) + self.assertEqual(val, 22.875) + + f2 = """6e 01 4b 46 7f ff 02 10 71 : crc=71 NO +6e 01 4b 46 7f ff 02 10 71 t=22875 +""" + val = self.sensors.do_sensor_name('blank', f2) + self.assertEqual(val, None) + + f3 = """6e 01 4b 46 7f ff 02 10 71 : crc=71 YES +6e 01 4b 46 7f ff 02 10 71 t=-1 +""" + val = self.sensors.do_sensor_name('blank', f3) + self.assertEqual(val, -0.001) + +unittest.main() diff -r 0895f5ad7731 -r 482d7852b511 py/utils.py --- a/py/utils.py Tue Nov 20 22:03:10 2012 +0800 +++ b/py/utils.py Mon Nov 26 23:21:03 2012 +0800 @@ -12,6 +12,10 @@ DEFAULT_TRIES = 3 READLINE_SELECT_TIMEOUT = 1 +def EX(msg, *args, **kwargs): + kwargs['exc_info'] = True + logging.error(msg, *args, **kwargs) + clock_gettime = None no_clock_gettime = True def monotonic_time():