diff --git a/HADiscovery.py b/HADiscovery.py new file mode 100644 index 0000000..925cbcb --- /dev/null +++ b/HADiscovery.py @@ -0,0 +1,66 @@ +import json +import re + +from HCSocket import now + + +def decamelcase(str): + split = re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', str) + return f"{split[0]} {' '.join(split[1:]).lower()}".strip() + + +HA_DISCOVERY_PREFIX = "homeassistant" + + +def publish_ha_states(state, client, mqtt_topic): + pass + + +def publish_ha_discovery(device, client, mqtt_topic): + print(f"{now()} Publishing HA discovery for {device}") + + device_ident = device["host"] + device_name = device["name"] + device_description = device.get("description", {}) + + version_parts = filter( + lambda d : d is not None, + [ + device_description.get("version"), + device_description.get("revision") + ] + ) + + device_info = { + "identifiers": [device_ident], + "name": device_name, + "manufacturer": device_description.get("brand"), + "model": device_description.get("model"), + "sw_version": ".".join(version_parts) + } + + for feature in device["features"].values(): + access = feature.get("access", "none") + available = feature.get("available", False) + if available and (access == "read" or access == "readWrite"): + name_parts = feature["name"].split(".") + name = name_parts[-1] + feature_type = name_parts[-2] + + component_type = "sensor" # TODO use appropriate types + + discovery_topic = f"{HA_DISCOVERY_PREFIX}/{component_type}/hcpy/{device_ident}_{name}/config" + state_topic = f"{mqtt_topic}/{feature_type}/{name}" + # print(discovery_topic, state_topic) + + discovery_payload = json.dumps({ + "name": decamelcase(name), + "device": device_info, + "state_topic": state_topic, + "object_id": f"{device_ident}_{name}", + "unique_id": f"{device_ident}_{name}", + }) + print(discovery_topic) + # print(discovery_payload) + + client.publish(discovery_topic, discovery_payload, retain=False) # TODO make retain=True when stable diff --git a/hc2mqtt.py b/hc2mqtt.py index 53ee0b5..76b46ce 100755 --- a/hc2mqtt.py +++ b/hc2mqtt.py @@ -13,6 +13,7 @@ import paho.mqtt.client as mqtt from HCDevice import HCDevice from HCSocket import HCSocket, now +from HADiscovery import publish_ha_states, publish_ha_discovery @click.command() @@ -29,6 +30,7 @@ from HCSocket import HCSocket, now @click.option("--mqtt_clientname", default="hcpy1") @click.option("--domain_suffix", default="") @click.option("--debug/--no-debug", default=False) +@click.option("--ha-discovery", is_flag=True) @click_config_file.configuration_option() def hc2mqtt( devices_file: str, @@ -44,6 +46,7 @@ def hc2mqtt( mqtt_clientname: str, domain_suffix: str, debug: bool, + ha_discovery: bool ): def on_connect(client, userdata, flags, rc): @@ -74,6 +77,8 @@ def hc2mqtt( now(), device["name"], f"program topic: {mqtt_selected_program_topic}" ) client.subscribe(mqtt_selected_program_topic) + if ha_discovery: + publish_ha_discovery(device, client, mqtt_topic) else: print(now(), f"ERROR MQTT connection failed: {rc}") @@ -117,7 +122,7 @@ def hc2mqtt( f"Hello {devices_file=} {mqtt_host=} {mqtt_prefix=} " f"{mqtt_port=} {mqtt_username=} {mqtt_password=} " f"{mqtt_ssl=} {mqtt_cafile=} {mqtt_certfile=} {mqtt_keyfile=} {mqtt_clientname=}" - f"{domain_suffix=} {debug=}" + f"{domain_suffix=} {debug=} {ha_discovery=}" ) with open(devices_file, "r") as f: @@ -146,9 +151,9 @@ def hc2mqtt( client.connect(host=mqtt_host, port=mqtt_port, keepalive=70) for device in devices: - mqtt_topic = mqtt_prefix + device["name"] + mqtt_topic = mqtt_prefix + device["host"] thread = Thread( - target=client_connect, args=(client, device, mqtt_topic, domain_suffix, debug) + target=client_connect, args=(client, device, mqtt_topic, domain_suffix, debug, ha_discovery) ) thread.start() @@ -159,7 +164,7 @@ global dev dev = {} -def client_connect(client, device, mqtt_topic, domain_suffix, debug): +def client_connect(client, device, mqtt_topic, domain_suffix, debug, ha_discovery): host = device["host"] name = device["name"] @@ -193,6 +198,8 @@ def client_connect(client, device, mqtt_topic, domain_suffix, debug): msg = json.dumps(state) print(now(), name, f"publish to {mqtt_topic} with {msg}") client.publish(f"{mqtt_topic}/state", msg, retain=True) + if ha_discovery: + publish_ha_states(state, client, mqtt_topic) else: print( now(),