Enhance homeassistant discovery (V5) (#1275)

* manual re-creation of #1246

* removed non-working approach to get interval

* postpone the MQTT service start until the ClassFlowControll::ReadParameter() gets called

* Increase the max_uri_handlers to 35 (was 30)

* removed newlines in logs

* added parameter to UI

* Register handler to re-start MQTT Discovery

* fix param
This commit is contained in:
CaCO3
2022-11-05 19:32:58 +01:00
committed by GitHub
parent 22fe50f80a
commit 3e082ed06e
10 changed files with 429 additions and 193 deletions

View File

@@ -1,4 +1,5 @@
#include <sstream>
#include <iomanip>
#include "ClassFlowMQTT.h"
#include "Helper.h"
#include "connect_wlan.h"
@@ -9,11 +10,12 @@
#include "ClassFlowPostProcessing.h"
#include "ClassFlowControll.h"
#include "server_mqtt.h"
#include <time.h>
#define __HIDE_PASSWORD
static const char *TAG = "class_flow_MQTT";
#define __HIDE_PASSWORD
#define LWT_TOPIC "connection"
#define LWT_CONNECTED "connected"
@@ -23,147 +25,6 @@ extern const char* libfive_git_version(void);
extern const char* libfive_git_revision(void);
extern const char* libfive_git_branch(void);
extern float AutoIntervalShared;
std::vector<NumberPost*>* NUMBERS;
bool HomeassistantDiscovery = false;
void sendHomeAssistantDiscoveryTopic(std::string maintopic, std::string group, std::string field,
std::string name, std::string icon, std::string unit, std::string deviceClass, std::string stateClass) {
std::string version = std::string(libfive_git_version());
if (version == "") {
version = std::string(libfive_git_branch()) + " (" + std::string(libfive_git_revision()) + ")";
}
std::string topic;
std::string topicFull;
std::string topicT;
std::string payload;
std::string nl = "\n";
if (group == "") {
topic = field;
topicT = field;
}
else {
topic = group + "/" + field;
topicT = group + "_" + field;
}
if (group != "") { // Prepend the group to the name
name = group + " " + name;
}
topicFull = "homeassistant/sensor/" + maintopic + "/" + topicT + "/config";
/* See https://www.home-assistant.io/docs/mqtt/discovery/ */
payload = "{" + nl +
"\"~\": \"" + maintopic + "\"," + nl +
"\"unique_id\": \"" + maintopic + "-" + topicT + "\"," + nl +
"\"object_id\": \"" + maintopic + "_" + topicT + "\"," + nl + // This used to generate the Entity ID
"\"name\": \"" + name + "\"," + nl +
"\"icon\": \"mdi:" + icon + "\"," + nl;
if (group != "") {
if (field == "problem") { // Special binary sensor which is based on error topic
payload += "\"state_topic\": \"~/" + group + "/error\"," + nl;
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\"," + nl;
}
else {
payload += "\"state_topic\": \"~/" + group + "/" + field + "\"," + nl;
}
}
else {
payload += "\"state_topic\": \"~/" + field + "\"," + nl;
}
if (unit != "") {
payload += "\"unit_of_meas\": \"" + unit + "\"," + nl;
}
if (deviceClass != "") {
payload += "\"device_class\": \"" + deviceClass + "\"," + nl;
/* if (deviceClass == "problem") {
payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\"," + nl;
}*/
}
if (stateClass != "") {
payload += "\"state_class\": \"" + stateClass + "\"," + nl;
}
payload +=
"\"availability_topic\": \"~/" + std::string(LWT_TOPIC) + "\"," + nl +
"\"payload_available\": \"" + LWT_CONNECTED + "\"," + nl +
"\"payload_not_available\": \"" + LWT_DISCONNECTED + "\"," + nl;
payload +=
"\"device\": {" + nl +
"\"identifiers\": [\"" + maintopic + "\"]," + nl +
"\"name\": \"" + maintopic + "\"," + nl +
"\"model\": \"Meter Digitizer\"," + nl +
"\"manufacturer\": \"AI on the Edge Device\"," + nl +
"\"sw_version\": \"" + version + "\"," + nl +
"\"configuration_url\": \"http://" + *getIPAddress() + "\"" + nl +
"}" + nl +
"}" + nl;
MQTTPublish(topicFull, payload, true);
}
void MQTThomeassistantDiscovery(std::string maintopic) {
LogFile.WriteToFile(ESP_LOG_INFO, "MQTT - Sending Homeassistant Discovery Topics...");
// maintopic group field User Friendly Name icon unit Device Class State Class
sendHomeAssistantDiscoveryTopic(maintopic, "", "uptime", "Uptime", "clock-time-eight-outline", "s", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, "", "IP", "IP", "network-outline", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, "", "MAC", "MAC Address", "network-outline", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, "", "hostname", "Hostname", "network-outline", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, "", "freeMem", "Free Memory", "memory", "B", "", "measurement");
sendHomeAssistantDiscoveryTopic(maintopic, "", "wifiRSSI", "Wi-Fi RSSI", "wifi", "dBm", "signal_strength", "");
sendHomeAssistantDiscoveryTopic(maintopic, "", "CPUtemp", "CPU Temperature", "thermometer", "°C", "temperature", "measurement");
for (int i = 0; i < (*NUMBERS).size(); ++i) {
// maintopic group field User Friendly Name icon unit Device Class State Class
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "value", "Value", "gauge", "", "", "total_increasing");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "error", "Error", "alert-circle-outline", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "rate", "Rate", "swap-vertical", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "changeabsolut", "Absolute Change", "arrow-expand-vertical", "", "", "measurement");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "raw", "Raw Value", "raw", "", "", "total_increasing");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "timestamp", "Timestamp", "clock-time-eight-outline", "", "timestamp", "");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "json", "JSON", "code-json", "", "", "");
sendHomeAssistantDiscoveryTopic(maintopic, (*NUMBERS)[i]->name, "problem", "Problem", "alert-outline", "", "", ""); // Special binary sensor which is based on error topic
}
}
void publishRuntimeData(std::string maintopic, int SetRetainFlag) {
char tmp_char[50];
sprintf(tmp_char, "%ld", (long)getUpTime());
MQTTPublish(maintopic + "/" + "uptime", std::string(tmp_char), SetRetainFlag);
sprintf(tmp_char, "%zu", esp_get_free_heap_size());
MQTTPublish(maintopic + "/" + "freeMem", std::string(tmp_char), SetRetainFlag);
sprintf(tmp_char, "%d", get_WIFI_RSSI());
MQTTPublish(maintopic + "/" + "wifiRSSI", std::string(tmp_char), SetRetainFlag);
sprintf(tmp_char, "%d", (int)temperatureRead());
MQTTPublish(maintopic + "/" + "CPUtemp", std::string(tmp_char), SetRetainFlag);
}
void GotConnected(std::string maintopic, int SetRetainFlag) {
if (HomeassistantDiscovery) {
MQTThomeassistantDiscovery(maintopic);
}
MQTTPublish(maintopic + "/" + "MAC", getMac(), SetRetainFlag);
MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), SetRetainFlag);
MQTTPublish(maintopic + "/" + "hostname", hostname, SetRetainFlag);
publishRuntimeData(maintopic, SetRetainFlag);
}
void ClassFlowMQTT::SetInitialParameter(void)
{
@@ -207,12 +68,6 @@ ClassFlowMQTT::ClassFlowMQTT(std::vector<ClassFlow*>* lfc)
flowpostprocessing = (ClassFlowPostProcessing*) (*ListFlowControll)[i];
}
}
NUMBERS = flowpostprocessing->GetNumbers();
keepAlive = AutoIntervalShared * 60 * 2.5; // TODO find better way to access AutoIntervall in ClassFlowControll
LogFile.WriteToFile(ESP_LOG_INFO, "Digitizer interval is " + std::to_string(AutoIntervalShared) +
" minutes => setting MQTT LWT timeout to " + std::to_string(keepAlive/60) + " minutes.");
}
ClassFlowMQTT::ClassFlowMQTT(std::vector<ClassFlow*>* lfc, ClassFlow *_prev)
@@ -262,13 +117,46 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
}
if ((toUpper(zerlegt[0]) == "SETRETAINFLAG") && (zerlegt.size() > 1))
{
if (toUpper(zerlegt[1]) == "TRUE")
if (toUpper(zerlegt[1]) == "TRUE") {
SetRetainFlag = 1;
setMqtt_Server_Retain(SetRetainFlag);
}
}
if ((toUpper(zerlegt[0]) == "HOMEASSISTANTDISCOVERY") && (zerlegt.size() > 1))
{
if (toUpper(zerlegt[1]) == "TRUE")
HomeassistantDiscovery = true;
SetHomeassistantDiscoveryEnabled(true);
}
if ((toUpper(zerlegt[0]) == "METERTYPE") && (zerlegt.size() > 1)) {
/* Use meter type for the device class
Make sure it is a listed one on https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes */
if (toUpper(zerlegt[1]) == "WATER_M3") {
mqttServer_setMeterType("water", "", "h", "m³/h");
}
else if (toUpper(zerlegt[1]) == "WATER_L") {
mqttServer_setMeterType("water", "L", "h", "L/h");
}
else if (toUpper(zerlegt[1]) == "WATER_FT3") {
mqttServer_setMeterType("water", "ft³", "m", "ft³/m"); // Minutes
}
else if (toUpper(zerlegt[1]) == "WATER_GAL") {
mqttServer_setMeterType("water", "gal", "h", "gal/h");
}
else if (toUpper(zerlegt[1]) == "GAS_M3") {
mqttServer_setMeterType("gas", "", "h", "m³/h");
}
else if (toUpper(zerlegt[1]) == "GAS_FT3") {
mqttServer_setMeterType("gas", "ft³", "m", "ft³/m"); // Minutes
}
else if (toUpper(zerlegt[1]) == "ENERGY_WH") {
mqttServer_setMeterType("energy", "Wh", "h", "W");
}
else if (toUpper(zerlegt[1]) == "ENERGY_KWH") {
mqttServer_setMeterType("energy", "kWh", "h", "kW");
}
else if (toUpper(zerlegt[1]) == "ENERGY_MWH") {
mqttServer_setMeterType("energy", "MWh", "h", "MW");
}
}
if ((toUpper(zerlegt[0]) == "CLIENTID") && (zerlegt.size() > 1))
@@ -279,16 +167,15 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
if (((toUpper(zerlegt[0]) == "TOPIC") || (toUpper(zerlegt[0]) == "MAINTOPIC")) && (zerlegt.size() > 1))
{
maintopic = zerlegt[1];
mqttServer_setMainTopic(maintopic);
}
}
MQTT_Configure(uri, clientname, user, password, maintopic, LWT_TOPIC, LWT_CONNECTED, LWT_DISCONNECTED, keepAlive, SetRetainFlag, (void *)&GotConnected);
/* Note:
* Originally, we started the MQTT client here.
* How ever we need the interval parameter from the ClassFlowControll, but that only gets started later.
* To work around this, we delay the start and trigger it from ClassFlowControll::ReadParameter() */
if (!MQTT_Init()) {
if (!MQTT_Init()) { // Retry
return false;
}
}
return true;
}
@@ -299,18 +186,44 @@ string ClassFlowMQTT::GetMQTTMainTopic()
}
bool ClassFlowMQTT::Start(float AutoIntervall) {
roundInterval = AutoIntervall; // Minutes
keepAlive = roundInterval * 60 * 2.5; // Seconds, make sure it is greater thatn 2 rounds!
std::stringstream stream;
stream << std::fixed << std::setprecision(1) << "Digitizer interval is " << roundInterval <<
" minutes => setting MQTT LWT timeout to " << ((float)keepAlive/60) << " minutes.";
LogFile.WriteToFile(ESP_LOG_INFO, stream.str());
mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval);
MQTT_Configure(uri, clientname, user, password, maintopic, LWT_TOPIC, LWT_CONNECTED, LWT_DISCONNECTED,
keepAlive, SetRetainFlag, (void *)&GotConnected);
if (!MQTT_Init()) {
if (!MQTT_Init()) { // Retry
return false;
}
}
return true;
}
bool ClassFlowMQTT::doFlow(string zwtime)
{
std::string result;
std::string resulterror = "";
std::string resultraw = "";
std::string resultrate = "";
std::string resultrate = ""; // Always Unit / Minute
std::string resultRatePerTimeUnit = ""; // According to selection
std::string resulttimestamp = "";
std::string resultchangabs = "";
string zw = "";
string namenumber = "";
publishRuntimeData(maintopic, SetRetainFlag);
publishSystemData();
if (flowpostprocessing)
{
@@ -321,8 +234,8 @@ bool ClassFlowMQTT::doFlow(string zwtime)
result = (*NUMBERS)[i]->ReturnValue;
resultraw = (*NUMBERS)[i]->ReturnRawValue;
resulterror = (*NUMBERS)[i]->ErrorMessageText;
resultrate = (*NUMBERS)[i]->ReturnRateValue;
resultchangabs = (*NUMBERS)[i]->ReturnChangeAbsolute;
resultrate = (*NUMBERS)[i]->ReturnRateValue; // Unit per minutes
resultchangabs = (*NUMBERS)[i]->ReturnChangeAbsolute; // Units per round
resulttimestamp = (*NUMBERS)[i]->timeStamp;
namenumber = (*NUMBERS)[i]->name;
@@ -331,17 +244,31 @@ bool ClassFlowMQTT::doFlow(string zwtime)
else
namenumber = maintopic + "/" + namenumber + "/";
LogFile.WriteToFile(ESP_LOG_INFO, "Publishing MQTT topics...");
if (result.length() > 0)
MQTTPublish(namenumber + "value", result, SetRetainFlag);
if (resulterror.length() > 0)
MQTTPublish(namenumber + "error", resulterror, SetRetainFlag);
if (resultrate.length() > 0)
if (resultrate.length() > 0) {
MQTTPublish(namenumber + "rate", resultrate, SetRetainFlag);
std::string resultRatePerTimeUnit;
if (getTimeUnit() == "h") { // Need conversion to be per hour
resultRatePerTimeUnit = resultRatePerTimeUnit = to_string((*NUMBERS)[i]->FlowRateAct / 60); // per minutes => per hour
}
else { // Keep per minute
resultRatePerTimeUnit = resultrate;
}
MQTTPublish(namenumber + "rate_per_time_unit", resultRatePerTimeUnit, SetRetainFlag);
}
if (resultchangabs.length() > 0)
MQTTPublish(namenumber + "changeabsolut", resultchangabs, SetRetainFlag);
if (resultchangabs.length() > 0) {
MQTTPublish(namenumber + "changeabsolut", resultchangabs, SetRetainFlag); // Legacy API
MQTTPublish(namenumber + "rate_per_digitalization_round", resultchangabs, SetRetainFlag);
}
if (resultraw.length() > 0)
MQTTPublish(namenumber + "raw", resultraw, SetRetainFlag);
@@ -349,7 +276,6 @@ bool ClassFlowMQTT::doFlow(string zwtime)
if (resulttimestamp.length() > 0)
MQTTPublish(namenumber + "timestamp", resulttimestamp, SetRetainFlag);
std::string json = "";
if (result.length() > 0)
@@ -359,10 +285,12 @@ bool ClassFlowMQTT::doFlow(string zwtime)
json += ",\"raw\":\""+resultraw;
json += "\",\"error\":\""+resulterror;
if (resultrate.length() > 0)
json += "\",\"rate\":"+resultrate;
else
json += "\",\"rate\":\"\"";
json += ",\"timestamp\":\""+resulttimestamp+"\"}";
MQTTPublish(namenumber + "json", json, SetRetainFlag);