2 # Iris -- the Burrow's goddess of rainbow
3 # Controls LEDs on the Rainbow according to the state of the house
4 # (c) 2022 Martin Mareš <mj@ucw.cz>
9 from datetime import datetime, timedelta
11 from logging.handlers import SysLogHandler
17 def __init__(self, mqtt):
19 self.leds = ["?"] * 12
20 self.new_leds = ["?"] * 12
24 self.now = datetime.now()
26 def received_msg(self, topic, val):
27 self.attrs[topic] = val
28 logger.debug(f'MQTT: {topic} -> {val}')
30 def get(self, key, default=None):
31 topic = "burrow/" + key
32 return self.attrs.get(topic, default)
34 def get_status(self, key, default=None):
35 topic = "status/" + key
36 return self.attrs.get(topic, default)
38 def get_sensor(self, key, default=None, timeout=120):
39 topic = "burrow/" + key
40 if topic in self.attrs:
41 s = self.attrs[topic].split(" ")
42 if len(s) >= 2 and timeout is not None and int(s[1]) < self.now.timestamp() - timeout:
43 logger.debug(f"< {key} EXPIRED")
46 logger.debug(f"< {key} = {s[0]}")
49 logger.debug(f"< {key} UNDEFINED")
52 async def set(self, key, val):
54 topic = "burrow/" + key
55 logger.debug(f'> {key} = {val}')
56 await self.mqtt.publish(topic, val, retain=True)
58 def set_led(self, i, color=None):
63 self.new_leds[i] = f"{r} {g} {b} iris"
65 async def update_leds(self):
66 for i in range(len(self.leds)):
67 if self.new_leds[i] != self.leds[i]:
68 await self.set(f"lights/rainbow/{i}", self.new_leds[i])
69 self.leds[i] = self.new_leds[i]
72 st = None # Current State
77 stat = st.get_status('bsb', 'ok')
81 err = st.get_sensor('heating/error', 0, timeout=None)
85 if st.get_sensor('heating/circuit1/pump', 0, timeout=3600) > 0:
88 if st.get_sensor('heating/circuit2/active', 0, timeout=3600) > 0:
91 if st.get_sensor('heating/water/active', 0, timeout=3600) > 0:
98 temp = st.get_sensor('temp/catarium')
114 def temperature_led():
115 for sensor in ['loft', 'ursarium', 'garage']: # FIXME: terarium
116 if st.get_sensor(f"temp/{sensor}", timeout=3600) is None:
123 ac = st.get_sensor('air/ac-on')
125 return (0.7, 0.7, 0.7) # white
131 l2 = st.get_sensor('power/current/l2')
135 return (0.5, 0.1, 0.02) # orange
142 st.set_led(10, boiler_led())
143 st.set_led(9, catarium_led())
144 # st.set_led(8, temperature_led())
145 st.set_led(8, ac_led())
146 st.set_led(6, kettle_led())
149 async def mqtt_process_msg(topic, val):
150 st.received_msg(topic, val)
154 async def mqtt_loop():
155 sctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
156 sctx.verify_mode = ssl.CERT_REQUIRED
157 sctx.load_cert_chain('/etc/burrow-mqtt/client.crt', '/etc/burrow-mqtt/client.key')
158 sctx.load_verify_locations(cafile='/etc/burrow-mqtt/ca.crt')
160 will = aiomqtt.Will(topic='status/iris', payload='dead', qos=1, retain=True)
162 mqtt = aiomqtt.Client(client_id='iris', hostname="burrow-mqtt", port=8883, tls_context=sctx, will=will)
166 async with mqtt.messages() as messages:
167 await mqtt.subscribe("burrow/air/ac-on")
168 await mqtt.subscribe("burrow/heating/#")
169 await mqtt.subscribe("burrow/temp/#")
170 await mqtt.publish("status/iris", "ok", retain=True)
171 async for msg in messages:
172 await mqtt_process_msg(msg.topic.value, msg.payload.decode())
175 async def mqtt_watcher():
178 logger.info("Starting MQTT")
180 except aiomqtt.MqttError as error:
181 logger.error(f"MQTT error: {error}")
182 await asyncio.sleep(10)
185 async def led_watcher():
187 await led_event.wait()
189 logger.debug('Recalculating LEDs')
193 await st.update_leds()
194 await asyncio.sleep(0.1)
198 global loop, led_event
199 loop = asyncio.get_event_loop()
200 led_event = asyncio.Event()
202 loop.create_task(mqtt_watcher()),
203 loop.create_task(led_watcher()),
205 for coro in asyncio.as_completed(coros):
207 done.result() # The coroutine probably died of an exception, which is raised here.
210 parser = argparse.ArgumentParser(description='The Goddess of Rainbow in the Burrow')
211 parser.add_argument('--debug', default=False, action='store_true', help='Run in debug mode')
212 args = parser.parse_args()
214 logger = logging.getLogger()
216 formatter = logging.Formatter(fmt="%(asctime)s %(name)s.%(levelname)s: %(message)s", datefmt='%Y-%m-%d %H:%M:%S')
217 log_handler = logging.StreamHandler(stream=sys.stdout)
218 logger.setLevel(logging.DEBUG)
219 logging.getLogger('mqtt').setLevel(logging.DEBUG)
221 formatter = logging.Formatter(fmt="%(message)s") # systemd will handle the rest
222 log_handler = SysLogHandler('/dev/log', facility=SysLogHandler.LOG_LOCAL1)
223 log_handler.ident = 'burrow-iris: '
224 logger.setLevel(logging.INFO)
225 log_handler.setFormatter(formatter)
226 logger.addHandler(log_handler)