if debug_mode:
print(("\t" * indent) + msg)
+def diff(x, y):
+ if x is None or y is None:
+ return None
+ else:
+ return x - y
+
class State:
def __init__(self):
- self.attrs = {}
+ self.attrs = {}
+ self.hyst_state = {}
+ self.running_averages = {}
def update(self):
self.now = time.time()
self.hour = tm.tm_hour
self.min = tm.tm_min
self.wday = tm.tm_wday
- self.hyst_state = {}
debug("[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d} wday={}]".format(
self.year, self.month, self.day,
self.hour, self.min, tm.tm_sec,
self.hyst_state[key] = new_state
return new_state
+ def update_average(self, key, window_seconds):
+ if key not in self.running_averages:
+ self.running_averages[key] = ([], 0, 0, None)
+ (history, sum, count, avg) = self.running_averages[key]
+
+ while len(history) > 0 and history[0][0] <= self.now - window_seconds:
+ if history[0][1] is not None:
+ sum -= history[0][1]
+ count -= 1
+ history.pop(0)
+
+ curr = self.get_sensor(key)
+ history.append((self.now, curr))
+ if curr is not None:
+ sum += curr
+ count += 1
+
+ if count > len(history) // 2:
+ avg = sum / count
+ else:
+ avg = None
+
+ self.running_averages[key] = (history, sum, count, avg)
+ debug("= avg {:.6} ({} samples, {} non-null)".format(avg, len(history), count))
+ if avg is not None:
+ self.set("avg/" + key, "{:.6} {}".format(avg, int(self.now)))
+
+ def get_sensor_avg(self, key):
+ val = self.running_averages[key][3]
+ debug("< {} = avg {:.6}".format(key, val))
+ return val
+
st = State()
def on_connect(mq, userdata, flags, rc):
def auto_air():
global st
- tii = st.get_sensor('air/inside-intake')
- tie = st.get_sensor('air/inside-exhaust')
- toi = st.get_sensor('air/outside-intake')
- tmix = st.get_sensor('air/mixed')
+ tii = st.get_sensor_avg('air/inside-intake')
+ tie = st.get_sensor_avg('air/inside-exhaust')
+ toi = st.get_sensor_avg('air/outside-intake')
+ tmix = st.get_sensor_avg('air/mixed')
house_warm = st.hysteresis('house_warm', tii, 23.5, 24.5)
house_hot = st.hysteresis('house_hot', tii, 24.5, 25)
ac_off = st.hysteresis('ac_off', tmix, tie - 5, tie - 4)
# Do we want to bypass the heat exchanger?
- if toi is None or tie is None:
+ outside_warmer = st.hysteresis('outside_warmer', diff(toi, tii), -0.5, 0.5)
+ if (house_warm > 0) and (outside_warmer > 0) or \
+ (house_warm < 0) and (outside_warmer < 0):
st.set('air/bypass', 0)
else:
- outside_warmer = (st.hysteresis('bypass', toi, tii - 0.5, tii + 0.5) >= 0)
- if (house_warm >= 0) == outside_warmer:
- st.set('air/bypass', 0)
- else:
- st.set('air/bypass', 1)
+ st.set('air/bypass', 1)
# Is mixed air colder than air from the inside?
- if tii is None or tmix is None:
- mixed_warmer = 0
- else:
- mixed_warmer = st.hysteresis('mixed_warmer', tmix, tii - 1, tii)
+ mixed_warmer = st.hysteresis('mixed_warmer', diff(tmix, tii), -1, 0)
# Do we want to boost heat exchanger fan?
if ac_off < 0 or (house_hot > 0 and mixed_warmer < 0):
while True:
st.update()
+
+ debug("averages")
+ indent += 1
+ st.update_average('air/outside-intake', 60)
+ st.update_average('air/inside-intake', 60)
+ st.update_average('air/inside-exhaust', 60)
+ st.update_average('air/mixed', 60)
+ indent -= 1
+
for name, func in checks:
if st.auto_enabled(name):
debug(name)