4 import paho.mqtt.client as mqtt
13 print(("\t" * indent) + msg)
20 self.now = time.time()
21 tm = time.localtime(self.now)
22 self.year = tm.tm_year
23 self.month = tm.tm_mon
25 self.hour = tm.tm_hour
27 self.wday = tm.tm_wday
29 debug("[{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d} wday={}]".format(
30 self.year, self.month, self.day,
31 self.hour, self.min, tm.tm_sec,
35 def get_sensor(self, key):
36 topic = "burrow/" + key
37 if topic in self.attrs:
38 s = self.attrs[topic].split(" ")
39 if len(s) >= 2 and int(s[1]) < self.now - 120:
40 debug("< {} EXPIRED".format(key))
43 debug("< {} = {}".format(key, s[0]))
46 debug("< {} UNDEFINED".format(key))
49 def set(self, key, val):
51 topic = "burrow/" + key
52 debug("> {} = {}".format(topic, val))
53 mq.publish(topic, val, qos=1, retain=True)
55 def auto_enabled(self, key):
56 topic = "burrow/auto/" + key
57 if topic in self.attrs:
58 return self.attrs[topic] != '0'
62 def hysteresis(self, key, value, low, high):
63 if key in self.hyst_state:
64 old_state = self.hyst_state[key]
79 self.hyst_state[key] = new_state
84 def on_connect(mq, userdata, flags, rc):
85 mq.subscribe("burrow/#")
87 def on_message(mq, userdata, msg):
89 # debug("Message {}: {}".format(msg.topic, msg.payload))
90 st.attrs[msg.topic] = msg.payload.decode('utf-8')
94 lt = st.get_sensor("temp/loft")
95 lt_high = st.hysteresis('lt_high', lt, 29, 30)
96 lt_mid = st.hysteresis('lt_mid', lt, 24, 25)
100 if st.hour in range(10, 20):
105 if st.hour in range(8, 22):
106 if st.min % 30 in range(0, 5):
112 # FIXME: Disabled for now
114 st.set("loft/fan", fs)
118 if st.hour in range(20, 22):
122 st.set("loft/circulation", c)
126 tii = st.get_sensor('air/inside-intake')
127 tie = st.get_sensor('air/inside-exhaust')
128 toi = st.get_sensor('air/outside-intake')
129 tmix = st.get_sensor('air/mixed')
130 house_warm = st.hysteresis('house_warm', tii, 23.5, 24.5)
131 house_hot = st.hysteresis('house_hot', tii, 24.5, 25)
133 # Is AC currently on (mixed air is significantly colder than inside exhaust)?
134 if tie is None or tmix is None:
137 ac_off = st.hysteresis('ac_off', tmix, tie - 5, tie - 4)
139 # Do we want to bypass the heat exchanger?
140 if toi is None or tie is None:
141 st.set('air/bypass', 0)
143 outside_warmer = (st.hysteresis('bypass', toi, tii - 0.5, tii + 0.5) >= 0)
144 if (house_warm >= 0) == outside_warmer:
145 st.set('air/bypass', 0)
147 st.set('air/bypass', 1)
149 # Is mixed air colder than air from the inside?
150 if tii is None or tmix is None:
153 mixed_warmer = st.hysteresis('mixed_warmer', tmix, tii - 1, tii)
155 # Do we want to boost heat exchanger fan?
156 if ac_off < 0 or (house_hot > 0 and mixed_warmer < 0):
157 st.set('air/exchanger-fan', 255)
159 st.set('air/exchanger-fan', 0)
161 debug("Air: house_warm={} house_hot={} ac_off={} outside_warmer={} mixed_warmer={}".format(house_warm, house_hot, ac_off, outside_warmer, mixed_warmer))
163 opts, args = getopt.gnu_getopt(sys.argv[1:], "", ["debug"])
170 mq.on_connect = on_connect
171 mq.on_message = on_message
172 mq.will_set("status/auto", "dead", retain=True)
173 mq.connect("127.0.0.1")
174 mq.publish("status/auto", "ok", retain=True)
177 # Heuristic delay to get all attributes from MQTT
181 ('loft-fan', auto_loft_fan),
188 for name, func in checks:
189 if st.auto_enabled(name):
195 debug("{} DISABLED".format(name))