Feature: Add DTU to Home Assistant Auto Discovery

This is based on PR 1365 from @CFenner with several fixes and optimizations
This commit is contained in:
Thomas Basler 2023-12-17 14:28:07 +01:00
parent e0cc1559d0
commit 84248ec9b6
2 changed files with 152 additions and 19 deletions

View File

@ -58,11 +58,20 @@ public:
private: private:
void loop(); void loop();
void publish(const String& subtopic, const String& payload); void publish(const String& subtopic, const String& payload);
void publishField(std::shared_ptr<InverterAbstract> 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<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload); void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
void publishInverterNumber(std::shared_ptr<InverterAbstract> 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 publishInverterNumber(std::shared_ptr<InverterAbstract> 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<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
void createDeviceInfo(JsonObject& object, std::shared_ptr<InverterAbstract> inv);
static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr<InverterAbstract> 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; Task _loopTask;

View File

@ -6,6 +6,7 @@
#include "MqttHandleInverter.h" #include "MqttHandleInverter.h"
#include "MqttSettings.h" #include "MqttSettings.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "Utils.h"
#include "defaults.h" #include "defaults.h"
MqttHandleHassClass MqttHandleHass; MqttHandleHassClass MqttHandleHass;
@ -52,6 +53,14 @@ void MqttHandleHassClass::publishConfig()
const CONFIG_T& config = Configuration.get(); 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 // Loop all inverters
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i); auto inv = Hoymiles.getInverterByPos(i);
@ -77,7 +86,7 @@ void MqttHandleHassClass::publishConfig()
if (t == TYPE_DC && !config.Mqtt.Hass.IndividualPanels) { if (t == TYPE_DC && !config.Mqtt.Hass.IndividualPanels) {
clear = true; 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<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear) void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract> 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)) { if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldType.fieldId)) {
return; return;
@ -135,8 +144,7 @@ void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, co
root["unit_of_meas"] = unit_of_measure; root["unit_of_meas"] = unit_of_measure;
} }
JsonObject deviceObj = root.createNestedObject("dev"); createInverterInfo(root, inv);
createDeviceInfo(deviceObj, inv);
if (Configuration.get().Mqtt.Hass.Expire) { if (Configuration.get().Mqtt.Hass.Expire) {
root["exp_aft"] = Hoymiles.getNumInverters() * max<uint32_t>(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold(); root["exp_aft"] = Hoymiles.getNumInverters() * max<uint32_t>(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold();
@ -183,8 +191,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
root["cmd_t"] = cmdTopic; root["cmd_t"] = cmdTopic;
root["payload_press"] = payload; root["payload_press"] = payload;
JsonObject deviceObj = root.createNestedObject("dev"); createInverterInfo(root, inv);
createDeviceInfo(deviceObj, inv);
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
@ -222,8 +229,7 @@ void MqttHandleHassClass::publishInverterNumber(
root["min"] = min; root["min"] = min;
root["max"] = max; root["max"] = max;
JsonObject deviceObj = root.createNestedObject("dev"); createInverterInfo(root, inv);
createDeviceInfo(deviceObj, inv);
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
@ -251,22 +257,140 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
root["pl_on"] = payload_on; root["pl_on"] = payload_on;
root["pl_off"] = payload_off; root["pl_off"] = payload_off;
JsonObject deviceObj = root.createNestedObject("dev"); createInverterInfo(root, inv);
createDeviceInfo(deviceObj, inv);
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
} }
void MqttHandleHassClass::createDeviceInfo(JsonObject& object, std::shared_ptr<InverterAbstract> 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(); String id = name;
object["ids"] = inv->serialString(); id.toLowerCase();
object["cu"] = String("http://") + NetworkSettings.localIP().toString(); id.replace(" ", "_");
object["mf"] = "OpenDTU"; String topic = subTopic;
object["mdl"] = inv->typeName(); if (topic == "") {
object["sw"] = AUTO_GIT_HASH; 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<InverterAbstract> 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) void MqttHandleHassClass::publish(const String& subtopic, const String& payload)