Mercurial > ~darius > hgwebdir.cgi > beermon
diff MonitorDev.py @ 6:45d9895a5020
Split into seperate files.
author | darius |
---|---|
date | Sat, 29 Sep 2007 14:39:59 +0000 |
parents | |
children | 24c7e85c3efd |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonitorDev.py Sat Sep 29 14:39:59 2007 +0000 @@ -0,0 +1,238 @@ +#!/usr/bin/env python + +############################################################################ +# Monitoring/control interface to hardware for beermon +# +# $Id: MonitorDev.py,v 1.1 2007/09/29 14:39:59 darius Exp $ +# +# Depends on: Python 2.3 (I think) +# +############################################################################ +# +# Copyright (C) 2007 Daniel O'Connor. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +############################################################################ + +import threading, re, time, pexpect + +class MonitorDev(threading.Thread): + """This class continually polls the hardware for temperature +readings and accepts state changes to heat & cool""" + + # 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('> ') + + # 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, _log, conf): + global log + log = _log + threading.Thread.__init__(self) + + # Collect parameters + self.coolRelay = conf.getint('hardware', 'coolRelay') + self.heatRelay = conf.getint('hardware', 'heatRelay') + self.fermenterId = conf.get('hardware', 'fermenterId') + self.fridgeId = conf.get('hardware', 'fridgeId') + self.ambientId = conf.get('hardware', 'ambientId') + self.minCoolOnTime = conf.getfloat('hardware', 'minCoolOnTime') + self.minCoolOffTime = conf.getfloat('hardware', 'minCoolOffTime') + self.minHeatOnTime = conf.getfloat('hardware', 'minHeatOnTime') + self.minHeatOffTime = conf.getfloat('hardware', 'minHeatOffTime') + self.minHeatOvershoot = conf.getfloat('hardware', 'minHeatOvershoot') + self.minCoolOvershoot = conf.getfloat('hardware', 'minCoolOvershoot') + + # Setup locking & spawn SSH + 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() + + # Search for 1-wire modules + self.devs = self.find1wire() + self.tempdevs = filter(self.istemp, self.devs) + + log.debug("fermenterId - %s" % (self.fermenterId)) + log.debug("fridgeId - %s" % (self.fridgeId)) + log.debug("ambientId - %s" % (self.ambientId)) + log.debug("minCoolOnTime - %d, minCoolOffTime - %d" % + (self.minCoolOnTime, self.minCoolOffTime)) + log.debug("minHeatOnTime - %d, minHeatOffTime - %d" % + (self.minHeatOnTime, self.minHeatOffTime)) + log.debug("minHeatOvershoot - %3.2f, minCoolOvershoot - %3.2f" % + (self.minHeatOvershoot, self.minCoolOvershoot)) + 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 OWReadError: + # 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 OWReadError + + 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() +