Bladeren bron

Merge branch 'rolling' of https://github.com/jomjol/AI-on-the-edge-device into rolling

jomjol 3 jaren geleden
bovenliggende
commit
23b5ffbb92

+ 6 - 3
code/components/jomjol_flowcontroll/ClassFlowControll.cpp

@@ -29,7 +29,6 @@ extern "C" {
 
 static const char* TAG = "flow_controll";
 
-float AutoIntervalShared = 10;
 
 
 std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _host){
@@ -142,7 +141,7 @@ void ClassFlowControll::SetInitialParameter(void)
 {
     AutoStart = false;
     SetupModeActive = false;
-    AutoIntervall = 10;
+    AutoIntervall = 10; // Minutes
     flowdigit = NULL;
     flowanalog = NULL;
     flowpostprocessing = NULL;
@@ -517,7 +516,11 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
         }
     }
 
-    AutoIntervalShared = AutoIntervall;
+    /* Start the MQTT service */
+    for (int i = 0; i < FlowControll.size(); ++i)
+        if (FlowControll[i]->name().compare("ClassFlowMQTT") == 0)
+            return ((ClassFlowMQTT*) (FlowControll[i]))->Start(AutoIntervall);
+
     return true;
 }
 

+ 93 - 165
code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp

@@ -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", "m³", "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", "m³", "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);

+ 3 - 1
code/components/jomjol_flowcontroll/ClassFlowMQTT.h

@@ -14,7 +14,8 @@ protected:
 	ClassFlowPostProcessing* flowpostprocessing;  
     std::string user, password; 
     int SetRetainFlag;
-    int keepAlive;
+    int keepAlive; // Seconds
+    float roundInterval; // Minutes
 
     std::string maintopic; 
 	void SetInitialParameter(void);        
@@ -25,6 +26,7 @@ public:
     ClassFlowMQTT(std::vector<ClassFlow*>* lfc, ClassFlow *_prev);
 
     string GetMQTTMainTopic();
+    bool Start(float AutoIntervall);
 
     bool ReadParameter(FILE* pfile, string& aktparamgraph);
     bool doFlow(string time);

+ 1 - 3
code/components/jomjol_mqtt/CMakeLists.txt

@@ -2,6 +2,4 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
 
 idf_component_register(SRCS ${app_sources}
                     INCLUDE_DIRS "."
-                    REQUIRES tflite-lib mqtt jomjol_logfile)
-
-
+                    REQUIRES tflite-lib mqtt  jomjol_tfliteclass jomjol_helper jomjol_mqtt jomjol_wlan)

+ 20 - 19
code/components/jomjol_mqtt/interface_mqtt.cpp

@@ -67,49 +67,50 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
     std::string topic = "";
     switch (event->event_id) {
         case MQTT_EVENT_BEFORE_CONNECT:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_BEFORE_CONNECT");
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_BEFORE_CONNECT");
             break;
         case MQTT_EVENT_CONNECTED:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_CONNECTED");
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_CONNECTED");
             mqtt_connected = true;
             MQTTconnected();
             break;
         case MQTT_EVENT_DISCONNECTED:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_DISCONNECTED");
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_DISCONNECTED");
+            LogFile.WriteToFile(ESP_LOG_WARN, "MQTT - Disconnected, going to re-connect...");
             mqtt_connected = false; // Force re-init on next call
             esp_mqtt_client_reconnect(client);
             break;
         case MQTT_EVENT_SUBSCRIBED:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
             msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
-            ESP_LOGI(TAG_INTERFACEMQTT, "sent publish successful, msg_id=%d", msg_id);
+            ESP_LOGD(TAG_INTERFACEMQTT, "sent publish successful, msg_id=%d", msg_id);
             break;
         case MQTT_EVENT_UNSUBSCRIBED:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
             break;
         case MQTT_EVENT_PUBLISHED:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
             break;
         case MQTT_EVENT_DATA:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_DATA");
-            ESP_LOGI(TAG_INTERFACEMQTT, "TOPIC=%.*s\r\n", event->topic_len, event->topic);
-            ESP_LOGI(TAG_INTERFACEMQTT, "DATA=%.*s\r\n", event->data_len, event->data);
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_DATA");
+            ESP_LOGD(TAG_INTERFACEMQTT, "TOPIC=%.*s", event->topic_len, event->topic);
+            ESP_LOGD(TAG_INTERFACEMQTT, "DATA=%.*s", event->data_len, event->data);
             topic.assign(event->topic, event->topic_len);
             if (subscribeFunktionMap != NULL) {
                 if (subscribeFunktionMap->find(topic) != subscribeFunktionMap->end()) {
-                    ESP_LOGD(TAG_INTERFACEMQTT, "call handler function\r\n");
+                    ESP_LOGD(TAG_INTERFACEMQTT, "call handler function");
                     (*subscribeFunktionMap)[topic](topic, event->data, event->data_len);
                 }
             } else {
-                ESP_LOGW(TAG_INTERFACEMQTT, "no handler available\r\n");
+                ESP_LOGW(TAG_INTERFACEMQTT, "no handler available");
             }
             break;
         case MQTT_EVENT_ERROR:
-            ESP_LOGI(TAG_INTERFACEMQTT, "MQTT_EVENT_ERROR");
+            ESP_LOGD(TAG_INTERFACEMQTT, "MQTT_EVENT_ERROR");
             mqtt_connected = false; // Force re-init on next call
             break;
         default:
-            ESP_LOGI(TAG_INTERFACEMQTT, "Other event id:%d", event->event_id);
+            ESP_LOGD(TAG_INTERFACEMQTT, "Other event id:%d", event->event_id);
             break;
     }
     return ESP_OK;
@@ -126,10 +127,10 @@ void MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us
         int _keepalive, int _SetRetainFlag, void *_callbackOnConnected){
 #ifdef __HIDE_PASSWORD
     LogFile.WriteToFile(ESP_LOG_INFO, "MQTT Configuration: uri: " + _mqttURI + ", clientname: " + _clientid + 
-            ", user: " + _user + ", password: XXXXXXXX, maintopic: " + _maintopic + ", last-will-topic: " + _maintopic + "/" + _lwt + ", keepAlive: " + std::to_string(_keepalive)); 
+            ", user: " + _user + ", password: XXXXXXXX, maintopic: " + _maintopic + ", last-will-topic: " + _maintopic + "/" + _lwt + ", keepAlive: " + std::to_string(_keepalive) + " s"); 
 #else
     LogFile.WriteToFile(ESP_LOG_INFO, "MQTT Configuration: uri: " + _mqttURI + ", clientname: " + _clientid + 
-            ", user: " + _user + ", password: " + _password + ", maintopic: " + _maintopic + ", last-will-topic: " + _maintopic + "/" + _lwt + ", keepAlive: " + std::to_string(_keepalive)); 
+            ", user: " + _user + ", password: " + _password + ", maintopic: " + _maintopic + ", last-will-topic: " + _maintopic + "/" + _lwt + ", keepAlive: " + std::to_string(_keepalive)+ " s"); 
 #endif
 
     uri = _mqttURI;
@@ -216,7 +217,7 @@ bool MQTTisConnected() {
 }
 
 void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
-    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisteronnectFunction %s\r\n", name.c_str());
+    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisteronnectFunction %s", name.c_str());
     if (connectFunktionMap == NULL) {
         connectFunktionMap = new std::map<std::string, std::function<void()>>();
     }
@@ -234,14 +235,14 @@ void MQTTregisterConnectFunction(std::string name, std::function<void()> func){
 }
 
 void MQTTunregisterConnectFunction(std::string name){
-    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisteronnectFunction %s\r\n", name.c_str());
+    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisteronnectFunction %s", name.c_str());
     if ((connectFunktionMap != NULL) && (connectFunktionMap->find(name) != connectFunktionMap->end())) {
         connectFunktionMap->erase(name);
     }
 }
 
 void MQTTregisterSubscribeFunction(std::string topic, std::function<bool(std::string, char*, int)> func){
-    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisterSubscribeFunction %s\r\n", topic.c_str());
+    ESP_LOGD(TAG_INTERFACEMQTT, "MQTTregisterSubscribeFunction %s", topic.c_str());
     if (subscribeFunktionMap == NULL) {
         subscribeFunktionMap = new std::map<std::string, std::function<bool(std::string, char*, int)>>();
     }

+ 251 - 0
code/components/jomjol_mqtt/server_mqtt.cpp

@@ -0,0 +1,251 @@
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <vector>
+
+#include "esp_log.h"
+#include "ClassLogFile.h"
+#include "connect_wlan.h"
+#include "server_mqtt.h"
+#include "interface_mqtt.h"
+#include "time_sntp.h"
+
+
+
+static const char *TAG = "MQTT SERVER";
+
+
+extern const char* libfive_git_version(void);
+extern const char* libfive_git_revision(void);
+extern const char* libfive_git_branch(void);
+
+std::vector<NumberPost*>* NUMBERS;
+bool HomeassistantDiscovery = false;
+std::string meterType = "";
+std::string valueUnit = "";
+std::string timeUnit = "";
+std::string rateUnit = "Unit/Minute";
+float roundInterval; // Minutes
+int keepAlive = 0; // Seconds
+int retainFlag;
+static std::string maintopic;
+
+
+void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int _keepAlive, float _roundInterval) {
+    NUMBERS = _NUMBERS;
+    keepAlive = _keepAlive;
+    roundInterval = _roundInterval; 
+}
+
+void mqttServer_setMeterType(std::string _meterType, std::string _valueUnit, std::string _timeUnit,std::string _rateUnit) {
+    meterType = _meterType;
+    valueUnit = _valueUnit;
+    timeUnit = _timeUnit;
+    rateUnit = _rateUnit;
+}
+
+void sendHomeAssistantDiscoveryTopic(std::string group, std::string field,
+    std::string name, std::string icon, std::string unit, std::string deviceClass, std::string stateClass, std::string entityCategory) {
+    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 ((*NUMBERS).size() > 1) { // There is more than one meter, prepend the group so we can differentiate them
+        if (group != "") { // But only if the group is set
+            name = group + " " + name;
+        }
+    }
+
+    if (field == "problem") { // Special binary sensor which is based on error topic
+        topicFull = "homeassistant/binary_sensor/" + maintopic + "/" + topicT + "/config";
+    }
+    else {
+        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 (stateClass != "") {
+        payload += "\"state_class\": \"" + stateClass + "\"," + nl;
+    } 
+
+    if (entityCategory != "") {
+        payload += "\"entity_category\": \"" + entityCategory + "\"," + 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() {    
+    LogFile.WriteToFile(ESP_LOG_INFO, "MQTT - Sending Homeassistant Discovery Topics (Meter Type: " + meterType + ", Value Unit: " + valueUnit + " , Rate Unit: " + rateUnit + ")...");
+
+    //                              Group | Field            | User Friendly Name | Icon                      | Unit | Device Class     | State Class  | Entity Category
+    sendHomeAssistantDiscoveryTopic("",     "uptime",          "Uptime",            "clock-time-eight-outline", "s",   "",                "",            "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "MAC",             "MAC Address",       "network-outline",          "",    "",                "",            "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "hostname",        "Hostname",          "network-outline",          "",    "",                "",            "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "freeMem",         "Free Memory",       "memory",                   "B",   "",                "measurement", "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "wifiRSSI",        "Wi-Fi RSSI",        "wifi",                     "dBm", "signal_strength", "",            "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "CPUtemp",         "CPU Temperature",   "thermometer",              "°C",  "temperature",     "measurement", "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "interval",        "Interval",          "clock-time-eight-outline", "min",  ""           ,    "measurement", "diagnostic");
+    sendHomeAssistantDiscoveryTopic("",     "IP",              "IP",                "network-outline",           "",    "",               "",            "diagnostic");
+
+    for (int i = 0; i < (*NUMBERS).size(); ++i) {
+         std::string group = (*NUMBERS)[i]->name;
+    //                                  Group | Field                 | User Friendly Name                | Icon                   | Unit     | Device Class | State Class       | Entity Category
+        sendHomeAssistantDiscoveryTopic(group,   "value",              "Value",                            "gauge",                 valueUnit, meterType,     "total_increasing", "");
+        sendHomeAssistantDiscoveryTopic(group,   "raw",                "Raw Value",                        "raw",                   valueUnit, "",            "total_increasing", "diagnostic");
+        sendHomeAssistantDiscoveryTopic(group,   "error",              "Error",                            "alert-circle-outline",  "",        "",            "",                 "diagnostic");
+        /* Not announcing "rate" as it is better to use rate_per_time_unit resp. rate_per_digitalization_round */
+        // sendHomeAssistantDiscoveryTopic(group,   "rate",               "Rate (Unit/Minute)",               "swap-vertical",         "",        "",            "",                 ""); // Legacy, always Unit per Minute
+        sendHomeAssistantDiscoveryTopic(group,   "rate_per_time_unit", "Rate (" + rateUnit + ")",          "swap-vertical",         rateUnit,  "",            "",                 "");        
+        sendHomeAssistantDiscoveryTopic(group,   "rate_per_digitalization_round",  "Change since last digitalization round", "arrow-expand-vertical", valueUnit, "",            "measurement",      ""); // correctly the Unit is Uint/Interval!
+        /* The timestamp string misses the Timezone, see PREVALUE_TIME_FORMAT_OUTPUT!
+           We need to know the timezone and append it! Until we do this, we simply
+           do not set the device class to "timestamp" to avoid errors in Homeassistant! */
+        // sendHomeAssistantDiscoveryTopic(group,   "timestamp",       "Timestamp",                  "clock-time-eight-outline", "",        "timestamp",   "",                 "diagnostic");
+        sendHomeAssistantDiscoveryTopic(group,   "timestamp",          "Timestamp",                  "clock-time-eight-outline", "",        "",            "",                 "diagnostic");
+        sendHomeAssistantDiscoveryTopic(group,   "json",               "JSON",                       "code-json",                "",        "",            "",                 "diagnostic");
+        sendHomeAssistantDiscoveryTopic(group,   "problem",            "Problem",                    "alert-outline",            "",        "",            "",                 ""); // Special binary sensor which is based on error topic
+    }
+}
+
+void publishSystemData() {
+    char tmp_char[50];
+
+    LogFile.WriteToFile(ESP_LOG_INFO, "Publishing system MQTT topics...");
+
+    sprintf(tmp_char, "%ld", (long)getUpTime());
+    MQTTPublish(maintopic + "/" + "uptime", std::string(tmp_char), retainFlag);
+    
+    sprintf(tmp_char, "%zu", esp_get_free_heap_size());
+    MQTTPublish(maintopic + "/" + "freeMem", std::string(tmp_char), retainFlag);
+
+    sprintf(tmp_char, "%d", get_WIFI_RSSI());
+    MQTTPublish(maintopic + "/" + "wifiRSSI", std::string(tmp_char), retainFlag);
+
+    sprintf(tmp_char, "%d", (int)temperatureRead());
+    MQTTPublish(maintopic + "/" + "CPUtemp", std::string(tmp_char), retainFlag);
+}
+
+
+void publishStaticData() {
+    LogFile.WriteToFile(ESP_LOG_INFO, "Publishing static MQTT topics...");
+    MQTTPublish(maintopic + "/" + "MAC", getMac(), retainFlag);
+    MQTTPublish(maintopic + "/" + "IP", *getIPAddress(), retainFlag);
+    MQTTPublish(maintopic + "/" + "hostname", hostname, retainFlag);
+
+    std::stringstream stream;
+    stream << std::fixed << std::setprecision(1) << roundInterval; // minutes
+    MQTTPublish(maintopic + "/" + "interval", stream.str(), retainFlag);
+}
+
+esp_err_t sendDiscovery_and_static_Topics(httpd_req_t *req) {
+    if (HomeassistantDiscovery) {
+        MQTThomeassistantDiscovery();
+    }
+
+    publishStaticData();
+
+    const char* resp_str = (const char*) req->user_ctx;
+    httpd_resp_send(req, resp_str, strlen(resp_str));  
+
+    return ESP_OK;
+}
+
+void GotConnected(std::string maintopic, int retainFlag) {
+    if (HomeassistantDiscovery) {
+        MQTThomeassistantDiscovery();
+    }
+
+    publishStaticData();
+    publishSystemData();
+}
+
+void register_server_mqtt_uri(httpd_handle_t server) {
+    httpd_uri_t uri = { };
+    uri.method    = HTTP_GET;
+
+    uri.uri       = "/mqtt_publish_discovery";
+    uri.handler   = sendDiscovery_and_static_Topics;
+    uri.user_ctx  = (void*) "MQTT Discovery and Static Topics sent";    
+    httpd_register_uri_handler(server, &uri); 
+}
+
+
+std::string getTimeUnit(void) {
+    return timeUnit;
+}
+
+
+void SetHomeassistantDiscoveryEnabled(bool enabled) {
+    HomeassistantDiscovery = enabled;
+}
+
+
+void setMqtt_Server_Retain(int _retainFlag) {
+    retainFlag = _retainFlag;
+}
+
+void mqttServer_setMainTopic( std::string _maintopic) {
+    maintopic = _maintopic;
+}

+ 19 - 0
code/components/jomjol_mqtt/server_mqtt.h

@@ -0,0 +1,19 @@
+#include "ClassFlowDefineTypes.h"
+
+#define LWT_TOPIC        "connection"
+#define LWT_CONNECTED    "connected"
+#define LWT_DISCONNECTED "connection lost"
+
+
+void SetHomeassistantDiscoveryEnabled(bool enabled);
+void mqttServer_setParameter(std::vector<NumberPost*>* _NUMBERS, int interval, float roundInterval);
+void mqttServer_setMeterType(std::string meterType, std::string valueUnit, std::string timeUnit,std::string rateUnit);
+void setMqtt_Server_Retain(int SetRetainFlag);
+void mqttServer_setMainTopic( std::string maintopic);
+
+void register_server_mqtt_uri(httpd_handle_t server);
+
+void publishSystemData();
+
+std::string getTimeUnit(void);
+void GotConnected(std::string maintopic, int SetRetainFlag);

+ 2 - 0
code/main/main.cpp

@@ -27,6 +27,7 @@
 #include "ClassControllCamera.h"
 #include "server_main.h"
 #include "server_camera.h"
+#include "server_mqtt.h"
 #include "Helper.h"
 
 extern const char* GIT_TAG;
@@ -259,6 +260,7 @@ extern "C" void app_main(void)
     register_server_tflite_uri(server);
     register_server_file_uri(server, "/sdcard");
     register_server_ota_sdcard_uri(server);
+    register_server_mqtt_uri(server);
 
     gpio_handler_create(server);
 

+ 33 - 2
sd-card/html/edit_config_param.html

@@ -625,10 +625,15 @@ textarea {
 				Enable or disable the retain flag for all MQTT entries
 			</td>
 		</tr>
+		<tr>
+			<td colspan="3" style="padding-left: 20px;"><h4>Homeassistant Discovery (using MQTT)</h4>
+			<span style="font-size: 80%;">The discovery topics and the static topics (IP, MAC, Hostname, Interval, ...) only get sent on startup.
+			To send them again, you can call the following URL: <a href=mqtt_publish_discovery target="_blank">http://&lt;IP&gt;/mqtt_publish_discovery</a></span></td>
+		</tr> 
 		<tr>
 			<td class="indent1">
 				<input type="checkbox" id="MQTT_HomeassistantDiscovery_enabled" value="1"  onclick = 'InvertEnableItem("MQTT", "HomeassistantDiscovery")' unchecked >
-				<label for=MQTT_HomeassistantDiscovery_enabled><class id="MQTT_HomeassistantDiscovery_text" style="color:black;">Enable Homeassistant Discovery</class></label>
+				<label for=MQTT_HomeassistantDiscovery_enabled><class id="MQTT_HomeassistantDiscovery_text" style="color:black;">Homeassistant Discovery</class></label>
 			</td>
 			<td>
 				<select id="MQTT_HomeassistantDiscovery_value1">
@@ -637,7 +642,31 @@ textarea {
 				</select>
 			</td>
 			<td style="font-size: 80%;">
-				Enable or disable the <a href=https://www.home-assistant.io/docs/mqtt/discovery/ target=_blank>Homeassistand Discovery</a>
+				Enable or disable the <a href=https://www.home-assistant.io/docs/mqtt/discovery/ target=_blank>Homeassistant Discovery</a>
+			</td>
+		</tr>
+		<tr>
+			<td class="indent1">
+				<input type="checkbox" id="MQTT_MeterType_enabled" value="1"  onclick = 'InvertEnableItem("MQTT", "MeterType")' unchecked >
+				<label for=MQTT_MeterType_enabled><class id="MQTT_MeterType_text" style="color:black;">Meter Type</class></label>
+			</td>
+			<td>
+				<select id="MQTT_MeterType_value1"> <!-- See https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes -->
+					<option value="other" selected>Other (no Units)</option>
+					<option value="water_m3">Watermeter (Value: m³, Rate: m³/h)</option>
+					<option value="water_l">Watermeter (Value: l, Rate: l/h)</option>
+					<option value="water_gal">Watermeter (Value: gal, Rate: gal/h)</option>
+					<option value="water_ft3">Watermeter (Value: ft³, Rate: ft³/m)</option>
+					<option value="gas_m3">Gasmeter (Value: m³, Rate: m³/h)</option>
+					<option value="gas_ft3">Gasmeter (Value: ft³, Rate: ft³/m)</option>
+					<option value="energy_wh">Energymeter (Value: Wh, Rate: W)</option>
+					<option value="energy_kwh">Energymeter (Value: kWh, Rate: kW)</option>
+					<option value="energy_mwh">Energymeter (Value: MWh, Rate: MW)</option>
+				</select>
+			</td>
+			<td style="font-size: 80%;">
+				Select the meter type so the sensors have the right units in Homeassistant.<br>
+				Note: For 'Watermeter' you need to have Homeassistant 2022.11 or never!
 			</td>
 		</tr>
 
@@ -1734,6 +1763,7 @@ function UpdateInput() {
 	WriteParameter(param, category, "MQTT", "password", true);
 	WriteParameter(param, category, "MQTT", "SetRetainFlag", true);
 	WriteParameter(param, category, "MQTT", "HomeassistantDiscovery", true);
+	WriteParameter(param, category, "MQTT", "MeterType", true);
 	
 	WriteParameter(param, category, "InfluxDB", "Uri", true);	
 	WriteParameter(param, category, "InfluxDB", "Database", true);	
@@ -1851,6 +1881,7 @@ function ReadParameterAll()
 	ReadParameter(param, "MQTT", "password", true);	
 	ReadParameter(param, "MQTT", "SetRetainFlag", true);	
 	ReadParameter(param, "MQTT", "HomeassistantDiscovery", true);
+	ReadParameter(param, "MQTT", "MeterType", true);
 
 	ReadParameter(param, "InfluxDB", "Uri", true);	
 	ReadParameter(param, "InfluxDB", "Database", true);	

+ 1 - 0
sd-card/html/readconfigparam.js

@@ -188,6 +188,7 @@ function ParseConfig() {
      ParamAddValue(param, catname, "password");
      ParamAddValue(param, catname, "SetRetainFlag");
      ParamAddValue(param, catname, "HomeassistantDiscovery");
+     ParamAddValue(param, catname, "MeterType");
 
      var catname = "InfluxDB";
      category[catname] = new Object();