Files
hcpy/HADiscovery.py
James Muscat 5a2611967b Move MQTT discovery information into devices.json.
We inject some "magic" overrides when `devices.json` is created, rather
than at runtime; this means that users can update the discovery
information directly in their `devices.json` configuration rather than
in the code. This way, they can change existing or add new discovery
information without needing a code change.
2024-08-11 16:17:05 +01:00

161 lines
5.1 KiB
Python

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"
# These "magic overrides" provide HA MQTT autodiscovery data that is injected
# into devices.json when `hc-login.py` is run; the data is then read from
# devices.json at runtime.
#
# Note: keys should be integer (not string) taken from a device's feature
# mapping.
MAGIC_OVERRIDES = {
5: { # BSH.Common.Status.BackendConnected
"component_type": "binary_sensor",
"payload_values": {
"device_class": "connectivity",
"payload_on": True,
"payload_off": False
}
},
21: { # BSH.Common.Event.SoftwareUpdateAvailable
"component_type": "binary_sensor",
"payload_values": {
"device_class": "update",
"payload_on": "Present"
}
},
527: { # BSH.Common.Status.DoorState
"component_type": "binary_sensor",
"payload_values": {
"device_class": "door",
"payload_on": "Open",
"payload_off": "Closed"
}
},
539: { # BSH.Common.Setting.PowerState
"component_type": "binary_sensor",
"payload_values": {
"device_class": "power"
}
},
4612: { # Dishcare.Dishwasher.Event.WaterheaterCalcified
"component_type": "binary_sensor",
"payload_values": {
"device_class": "problem",
"payload_on": "Present"
}
},
5624: { # Dishcare.Dishwasher.Event.SaltLack
"component_type": "binary_sensor",
"payload_values": {
"device_class": "problem",
"payload_on": "Present"
}
},
5625: { # Dishcare.Dishwasher.Event.RinseAidLack
"component_type": "binary_sensor",
"payload_values": {
"device_class": "problem",
"payload_on": "Present"
}
},
4626: { # Dishcare.Dishwasher.Event.SaltNearlyEmpty
"component_type": "binary_sensor",
"payload_values": {
"device_class": "problem",
"payload_on": "Present"
}
},
5627: { # Dishcare.Dishwasher.Event.RinseAidNearlyEmpty
"component_type": "binary_sensor",
"payload_values": {
"device_class": "problem",
"payload_on": "Present"
}
},
}
def augment_device_features(features):
for id, feature in features.items():
if id in MAGIC_OVERRIDES:
feature["discovery"] = MAGIC_OVERRIDES[id]
return features
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():
name_parts = feature["name"].split(".")
name = name_parts[-1]
feature_type = name_parts[-2]
access = feature.get("access", "none")
available = feature.get("available", False)
if (feature_type == "Setting" and available and (access == "read" or access == "readWrite")) or \
(feature_type == "Status" and available and (access == "read" or access == "readWrite")) or \
feature_type == "Event" or \
feature_type == "Option":
default_component_type = "binary_sensor" if feature_type == "Event" else "sensor" # TODO use more appropriate types
overrides = feature.get("discovery", {})
component_type = overrides.get("component_type", default_component_type)
extra_payload_values = overrides.get("payload_values", {})
discovery_topic = f"{HA_DISCOVERY_PREFIX}/{component_type}/hcpy/{device_ident}_{name}/config"
# print(discovery_topic, state_topic)
discovery_payload = {
"name": decamelcase(name),
"device": device_info,
"state_topic": f"{mqtt_topic}/state",
# "availability_topic": f"{mqtt_topic}/LWT",
"value_template": "{{value_json." + name + " | default('unavailable')}}",
"object_id": f"{device_ident}_{name}",
"unique_id": f"{device_ident}_{name}",
**extra_payload_values
}
if component_type == "binary_sensor":
discovery_payload.setdefault("payload_on", "On")
discovery_payload.setdefault("payload_off", "Off")
# print(discovery_topic)
# print(discovery_payload)
client.publish(discovery_topic, json.dumps(discovery_payload), retain=True)