comparison beermon.py @ 4:32a56dd33e42

- Reduce hystersis down to 0.5C - Properly track stale data (hopefully anyway :) - Check if the monitor thread has died (ie unexpected exception) - Re-work some variable names to be clearer (& add comments)
author darius
date Fri, 28 Sep 2007 13:05:11 +0000
parents 1af7c85d5a0e
children 8d471840b153
comparison
equal deleted inserted replaced
3:1af7c85d5a0e 4:32a56dd33e42
2 2
3 ############################################################################ 3 ############################################################################
4 # Monitor & control fermenter temperature 4 # Monitor & control fermenter temperature
5 # v1.0 5 # v1.0
6 # 6 #
7 # $Id: beermon.py,v 1.4 2007/09/24 13:34:47 darius Exp $ 7 # $Id: beermon.py,v 1.5 2007/09/28 13:05:11 darius Exp $
8 # 8 #
9 # Depends on: Python 2.3 (I think) 9 # Depends on: Python 2.3 (I think)
10 # 10 #
11 ############################################################################ 11 ############################################################################
12 # 12 #
40 from logging.handlers import RotatingFileHandler 40 from logging.handlers import RotatingFileHandler
41 41
42 class ROMReadError(Exception): 42 class ROMReadError(Exception):
43 pass 43 pass
44 44
45 class ThreadDied(Exception):
46 pass
47
45 class Control(): 48 class Control():
46 targetTemp = 18 49 targetTemp = 18
47 hysteresis = 1 50 hysteresis = 0.5
48 pollInterval = 30 51 pollInterval = 30
52 staleDataTime = 30
49 53
50 def __init__(self, m, _log): 54 def __init__(self, m, _log):
51 self.m = m 55 self.m = m
52 global log 56 global log
53 log = _log 57 log = _log
64 log.debug("pollInterval - %d" % (self.pollInterval)) 68 log.debug("pollInterval - %d" % (self.pollInterval))
65 69
66 log.debug("=== Starting ===") 70 log.debug("=== Starting ===")
67 log.debug("Fermenter Fridge Ambient State New State") 71 log.debug("Fermenter Fridge Ambient State New State")
68 while True: 72 while True:
69 if (self.m.lastUpdate == 0): 73 # Check if our monitor thread has died
70 log.debug("Invalid data") 74 if (not self.m.isAlive()):
75 raise ThreadDied, "Monitor thread has died"
76
77 # Check for stale data
78 if (self.m.lastUpdate[self.m.fermenterId] + self.staleDataTime < time.time()):
79 log.debug("Stale data")
71 self.cv.wait(self.pollInterval) 80 self.cv.wait(self.pollInterval)
72 self.m.setState('idle') 81 self.m.setState('idle')
73 continue 82 continue
74 83
84 # Work out what state we should go into
75 nextState = "-" 85 nextState = "-"
76
77 diff = self.m.temps[self.m.fermenterId] - self.targetTemp 86 diff = self.m.temps[self.m.fermenterId] - self.targetTemp
78 if (self.m.currState == 'idle'): 87 if (self.m.currState == 'idle'):
79 # If we're idle then only heat or cool if the temperate difference is out of the 88 # If we're idle then only heat or cool if the temperate difference is out of the
80 # hysteresis range 89 # hysteresis band
81 if (abs(diff) > self.hysteresis): 90 if (abs(diff) > self.hysteresis):
82 if (diff < 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()): 91 if (diff < 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()):
83 nextState = 'heat' 92 nextState = 'heat'
84 elif (diff > 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()): 93 elif (diff > 0 and self.m.minHeatOffTime + self.m.lastHeatOff < time.time()):
85 nextState = 'cool' 94 nextState = 'cool'
86 elif (self.m.currState == 'cool'): 95 elif (self.m.currState == 'cool'):
87 # Go idle as soon as we can, there will be overshoot anyway 96 # Go idle as soon as we can, there will be overshoot anyway
88 if (diff < 0 and self.m.minCoolOnTime + self.m.lastCoolOn < time.time()): 97 if (diff < 0 and self.m.minCoolOnTime + self.m.lastCoolOn < time.time()):
89 nextState = 'idle' 98 nextState = 'idle'
90 elif (self.m.currState == 'heat'): 99 elif (self.m.currState == 'heat'):
100 # Ditto
91 if (diff > 0 and self.m.minHeatOnTime + self.m.lastHeatOn < time.time()): 101 if (diff > 0 and self.m.minHeatOnTime + self.m.lastHeatOn < time.time()):
92 nextState = 'idle' 102 nextState = 'idle'
93 else: 103 else:
104 # Not possible..
94 raise KeyError 105 raise KeyError
95 106
96 log.debug("%3.2f %3.2f %3.2f %s %s" % 107 log.debug("%3.2f %3.2f %3.2f %s %s" %
97 (self.m.temps[self.m.fermenterId], 108 (self.m.temps[self.m.fermenterId],
98 self.m.temps[self.m.fridgeId], self.m.temps[self.m.ambientId], 109 self.m.temps[self.m.fridgeId], self.m.temps[self.m.ambientId],
99 self.m.currState, nextState)) 110 self.m.currState, nextState))
111
100 if (nextState != "-"): 112 if (nextState != "-"):
101 self.m.setState(nextState) 113 self.m.setState(nextState)
102 114
103 self.cv.wait(self.pollInterval) 115 self.cv.wait(self.pollInterval)
104 116
105 117
106 class MonitorDev(threading.Thread): 118 class MonitorDev(threading.Thread):
107 # Match a ROM ID (eg 00:11:22:33:44:55:66:77) 119 # Match a ROM ID (eg 00:11:22:33:44:55:66:77)
122 134
123 # minimum time the heater must spend on/off 135 # minimum time the heater must spend on/off
124 minHeatOnTime = 60 136 minHeatOnTime = 60
125 minHeatOffTime = 60 137 minHeatOffTime = 60
126 138
139 # Dictionary of sensor IDs & temperatures
127 temps = {} 140 temps = {}
128 lastUpdate = 0 141 # Dictionary of sensor IDs & epoch times
129 142 lastUpdate = {}
143
144 # List of all device IDs
145 devs = []
146 # List of temperature sensor IDs
147 tempdevs = []
148
130 # Lock to gate access to the comms 149 # Lock to gate access to the comms
131 commsLock = None 150 commsLock = None
132 151
133 currState = 'idle' 152 currState = 'idle'
134 153
143 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)']) 162 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)'])
144 assert(self.p.expect('logged in') == 0) 163 assert(self.p.expect('logged in') == 0)
145 self.p.timeout = 3 164 self.p.timeout = 3
146 self.setspeed() 165 self.setspeed()
147 self.devs = self.find1wire() 166 self.devs = self.find1wire()
148 self.temps = filter(self.istemp, self.devs) 167 self.tempdevs = filter(self.istemp, self.devs)
149 168
150 self.start() 169 self.start()
151 170
152 def setspeed(self): 171 def setspeed(self):
153 self.commsLock.acquire() 172 self.commsLock.acquire()
201 return True 220 return True
202 else: 221 else:
203 return False 222 return False
204 223
205 def updateTemps(self): 224 def updateTemps(self):
206 tmp = {} 225 for i in self.tempdevs:
207 for i in self.temps: 226 try:
208 tmp[i] = float(self.readTemp(i)) 227 self.temps[i] = float(self.readTemp(i))
209 228 self.lastUpdate[i] = time.time()
210 self.temps = tmp 229 except ROMReadError:
211 self.lastUpdate = time.time() 230 # Ignore this - just results in no update reflected by lastUpdate
231 pass
232
212 return(self.temps) 233 return(self.temps)
213 234
214 def readTemp(self, id): 235 def readTemp(self, id):
215 self.commsLock.acquire() 236 self.commsLock.acquire()
216 cmd = 'te ' + id 237 cmd = 'te ' + id
307 328
308 global log 329 global log
309 log = initLog() 330 log = initLog()
310 331
311 log.debug("=== Initing ===") 332 log.debug("=== Initing ===")
312 log.debug("$Id: beermon.py,v 1.4 2007/09/24 13:34:47 darius Exp $") 333 log.debug("$Id: beermon.py,v 1.5 2007/09/28 13:05:11 darius Exp $")
313 334
314 m = None 335 m = None
315 exitCode = 0 336 exitCode = 0
316 try: 337 try:
317 m = beermon.MonitorDev() 338 m = beermon.MonitorDev()