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:
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):
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
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)
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
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):
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='{',
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)