#!/usr/bin/python3
import argparse
-from datetime import datetime
import json
+import logging
import paho.mqtt.client as mqtt
import random
import sys
out_topic = ""
# mqc = mqtt client
+ def process_message(self, m):
+ if len(m) > 6 and m[0] == 0x30 and m[1] == 0x0a:
+ return self.process_report(m)
+ else:
+ return 'Unknown message type'
+
+ def process_report(self, m):
+ main_len = 256*m[4] + m[5]
+ if 6 + main_len != len(m):
+ return 'Report length mismatch'
+
+ i = 6
+ while i < len(m):
+ if i + 4 > len(m):
+ return 'Malformed sub-packet header'
+ ln = 256*m[i+2] + m[i+3]
+ if 4 + ln > len(m):
+ return 'Truncated sub-packet'
+ self.process_subreport(m[i], m[i+1], m[i+4:i+4+ln])
+ i += 4 + ln
+
+ return None
+
+ def process_subreport(self, mjr, mnr, val):
+ # logger.debug(f'<<< {mjr:02x} {mnr:02x} = {val.hex(" ")}')
+
+ if mjr == 0x30 and mnr == 0x0b and len(val) == 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
+ logger.info(f'Temp: desired={t_desired} actual={t_actual} analog={analog} manual={t_manual} auto={t_auto}')
+
def on_connect(mqc, therm, flags, rc):
- print(f'# Connect: rc={rc}')
if rc == 0:
+ logger.info('MQTT connected')
therm.connected = True
mqc.subscribe(therm.in_topic, 0)
mqc.subscribe(therm.out_topic, 0)
+ else:
+ logger.warning(f'MQTT connect failed: rc={rc}')
def on_disconnect(mqc, therm, rc):
- print(f'# Disconnect: rc={rc}')
+ logger.info(f'MQTT disconnected: 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(' '))
+ logger.debug(f'<< {msg.topic} ' + 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 !!!')
+ if msg.topic == therm.out_topic:
+ err = therm.process_message(msg.payload)
+ if err:
+ logger.warning(f'Cannot parse message ({err}): {msg.payload.hex(" ")}')
def on_subscribe(mqc, therm, mid, granted_qos):
- print(f'# Subscribed: mid={mid}, granted={granted_qos}')
+ logger.debug(f'MQTT subscribed: mid={mid}, granted={granted_qos}')
def on_log(mqc, therm, level, string):
- print(f'# Log[{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}')
def gen_client_id():
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('--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='{',
+ datefmt='%Y-%m-%d %H:%M:%S',
+ level=logging.DEBUG if args.debug else logging.INFO,
+)
+
+logger = logging.getLogger('bocktherm')
+
therm = init_therm(config)
time.sleep(1)
while True:
if therm.connected:
- print('Brum: Connected')
+ logger.debug('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'
therm.mqc.publish('TSWIFI/I/024299', request)
time.sleep(60)
else:
- print('Brum: Not connected')
time.sleep(5)