From 515519bc8745a627dd02cf33c53933d5d4e4eeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= Date: Mon, 15 Nov 2021 15:49:18 +0100 Subject: [PATCH] Provide an option to select MQTT unique_id generator (#2701) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/mqtt/__init__.py | 23 ++++++++++++++++++++-- esphome/components/mqtt/mqtt_client.cpp | 4 +++- esphome/components/mqtt/mqtt_client.h | 11 ++++++++++- esphome/components/mqtt/mqtt_component.cpp | 14 ++++++++++--- esphome/const.py | 1 + tests/test1.yaml | 1 + 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 0f7d246473..73af0bad90 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, + CONF_DISCOVERY_UNIQUE_ID_GENERATOR, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) +MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") +MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { + "legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR, + "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +} + def validate_config(value): # Populate default fields @@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( + MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS + ), cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -231,13 +241,22 @@ async def to_code(config): discovery = config[CONF_DISCOVERY] discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_prefix = config[CONF_DISCOVERY_PREFIX] + discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] if not discovery: cg.add(var.disable_discovery()) elif discovery == "CLEAN": - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain, True + ) + ) elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: - cg.add(var.set_discovery_info(discovery_prefix, discovery_retain)) + cg.add( + var.set_discovery_info( + discovery_prefix, discovery_unique_id_generator, discovery_retain + ) + ) cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 040b0001fe..43c49e9f7f 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) { void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); } -void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) { +void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, + bool retain, bool clean) { this->discovery_info_.prefix = std::move(prefix); + this->discovery_info_.unique_id_generator = unique_id_generator; this->discovery_info_.retain = retain; this->discovery_info_.clean = clean; } diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index fa689eaa04..d6194da794 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -55,6 +55,12 @@ struct Availability { std::string payload_not_available; }; +/// available discovery unique_id generators +enum MQTTDiscoveryUniqueIdGenerator { + MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0, + MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, +}; + /** Internal struct for MQTT Home Assistant discovery * * See MQTT Discovery. @@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo { std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled. bool retain; ///< Whether to retain discovery messages. bool clean; + MQTTDiscoveryUniqueIdGenerator unique_id_generator; }; enum MQTTClientState { @@ -98,9 +105,11 @@ class MQTTClientComponent : public Component { * * See MQTT Discovery. * @param prefix The Home Assistant discovery prefix. + * @param unique_id_generator Controls how UniqueId is generated. * @param retain Whether to retain discovery messages. */ - void set_discovery_info(std::string &&prefix, bool retain, bool clean = false); + void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain, + bool clean = false); /// Get Home Assistant discovery info. const MQTTDiscoveryInfo &get_discovery_info() const; /// Globally disable Home Assistant discovery. diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index e3ae4dea50..bf9f5e34b8 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() { if (!unique_id.empty()) { root[MQTT_UNIQUE_ID] = unique_id; } else { - // default to almost-unique ID. It's a hack but the only way to get that - // gorgeous device registry view. - root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); + if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { + char friendly_name_hash[9]; + sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name())); + friendly_name_hash[8] = 0; // ensure the hash-string ends with null + root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; + } else { + // default to almost-unique ID. It's a hack but the only way to get that + // gorgeous device registry view. + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + } } JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); diff --git a/esphome/const.py b/esphome/const.py index fb241f04b5..9a778edfeb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -167,6 +167,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" +CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" CONF_DISTANCE = "distance" CONF_DITHER = "dither" CONF_DIV_RATIO = "div_ratio" diff --git a/tests/test1.yaml b/tests/test1.yaml index 263754dc4f..18c6610b08 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -98,6 +98,7 @@ mqtt: discovery: True discovery_retain: False discovery_prefix: discovery + discovery_unique_id_generator: legacy topic_prefix: helloworld log_topic: topic: helloworld/hi