From 9ec8ff69af67e2ce80a726c1d40c81a7006b1d9f Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Tue, 31 Dec 2024 14:29:45 +0100 Subject: [PATCH] Bocktherm: Burrow MQTT --- bocktherm/burrow-bocktherm.py | 98 ++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/bocktherm/burrow-bocktherm.py b/bocktherm/burrow-bocktherm.py index 9be09fd..595be14 100755 --- a/bocktherm/burrow-bocktherm.py +++ b/bocktherm/burrow-bocktherm.py @@ -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) -- 2.39.5