Mercurial > ~darius > hgwebdir.cgi > beermon
diff beermon.py @ 6:45d9895a5020
Split into seperate files.
author | darius |
---|---|
date | Sat, 29 Sep 2007 14:39:59 +0000 |
parents | 8d471840b153 |
children | 17449d52d5e5 |
line wrap: on
line diff
--- a/beermon.py Sat Sep 29 02:23:24 2007 +0000 +++ b/beermon.py Sat Sep 29 14:39:59 2007 +0000 @@ -4,7 +4,7 @@ # Monitor & control fermenter temperature # v1.0 # -# $Id: beermon.py,v 1.6 2007/09/29 02:23:24 darius Exp $ +# $Id: beermon.py,v 1.7 2007/09/29 14:39:59 darius Exp $ # # Depends on: Python 2.3 (I think) # @@ -36,274 +36,9 @@ ############################################################################ -import pexpect, re, threading, time, logging, sys, traceback +import time, logging, sys, traceback, ConfigParser, MonitorDev, Control from logging.handlers import RotatingFileHandler -class ROMReadError(Exception): - pass - -class ThreadDied(Exception): - pass - -class Control(): - targetTemp = 18 - hysteresis = 0.5 - pollInterval = 30 - staleDataTime = 30 - - def __init__(self, m, _log): - self.m = m - global log - log = _log - self.cv = threading.Condition() - self.cv.acquire() - - def doit(self): - log.debug("target temperature - %3.2f" % (self.targetTemp)) - log.debug("fermenterId - %s" % (self.m.fermenterId)) - log.debug("fridgeId - %s" % (self.m.fridgeId)) - log.debug("ambientId - %s" % (self.m.ambientId)) - log.debug("minCoolOnTime - %d, minCoolOffTime - %d" % (self.m.minCoolOnTime, self.m.minCoolOffTime)) - log.debug("minHeatOnTime - %d, minHeatOffTime - %d" % (self.m.minHeatOnTime, self.m.minHeatOffTime)) - log.debug("pollInterval - %d" % (self.pollInterval)) - - log.debug("=== Starting ===") - log.debug("Fermenter Fridge Ambient State New State") - while True: - # Check if our monitor thread has died - if (not self.m.isAlive()): - raise ThreadDied, "Monitor thread has died" - - # Check for stale data - if (self.m.lastUpdate[self.m.fermenterId] + self.staleDataTime < time.time()): - log.debug("Stale data") - self.cv.wait(self.pollInterval) - self.m.setState('idle') - continue - - # Work out what state we should go into - nextState = "-" - # Temperature diff, -ve => too cold, +ve => too warm - diff = self.m.temps[self.m.fermenterId] - self.targetTemp - if (self.m.currState == 'idle'): - # If we're idle then only heat or cool if the temperate difference is out of the - # hysteresis band - if (abs(diff) > self.hysteresis): - if (diff < 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()): - nextState = 'heat' - elif (diff > 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()): - nextState = 'cool' - elif (self.m.currState == 'cool'): - # Work out if we should go idle (based on min on time & overshoot) - if (diff + self.m.minCoolOvershoot < 0 and self.m.minCoolOnTime + self.m.lastCoolOn < time.time()): - nextState = 'idle' - elif (self.m.currState == 'heat'): - # Ditto - if (diff - self.m.minHeatOvershoot > 0 and self.m.minHeatOnTime + self.m.lastHeatOn < time.time()): - nextState = 'idle' - else: - # Not possible.. - raise KeyError - - log.debug("%3.2f %3.2f %3.2f %s %s" % - (self.m.temps[self.m.fermenterId], - self.m.temps[self.m.fridgeId], self.m.temps[self.m.ambientId], - self.m.currState, nextState)) - - if (nextState != "-"): - self.m.setState(nextState) - - self.cv.wait(self.pollInterval) - - -class MonitorDev(threading.Thread): - # Match a ROM ID (eg 00:11:22:33:44:55:66:77) - romre = re.compile('([0-9a-f]{2}:){7}[0-9a-f]{2}') - # Match the prompt - promptre = re.compile('> ') - - coolRelay = 7 - heatRelay = 6 - - fermenterId = '10:eb:48:21:01:08:00:df' - fridgeId = '10:a6:2a:c4:00:08:00:11' - ambientId = '10:97:1b:fe:00:08:00:d1' - - # minimum time the cooler must spend on/off - minCoolOnTime = 10 * 60 - minCoolOffTime = 10 * 60 - - # minimum time the heater must spend on/off - minHeatOnTime = 60 - minHeatOffTime = 60 - - # minimum to overshoot on heating/cooling - minHeatOvershoot = 1 - minCoolOvershoot = 0 - - # Dictionary of sensor IDs & temperatures - temps = {} - # Dictionary of sensor IDs & epoch times - lastUpdate = {} - - # List of all device IDs - devs = [] - # List of temperature sensor IDs - tempdevs = [] - - # Lock to gate access to the comms - commsLock = None - - currState = 'idle' - - lastHeatOn = 0 - lastHeatOff = 0 - lastCoolOn = 0 - lastCoolOff = 0 - - def __init__(self): - threading.Thread.__init__(self) - self.commsLock = threading.Lock() - self.p = pexpect.spawn('/usr/bin/ssh', ['-xt', '-enone', '-i', '/home/darius/.ssh/id_wrt', 'root@wrt', '(echo logged in; microcom -D/dev/cua/1)']) - assert(self.p.expect('logged in') == 0) - self.p.timeout = 3 - self.setspeed() - self.devs = self.find1wire() - self.tempdevs = filter(self.istemp, self.devs) - - self.start() - - def setspeed(self): - self.commsLock.acquire() - self.p.send('~') - assert(self.p.expect('t - set terminal') == 0) - self.p.send('t') - assert(self.p.expect('p - set speed') == 0) - self.p.send('p') - assert(self.p.expect('f - 38400') == 0) - self.p.send('f') - assert(self.p.expect('done!') == 0) - self.commsLock.release() - - def find1wire(self): - self.commsLock.acquire() - self.p.sendline('') - assert(self.p.expect('> ') == 0) - self.p.sendline('sr') - # Echo - assert(self.p.expect('sr') == 0) - - # Send a new line which will give us a command prompt to stop on - # later. We could use read() but that would make the code a lot - # uglier - self.p.sendline('') - - devlist = [] - - # Loop until we get the command prompt (> ) collecting ROM IDs - while True: - idx = self.p.expect([self.romre, self.promptre]) - if (idx == 0): - # Matched a ROM - #print "Found ROM " + self.p.match.group() - devlist.append(self.p.match.group(0)) - elif (idx == 1): - # Matched prompt, exit - break - else: - # Unpossible! - self.commsLock.release() - raise SystemError() - - self.commsLock.release() - - return(devlist) - - def istemp(self, id): - [family, a, b, c, d, e, f, g] = id.split(':') - if (family == '10'): - return True - else: - return False - - def updateTemps(self): - for i in self.tempdevs: - try: - self.temps[i] = float(self.readTemp(i)) - self.lastUpdate[i] = time.time() - except ROMReadError: - # Ignore this - just results in no update reflected by lastUpdate - pass - - return(self.temps) - - def readTemp(self, id): - self.commsLock.acquire() - cmd = 'te ' + id - self.p.sendline(cmd) - # Echo - assert(self.p.expect(cmd) == 0) - # Eat EOL left from expect - self.p.readline() - - line = self.p.readline().strip() - self.commsLock.release() - # 'CRC mismatch' can mean that we picked the wrong ROM.. - if (re.match('CRC mismatch', line) != None): - raise ROMReadError - - return(line) - - def setState(self, state): - if (state == 'cool'): - relay = 1 << self.coolRelay - elif (state == 'heat'): - relay = 1 << self.heatRelay - elif (state == 'idle'): - relay = 0 - else: - raise(ValueError) - - if (state == self.currState): - return - - # Keep track of when we last turned off or on - if (state == 'cool'): - if (self.currState == 'heat'): - self.lastHeatOff = time.time() - self.lastCoolOn = time.time() - elif (state == 'heat'): - if (self.currState == 'cool'): - self.lastCoolOff = time.time() - self.lastHeatOn = time.time() - else: - if (self.currState == 'cool'): - self.lastCoolOff = time.time() - if (self.currState == 'heat'): - self.lastHeatOff = time.time() - - self.currState = state - - self.commsLock.acquire() - # Need the extra spaces cause the parser in the micro is busted - cmd = 'out c %02x' % relay - self.p.sendline(cmd) - # Echo - assert(self.p.expect(cmd) == 0) - self.commsLock.release() - - def polltemps(self, temps): - while True: - for d in temps: - #print d - t = gettemp(p, d) - print "%s -> %s" % (d, t) - print - - def run(self): - while True: - self.updateTemps() - def initLog(): # Init our logging log = logging.getLogger("monitor") @@ -329,22 +64,34 @@ return(log) def main(): - import beermon - global log log = initLog() + conf = ConfigParser.ConfigParser() + conf.read('beermon.ini') + + for s in ['control', 'hardware']: + if (not conf.has_section(s)): + log.debug("Mandatory '%s' section missing from config file, exiting" % (s)) + sys.exit(1) + log.debug("=== Initing ===") - log.debug("$Id: beermon.py,v 1.6 2007/09/29 02:23:24 darius Exp $") + log.debug("$Id: beermon.py,v 1.7 2007/09/29 14:39:59 darius Exp $") - m = None + try: + m = MonitorDev.MonitorDev(log, conf) + c = Control.Control(log, m, conf) + except ConfigParser.NoOptionError, e: + log.debug("Mandatory option '%s' missing from section '%s'" % (e.option, e.section)) + sys.exit(1) + except ValueError, e: + log.debug("Unable to parse option - " + str(e)) + exitCode = 0 try: - m = beermon.MonitorDev() - - c = beermon.Control(m, log) # Wait for the first temperature readings to come through, saves # getting an 'invalid data' message + # XXX: sleep on condvar holding data? time.sleep(3) c.doit() log.debug("doit exited") @@ -358,7 +105,7 @@ log.debug(reduce(lambda x, y: x + y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))) exitCode = 1 - + finally: # Make sure we try and turn it off if something goes wrong if (m != None):