From 9eda49b46a188e60b98e7bc56e76d72df5330793 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 22 Feb 2020 11:37:55 +0100 Subject: [PATCH] added mqtt_nestedjson --- CHANGELOG.md | 3 +- src/MyESP.cpp | 61 +++-- src/MyESP.h | 65 +++-- src/ems-esp.cpp | 697 +++++++++++++++++++++++++----------------------- src/version.h | 2 +- 5 files changed, 434 insertions(+), 394 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8d0af1c1..56df86ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `boiler wwonetime` command from Telnet - `set bus_id ` to support multiple EMS-ESP circuits. Default is 0x0B to mimic a service key. - MQTT publish messages are queued and gracefully published every second to avoid TCP blocks +- Added `mqtt_nestedjson` option to disable multiple data records being nested into a single JSON string ### Fixed - set boiler warm water temp on Junkers/Bosch HT3 @@ -29,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `autodetect scan` - - `mqttlog` and showing MQTT log in the web interface - no point showing history of previous mqtt publishes in ESP's precious memory. For debugging recommend using MQTT Explorer or another external tool. + - `mqttlog all` and showing MQTT log in the web interface - no point showing history of previous mqtt publishes in ESP's precious memory. For debugging I recommend using MQTT Explorer or another external tool. ## [1.9.4] 15-12-2019 diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 3e52d6bad..8712ade7f 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -83,6 +83,7 @@ MyESP::MyESP() { _mqtt_heartbeat = false; _mqtt_keepalive = MQTT_KEEPALIVE; _mqtt_qos = MQTT_QOS; + _mqtt_nestedjson = false; _mqtt_retain = MQTT_RETAIN; _mqtt_will_topic = strdup(MQTT_WILL_TOPIC); _mqtt_will_online_payload = strdup(MQTT_WILL_ONLINE_PAYLOAD); @@ -527,9 +528,8 @@ void MyESP::_mqttPublishQueue() { #endif if (packet_id == 0) { - // it failed - // if we retried 3 times, give up. remove from queue - if (element.retry_count == 2) { + // it failed. if we retried n times, give up. remove from queue + if (element.retry_count == (MQTT_PUBLISH_MAX_RETRY - 1)) { myDebug_P(PSTR("[MQTT] Failed to publish to %s with payload %s"), _mqttTopic(element.topic), element.payload); _mqtt_publish_fails++; // increment failure counter _mqttRemoveLastPublish(); @@ -879,6 +879,7 @@ void MyESP::_printSetCommands() { myDebug_P(PSTR(" set mqtt_qos [0-3]")); myDebug_P(PSTR(" set mqtt_keepalive [seconds]")); myDebug_P(PSTR(" set mqtt_retain [on | off]")); + myDebug_P(PSTR(" set mqtt_nestedjson [on | off]")); myDebug_P(PSTR(" set ntp_enabled ")); myDebug_P(PSTR(" set ntp_interval [minutes]")); myDebug_P(PSTR(" set ntp_timezone [n]")); @@ -941,6 +942,7 @@ void MyESP::_printSetCommands() { myDebug_P(PSTR(" mqtt_retain=%s"), (_mqtt_retain) ? "on" : "off"); myDebug_P(PSTR(" mqtt_qos=%d"), _mqtt_qos); myDebug_P(PSTR(" mqtt_heartbeat=%s"), (_mqtt_heartbeat) ? "on" : "off"); + myDebug_P(PSTR(" mqtt_nestedjson=%s"), (_mqtt_nestedjson) ? "on" : "off"); #ifdef FORCE_SERIAL myDebug_P(PSTR(" serial=%s (this is always when compiled with -DFORCE_SERIAL)"), (_general_serial) ? "on" : "off"); @@ -1045,6 +1047,8 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) restart = save_config; } else if (strcmp(setting, "mqtt_heartbeat") == 0) { save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false); + } else if (strcmp(setting, "mqtt_nestedjson") == 0) { + save_config = fs_setSettingValue(&_mqtt_nestedjson, value, false); } else if (strcmp(setting, "ntp_enabled") == 0) { save_config = fs_setSettingValue(&_ntp_enabled, value, false); } else if (strcmp(setting, "ntp_interval") == 0) { @@ -1867,17 +1871,18 @@ bool MyESP::_fs_loadConfig() { _general_serial = general["serial"]; #endif - JsonObject mqtt = doc["mqtt"]; - _mqtt_enabled = mqtt["enabled"]; - _mqtt_heartbeat = mqtt["heartbeat"]; - _mqtt_ip = strdup(mqtt["ip"] | ""); - _mqtt_user = strdup(mqtt["user"] | ""); - _mqtt_port = mqtt["port"] | MQTT_PORT; - _mqtt_keepalive = mqtt["keepalive"] | MQTT_KEEPALIVE; - _mqtt_retain = mqtt["retain"]; - _mqtt_qos = mqtt["qos"] | MQTT_QOS; - _mqtt_password = strdup(mqtt["password"] | ""); - _mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT); + JsonObject mqtt = doc["mqtt"]; + _mqtt_enabled = mqtt["enabled"]; + _mqtt_heartbeat = mqtt["heartbeat"]; + _mqtt_ip = strdup(mqtt["ip"] | ""); + _mqtt_user = strdup(mqtt["user"] | ""); + _mqtt_port = mqtt["port"] | MQTT_PORT; + _mqtt_keepalive = mqtt["keepalive"] | MQTT_KEEPALIVE; + _mqtt_retain = mqtt["retain"]; + _mqtt_qos = mqtt["qos"] | MQTT_QOS; + _mqtt_nestedjson = mqtt["nestedjson"] | true; // default to on + _mqtt_password = strdup(mqtt["password"] | ""); + _mqtt_base = strdup(mqtt["base"] | MQTT_BASE_DEFAULT); JsonObject ntp = doc["ntp"]; _ntp_server = strdup(ntp["server"] | ""); @@ -2080,17 +2085,18 @@ bool MyESP::_fs_writeConfig() { general["log_ip"] = _general_log_ip; general["version"] = _app_version; - JsonObject mqtt = doc.createNestedObject("mqtt"); - mqtt["enabled"] = _mqtt_enabled; - mqtt["heartbeat"] = _mqtt_heartbeat; - mqtt["ip"] = _mqtt_ip; - mqtt["user"] = _mqtt_user; - mqtt["port"] = _mqtt_port; - mqtt["qos"] = _mqtt_qos; - mqtt["keepalive"] = _mqtt_keepalive; - mqtt["retain"] = _mqtt_retain; - mqtt["password"] = _mqtt_password; - mqtt["base"] = _mqtt_base; + JsonObject mqtt = doc.createNestedObject("mqtt"); + mqtt["enabled"] = _mqtt_enabled; + mqtt["heartbeat"] = _mqtt_heartbeat; + mqtt["ip"] = _mqtt_ip; + mqtt["user"] = _mqtt_user; + mqtt["port"] = _mqtt_port; + mqtt["qos"] = _mqtt_qos; + mqtt["keepalive"] = _mqtt_keepalive; + mqtt["retain"] = _mqtt_retain; + mqtt["password"] = _mqtt_password; + mqtt["base"] = _mqtt_base; + mqtt["nestedjson"] = _mqtt_nestedjson; JsonObject ntp = doc.createNestedObject("ntp"); ntp["server"] = _ntp_server; @@ -2192,6 +2198,11 @@ void MyESP::_calculateLoad() { } } +// returns true if nested JSON setting is enabled +bool MyESP::mqttUseNestedJson() { + return _mqtt_nestedjson; +} + // returns true is MQTT is alive bool MyESP::isMQTTConnected() { return mqttClient.connected(); diff --git a/src/MyESP.h b/src/MyESP.h index 6a6ec485f..faa404cbd 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -97,9 +97,11 @@ extern struct rst_info resetInfo; #define MQTT_WILL_TOPIC "status" // for last will & testament topic name #define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic #define MQTT_MAX_PAYLOAD_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/ -#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object, like for sending MQTT log -#define MQTT_QUEUE_MAX_SIZE 20 // Size of the MQTT queue -#define MQTT_PUBLISH_WAIT 1000 // every 2 seconds check MQTT queue +#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object +#define MQTT_MAX_PAYLOAD_SIZE_SMALL 200 +#define MQTT_QUEUE_MAX_SIZE 20 // Size of the MQTT queue +#define MQTT_PUBLISH_WAIT 1000 // every 1 second check MQTT queue +#define MQTT_PUBLISH_MAX_RETRY 4 // max retries for giving up on publishing // Internal MQTT events #define MQTT_CONNECT_EVENT 0 @@ -285,6 +287,7 @@ class MyESP { void mqttPublish(const char * topic, const char * payload); void mqttPublish(const char * topic, const char * payload, bool retain); void setMQTT(mqtt_callback_f callback); + bool mqttUseNestedJson(); // OTA void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post); @@ -329,7 +332,6 @@ class MyESP { uint32_t getSystemLoadAverage(); uint32_t getSystemResetReason(); uint8_t getSystemBootStatus(); - bool _have_ntp_time; unsigned long getSystemTime(); void heartbeatPrint(); void heartbeatCheck(bool force = false); @@ -365,6 +367,7 @@ class MyESP { uint32_t _mqtt_last_connection; bool _mqtt_connecting; bool _mqtt_heartbeat; + bool _mqtt_nestedjson; uint16_t _mqtt_publish_fails; // wifi @@ -408,20 +411,18 @@ class MyESP { void _syslog_setup(); // fs and settings - void _fs_setup(); - bool _fs_loadConfig(); - bool _fs_loadCustomConfig(); - void _fs_eraseConfig(); - bool _fs_writeConfig(); - bool _fs_createCustomConfig(); - bool _fs_sendConfig(); - size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc); - size_t _fs_validateLogFile(const char * filename); - + void _fs_setup(); + bool _fs_loadConfig(); + bool _fs_loadCustomConfig(); + void _fs_eraseConfig(); + bool _fs_writeConfig(); + bool _fs_createCustomConfig(); + bool _fs_sendConfig(); + size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc); + size_t _fs_validateLogFile(const char * filename); fs_loadsave_callback_f _fs_loadsave_callback_f; fs_setlist_callback_f _fs_setlist_callback_f; - - void _printSetCommands(); + void _printSetCommands(); // general char * _general_hostname; @@ -444,34 +445,27 @@ class MyESP { void _kick(); // reset reason and rtcmem - bool _rtcmem_status; - bool _rtcmemStatus(); - bool _getRtcmemStatus(); - - void _rtcmemInit(); - void _rtcmemSetup(); - - void _deferredReset(unsigned long delay, uint8_t reason); - + bool _rtcmem_status; + bool _rtcmemStatus(); + bool _getRtcmemStatus(); + void _rtcmemInit(); + void _rtcmemSetup(); + void _deferredReset(unsigned long delay, uint8_t reason); uint8_t _getSystemStabilityCounter(); void _setSystemStabilityCounter(uint8_t counter); - uint8_t _getSystemDropoutCounter(); void _setSystemDropoutCounter(uint8_t counter); void _increaseSystemDropoutCounter(); - void _setSystemResetReason(uint8_t reason); uint8_t _getCustomResetReason(); void _setCustomResetReason(uint8_t reason); uint8_t _getSystemResetReason(); - - void _setSystemBootStatus(uint8_t status); - - bool _systemStable; - void _bootupSequence(); - bool _getSystemCheck(); - void _systemCheckLoop(); - void _setSystemCheck(bool stable); + void _setSystemBootStatus(uint8_t status); + bool _systemStable; + void _bootupSequence(); + bool _getSystemCheck(); + void _systemCheckLoop(); + void _setSystemCheck(bool stable); // load average (0..100) and heap ram void _calculateLoad(); @@ -497,6 +491,7 @@ class MyESP { uint16_t _ntp_interval; bool _ntp_enabled; uint8_t _ntp_timezone; + bool _have_ntp_time; }; extern MyESP myESP; diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 103ad57c4..347e71677 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -582,13 +582,34 @@ void publishSensorValues() { bool hasdata = false; char buffer[128] = {0}; // temp string buffer + // if we're not using nested JSON, send each sensor out seperately + if (!myESP.mqttUseNestedJson()) { + for (uint8_t i = 0; i < EMSESP_Settings.dallas_sensors; i++) { + float sensorValue = ds18.getValue(i); + if (sensorValue != DS18_DISCONNECTED) { + hasdata = true; + char topic[30]; // sensors{1-n} + strlcpy(topic, TOPIC_EXTERNAL_SENSORS, sizeof(topic)); // create topic + strlcat(topic, _int_to_char(buffer, i + 1), sizeof(topic)); + sensors[PAYLOAD_EXTERNAL_SENSOR_ID] = ds18.getDeviceID(buffer, i); // add ID + sensors[PAYLOAD_EXTERNAL_SENSOR_TEMP] = sensorValue; // add temp value + char data[100] = {0}; + serializeJson(doc, data, sizeof(data)); // convert to string + myESP.mqttPublish(topic, data); // and publish + } + } + if (hasdata) { + myDebugLog("Publishing external sensor data via MQTT"); + } + return; // exit + } + // see if the sensor values have changed, if so send it on for (uint8_t i = 0; i < EMSESP_Settings.dallas_sensors; i++) { float sensorValue = ds18.getValue(i); if (sensorValue != DS18_DISCONNECTED) { hasdata = true; - // create a nested object - // https://github.com/proddy/EMS-ESP/issues/327 + // create a nested object - https://github.com/proddy/EMS-ESP/issues/327 char sensorID[10]; // sensor{1-n} strlcpy(sensorID, PAYLOAD_EXTERNAL_SENSOR_NUM, sizeof(sensorID)); strlcat(sensorID, _int_to_char(buffer, i + 1), sizeof(sensorID)); @@ -598,343 +619,313 @@ void publishSensorValues() { } } - /* test code - https://github.com/proddy/EMS-ESP/issues/326 - float sensorValue = 23.43; - hasdata = true; - char sensorID[10]; // sensor{1-n} - for (uint8_t i = 0; i < 10; i++) { - strlcpy(sensorID, PAYLOAD_EXTERNAL_SENSOR_NUM, sizeof(sensorID)); - strlcat(sensorID, _int_to_char(buffer, i + 1), sizeof(sensorID)); - JsonObject dataSensor = sensors.createNestedObject(sensorID); - dataSensor[PAYLOAD_EXTERNAL_SENSOR_ID] = "28D45A79A2190310"; - dataSensor[PAYLOAD_EXTERNAL_SENSOR_TEMP] = sensorValue; - } - */ - - if (!hasdata) { - return; // nothing to send - } - char data[DS18_MQTT_PAYLOAD_MAXSIZE] = {0}; serializeJson(doc, data, sizeof(data)); - - myDebugLog("Publishing external sensor data via MQTT"); myESP.mqttPublish(TOPIC_EXTERNAL_SENSORS, data); + + if (hasdata) { + myDebugLog("Publishing external sensor data via MQTT"); + } } -// send values via MQTT -// a json object is created for each device type -void publishEMSValues(bool force) { - // don't send if MQTT is not connected or EMS bus is not connected - if (!myESP.isMQTTConnected() || (!ems_getBusConnected()) || (EMSESP_Settings.publish_time == -1)) { - return; - } - +// publish Boiler data via MQTT +void publishEMSValues_boiler() { char s[20] = {0}; // for formatting strings StaticJsonDocument doc; char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; + JsonObject rootBoiler = doc.to(); - // do we have boiler changes? - if (ems_getBoilerEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_BOILER) || force)) { - JsonObject rootBoiler = doc.to(); - - if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { - rootBoiler["wWComfort"] = "Hot"; - } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Eco) { - rootBoiler["wWComfort"] = "Eco"; - } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Intelligent) { - rootBoiler["wWComfort"] = "Intelligent"; - } - - if (EMS_Boiler.wWSelTemp != EMS_VALUE_INT_NOTSET) - rootBoiler["wWSelTemp"] = EMS_Boiler.wWSelTemp; - if (EMS_Boiler.wWDesinfectTemp != EMS_VALUE_INT_NOTSET) - rootBoiler["wWDesinfectionTemp"] = EMS_Boiler.wWDesinfectTemp; - if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET) - rootBoiler["selFlowTemp"] = EMS_Boiler.selFlowTemp; - if (EMS_Boiler.selBurnPow != EMS_VALUE_INT_NOTSET) - rootBoiler["selBurnPow"] = EMS_Boiler.selBurnPow; - if (EMS_Boiler.curBurnPow != EMS_VALUE_INT_NOTSET) - rootBoiler["curBurnPow"] = EMS_Boiler.curBurnPow; - if (EMS_Boiler.pumpMod != EMS_VALUE_INT_NOTSET) - rootBoiler["pumpMod"] = EMS_Boiler.pumpMod; - if (EMS_Boiler.wWCircPump != EMS_VALUE_BOOL_NOTSET) - rootBoiler["wWCircPump"] = EMS_Boiler.wWCircPump; - - if (EMS_Boiler.extTemp > EMS_VALUE_SHORT_NOTSET) - rootBoiler["outdoorTemp"] = (float)EMS_Boiler.extTemp / 10; - if (EMS_Boiler.wWCurTmp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["wWCurTmp"] = (float)EMS_Boiler.wWCurTmp / 10; - if (EMS_Boiler.wWCurFlow != EMS_VALUE_INT_NOTSET) - rootBoiler["wWCurFlow"] = (float)EMS_Boiler.wWCurFlow / 10; - if (EMS_Boiler.curFlowTemp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["curFlowTemp"] = (float)EMS_Boiler.curFlowTemp / 10; - if (EMS_Boiler.retTemp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["retTemp"] = (float)EMS_Boiler.retTemp / 10; - if (EMS_Boiler.switchTemp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["switchTemp"] = (float)EMS_Boiler.switchTemp / 10; - if (EMS_Boiler.sysPress != EMS_VALUE_INT_NOTSET) - rootBoiler["sysPress"] = (float)EMS_Boiler.sysPress / 10; - if (EMS_Boiler.boilTemp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["boilTemp"] = (float)EMS_Boiler.boilTemp / 10; - if (EMS_Boiler.exhaustTemp < EMS_VALUE_USHORT_NOTSET) - rootBoiler["exhaustTemp"] = (float)EMS_Boiler.exhaustTemp / 10; - if (EMS_Boiler.wWActivated != EMS_VALUE_BOOL_NOTSET) - rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); - - if (EMS_Boiler.wWActivated != EMS_VALUE_BOOL_NOTSET) - rootBoiler["wWOnetime"] = _bool_to_char(s, EMS_Boiler.wWOneTime); - - if (EMS_Boiler.wWCirc != EMS_VALUE_BOOL_NOTSET) - rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); - - if (EMS_Boiler.burnGas != EMS_VALUE_BOOL_NOTSET) - rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); - - if (EMS_Boiler.flameCurr < EMS_VALUE_USHORT_NOTSET) - rootBoiler["flameCurr"] = (float)(int16_t)EMS_Boiler.flameCurr / 10; - - if (EMS_Boiler.heatPmp != EMS_VALUE_BOOL_NOTSET) - rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); - - if (EMS_Boiler.fanWork != EMS_VALUE_BOOL_NOTSET) - rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); - - if (EMS_Boiler.ignWork != EMS_VALUE_BOOL_NOTSET) - rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); - - if (EMS_Boiler.heating_temp != EMS_VALUE_INT_NOTSET) - rootBoiler["heating_temp"] = EMS_Boiler.heating_temp; - if (EMS_Boiler.pump_mod_max != EMS_VALUE_INT_NOTSET) - rootBoiler["pump_mod_max"] = EMS_Boiler.pump_mod_max; - if (EMS_Boiler.pump_mod_min != EMS_VALUE_INT_NOTSET) - rootBoiler["pump_mod_min"] = EMS_Boiler.pump_mod_min; - - if (EMS_Boiler.wWHeat != EMS_VALUE_BOOL_NOTSET) - rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); - - if (abs(EMS_Boiler.wWStarts) != EMS_VALUE_LONG_NOTSET) - rootBoiler["wWStarts"] = (float)EMS_Boiler.wWStarts; - if (abs(EMS_Boiler.wWWorkM) != EMS_VALUE_LONG_NOTSET) - rootBoiler["wWWorkM"] = (float)EMS_Boiler.wWWorkM; - if (abs(EMS_Boiler.UBAuptime) != EMS_VALUE_LONG_NOTSET) - rootBoiler["UBAuptime"] = (float)EMS_Boiler.UBAuptime; - - if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) - rootBoiler["burnStarts"] = (float)EMS_Boiler.burnStarts; - if (abs(EMS_Boiler.burnWorkMin) != EMS_VALUE_LONG_NOTSET) - rootBoiler["burnWorkMin"] = (float)EMS_Boiler.burnWorkMin; - if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET) - rootBoiler["heatWorkMin"] = (float)EMS_Boiler.heatWorkMin; - - if (EMS_Boiler.serviceCode != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; - rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; - } - - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing boiler data via MQTT"); - myESP.mqttPublish(TOPIC_BOILER_DATA, data); - - // see if the heating or hot tap water has changed, if so send - // last_boilerActive stores heating in bit 1 and tap water in bit 2 - static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off - if ((last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) || force) { - myDebugLog("Publishing hot water and heating states via MQTT"); - myESP.mqttPublish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0"); - myESP.mqttPublish(TOPIC_BOILER_HEATING_ACTIVE, EMS_Boiler.heatingActive == 1 ? "1" : "0"); - - last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state - } - - ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_BOILER); // unset flag + if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { + rootBoiler["wWComfort"] = "Hot"; + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Eco) { + rootBoiler["wWComfort"] = "Eco"; + } else if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Intelligent) { + rootBoiler["wWComfort"] = "Intelligent"; } - // handle the thermostat values - if (ems_getThermostatEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_THERMOSTAT) || force)) { - doc.clear(); - JsonObject rootThermostat = doc.to(); + if (EMS_Boiler.wWSelTemp != EMS_VALUE_INT_NOTSET) + rootBoiler["wWSelTemp"] = EMS_Boiler.wWSelTemp; + if (EMS_Boiler.wWDesinfectTemp != EMS_VALUE_INT_NOTSET) + rootBoiler["wWDesinfectionTemp"] = EMS_Boiler.wWDesinfectTemp; + if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET) + rootBoiler["selFlowTemp"] = EMS_Boiler.selFlowTemp; + if (EMS_Boiler.selBurnPow != EMS_VALUE_INT_NOTSET) + rootBoiler["selBurnPow"] = EMS_Boiler.selBurnPow; + if (EMS_Boiler.curBurnPow != EMS_VALUE_INT_NOTSET) + rootBoiler["curBurnPow"] = EMS_Boiler.curBurnPow; + if (EMS_Boiler.pumpMod != EMS_VALUE_INT_NOTSET) + rootBoiler["pumpMod"] = EMS_Boiler.pumpMod; + if (EMS_Boiler.wWCircPump != EMS_VALUE_BOOL_NOTSET) + rootBoiler["wWCircPump"] = EMS_Boiler.wWCircPump; - for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { - _EMS_Thermostat_HC * thermostat = &EMS_Thermostat.hc[hc_v - 1]; + if (EMS_Boiler.extTemp > EMS_VALUE_SHORT_NOTSET) + rootBoiler["outdoorTemp"] = (float)EMS_Boiler.extTemp / 10; + if (EMS_Boiler.wWCurTmp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["wWCurTmp"] = (float)EMS_Boiler.wWCurTmp / 10; + if (EMS_Boiler.wWCurFlow != EMS_VALUE_INT_NOTSET) + rootBoiler["wWCurFlow"] = (float)EMS_Boiler.wWCurFlow / 10; + if (EMS_Boiler.curFlowTemp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["curFlowTemp"] = (float)EMS_Boiler.curFlowTemp / 10; + if (EMS_Boiler.retTemp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["retTemp"] = (float)EMS_Boiler.retTemp / 10; + if (EMS_Boiler.switchTemp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["switchTemp"] = (float)EMS_Boiler.switchTemp / 10; + if (EMS_Boiler.sysPress != EMS_VALUE_INT_NOTSET) + rootBoiler["sysPress"] = (float)EMS_Boiler.sysPress / 10; + if (EMS_Boiler.boilTemp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["boilTemp"] = (float)EMS_Boiler.boilTemp / 10; + if (EMS_Boiler.exhaustTemp < EMS_VALUE_USHORT_NOTSET) + rootBoiler["exhaustTemp"] = (float)EMS_Boiler.exhaustTemp / 10; + if (EMS_Boiler.wWActivated != EMS_VALUE_BOOL_NOTSET) + rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); - // only send if we have an active Heating Circuit with an actual setpoint temp temperature values - if ((thermostat->active) && (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET)) { - // build new json object - char hc[10]; // hc{1-4} - strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); - strlcat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); - JsonObject dataThermostat = rootThermostat.createNestedObject(hc); - uint8_t model = ems_getThermostatModel(); + if (EMS_Boiler.wWActivated != EMS_VALUE_BOOL_NOTSET) + rootBoiler["wWOnetime"] = _bool_to_char(s, EMS_Boiler.wWOneTime); - // different logic depending on thermostat types - if (model == EMS_DEVICE_FLAG_EASY) { - if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 100; - if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 100; - } else if (model == EMS_DEVICE_FLAG_JUNKERS) { - if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 10; - if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 10; - } else { - if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 2; - if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) - dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 10; + if (EMS_Boiler.wWCirc != EMS_VALUE_BOOL_NOTSET) + rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); - if (thermostat->daytemp != EMS_VALUE_INT_NOTSET) - dataThermostat[THERMOSTAT_DAYTEMP] = (float)thermostat->daytemp / 2; - if (thermostat->nighttemp != EMS_VALUE_INT_NOTSET) - dataThermostat[THERMOSTAT_NIGHTTEMP] = (float)thermostat->nighttemp / 2; - if (thermostat->holidaytemp != EMS_VALUE_INT_NOTSET) - dataThermostat[THERMOSTAT_HOLIDAYTEMP] = (float)thermostat->holidaytemp / 2; + if (EMS_Boiler.burnGas != EMS_VALUE_BOOL_NOTSET) + rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); - if (thermostat->heatingtype != EMS_VALUE_INT_NOTSET) - dataThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; + if (EMS_Boiler.flameCurr < EMS_VALUE_USHORT_NOTSET) + rootBoiler["flameCurr"] = (float)(int16_t)EMS_Boiler.flameCurr / 10; - if (thermostat->circuitcalctemp != EMS_VALUE_INT_NOTSET) - dataThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; - } + if (EMS_Boiler.heatPmp != EMS_VALUE_BOOL_NOTSET) + rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); - // Thermostat Mode - _EMS_THERMOSTAT_MODE thermoMode = _getThermostatMode(hc_v); - if (thermoMode == EMS_THERMOSTAT_MODE_OFF) { - dataThermostat[THERMOSTAT_MODE] = "off"; - } else if (thermoMode == EMS_THERMOSTAT_MODE_MANUAL) { - dataThermostat[THERMOSTAT_MODE] = "manual"; - } else if (thermoMode == EMS_THERMOSTAT_MODE_AUTO) { - dataThermostat[THERMOSTAT_MODE] = "auto"; - } else if (thermoMode == EMS_THERMOSTAT_MODE_DAY) { - dataThermostat[THERMOSTAT_MODE] = "day"; - } else if (thermoMode == EMS_THERMOSTAT_MODE_NIGHT) { - dataThermostat[THERMOSTAT_MODE] = "night"; - } + if (EMS_Boiler.fanWork != EMS_VALUE_BOOL_NOTSET) + rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); + + if (EMS_Boiler.ignWork != EMS_VALUE_BOOL_NOTSET) + rootBoiler["ignWork"] = _bool_to_char(s, EMS_Boiler.ignWork); + + if (EMS_Boiler.heating_temp != EMS_VALUE_INT_NOTSET) + rootBoiler["heating_temp"] = EMS_Boiler.heating_temp; + if (EMS_Boiler.pump_mod_max != EMS_VALUE_INT_NOTSET) + rootBoiler["pump_mod_max"] = EMS_Boiler.pump_mod_max; + if (EMS_Boiler.pump_mod_min != EMS_VALUE_INT_NOTSET) + rootBoiler["pump_mod_min"] = EMS_Boiler.pump_mod_min; + + if (EMS_Boiler.wWHeat != EMS_VALUE_BOOL_NOTSET) + rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); + + if (abs(EMS_Boiler.wWStarts) != EMS_VALUE_LONG_NOTSET) + rootBoiler["wWStarts"] = (float)EMS_Boiler.wWStarts; + if (abs(EMS_Boiler.wWWorkM) != EMS_VALUE_LONG_NOTSET) + rootBoiler["wWWorkM"] = (float)EMS_Boiler.wWWorkM; + if (abs(EMS_Boiler.UBAuptime) != EMS_VALUE_LONG_NOTSET) + rootBoiler["UBAuptime"] = (float)EMS_Boiler.UBAuptime; + + if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) + rootBoiler["burnStarts"] = (float)EMS_Boiler.burnStarts; + if (abs(EMS_Boiler.burnWorkMin) != EMS_VALUE_LONG_NOTSET) + rootBoiler["burnWorkMin"] = (float)EMS_Boiler.burnWorkMin; + if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET) + rootBoiler["heatWorkMin"] = (float)EMS_Boiler.heatWorkMin; + + if (EMS_Boiler.serviceCode != EMS_VALUE_USHORT_NOTSET) { + rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; + rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; + } + + serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing boiler data via MQTT"); + myESP.mqttPublish(TOPIC_BOILER_DATA, data); + + // see if the heating or hot tap water has changed, if so send + // last_boilerActive stores heating in bit 1 and tap water in bit 2 + static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off + if (last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) { + myDebugLog("Publishing hot water and heating states via MQTT"); + myESP.mqttPublish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0"); + myESP.mqttPublish(TOPIC_BOILER_HEATING_ACTIVE, EMS_Boiler.heatingActive == 1 ? "1" : "0"); + + last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state + } +} + +// handle the thermostat values +void publishEMSValues_thermostat() { + char s[20] = {0}; // for formatting strings + StaticJsonDocument doc; + char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; + JsonObject rootThermostat = doc.to(); + + for (uint8_t hc_v = 1; hc_v <= EMS_THERMOSTAT_MAXHC; hc_v++) { + _EMS_Thermostat_HC * thermostat = &EMS_Thermostat.hc[hc_v - 1]; + + // only send if we have an active Heating Circuit with an actual setpoint temp temperature values + if ((thermostat->active) && (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET)) { + // build new json object + char hc[10]; // hc{1-4} + strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); + strlcat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); + JsonObject dataThermostat = rootThermostat.createNestedObject(hc); + uint8_t model = ems_getThermostatModel(); + + // different logic depending on thermostat types + if (model == EMS_DEVICE_FLAG_EASY) { + if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 100; + if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 100; + } else if (model == EMS_DEVICE_FLAG_JUNKERS) { + if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 10; + if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 10; + } else { + if (thermostat->setpoint_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_SELTEMP] = (float)thermostat->setpoint_roomTemp / 2; + if (thermostat->curr_roomTemp > EMS_VALUE_SHORT_NOTSET) + dataThermostat[THERMOSTAT_CURRTEMP] = (float)thermostat->curr_roomTemp / 10; + + if (thermostat->daytemp != EMS_VALUE_INT_NOTSET) + dataThermostat[THERMOSTAT_DAYTEMP] = (float)thermostat->daytemp / 2; + if (thermostat->nighttemp != EMS_VALUE_INT_NOTSET) + dataThermostat[THERMOSTAT_NIGHTTEMP] = (float)thermostat->nighttemp / 2; + if (thermostat->holidaytemp != EMS_VALUE_INT_NOTSET) + dataThermostat[THERMOSTAT_HOLIDAYTEMP] = (float)thermostat->holidaytemp / 2; + + if (thermostat->heatingtype != EMS_VALUE_INT_NOTSET) + dataThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype; + + if (thermostat->circuitcalctemp != EMS_VALUE_INT_NOTSET) + dataThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp; + } + + // Thermostat Mode + _EMS_THERMOSTAT_MODE thermoMode = _getThermostatMode(hc_v); + if (thermoMode == EMS_THERMOSTAT_MODE_OFF) { + dataThermostat[THERMOSTAT_MODE] = "off"; + } else if (thermoMode == EMS_THERMOSTAT_MODE_MANUAL) { + dataThermostat[THERMOSTAT_MODE] = "manual"; + } else if (thermoMode == EMS_THERMOSTAT_MODE_AUTO) { + dataThermostat[THERMOSTAT_MODE] = "auto"; + } else if (thermoMode == EMS_THERMOSTAT_MODE_DAY) { + dataThermostat[THERMOSTAT_MODE] = "day"; + } else if (thermoMode == EMS_THERMOSTAT_MODE_NIGHT) { + dataThermostat[THERMOSTAT_MODE] = "night"; } } - - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing thermostat data via MQTT"); - myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); - ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_THERMOSTAT); // unset flag } - // handle the mixing values - if (ems_getMixingModuleEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_MIXING) || force)) { - doc.clear(); - JsonObject rootMixing = doc.to(); + serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing thermostat data via MQTT"); + myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); +} - for (uint8_t hc_v = 1; hc_v <= EMS_MIXING_MAXHC; hc_v++) { - _EMS_MixingModule_HC * mixingHC = &EMS_MixingModule.hc[hc_v - 1]; +// publish mixing data +void publishEMSValues_mixing() { + char s[20] = {0}; // for formatting strings + StaticJsonDocument doc; + char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; + JsonObject rootMixing = doc.to(); - // only send if we have an active Heating Circuit with real data - if (mixingHC->active) { - char hc[10]; // hc{1-4} - strlcpy(hc, MIXING_HC, sizeof(hc)); - strlcat(hc, _int_to_char(s, mixingHC->hc), sizeof(hc)); - JsonObject dataMixingHC = rootMixing.createNestedObject(hc); - if (mixingHC->flowTemp < EMS_VALUE_USHORT_NOTSET) - dataMixingHC["flowTemp"] = (float)mixingHC->flowTemp / 10; - if (mixingHC->flowSetTemp != EMS_VALUE_INT_NOTSET) - dataMixingHC["setflowTemp"] = mixingHC->flowSetTemp; - if (mixingHC->pumpMod != EMS_VALUE_INT_NOTSET) - dataMixingHC["pumpMod"] = mixingHC->pumpMod; - if (mixingHC->valveStatus != EMS_VALUE_INT_NOTSET) - dataMixingHC["valveStatus"] = mixingHC->valveStatus; - } + for (uint8_t hc_v = 1; hc_v <= EMS_MIXING_MAXHC; hc_v++) { + _EMS_MixingModule_HC * mixingHC = &EMS_MixingModule.hc[hc_v - 1]; + + // only send if we have an active Heating Circuit with real data + if (mixingHC->active) { + char hc[10]; // hc{1-4} + strlcpy(hc, MIXING_HC, sizeof(hc)); + strlcat(hc, _int_to_char(s, mixingHC->hc), sizeof(hc)); + JsonObject dataMixingHC = rootMixing.createNestedObject(hc); + if (mixingHC->flowTemp < EMS_VALUE_USHORT_NOTSET) + dataMixingHC["flowTemp"] = (float)mixingHC->flowTemp / 10; + if (mixingHC->flowSetTemp != EMS_VALUE_INT_NOTSET) + dataMixingHC["setflowTemp"] = mixingHC->flowSetTemp; + if (mixingHC->pumpMod != EMS_VALUE_INT_NOTSET) + dataMixingHC["pumpMod"] = mixingHC->pumpMod; + if (mixingHC->valveStatus != EMS_VALUE_INT_NOTSET) + dataMixingHC["valveStatus"] = mixingHC->valveStatus; } - - for (uint8_t wwc_v = 1; wwc_v <= EMS_MIXING_MAXWWC; wwc_v++) { - _EMS_MixingModule_WWC * mixingWWC = &EMS_MixingModule.wwc[wwc_v - 1]; - // only send if we have an active Warm water Circuit with real data - if (mixingWWC->active) { - char wwc[10]; // wwc{1-2} - strlcpy(wwc, MIXING_WWC, sizeof(wwc)); - strlcat(wwc, _int_to_char(s, mixingWWC->wwc), sizeof(wwc)); - JsonObject dataMixing = rootMixing.createNestedObject(wwc); - if (mixingWWC->flowTemp < EMS_VALUE_USHORT_NOTSET) - dataMixing["wwTemp"] = (float)mixingWWC->flowTemp / 10; - if (mixingWWC->pumpMod != EMS_VALUE_INT_NOTSET) - dataMixing["pumpStatus"] = mixingWWC->pumpMod; - if (mixingWWC->tempStatus != EMS_VALUE_INT_NOTSET) - dataMixing["tempStatus"] = mixingWWC->tempStatus; - } - } - - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing mixing data via MQTT"); - myESP.mqttPublish(TOPIC_MIXING_DATA, data); - ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_MIXING); // unset flag } - // For SM10 and SM100/SM200 Solar Modules - if (ems_getSolarModuleEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_SOLAR) || force)) { - // build new json object - doc.clear(); - JsonObject rootSM = doc.to(); - - if (EMS_SolarModule.collectorTemp > EMS_VALUE_SHORT_NOTSET) - rootSM[SM_COLLECTORTEMP] = (float)EMS_SolarModule.collectorTemp / 10; - - if (EMS_SolarModule.bottomTemp > EMS_VALUE_SHORT_NOTSET) - rootSM[SM_BOTTOMTEMP] = (float)EMS_SolarModule.bottomTemp / 10; - - if (EMS_SolarModule.pumpModulation != EMS_VALUE_INT_NOTSET) - rootSM[SM_PUMPMODULATION] = EMS_SolarModule.pumpModulation; - - if (EMS_SolarModule.pump != EMS_VALUE_BOOL_NOTSET) { - rootSM[SM_PUMP] = _bool_to_char(s, EMS_SolarModule.pump); + for (uint8_t wwc_v = 1; wwc_v <= EMS_MIXING_MAXWWC; wwc_v++) { + _EMS_MixingModule_WWC * mixingWWC = &EMS_MixingModule.wwc[wwc_v - 1]; + // only send if we have an active Warm water Circuit with real data + if (mixingWWC->active) { + char wwc[10]; // wwc{1-2} + strlcpy(wwc, MIXING_WWC, sizeof(wwc)); + strlcat(wwc, _int_to_char(s, mixingWWC->wwc), sizeof(wwc)); + JsonObject dataMixing = rootMixing.createNestedObject(wwc); + if (mixingWWC->flowTemp < EMS_VALUE_USHORT_NOTSET) + dataMixing["wwTemp"] = (float)mixingWWC->flowTemp / 10; + if (mixingWWC->pumpMod != EMS_VALUE_INT_NOTSET) + dataMixing["pumpStatus"] = mixingWWC->pumpMod; + if (mixingWWC->tempStatus != EMS_VALUE_INT_NOTSET) + dataMixing["tempStatus"] = mixingWWC->tempStatus; } - - if (EMS_SolarModule.pumpWorkMin != EMS_VALUE_LONG_NOTSET) { - rootSM[SM_PUMPWORKMIN] = (float)EMS_SolarModule.pumpWorkMin; - } - - if (EMS_SolarModule.EnergyLastHour < EMS_VALUE_USHORT_NOTSET) - rootSM[SM_ENERGYLASTHOUR] = (float)EMS_SolarModule.EnergyLastHour / 10; - - if (EMS_SolarModule.EnergyToday < EMS_VALUE_USHORT_NOTSET) - rootSM[SM_ENERGYTODAY] = EMS_SolarModule.EnergyToday; - - if (EMS_SolarModule.EnergyTotal < EMS_VALUE_USHORT_NOTSET) - rootSM[SM_ENERGYTOTAL] = (float)EMS_SolarModule.EnergyTotal / 10; - - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing SM data via MQTT"); - myESP.mqttPublish(TOPIC_SM_DATA, data); - ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_SOLAR); // unset flag } - // handle HeatPump - if (ems_getHeatPumpEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_HEATPUMP) || force)) { - // build new json object - doc.clear(); - JsonObject rootSM = doc.to(); + serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing mixing data via MQTT"); + myESP.mqttPublish(TOPIC_MIXING_DATA, data); +} - if (EMS_HeatPump.HPModulation != EMS_VALUE_INT_NOTSET) - rootSM[HP_PUMPMODULATION] = EMS_HeatPump.HPModulation; +// For SM10 and SM100/SM200 Solar Modules +void publishEMSValues_solar() { + char s[20] = {0}; // for formatting strings + StaticJsonDocument doc; + char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; + JsonObject rootSM = doc.to(); - if (EMS_HeatPump.HPSpeed != EMS_VALUE_INT_NOTSET) - rootSM[HP_PUMPSPEED] = EMS_HeatPump.HPSpeed; + if (EMS_SolarModule.collectorTemp > EMS_VALUE_SHORT_NOTSET) + rootSM[SM_COLLECTORTEMP] = (float)EMS_SolarModule.collectorTemp / 10; - data[0] = '\0'; // reset data for next package - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing HeatPump data via MQTT"); - myESP.mqttPublish(TOPIC_HP_DATA, data); - ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_HEATPUMP); // unset flag + if (EMS_SolarModule.bottomTemp > EMS_VALUE_SHORT_NOTSET) + rootSM[SM_BOTTOMTEMP] = (float)EMS_SolarModule.bottomTemp / 10; + + if (EMS_SolarModule.pumpModulation != EMS_VALUE_INT_NOTSET) + rootSM[SM_PUMPMODULATION] = EMS_SolarModule.pumpModulation; + + if (EMS_SolarModule.pump != EMS_VALUE_BOOL_NOTSET) { + rootSM[SM_PUMP] = _bool_to_char(s, EMS_SolarModule.pump); } + + if (EMS_SolarModule.pumpWorkMin != EMS_VALUE_LONG_NOTSET) { + rootSM[SM_PUMPWORKMIN] = (float)EMS_SolarModule.pumpWorkMin; + } + + if (EMS_SolarModule.EnergyLastHour < EMS_VALUE_USHORT_NOTSET) + rootSM[SM_ENERGYLASTHOUR] = (float)EMS_SolarModule.EnergyLastHour / 10; + + if (EMS_SolarModule.EnergyToday < EMS_VALUE_USHORT_NOTSET) + rootSM[SM_ENERGYTODAY] = EMS_SolarModule.EnergyToday; + + if (EMS_SolarModule.EnergyTotal < EMS_VALUE_USHORT_NOTSET) + rootSM[SM_ENERGYTOTAL] = (float)EMS_SolarModule.EnergyTotal / 10; + + serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing SM data via MQTT"); + myESP.mqttPublish(TOPIC_SM_DATA, data); +} + +// handle HeatPump +void publishEMSValues_heatpump() { + StaticJsonDocument doc; + char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; + JsonObject rootHP = doc.to(); + + if (EMS_HeatPump.HPModulation != EMS_VALUE_INT_NOTSET) + rootHP[HP_PUMPMODULATION] = EMS_HeatPump.HPModulation; + + if (EMS_HeatPump.HPSpeed != EMS_VALUE_INT_NOTSET) + rootHP[HP_PUMPSPEED] = EMS_HeatPump.HPSpeed; + + serializeJson(doc, data, sizeof(data)); + myDebugLog("Publishing HeatPump data via MQTT"); + myESP.mqttPublish(TOPIC_HP_DATA, data); } // Publish shower data void do_publishShowerData() { - StaticJsonDocument<200> doc; - JsonObject rootShower = doc.to(); - rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0"; - rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0"; + StaticJsonDocument doc; + JsonObject rootShower = doc.to(); + rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0"; + rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0"; // only publish shower duration if there is a value char s[50] = {0}; @@ -956,17 +947,58 @@ void do_publishShowerData() { myESP.mqttPublish(TOPIC_SHOWER_DATA, data, false); } -// call PublishValues with forcing forcing -void do_publishValues() { +// send values via MQTT +// a json object is created for each device type +void publishEMSValues(bool force) { + // don't send if MQTT is not connected or EMS bus is not connected + if (!myESP.isMQTTConnected() || (!ems_getBusConnected()) || (EMSESP_Settings.publish_time == -1)) { + return; + } + + if (ems_getBoilerEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_BOILER) || force)) { + publishEMSValues_boiler(); + ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_BOILER); // unset flag + } + + if (ems_getThermostatEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_THERMOSTAT) || force)) { + publishEMSValues_thermostat(); + ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_THERMOSTAT); // unset flag + } + + if (ems_getMixingModuleEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_MIXING) || force)) { + publishEMSValues_mixing(); + ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_MIXING); // unset flag + } + + if (ems_getSolarModuleEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_SOLAR) || force)) { + publishEMSValues_solar(); + ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_SOLAR); // unset flag + } + + if (ems_getHeatPumpEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_HEATPUMP) || force)) { + publishEMSValues_heatpump(); + ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_HEATPUMP); // unset flag + } +} + +// publishes value via MQTT +void publishValues(bool force, bool send_sensor) { if (EMSESP_Settings.publish_time == -1) { - myDebugLog("Publishing is disabled."); return; } myDebugLog("Starting scheduled MQTT publish..."); - publishEMSValues(false); - publishSensorValues(); - myESP.heartbeatCheck(true); + publishEMSValues(force); + if (send_sensor) { + publishSensorValues(); + } + // myESP.heartbeatCheck(true); +} + +// calls publishValues fron the Ticker loop, also sending sensor data +// but not using false for force so only data that has changed will be sent +void do_publishValues() { + publishValues(false, true); } // callback to light up the LED, called via Ticker every second @@ -2096,6 +2128,10 @@ void initEMSESP() { * Shower Logic */ void showerCheck() { + if (!EMSESP_Settings.shower_timer) { + return; + } + uint32_t time_now = millis(); // if already in cold mode, ignore all this logic until we're out of the cold blast if (!EMSESP_Shower.doingColdShot) { @@ -2231,7 +2267,7 @@ void setup() { if (EMSESP_Settings.publish_time > 0) { publishValuesTimer.attach(EMSESP_Settings.publish_time, do_publishValues); // post MQTT EMS values } else if (EMSESP_Settings.publish_time == 0) { - // automatic mode. use this Ticker to send out sensor values + // automatic mode. use this Ticker to send out sensor values only. the EMS ones are done in the loop. publishValuesTimer.attach(DEFAULT_SENSOR_PUBLISHTIME, publishSensorValues); } @@ -2256,33 +2292,30 @@ void setup() { void loop() { myESP.loop(); // handle telnet, mqtt, wifi etc - // to prevent load, only run checks every second + // get Dallas Sensor readings every 2 seconds static uint32_t last_check = 0; - if (millis() - last_check < 1000) { - return; - } - last_check = millis(); - - // get Dallas Sensor readings - if (EMSESP_Settings.dallas_sensors) { - ds18.loop(); - } - - // if we have an EMS bus connection go and fetch some data and MQTT publish it - if (_need_first_publish) { - publishEMSValues(false); - publishSensorValues(); - _need_first_publish = false; // reset flag - } else { - // check if we're on auto mode for publishing - // then send EMS values, only if its been flagged to update - if (EMSESP_Settings.publish_time == 0) { - publishEMSValues(false); + uint32_t time_now = millis(); + if (time_now - last_check > 2000) { + last_check = time_now; + if (EMSESP_Settings.dallas_sensors) { + ds18.loop(); } } - // do shower logic, if enabled - if (EMSESP_Settings.shower_timer) { - showerCheck(); + // if we just have an EMS bus connection go and fetch the data and MQTT publish it to get started + if (_need_first_publish) { + publishValues(true, true); + _need_first_publish = false; + return; } + + // check if we're on auto mode for publishing + // then send EMS values, only if its been flagged to update. + // Don't send sensor data as this is done by the Ticker + if (EMSESP_Settings.publish_time == 0) { + publishValues(false, false); + } + + // do shower logic + showerCheck(); } diff --git a/src/version.h b/src/version.h index 79d69bf19..e64fc27fb 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.5b38" +#define APP_VERSION "1.9.5b39"