#!/usr/bin/env python3 # Contact Bosh-Siemens Home Connect devices # and connect their messages to the mqtt server import json import sys import time import ssl from threading import Thread import click import paho.mqtt.client as mqtt from HCDevice import HCDevice from HCSocket import HCSocket, now @click.command() @click.argument("config_file") @click.option("-h", "--mqtt_host", default="localhost") @click.option("-p", "--mqtt_prefix", default="homeconnect/") @click.option("--mqtt_port", default=1883, type=int) @click.option("--mqtt_username") @click.option("--mqtt_password") @click.option("--mqtt_ssl", is_flag=True) @click.option("--mqtt_cafile") @click.option("--mqtt_certfile") @click.option("--mqtt_keyfile") @click.option("--mqtt_clientname", default="hcpy") def hc2mqtt(config_file: str, mqtt_host: str, mqtt_prefix: str, mqtt_port: int, mqtt_username: str, mqtt_password: str, mqtt_ssl: bool, mqtt_cafile: str, mqtt_certfile: str, mqtt_keyfile: str, mqtt_clientname: str): click.echo(f"Hello {config_file=} {mqtt_host=} {mqtt_prefix=} {mqtt_port=} {mqtt_username=} {mqtt_password=} " f"{mqtt_ssl=} {mqtt_cafile=} {mqtt_certfile=} {mqtt_keyfile=} {mqtt_clientname=}") with open(config_file, "r") as f: devices = json.load(f) client = mqtt.Client(mqtt_clientname) if mqtt_username and mqtt_password: client.username_pw_set(mqtt_username, mqtt_password) if mqtt_ssl: if mqtt_cafile and mqtt_certfile and mqtt_keyfile: client.tls_set(ca_certs=mqtt_cafile, certfile=mqtt_certfile, keyfile=mqtt_keyfile, cert_reqs=ssl.CERT_REQUIRED) else: client.tls_set(cert_reqs=ssl.CERT_NONE) client.connect(host=mqtt_host, port=mqtt_port, keepalive=70) for device in devices: mqtt_topic = mqtt_prefix + device["name"] print(now(), f"topic: {mqtt_topic}") thread = Thread(target=client_connect, args=(client, device, mqtt_topic)) thread.start() client.loop_forever() # Map their value names to easier state names topics = { "InternalError": "Error", "FatalErrorOccured": "Error", } global dev dev = {} def client_connect(client, device, mqtt_topic): def on_message(client, userdata, msg): print(msg.topic) mqtt_state = msg.payload.decode() mqtt_topic = msg.topic.split('/') print(now(), f"received mqtt message {mqtt_state}") try: msg = json.loads(mqtt_state) if 'uid' in msg: dev[mqtt_topic[-2]].get("/ro/values", 1, "POST", msg) else: raise Exception(f"Payload {msg} is not correctly formatted") except Exception as e: print("ERROR", e, file=sys.stderr) host = device["host"] device_topics = topics for value in device["features"]: if "access" in device["features"][value] and "read" in device["features"][value]['access'].lower(): name = device["features"][value]['name'].split(".") device_topics[name[-1]] = name[-1] device_topics[value] = name[-1] #sometimes the returned key is a digit, making translation possible state = {} for topic in device_topics: if not topic.isdigit(): #We only want the named topics state[device_topics[topic]] = None mqtt_set_topic = mqtt_topic + "/set" print(now(), device["name"], f"set topic: {mqtt_set_topic}") client.subscribe(mqtt_set_topic) client.on_message = on_message while True: try: print(now(), device["name"], f"connecting to {host}") ws = HCSocket(host, device["key"], device.get("iv",None)) dev[device["name"]] = HCDevice(ws, device.get("features", None), device["name"]) #ws.debug = True ws.reconnect() while True: msg = dev[device["name"]].recv() if msg is None: break if len(msg) > 0: print(now(), device["name"], msg) update = False for topic in device_topics: value = msg.get(topic, None) if value is None: continue # new_topic = topics[topic] # if new_topic == "remaining": # state["remainingseconds"] = value # value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60) new_topic = device_topics[topic] state[new_topic] = value update = True if not update: continue msg = json.dumps(state) print(now(), device["name"], f"publish to {mqtt_topic} with {msg}") client.publish(mqtt_topic + "/state", msg) except Exception as e: print("ERROR", host, e, file=sys.stderr) time.sleep(5) if __name__ == "__main__": hc2mqtt()