Files
hcpy/hc2mqtt
2023-07-07 15:44:43 +02:00

143 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
# Contact Bosh-Siemens Home Connect devices
# and connect their messages to the mqtt server
import json
import sys
import time
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")
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):
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=}")
with open(config_file, "r") as f:
devices = json.load(f)
client = mqtt.Client()
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 = {
"OperationState": "state",
"DoorState": "door",
"RemainingProgramTime": "remaining",
"PowerState": "power",
"LowWaterPressure": "lowwaterpressure",
"AquaStopOccured": "aquastop",
"InternalError": "error",
"FatalErrorOccured": "error",
}
def client_connect(client, device, mqtt_topic):
def on_message(client, userdata, msg):
global dev
mqtt_state = msg.payload.decode()
print(now(),f"received mqtt message {mqtt_state}")
try:
msg = json.loads(mqtt_state)
if 'uid' in msg:
dev.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)
global dev
host = device["host"]
state = {}
for topic in topics:
state[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:
ws = HCSocket(host, device["key"], device.get("iv",None))
dev = HCDevice(ws, device.get("features", None), device["name"])
#ws.debug = True
ws.reconnect()
while True:
msg = dev.recv()
if msg is None:
break
if len(msg) > 0:
print(now(), device["name"], msg)
update = False
for topic in topics:
value = msg.get(topic, None)
if value is None:
continue
# Convert "On" to True, "Off" to False
if value == "On":
value = True
elif value == "Off":
value = False
new_topic = topics[topic]
if new_topic == "remaining":
state["remainingseconds"] = value
value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60)
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()