]> mj.ucw.cz Git - home-hw.git/commitdiff
Bocktherm: Burrow MQTT
authorMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 13:29:45 +0000 (14:29 +0100)
committerMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 13:29:45 +0000 (14:29 +0100)
bocktherm/burrow-bocktherm.py

index 9be09fd8edc394d1f80eb83cff29b81c83b07fe4..595be14e9f28ad602c6b8801cd23ee085a6389d5 100755 (executable)
@@ -5,6 +5,7 @@ import json
 import logging
 import paho.mqtt.client as mqtt
 import random
+import ssl
 import sys
 import time
 
@@ -13,10 +14,14 @@ class Thermostat:
     connected = False
     ident = ""
     bock_mqtt: 'BockMQTT'
+    burrow_mqtt: 'BurrowMQTT'
 
     def __init__(self, config):
         self.ident = str(config['uniqueIdentifier']).rjust(6, '0')
 
+    def status_notify(self, stat):
+        self.burrow_mqtt.send_status(stat)
+
     def process_message(self, m):
         if len(m) > 6 and m[0] == 0x30 and m[1] == 0x0a:
             return self.process_report(m)
@@ -54,6 +59,19 @@ class Thermostat:
             t_manual = val[3] / 2
             t_auto = val[4] / 2
             logger.info(f'Temp: desired={t_desired} actual={t_actual} analog={analog} manual={t_manual} auto={t_auto}')
+            self.burrow_mqtt.send_measurement(t_actual)
+
+
+def generic_mqtt_log(prefix, level, string):
+    msg = f'{prefix}: {string}'
+    if level == mqtt.MQTT_LOG_DEBUG:
+        logger.debug(msg)
+    elif level == mqtt.MQTT_LOG_WARNING:
+        logger.warning(msg)
+    elif level == mqtt.MQTT_LOG_ERROR:
+        logger.error(msg)
+    else:
+        logger.info(msg)
 
 
 class BockMQTT:
@@ -75,21 +93,24 @@ class BockMQTT:
         mqc.ws_set_options("/wss")
         mqc.tls_set_context()
         mqc.username_pw_set(f'TSWIFI_{therm.ident}', config['mqttPass'])
-        mqc.connect("mqtt.elbock.cz", 8081, 60)
-        mqc.loop_start()
+
+    def start(self):
+        self.mqc.connect("mqtt.elbock.cz", 8081, 60)
+        self.mqc.loop_start()
 
     def on_connect(self, mqc, userdata, flags, rc):
         if rc == 0:
-            logger.info('MQTT connected')
+            logger.info('BockMQTT connected')
             self.connected = True
             mqc.subscribe(self.in_topic, 0)
             mqc.subscribe(self.out_topic, 0)
         else:
-            logger.warning(f'MQTT connect failed: rc={rc}')
+            logger.warning(f'BockMQTT connect failed: rc={rc}')
 
     def on_disconnect(self, mqc, userdata, rc):
-        logger.info(f'MQTT disconnected: rc={rc}')
+        logger.info(f'BockMQTT disconnected: rc={rc}')
         self.connected = False
+        self.therm.status_notify('error')
 
     def on_message(self, mqc, userdata, msg):
         logger.debug(f'<< {msg.topic} ' + msg.payload.hex(' '))
@@ -100,17 +121,10 @@ class BockMQTT:
                 logger.warning(f'Cannot parse message ({err}): {msg.payload.hex(" ")}')
 
     def on_subscribe(self, mqc, userdata, mid, granted_qos):
-        logger.debug(f'MQTT subscribed: mid={mid}')
+        logger.debug(f'BockMQTT subscribed: mid={mid}')
 
     def on_log(self, mqc, userdata, level, string):
-        if level == mqtt.MQTT_LOG_DEBUG:
-            logger.debug(f'MQTT: {string}')
-        elif level == mqtt.MQTT_LOG_WARNING:
-            logger.warning(f'MQTT: {string}')
-        elif level == mqtt.MQTT_LOG_ERROR:
-            logger.error(f'MQTT: {string}')
-        else:
-            logger.info(f'MQTT: {string}')
+        generic_mqtt_log('BockMQTT', level, string)
 
     def gen_client_id(self):
         return 'APP_' + "".join(["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890"[random.randrange(60)] for _ in range(18)])
@@ -133,9 +147,62 @@ def read_device_config(config_file, device_name):
     sys.exit(1)
 
 
+class BurrowMQTT:
+
+    def __init__(self, sensor):
+        self.sensor = sensor
+        self.connected = False
+        self.status = '?'
+
+        mqc = mqtt.Client()
+        self.mqc = mqc
+
+        sctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+        sctx.verify_mode = ssl.CERT_REQUIRED
+        sctx.load_cert_chain('/etc/burrow-mqtt/client.crt', '/etc/burrow-mqtt/client.key')
+        sctx.load_verify_locations(cafile='/etc/burrow-mqtt/ca.crt')
+        mqc.tls_set_context(sctx)
+
+        mqc.on_connect = self.on_connect
+        mqc.on_disconnect = self.on_disconnect
+        mqc.on_log = self.on_log
+
+    def start(self):
+        self.mqc.will_set(f"status/bocktherm/{self.sensor}", "dead", retain=True)
+        self.mqc.connect("burrow-mqtt", 8883)
+        self.send_status('init')
+        self.mqc.loop_start()
+
+    def on_connect(self, mqc, userdata, flags, rc):
+        if rc == 0:
+            logger.info('BurrowMQTT connected')
+            self.connected = True
+        else:
+            logger.warning(f'BurrowMQTT connect failed: rc={rc}')
+
+    def on_disconnect(self, mqc, userdata, rc):
+        logger.info(f'BurrowMQTT disconnected: rc={rc}')
+        self.connected = False
+
+    def on_log(self, mqc, userdata, level, string):
+        generic_mqtt_log('BurrowMQTT', level, string)
+
+    def send_status(self, stat):
+        if stat != self.status:
+            self.status = stat
+            self.mqc.publish(f"status/bocktherm/{self.sensor}", stat, retain=True)
+
+    def send_measurement(self, value):
+        if self.connected:
+            now = int(time.time())
+            self.mqc.publish(f'mill/thermostat/{self.sensor}', f'{value} {now}')
+            self.send_status('ok')
+
+
 parser = argparse.ArgumentParser(description='A daemon watching Elektrobock thermostats')
 parser.add_argument('--config', required=True, type=str, help='Configuration file from the web app (devices.json)')
 parser.add_argument('--device', required=True, type=str, help='Device name as in the configuration file')
+parser.add_argument('--sensor', required=True, type=str, help='Sensor name in Burrow MQTT')
 parser.add_argument('--debug', default=False, action='store_true', help='Log debug messages')
 
 args = parser.parse_args()
@@ -153,6 +220,9 @@ logger = logging.getLogger('bocktherm')
 
 therm = Thermostat(config)
 therm.bock_mqtt = BockMQTT(therm)
+therm.burrow_mqtt = BurrowMQTT(args.sensor)
+therm.bock_mqtt.start()
+therm.burrow_mqtt.start()
 
 
 time.sleep(1)