Mercurial > ~darius > hgwebdir.cgi > beermon
view MonitorDev.py @ 20:4792fbc1e255 default tip
Random ignore crap.
author | darius |
---|---|
date | Tue, 29 Jan 2008 12:36:00 +0000 |
parents | 67a4dc218bbf |
children |
line wrap: on
line source
#!/usr/bin/env python ############################################################################ # Monitoring/control interface to hardware for beermon # # $Id: MonitorDev.py,v 1.4 2008/01/29 11:39:22 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 OWReadError(Exception): """Raised when we failed to read from a 1-wire device, could be a timeout or the device is non-existent, etc""" pass 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): """_log is a logging object, conf is a ConfigParser object""" 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/cu', ['-l', '/dev/cuaU1', '-s', '38400']) 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)']) self.p.timeout = 30 #assert(self.p.expect('Connected') == 0) 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): """Set the speed microcom talks to the serial port to 38400""" 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): """Scan the bus for 1-wire devices""" 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 #log.debug("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): """Returns true if the 1-wire device is a temperature sensor""" [family, a, b, c, d, e, f, g] = id.split(':') if (family == '10'): return True else: return False def updateTemps(self): """Update our cached copy of temperatures""" 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): """Read the temperature of a sensor""" 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): """Set the heat/cool state, track the on/off time""" 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 self.p.sendline('') assert(self.p.expect('> ') == 0) cmd = 'out c %02x' % relay self.p.sendline(cmd) # Echo assert(self.p.expect(cmd) == 0) self.commsLock.release() def run(self): """Sit in a loop polling temperatures""" while True: self.updateTemps()