]> mj.ucw.cz Git - home-hw.git/commitdiff
Bocktherm: Multi-thermostat mode
authorMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 14:07:31 +0000 (15:07 +0100)
committerMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 14:07:31 +0000 (15:07 +0100)
bocktherm/Makefile [new file with mode: 0644]
bocktherm/burrow-bocktherm.py

diff --git a/bocktherm/Makefile b/bocktherm/Makefile
new file mode 100644 (file)
index 0000000..3983712
--- /dev/null
@@ -0,0 +1,8 @@
+all:
+
+install: burrow-bocktherm.py
+       install burrow-bocktherm.py /usr/local/sbin/burrow-bocktherm
+
+clean:
+
+.PHONY: all install clean
index 595be14e9f28ad602c6b8801cd23ee085a6389d5..783024a88628eb98c805e378ace89efbed388c8a 100755 (executable)
@@ -15,12 +15,12 @@ class Thermostat:
     ident = ""
     bock_mqtt: 'BockMQTT'
     burrow_mqtt: 'BurrowMQTT'
+    burrow_name: str
 
-    def __init__(self, config):
+    def __init__(self, config, burrow_name):
+        self.config = config
         self.ident = str(config['uniqueIdentifier']).rjust(6, '0')
-
-    def status_notify(self, stat):
-        self.burrow_mqtt.send_status(stat)
+        self.burrow_name = burrow_name
 
     def process_message(self, m):
         if len(m) > 6 and m[0] == 0x30 and m[1] == 0x0a:
@@ -59,7 +59,7 @@ 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)
+            self.burrow_mqtt.send_measurement(self.burrow_name, t_actual)
 
 
 def generic_mqtt_log(prefix, level, string):
@@ -78,6 +78,7 @@ class BockMQTT:
 
     def __init__(self, therm: Thermostat):
         self.therm = therm
+        self.log_prefix = f'MQTT({therm.burrow_name})'
         self.in_topic = f'TSWIFI/I/{therm.ident}'
         self.out_topic = f'TSWIFI/O/{therm.ident}'
         self.connected = False
@@ -92,7 +93,7 @@ class BockMQTT:
 
         mqc.ws_set_options("/wss")
         mqc.tls_set_context()
-        mqc.username_pw_set(f'TSWIFI_{therm.ident}', config['mqttPass'])
+        mqc.username_pw_set(f'TSWIFI_{therm.ident}', therm.config['mqttPass'])
 
     def start(self):
         self.mqc.connect("mqtt.elbock.cz", 8081, 60)
@@ -100,59 +101,60 @@ class BockMQTT:
 
     def on_connect(self, mqc, userdata, flags, rc):
         if rc == 0:
-            logger.info('BockMQTT connected')
+            logger.info(f'{self.log_prefix} connected')
             self.connected = True
             mqc.subscribe(self.in_topic, 0)
             mqc.subscribe(self.out_topic, 0)
         else:
-            logger.warning(f'BockMQTT connect failed: rc={rc}')
+            logger.warning(f'{self.log_prefix} connect failed: rc={rc}')
 
     def on_disconnect(self, mqc, userdata, rc):
-        logger.info(f'BockMQTT disconnected: rc={rc}')
+        logger.info(f'{self.log_prefix} 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(' '))
+        logger.debug(f'{self.log_prefix} << {msg.topic} ' + msg.payload.hex(' '))
 
         if msg.topic == self.out_topic:
             err = self.therm.process_message(msg.payload)
             if err:
-                logger.warning(f'Cannot parse message ({err}): {msg.payload.hex(" ")}')
+                logger.warning(f'{self.log_prefix} parse error ({err}): {msg.payload.hex(" ")}')
 
     def on_subscribe(self, mqc, userdata, mid, granted_qos):
-        logger.debug(f'BockMQTT subscribed: mid={mid}')
+        logger.debug(f'{self.log_prefix} subscribed: mid={mid}')
 
     def on_log(self, mqc, userdata, level, string):
-        generic_mqtt_log('BockMQTT', level, string)
+        generic_mqtt_log(self.log_prefix, level, string)
 
     def gen_client_id(self):
         return 'APP_' + "".join(["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890"[random.randrange(60)] for _ in range(18)])
 
     def send_request(self):
+        logger.debug(f'{self.log_prefix} sending request')
         # Request message sent by the web interface, but with a random message ID
         request = b'\x10\x0a' + random.randbytes(2) + b'\x00\x24\x20\x01\x00\x00\x20\x02\x00\x00\x20\x03\x00\x00\x20\x06\x00\x00\x20\x07\x00\x00\x20\x05\x00\x00\x20\x09\x00\x00\x20\x0b\x00\x00\x20\x0c\x00\x00'
-        self.mqc.publish('TSWIFI/I/024299', request)
+        self.mqc.publish(self.in_topic, request)
 
 
-def read_device_config(config_file, device_name):
+def read_config(config_file):
     with open(config_file) as f:
-        js = json.load(f)
-        assert isinstance(js, list)
-        for cf in js:
-            assert isinstance(cf, dict)
-            if cf['name'] == device_name:
-                return cf
-    print('Device not found in configuration file', file=sys.stderr)
+        return json.load(f)
+
+
+def find_device_config(config, device_name):
+    assert isinstance(config, list)
+    for cf in config:
+        assert isinstance(cf, dict)
+        if cf['name'] == device_name:
+            return cf
+    print(f'Device {device_name} not found in configuration file', file=sys.stderr)
     sys.exit(1)
 
 
 class BurrowMQTT:
 
-    def __init__(self, sensor):
-        self.sensor = sensor
+    def __init__(self):
         self.connected = False
-        self.status = '?'
 
         mqc = mqtt.Client()
         self.mqc = mqc
@@ -168,9 +170,9 @@ class BurrowMQTT:
         mqc.on_log = self.on_log
 
     def start(self):
-        self.mqc.will_set(f"status/bocktherm/{self.sensor}", "dead", retain=True)
+        self.mqc.will_set("status/bocktherm", "dead", retain=True)
         self.mqc.connect("burrow-mqtt", 8883)
-        self.send_status('init')
+        self.mqc.publish("status/bocktherm", "ok", retain=True)
         self.mqc.loop_start()
 
     def on_connect(self, mqc, userdata, flags, rc):
@@ -187,28 +189,19 @@ class BurrowMQTT:
     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):
+    def send_measurement(self, sensor, value):
         if self.connected:
             now = int(time.time())
-            self.mqc.publish(f'mill/thermostat/{self.sensor}', f'{value} {now}')
-            self.send_status('ok')
+            self.mqc.publish(f'mill/thermostat/{sensor}', f'{value} {now}')
 
 
 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('--devices', nargs='+', type=str, help='Device names: <burrow-name>:<config-name>')
 parser.add_argument('--debug', default=False, action='store_true', help='Log debug messages')
 
 args = parser.parse_args()
 
-config = read_device_config(args.config, args.device)
-
 logging.basicConfig(
     format='{asctime}.{msecs:03.0f} {levelname} ({name}) {message}',
     style='{',
@@ -218,18 +211,26 @@ logging.basicConfig(
 
 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()
+config = read_config(args.config)
+burrow_mqtt = BurrowMQTT()
+thermostats = []
+
+for dev in args.devices:
+    burrow_name, config_name = dev.split(':')
+    dev_config = find_device_config(config, config_name)
 
+    therm = Thermostat(dev_config, burrow_name)
+    therm.bock_mqtt = BockMQTT(therm)
+    therm.burrow_mqtt = burrow_mqtt
+    thermostats.append(therm)
+
+burrow_mqtt.start()
+for therm in thermostats:
+    therm.bock_mqtt.start()
 
 time.sleep(1)
 while True:
-    if therm.bock_mqtt.connected:
-        logger.debug('Sending request')
-        therm.bock_mqtt.send_request()
-        time.sleep(60)
-    else:
-        time.sleep(5)
+    for therm in thermostats:
+        if therm.bock_mqtt.connected:
+            therm.bock_mqtt.send_request()
+    time.sleep(60)