From 08baf1824cbcbf449d55a2c754032eeef7cf5cbc Mon Sep 17 00:00:00 2001 From: gneluka <32097881+gneluka@users.noreply.github.com> Date: Mon, 2 Dec 2024 00:07:02 +0100 Subject: [PATCH] Add support for Domoticz MQTT integration (#3359) * initial changes to add domoticz support * error correction * further changes * further changes * Refactoring * further refactoring * further refactoring * Update DomoticzTopicIn.md * Update NUMBER.DomoticzIDX.md * Update DomoticzTopicIn.md * Update NUMBER.DomoticzIDX.md * Update config.ini * Update edit_config_template.html * Update edit_config_template.html * Update edit_config_template.html * Update DomoticzTopicIn.md --------- Co-authored-by: CaCO3 --- .../ClassFlowDefineTypes.h | 2 + .../jomjol_flowcontroll/ClassFlowMQTT.cpp | 70 +++++++++++--- .../jomjol_flowcontroll/ClassFlowMQTT.h | 4 +- .../components/jomjol_mqtt/interface_mqtt.cpp | 5 +- code/components/jomjol_mqtt/interface_mqtt.h | 2 +- code/components/jomjol_mqtt/server_mqtt.cpp | 8 +- code/components/jomjol_mqtt/server_mqtt.h | 3 + .../parameter-pages/MQTT/DomoticzTopicIn.md | 6 ++ .../MQTT/NUMBER.DomoticzIDX.md | 4 + sd-card/config/config.ini | 2 + sd-card/html/edit_config_template.html | 91 ++++++++++++++++++- sd-card/html/readconfigparam.js | 2 + 12 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 param-docs/parameter-pages/MQTT/DomoticzTopicIn.md create mode 100644 param-docs/parameter-pages/MQTT/NUMBER.DomoticzIDX.md diff --git a/code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h b/code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h index 63e8afe1..466f92ef 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h +++ b/code/components/jomjol_flowcontroll/ClassFlowDefineTypes.h @@ -66,6 +66,8 @@ struct NumberPost { float AnalogToDigitTransitionStart; // AnalogToDigitTransitionStartValue; FIXME: need a better description; When is the digit > x.1, i.e. when does it start to tilt? int Nachkomma; // decimalPlaces; usually defined by the number of analog ROIs; affected by DecimalShift + string DomoticzIdx; // Domoticz counter Idx + string FieldV1; // influxdbFieldName_v1; Name of the Field in InfluxDBv1 string MeasurementV1; // influxdbMeasurementName_v1; Name of the Measurement in InfluxDBv1 diff --git a/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp b/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp index 83b8ba1d..287f2da5 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp +++ b/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp @@ -51,6 +51,7 @@ void ClassFlowMQTT::SetInitialParameter(void) ListFlowControll = NULL; disabled = false; keepAlive = 25*60; + domoticzintopic = ""; } ClassFlowMQTT::ClassFlowMQTT() @@ -105,43 +106,44 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph) while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph)) { splitted = ZerlegeZeile(aktparamgraph); - if ((toUpper(splitted[0]) == "CACERT") && (splitted.size() > 1)) + std::string _param = GetParameterName(splitted[0]); + if ((toUpper(_param) == "CACERT") && (splitted.size() > 1)) { this->caCertFilename = splitted[1]; } - if ((toUpper(splitted[0]) == "CLIENTCERT") && (splitted.size() > 1)) + if ((toUpper(_param) == "CLIENTCERT") && (splitted.size() > 1)) { this->clientCertFilename = splitted[1]; } - if ((toUpper(splitted[0]) == "CLIENTKEY") && (splitted.size() > 1)) + if ((toUpper(_param) == "CLIENTKEY") && (splitted.size() > 1)) { this->clientKeyFilename = splitted[1]; } - if ((toUpper(splitted[0]) == "USER") && (splitted.size() > 1)) + if ((toUpper(_param) == "USER") && (splitted.size() > 1)) { this->user = splitted[1]; } - if ((toUpper(splitted[0]) == "PASSWORD") && (splitted.size() > 1)) + if ((toUpper(_param) == "PASSWORD") && (splitted.size() > 1)) { this->password = splitted[1]; } - if ((toUpper(splitted[0]) == "URI") && (splitted.size() > 1)) + if ((toUpper(_param) == "URI") && (splitted.size() > 1)) { this->uri = splitted[1]; } - if ((toUpper(splitted[0]) == "RETAINMESSAGES") && (splitted.size() > 1)) + if ((toUpper(_param) == "RETAINMESSAGES") && (splitted.size() > 1)) { if (toUpper(splitted[1]) == "TRUE") { SetRetainFlag = true; setMqtt_Server_Retain(SetRetainFlag); } } - if ((toUpper(splitted[0]) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1)) + if ((toUpper(_param) == "HOMEASSISTANTDISCOVERY") && (splitted.size() > 1)) { if (toUpper(splitted[1]) == "TRUE") SetHomeassistantDiscoveryEnabled(true); } - if ((toUpper(splitted[0]) == "METERTYPE") && (splitted.size() > 1)) { + if ((toUpper(_param) == "METERTYPE") && (splitted.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(splitted[1]) == "WATER_M3") { @@ -176,15 +178,26 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph) } } - if ((toUpper(splitted[0]) == "CLIENTID") && (splitted.size() > 1)) + if ((toUpper(_param) == "CLIENTID") && (splitted.size() > 1)) { this->clientname = splitted[1]; } - if (((toUpper(splitted[0]) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1)) + if (((toUpper(_param) == "TOPIC") || (toUpper(splitted[0]) == "MAINTOPIC")) && (splitted.size() > 1)) { maintopic = splitted[1]; } + + if (((toUpper(_param) == "DOMOTICZTOPICIN")) && (splitted.size() > 1)) + { + this->domoticzintopic = splitted[1]; + } + + if (((toUpper(_param) == "DOMOTICZIDX")) && (splitted.size() > 1)) + { + handleIdx(splitted[0], splitted[1]); + } + } /* Note: @@ -193,6 +206,7 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph) * To work around this, we delay the start and trigger it from ClassFlowControll::ReadParameter() */ mqttServer_setMainTopic(maintopic); + mqttServer_setDmoticzInTopic(domoticzintopic); return true; } @@ -210,7 +224,7 @@ bool ClassFlowMQTT::Start(float AutoInterval) mqttServer_setParameter(flowpostprocessing->GetNumbers(), keepAlive, roundInterval); - bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, LWT_TOPIC, LWT_CONNECTED, + bool MQTTConfigCheck = MQTT_Configure(uri, clientname, user, password, maintopic, domoticzintopic, LWT_TOPIC, LWT_CONNECTED, LWT_DISCONNECTED, caCertFilename, clientCertFilename, clientKeyFilename, keepAlive, SetRetainFlag, (void *)&GotConnected); @@ -235,6 +249,8 @@ bool ClassFlowMQTT::doFlow(string zwtime) std::string resultchangabs = ""; string zw = ""; string namenumber = ""; + string domoticzpayload = ""; + string DomoticzIdx = ""; int qos = 1; /* Send the the Homeassistant Discovery and the Static Topics in case they where scheduled */ @@ -258,16 +274,20 @@ bool ClassFlowMQTT::doFlow(string zwtime) resultchangabs = (*NUMBERS)[i]->ReturnChangeAbsolute; // Units per round resulttimestamp = (*NUMBERS)[i]->timeStamp; + DomoticzIdx = (*NUMBERS)[i]->DomoticzIdx; + domoticzpayload = "{\"command\":\"udevice\",\"idx\":" + DomoticzIdx + ",\"svalue\":\""+ result + "\"}"; + namenumber = (*NUMBERS)[i]->name; if (namenumber == "default") namenumber = maintopic + "/"; else namenumber = maintopic + "/" + namenumber + "/"; + if ((domoticzintopic.length() > 0) && (result.length() > 0)) + success |= MQTTPublish(domoticzintopic, domoticzpayload, qos, SetRetainFlag); - if (result.length() > 0) + if (result.length() > 0) success |= MQTTPublish(namenumber + "value", result, qos, SetRetainFlag); - if (resulterror.length() > 0) success |= MQTTPublish(namenumber + "error", resulterror, qos, SetRetainFlag); @@ -325,6 +345,26 @@ bool ClassFlowMQTT::doFlow(string zwtime) return true; } - +void ClassFlowMQTT::handleIdx(string _decsep, string _value) +{ + string _digit, _decpos; + int _pospunkt = _decsep.find_first_of("."); +// ESP_LOGD(TAG, "Name: %s, Pospunkt: %d", _decsep.c_str(), _pospunkt); + if (_pospunkt > -1) + _digit = _decsep.substr(0, _pospunkt); + else + _digit = "default"; + for (int j = 0; j < flowpostprocessing->NUMBERS.size(); ++j) + { + if (_digit == "default") // Set to default first (if nothing else is set) + { + flowpostprocessing->NUMBERS[j]->DomoticzIdx = _value; + } + if (flowpostprocessing->NUMBERS[j]->name == _digit) + { + flowpostprocessing->NUMBERS[j]->DomoticzIdx = _value; + } + } +} #endif //ENABLE_MQTT \ No newline at end of file diff --git a/code/components/jomjol_flowcontroll/ClassFlowMQTT.h b/code/components/jomjol_flowcontroll/ClassFlowMQTT.h index 7f89fbdf..fcea5b63 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowMQTT.h +++ b/code/components/jomjol_flowcontroll/ClassFlowMQTT.h @@ -23,9 +23,9 @@ protected: bool SetRetainFlag; int keepAlive; // Seconds float roundInterval; // Minutes - - std::string maintopic; + std::string maintopic, domoticzintopic; void SetInitialParameter(void); + void handleIdx(string _decsep, string _value); public: ClassFlowMQTT(); diff --git a/code/components/jomjol_mqtt/interface_mqtt.cpp b/code/components/jomjol_mqtt/interface_mqtt.cpp index cb7a0ce4..ced320f2 100644 --- a/code/components/jomjol_mqtt/interface_mqtt.cpp +++ b/code/components/jomjol_mqtt/interface_mqtt.cpp @@ -34,7 +34,7 @@ bool mqtt_initialized = false; bool mqtt_connected = false; esp_mqtt_client_handle_t client = NULL; -std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic; +std::string uri, client_id, lwt_topic, lwt_connected, lwt_disconnected, user, password, maintopic, domoticz_in_topic; std::string caCert, clientCert, clientKey; int keepalive; bool SetRetainFlag; @@ -205,7 +205,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, - std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected, + std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected, std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename, int _keepalive, bool _SetRetainFlag, void *_callbackOnConnected) { if ((_mqttURI.length() == 0) || (_maintopic.length() == 0) || (_clientid.length() == 0)) @@ -222,6 +222,7 @@ bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _us keepalive = _keepalive; SetRetainFlag = _SetRetainFlag; maintopic = _maintopic; + domoticz_in_topic = _domoticz_in_topic; callbackOnConnected = ( void (*)(std::string, bool) )(_callbackOnConnected); if (_clientcertfilename.length() && _clientkeyfilename.length()){ diff --git a/code/components/jomjol_mqtt/interface_mqtt.h b/code/components/jomjol_mqtt/interface_mqtt.h index b2e736e4..79544736 100644 --- a/code/components/jomjol_mqtt/interface_mqtt.h +++ b/code/components/jomjol_mqtt/interface_mqtt.h @@ -10,7 +10,7 @@ #include bool MQTT_Configure(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, - std::string _maintopic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected, + std::string _maintopic, std::string _domoticz_in_topic, std::string _lwt, std::string _lwt_connected, std::string _lwt_disconnected, std::string _cacertfilename, std::string _clientcertfilename, std::string _clientkeyfilename, int _keepalive, bool SetRetainFlag, void *callbackOnConnected); int MQTT_Init(); diff --git a/code/components/jomjol_mqtt/server_mqtt.cpp b/code/components/jomjol_mqtt/server_mqtt.cpp index 33c823f6..cf4b970e 100644 --- a/code/components/jomjol_mqtt/server_mqtt.cpp +++ b/code/components/jomjol_mqtt/server_mqtt.cpp @@ -32,10 +32,11 @@ std::string rateUnit = "Unit/Minute"; float roundInterval; // Minutes int keepAlive = 0; // Seconds bool retainFlag; -static std::string maintopic; +static std::string maintopic, domoticzintopic; bool sendingOf_DiscoveryAndStaticTopics_scheduled = true; // Set it to true to make sure it gets sent at least once after startup + void mqttServer_setParameter(std::vector* _NUMBERS, int _keepAlive, float _roundInterval) { NUMBERS = _NUMBERS; keepAlive = _keepAlive; @@ -367,4 +368,9 @@ std::string mqttServer_getMainTopic() { return maintopic; } +void mqttServer_setDmoticzInTopic( std::string _domoticzintopic) { + domoticzintopic = _domoticzintopic; +} + + #endif //ENABLE_MQTT diff --git a/code/components/jomjol_mqtt/server_mqtt.h b/code/components/jomjol_mqtt/server_mqtt.h index 41c359c9..2754b404 100644 --- a/code/components/jomjol_mqtt/server_mqtt.h +++ b/code/components/jomjol_mqtt/server_mqtt.h @@ -12,6 +12,9 @@ void mqttServer_setParameter(std::vector* _NUMBERS, int interval, f void mqttServer_setMeterType(std::string meterType, std::string valueUnit, std::string timeUnit,std::string rateUnit); void setMqtt_Server_Retain(bool SetRetainFlag); void mqttServer_setMainTopic( std::string maintopic); +void mqttServer_setDmoticzInTopic( std::string domoticzintopic); + + std::string mqttServer_getMainTopic(); void register_server_mqtt_uri(httpd_handle_t server); diff --git a/param-docs/parameter-pages/MQTT/DomoticzTopicIn.md b/param-docs/parameter-pages/MQTT/DomoticzTopicIn.md new file mode 100644 index 00000000..ef8ad8e2 --- /dev/null +++ b/param-docs/parameter-pages/MQTT/DomoticzTopicIn.md @@ -0,0 +1,6 @@ +# Parameter `DomoticzTopicIn` +Default Value: `domoticz/in` + +Domoticz "in" topic as configured on the "MQTT Client Gateway" setup page on the Domoticz system. Used to publish counter/s value/s. + +Parameter <NUMBER>.DomoticzIDX is required (see below). diff --git a/param-docs/parameter-pages/MQTT/NUMBER.DomoticzIDX.md b/param-docs/parameter-pages/MQTT/NUMBER.DomoticzIDX.md new file mode 100644 index 00000000..8ec2c70b --- /dev/null +++ b/param-docs/parameter-pages/MQTT/NUMBER.DomoticzIDX.md @@ -0,0 +1,4 @@ +# Parameter `.DomoticzIDX` +Default Value: `0` + +The Idx number for the counter device. Can be obtained from the devices setup page on the Domoticz system. diff --git a/sd-card/config/config.ini b/sd-card/config/config.ini index 074ee1fd..86052149 100644 --- a/sd-card/config/config.ini +++ b/sd-card/config/config.ini @@ -87,6 +87,8 @@ HomeassistantDiscovery = false ;CACert = /config/certs/RootCA.pem ;ClientCert = /config/certs/client.pem.crt ;ClientKey = /config/certs/client.pem.key +;DomoticzTopicIn = domoticz/in +;main.DomoticzIDX = 0 ;[InfluxDB] ;Uri = undefined diff --git a/sd-card/html/edit_config_template.html b/sd-card/html/edit_config_template.html index ba1602b0..2586231b 100644 --- a/sd-card/html/edit_config_template.html +++ b/sd-card/html/edit_config_template.html @@ -1178,6 +1178,36 @@ $TOOLTIP_MQTT_MeterType + + + + + + + + + $TOOLTIP_MQTT_DomoticzTopicIn + + + + + Parameter per number sequence: + + + + + + + + + + + + $TOOLTIP_MQTT_NUMBER.DomoticzIDX + + @@ -2120,6 +2150,7 @@ function LoadConfigNeu() { UpdateInput(); var sel = document.getElementById("Numbers_value1"); UpdateInputIndividual(sel); + UpdateExpertModus(); UpdateTooltipModus(); document.getElementById("divall").style.display = ''; @@ -2162,6 +2193,11 @@ function InitIndivParameter() { _indexInfluxv1.remove(0); } + var _indexMQTTIdx = document.getElementById("NumbersMQTTIdx_value1"); + while (_indexMQTTIdx.length) { + _indexMQTTIdx.remove(0); + } + for (var i = 0; i < NUMBERS.length; ++i) { var option = document.createElement("option"); option.text = NUMBERS[i]["name"]; @@ -2177,11 +2213,17 @@ function InitIndivParameter() { optionInfluxv1.text = NUMBERS[i]["name"]; optionInfluxv1.value = i; _indexInfluxv1.add(optionInfluxv1); + + var optionMQTTIdx = document.createElement("option"); + optionMQTTIdx.text = NUMBERS[i]["name"]; + optionMQTTIdx.value = i; + _indexMQTTIdx.add(optionMQTTIdx); } _index.selectedIndex = 0; _indexInflux.selectedIndex = 0; _indexInfluxv1.selectedIndex = 0; + _indexMQTTIdx.selectedIndex = 0; } function UpdateInputIndividual(sel) { @@ -2200,6 +2242,7 @@ function UpdateInputIndividual(sel) { ReadParameter(param, "InfluxDBv2", "Field", true, NUNBERSAkt); ReadParameter(param, "InfluxDB", "Measurement", true, NUNBERSAkt); ReadParameter(param, "InfluxDBv2", "Measurement", true, NUNBERSAkt); + ReadParameter(param, "MQTT", "DomoticzIDX", true, NUNBERSAkt); } // var sel = document.getElementById("Numbers_value1"); @@ -2218,6 +2261,8 @@ function UpdateInputIndividual(sel) { WriteParameter(param, category, "InfluxDBv2", "Field", true, NUNBERSAkt); WriteParameter(param, category, "InfluxDB", "Measurement", true, NUNBERSAkt); WriteParameter(param, category, "InfluxDBv2", "Measurement", true, NUNBERSAkt); + WriteParameter(param, category, "MQTT", "DomoticzIDX", true, NUNBERSAkt); + } function UpdateInput() { @@ -2306,6 +2351,7 @@ function UpdateInput() { WriteParameter(param, category, "MQTT", "CACert", true); WriteParameter(param, category, "MQTT", "ClientCert", true); WriteParameter(param, category, "MQTT", "ClientKey", true); + WriteParameter(param, category, "MQTT", "DomoticzTopicIn", true); WriteParameter(param, category, "InfluxDB", "Uri", true); WriteParameter(param, category, "InfluxDB", "Database", true); @@ -2473,6 +2519,7 @@ function ReadParameterAll() { ReadParameter(param, "MQTT", "CACert", true); ReadParameter(param, "MQTT", "ClientCert", true); ReadParameter(param, "MQTT", "ClientKey", true); + ReadParameter(param, "MQTT", "DomoticzTopicIn", true); ReadParameter(param, "InfluxDB", "Uri", true); ReadParameter(param, "InfluxDB", "Database", true); @@ -2527,7 +2574,7 @@ function ReadParameterAll() { var sel = document.getElementById("Numbers_value1"); UpdateInputIndividual(sel); - + // FormatDecimalValue(param, "PostProcessing", "MaxRateValue"); } @@ -2871,6 +2918,38 @@ function numberChanged() { if (_selInflux.selectedIndex != _neu) { _selInflux.selectedIndex = _neu } + + var _sel3 = document.getElementById("NumbersInfluxDB_value1"); + if (_sel3.selectedIndex != _neu) { + _sel3.selectedIndex = _neu + } + + var _sel4 = document.getElementById("NumbersMQTTIdx_value1"); + if (_sel4.selectedIndex != _neu) { + _sel4.selectedIndex = _neu + } + +} + +function numberMQTTIdxChanged() { + var sel = document.getElementById("NumbersMQTTIdx_value1"); + _neu = sel.selectedIndex; + UpdateInputIndividual(sel); + + var _sel2 = document.getElementById("Numbers_value1"); + if (_sel2.selectedIndex != _neu) { + _sel2.selectedIndex = _neu + } + + var _sel3 = document.getElementById("NumbersInfluxDB_value1"); + if (_sel3.selectedIndex != _neu) { + _sel3.selectedIndex = _neu + } + + var _sel4 = document.getElementById("NumbersInfluxDBv2_value1"); + if (_sel4.selectedIndex != _neu) { + _sel4.selectedIndex = _neu + } } function numberInfluxDBv2Changed() { @@ -2887,6 +2966,11 @@ function numberInfluxDBv2Changed() { if (_sel3.selectedIndex != _neu) { _sel3.selectedIndex = _neu } + + var _sel4 = document.getElementById("NumbersMQTTIdx_value1"); + if (_sel4.selectedIndex != _neu) { + _sel4.selectedIndex = _neu + } } function numberInfluxDBChanged() { @@ -2903,6 +2987,11 @@ function numberInfluxDBChanged() { if (_sel3.selectedIndex != _neu) { _sel3.selectedIndex = _neu } + + var _sel4 = document.getElementById("NumbersMQTTIdx_value1"); + if (_sel4.selectedIndex != _neu) { + _sel4.selectedIndex = _neu + } } function getParameterByName(name, url = window.location.href) { diff --git a/sd-card/html/readconfigparam.js b/sd-card/html/readconfigparam.js index 91bd1d7f..d47c222e 100644 --- a/sd-card/html/readconfigparam.js +++ b/sd-card/html/readconfigparam.js @@ -198,6 +198,8 @@ function ParseConfig() { ParamAddValue(param, catname, "user"); ParamAddValue(param, catname, "password"); ParamAddValue(param, catname, "RetainMessages"); + ParamAddValue(param, catname, "DomoticzTopicIn"); + ParamAddValue(param, catname, "DomoticzIDX", 1, true); ParamAddValue(param, catname, "HomeassistantDiscovery"); ParamAddValue(param, catname, "MeterType"); ParamAddValue(param, catname, "CACert");