From 84248ec9b65e18a224a77d02d273bc0fb73548f0 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sun, 17 Dec 2023 14:28:07 +0100 Subject: [PATCH] Feature: Add DTU to Home Assistant Auto Discovery This is based on PR 1365 from @CFenner with several fixes and optimizations --- include/MqttHandleHass.h | 13 +++- src/MqttHandleHass.cpp | 158 ++++++++++++++++++++++++++++++++++----- 2 files changed, 152 insertions(+), 19 deletions(-) diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index 02ff8bd..41f7bf8 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -58,11 +58,20 @@ public: private: void loop(); void publish(const String& subtopic, const String& payload); - void publishField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false); + void publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic); + void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = ""); + void publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false); void publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload); void publishInverterNumber(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100); void publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); - void createDeviceInfo(JsonObject& object, std::shared_ptr inv); + + static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr inv); + static void createDtuInfo(DynamicJsonDocument& doc); + + static void createDeviceInfo(DynamicJsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = ""); + + static String getDtuUniqueId(); + static String getDtuUrl(); Task _loopTask; diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index cb10230..88553e1 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -6,6 +6,7 @@ #include "MqttHandleInverter.h" #include "MqttSettings.h" #include "NetworkSettings.h" +#include "Utils.h" #include "defaults.h" MqttHandleHassClass MqttHandleHass; @@ -52,6 +53,14 @@ void MqttHandleHassClass::publishConfig() const CONFIG_T& config = Configuration.get(); + // publish DTU sensors + publishDtuSensor("IP", "", "diagnostic", "mdi:network-outline", "", ""); + publishDtuSensor("WiFi Signal", "signal_strength", "diagnostic", "", "dBm", "rssi"); + publishDtuSensor("Uptime", "duration", "diagnostic", "", "s", ""); + publishDtuBinarySensor("Status", "connectivity", "diagnostic", config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, config.Mqtt.Lwt.Topic); + + yield(); + // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -77,7 +86,7 @@ void MqttHandleHassClass::publishConfig() if (t == TYPE_DC && !config.Mqtt.Hass.IndividualPanels) { clear = true; } - publishField(inv, t, c, deviceFieldAssignment[f], clear); + publishInverterField(inv, t, c, deviceFieldAssignment[f], clear); } } } @@ -86,7 +95,7 @@ void MqttHandleHassClass::publishConfig() } } -void MqttHandleHassClass::publishField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear) +void MqttHandleHassClass::publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear) { if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldType.fieldId)) { return; @@ -135,8 +144,7 @@ void MqttHandleHassClass::publishField(std::shared_ptr inv, co root["unit_of_meas"] = unit_of_measure; } - JsonObject deviceObj = root.createNestedObject("dev"); - createDeviceInfo(deviceObj, inv); + createInverterInfo(root, inv); if (Configuration.get().Mqtt.Hass.Expire) { root["exp_aft"] = Hoymiles.getNumInverters() * max(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold(); @@ -183,8 +191,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv) +void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic) { - object["name"] = inv->name(); - object["ids"] = inv->serialString(); - object["cu"] = String("http://") + NetworkSettings.localIP().toString(); - object["mf"] = "OpenDTU"; - object["mdl"] = inv->typeName(); - object["sw"] = AUTO_GIT_HASH; + String id = name; + id.toLowerCase(); + id.replace(" ", "_"); + String topic = subTopic; + if (topic == "") { + topic = id; + } + + DynamicJsonDocument root(1024); + root["name"] = name; + root["uniq_id"] = getDtuUniqueId() + "_" + id; + if (strcmp(device_class, "")) { + root["dev_cla"] = device_class; + } + if (strcmp(category, "")) { + root["ent_cat"] = category; + } + if (strcmp(icon, "")) { + root["ic"] = icon; + } + if (strcmp(unit_of_measure, "")) { + root["unit_of_meas"] = unit_of_measure; + } + root["stat_t"] = MqttSettings.getPrefix() + "dtu" + "/" + topic; + + root["avty_t"] = MqttSettings.getPrefix() + Configuration.get().Mqtt.Lwt.Topic; + + const CONFIG_T& config = Configuration.get(); + root["pl_avail"] = config.Mqtt.Lwt.Value_Online; + root["pl_not_avail"] = config.Mqtt.Lwt.Value_Offline; + + createDtuInfo(root); + + String buffer; + const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config"; + serializeJson(root, buffer); + publish(configTopic, buffer); +} + +void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic) +{ + String id = name; + id.toLowerCase(); + id.replace(" ", "_"); + + String topic = subTopic; + if (!strcmp(subTopic, "")) { + topic = String("dtu/") + "/" + id; + } + + DynamicJsonDocument root(1024); + root["name"] = name; + root["uniq_id"] = getDtuUniqueId() + "_" + id; + root["stat_t"] = MqttSettings.getPrefix() + topic; + root["pl_on"] = payload_on; + root["pl_off"] = payload_off; + + if (strcmp(device_class, "")) { + root["dev_cla"] = device_class; + } + if (strcmp(category, "")) { + root["ent_cat"] = category; + } + + createDtuInfo(root); + + String buffer; + const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config"; + serializeJson(root, buffer); + publish(configTopic, buffer); +} + +void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::shared_ptr inv) +{ + createDeviceInfo( + root, + inv->name(), + inv->serialString(), + getDtuUrl(), + "OpenDTU", + inv->typeName(), + AUTO_GIT_HASH, + getDtuUniqueId()); +} + +void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root) +{ + createDeviceInfo( + root, + NetworkSettings.getHostname(), + getDtuUniqueId(), + getDtuUrl(), + "OpenDTU", + "OpenDTU", + AUTO_GIT_HASH); +} + +void MqttHandleHassClass::createDeviceInfo( + DynamicJsonDocument& root, + const String& name, const String& identifiers, const String& configuration_url, + const String& manufacturer, const String& model, const String& sw_version, + const String& via_device) +{ + auto object = root.createNestedObject("dev"); + + object["name"] = name; + object["ids"] = identifiers; + object["cu"] = configuration_url; + object["mf"] = manufacturer; + object["mdl"] = model; + object["sw"] = sw_version; + + if (via_device != "") { + object["via_device"] = via_device; + } +} + +String MqttHandleHassClass::getDtuUniqueId() +{ + return NetworkSettings.getHostname() + "_" + Utils::getChipId(); +} + +String MqttHandleHassClass::getDtuUrl() +{ + return String("http://") + NetworkSettings.localIP().toString(); } void MqttHandleHassClass::publish(const String& subtopic, const String& payload)