]> mj.ucw.cz Git - home-hw.git/commitdiff
Bocktherm: WIP
authorMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 12:06:28 +0000 (13:06 +0100)
committerMartin Mares <mj@ucw.cz>
Tue, 31 Dec 2024 12:06:28 +0000 (13:06 +0100)
bocktherm/burrow-bocktherm.py [new file with mode: 0755]
bocktherm/test.py [deleted file]

diff --git a/bocktherm/burrow-bocktherm.py b/bocktherm/burrow-bocktherm.py
new file mode 100755 (executable)
index 0000000..3f23315
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/python3
+
+import argparse
+from datetime import datetime
+import json
+import paho.mqtt.client as mqtt
+import random
+import sys
+import time
+
+
+class Thermostat:
+    connected = False
+    ident = ""
+    in_topic = ""
+    out_topic = ""
+    # mqc = mqtt client
+
+
+def on_connect(mqc, therm, flags, rc):
+    print(f'# Connect: rc={rc}')
+    if rc == 0:
+        therm.connected = True
+        mqc.subscribe(therm.in_topic, 0)
+        mqc.subscribe(therm.out_topic, 0)
+
+
+def on_disconnect(mqc, therm, rc):
+    print(f'# Disconnect: rc={rc}')
+    therm.connected = False
+
+
+def on_message(mqc, therm, msg):
+    t = datetime.now().isoformat()
+    print(f'@ {t} {msg.topic}')
+    print('\t' + msg.payload.hex(' '))
+
+    m = msg.payload
+    if len(m) > 6 and m[0] in (0x10, 0x30) and m[1] == 0x0a:
+        print(f'\t\t{m[:6].hex(" ")}')
+        i = 6
+        while i + 4 <= len(m):
+            ln = 256*m[i+2] + m[i+3]
+            if 4 + ln > len(m):
+                break
+            mj = m[i]
+            mn = m[i+1]
+            val = m[i+4:i+4+ln]
+            print(f'\t\t\t{mj:02x} {mn:02x} = {val.hex(" ")}')
+            if mj == 0x30 and mn == 0x0b and ln == 5:
+                t_desired = val[0] / 2
+                t_actual = val[1] + (val[2] & 0x0f) / 10
+                if val[2] & 0x10 != 0:
+                    t_actual = -t_actual
+                if val[2] & 0x40 != 0:
+                    t_actual = -999
+                analog = val[2] & 0x20 != 0
+                t_manual = val[3] / 2
+                t_auto = val[4] / 2
+                print(f'\t\t\t\t-> desired={t_desired} actual={t_actual} analog={analog} manual={t_manual} auto={t_auto}')
+            i += 4 + ln
+        if i != len(m):
+            print('\t\t!!! Partial Decode !!!')
+    else:
+        print('\t\t!!! Unknown Packet !!!')
+
+
+def on_subscribe(mqc, therm, mid, granted_qos):
+    print(f'# Subscribed: mid={mid}, granted={granted_qos}')
+
+
+def on_log(mqc, therm, level, string):
+    print(f'# Log[{level}]: {string}')
+
+
+def gen_client_id():
+    return 'APP_' + "".join(["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890"[random.randrange(60)] for _ in range(18)])
+
+
+def read_device_config(config_file, device_name):
+    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)
+    sys.exit(1)
+
+
+def init_therm(config):
+    therm = Thermostat()
+    therm.ident = str(config['uniqueIdentifier']).rjust(6, '0')
+    therm.in_topic = f'TSWIFI/I/{therm.ident}'
+    therm.out_topic = f'TSWIFI/O/{therm.ident}'
+
+    mqc = mqtt.Client(transport='websockets', client_id=gen_client_id(), userdata=therm)
+    mqc.on_message = on_message
+    mqc.on_connect = on_connect
+    mqc.on_disconnect = on_disconnect
+    mqc.on_subscribe = on_subscribe
+    mqc.on_log = on_log
+
+    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()
+
+    therm.mqc = mqc
+    return therm
+
+
+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')
+
+args = parser.parse_args()
+
+config = read_device_config(args.config, args.device)
+therm = init_therm(config)
+
+time.sleep(1)
+while True:
+    if therm.connected:
+        print('Brum: Connected')
+        # 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'
+        therm.mqc.publish('TSWIFI/I/024299', request)
+        time.sleep(60)
+    else:
+        print('Brum: Not connected')
+        time.sleep(5)
diff --git a/bocktherm/test.py b/bocktherm/test.py
deleted file mode 100755 (executable)
index 9a1533a..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/python3
-
-import argparse
-from datetime import datetime
-import paho.mqtt.client as mqtt
-import random
-import time
-
-
-connected = False
-
-
-def on_connect(mqc, userdata, flags, rc):
-    print(f'# Connect: rc={rc}')
-    if rc == 0:
-        global connected
-        connected = True
-
-
-def on_disconnect(mqc, userdata, rc):
-    print(f'# Disconnect: rc={rc}')
-    global connected
-    connected = False
-
-
-def on_message(mqc, userdata, msg):
-    t = datetime.now().isoformat()
-    print(f'@ {t} {msg.topic}')
-    print('\t' + msg.payload.hex(' '))
-
-    m = msg.payload
-    if len(m) > 6 and m[0] in (0x10, 0x30) and m[1] == 0x0a:
-        print(f'\t\t{m[:6].hex(" ")}')
-        i = 6
-        while i + 4 <= len(m):
-            ln = 256*m[i+2] + m[i+3]
-            if 4 + ln > len(m):
-                break
-            mj = m[i]
-            mn = m[i+1]
-            val = m[i+4:i+4+ln]
-            print(f'\t\t\t{mj:02x} {mn:02x} = {val.hex(" ")}')
-            if mj == 0x30 and mn == 0x0b and ln == 5:
-                t_desired = val[0] / 2
-                t_actual = val[1] + (val[2] & 0x0f) / 10
-                if val[2] & 0x10 != 0:
-                    t_actual = -t_actual
-                if val[2] & 0x40 != 0:
-                    t_actual = -999
-                analog = val[2] & 0x20 != 0
-                t_manual = val[3] / 2
-                t_auto = val[4] / 2
-                print(f'\t\t\t\t-> desired={t_desired} actual={t_actual} analog={analog} manual={t_manual} auto={t_auto}')
-            i += 4 + ln
-        if i != len(m):
-            print('\t\t!!! Partial Decode !!!')
-    else:
-        print('\t\t!!! Unknown Packet !!!')
-
-
-def on_subscribe(mqc, userdata, mid, granted_qos):
-    print(f'# Subscribed: mid={mid}, granted={granted_qos}')
-
-
-def on_log(mqc, userdata, level, string):
-    print(f'# Log[{level}]: {string}')
-
-
-def gen_client_id():
-    return 'APP_' + "".join(["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ1234567890"[random.randrange(60)] for _ in range(18)])
-
-
-mqc = mqtt.Client(transport='websockets', client_id=gen_client_id())
-mqc.on_message = on_message
-mqc.on_connect = on_connect
-mqc.on_disconnect = on_disconnect
-mqc.on_subscribe = on_subscribe
-mqc.on_log = on_log
-
-mqc.ws_set_options("/wss")
-mqc.tls_set_context()
-mqc.username_pw_set('TSWIFI_024299', 'iqjYBfrZGe')
-mqc.connect("mqtt.elbock.cz", 8081, 60)
-
-# FIXME: Move to connect callback
-mqc.subscribe("TSWIFI/I/024299", 0)
-mqc.subscribe("TSWIFI/O/024299", 0)
-
-mqc.loop_start()
-
-while True:
-    if connected:
-        print('Brum: Connected')
-        # 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'
-        mqc.publish('TSWIFI/I/024299', request)
-        time.sleep(60)
-    else:
-        print('Brum: Not connected')
-        time.sleep(5)