X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;ds=sidebyside;f=auto%2Fburrow-auto;h=4c30f59cc813e50c8641f25b23aa0d648956ef86;hb=9d5a1c85210e6ff746b35b52ca280407896164c1;hp=b7eb527fccab2f6f773ae750c26f5b1b1df5dc41;hpb=44cd56959a1ee06ea845b30ac28a704007bf4b90;p=home-hw.git diff --git a/auto/burrow-auto b/auto/burrow-auto index b7eb527..4c30f59 100755 --- a/auto/burrow-auto +++ b/auto/burrow-auto @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 import getopt import paho.mqtt.client as mqtt @@ -6,40 +6,78 @@ import sys import time debug_mode = False +indent = 0 def debug(msg): if debug_mode: - print msg + print(("\t" * indent) + msg) class State: def __init__(self): - self.attrs = {} + self.attrs = {} def update(self): - self.now = time.time() - tm = time.localtime(self.now) - self.year = tm.tm_year - self.month = tm.tm_mon - self.day = tm.tm_mday - self.hour = tm.tm_hour - self.min = tm.tm_min - self.wday = tm.tm_wday + self.now = time.time() + tm = time.localtime(self.now) + self.year = tm.tm_year + self.month = tm.tm_mon + self.day = tm.tm_mday + 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.wday + )) def get_sensor(self, key): topic = "burrow/" + key - if topic in self.attrs: - s = self.attrs[topic].split(" ") - if len(s) >= 2 and s[1] < self.now - 120: - return None - return float(s[0]) - else: - return None + if topic in self.attrs: + s = self.attrs[topic].split(" ") + if len(s) >= 2 and int(s[1]) < self.now - 120: + debug("< {} EXPIRED".format(key)) + return None + else: + debug("< {} = {}".format(key, s[0])) + return float(s[0]) + else: + debug("< {} UNDEFINED".format(key)) + return None def set(self, key, val): global mq topic = "burrow/" + key - debug("Setting {} to {}".format(topic, val)) - mq.publish(topic, val, qos=1, retain=True) + debug("> {} = {}".format(topic, val)) + mq.publish(topic, val, qos=1, retain=True) + + def auto_enabled(self, key): + topic = "burrow/auto/" + key + if topic in self.attrs: + return self.attrs[topic] != '0' + else: + return True + + def hysteresis(self, key, value, low, high): + if key in self.hyst_state: + old_state = self.hyst_state[key] + else: + old_state = 0 + if value is None: + new_state = 0 + elif old_state <= 0: + if value >= high: + new_state = 1 + else: + new_state = -1 + else: + if value <= low: + new_state = -1 + else: + new_state = 1 + self.hyst_state[key] = new_state + return new_state st = State() @@ -48,28 +86,80 @@ def on_connect(mq, userdata, flags, rc): def on_message(mq, userdata, msg): global st - debug("Message {}: {}".format(msg.topic, msg.payload)) - st.attrs[msg.topic] = msg.payload + # debug("Message {}: {}".format(msg.topic, msg.payload)) + st.attrs[msg.topic] = msg.payload.decode('utf-8') def auto_loft_fan(): global st lt = st.get_sensor("temp/loft") - if lt is not None and lt >= 30: - fs = 3 - elif st.hour in range(10, 20): - fs = 2 + lt_high = st.hysteresis('lt_high', lt, 29, 30) + lt_mid = st.hysteresis('lt_mid', lt, 24, 25) + if lt_high > 0: + fs = 3 + elif lt_mid > 0: + if st.hour in range(10, 20): + fs = 3 + else: + fs = 1 else: - fs = 1 + if st.hour in range(8, 22): + if st.min % 30 in range(0, 5): + fs = 3 + else: + fs = 0 + else: + fs = 0 + # FIXME: Disabled for now + fs = 0 st.set("loft/fan", fs) def auto_circ(): global st - if st.hour in range(19, 23): + if st.hour in range(20, 22): c = 1 else: c = 0; st.set("loft/circulation", c) +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') + house_warm = st.hysteresis('house_warm', tii, 23.5, 24.5) + house_hot = st.hysteresis('house_hot', tii, 24.5, 25) + + # Is AC currently on (mixed air is significantly colder than inside exhaust)? + if tie is None or tmix is None: + ac_off = 1 + else: + 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: + 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) + + # 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) + + # Do we want to boost heat exchanger fan? + if ac_off < 0 or (house_hot > 0 and mixed_warmer < 0): + st.set('air/exchanger-fan', 255) + else: + st.set('air/exchanger-fan', 0) + + debug("Air: house_warm={} house_hot={} ac_off={} outside_warmer={} mixed_warmer={}".format(house_warm, house_hot, ac_off, outside_warmer, mixed_warmer)) + opts, args = getopt.gnu_getopt(sys.argv[1:], "", ["debug"]) for opt in opts: o, arg = opt @@ -79,13 +169,29 @@ for opt in opts: mq = mqtt.Client() mq.on_connect = on_connect mq.on_message = on_message -mq.will_set("status/auto", "failed", retain=True) +mq.will_set("status/auto", "dead", retain=True) mq.connect("127.0.0.1") mq.publish("status/auto", "ok", retain=True) mq.loop_start() +# Heuristic delay to get all attributes from MQTT +time.sleep(3) + +checks = [ + ('loft-fan', auto_loft_fan), + ('circ', auto_circ), + ('air', auto_air) +] + while True: st.update() - auto_loft_fan() - auto_circ() + for name, func in checks: + if st.auto_enabled(name): + debug(name) + indent += 1 + func() + indent -= 1 + else: + debug("{} DISABLED".format(name)) + debug("=" * 80) time.sleep(10)