diff --git a/src/MyESP.cpp b/src/MyESP.cpp index 194645247..018408143 100644 --- a/src/MyESP.cpp +++ b/src/MyESP.cpp @@ -433,22 +433,23 @@ void MyESP::_printMQTTLog() { // Publish using the user's custom retain flag void MyESP::mqttPublish(const char * topic, const char * payload) { - mqttPublish(topic, payload, _mqtt_retain); + _mqttQueue(topic, payload, _mqtt_retain); +} +void MyESP::mqttPublish(const char * topic, JsonDocument payload) { + _mqttQueue(topic, payload, _mqtt_retain); } // MQTT Publish void MyESP::mqttPublish(const char * topic, const char * payload, bool retain) { - if (!_hasValue(topic)) { - return; - } - - _mqttQueue(topic, payload, retain); // queue the message + _mqttQueue(topic, payload, retain); +} +void MyESP::mqttPublish(const char * topic, JsonDocument payload, bool retain) { + _mqttQueue(topic, payload, retain); } +// put a payload string into the queue bool MyESP::_mqttQueue(const char * topic, const char * payload, bool retain) { - // Queue is not meant to send message "offline" - // We must prevent the queue does not get full while offline - if (!mqttClient.connected() || (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE)) { + if (!mqttClient.connected() || _mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE || !_hasValue(topic)) { return false; } @@ -458,7 +459,7 @@ bool MyESP::_mqttQueue(const char * topic, const char * payload, bool retain) { element.retain = retain; element.packetId = 0; element.retry_count = 0; - if (NULL != payload) { + if (payload != NULL) { element.payload = strdup(payload); } #ifdef MYESP_DEBUG @@ -469,6 +470,34 @@ bool MyESP::_mqttQueue(const char * topic, const char * payload, bool retain) { return true; } +// convert json doc to a string buffer and place on queue +bool MyESP::_mqttQueue(const char * topic, JsonDocument payload, bool retain) { + if (!mqttClient.connected() || _mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE || !_hasValue(topic)) { + return false; + } + + // create a new message + mqtt_message_t element; + element.topic = strdup(topic); + element.retain = retain; + element.packetId = 0; + element.retry_count = 0; + + // reserve space for buffer and serialize json into it + const size_t capacity = measureJson(payload) + 1; + if (capacity) { + element.payload = (char *)malloc(capacity); + serializeJson(payload, (char *)element.payload, capacity); + } + +#ifdef MYESP_DEBUG + myDebug_P(PSTR("[MQTT] Adding to queue: #%d [%s] %s"), _mqtt_queue.size(), element.topic, element.payload); +#endif + _mqtt_queue.push_back(element); + + return true; +} + // called when an MQTT Publish ACK is received // check if ACK matches the last Publish we sent, if not report an error // and always remove from queue @@ -1604,11 +1633,10 @@ void MyESP::heartbeatCheck(bool force) { uint32_t free_memory = ESP.getFreeHeap(); uint8_t mem_available = 100 * free_memory / total_memory; // as a % - StaticJsonDocument doc; - JsonObject rootHeartbeat = doc.to(); + const size_t capacity = JSON_OBJECT_SIZE(6); + StaticJsonDocument doc; + JsonObject rootHeartbeat = doc.to(); - //rootHeartbeat["version"] = _app_version; - //rootHeartbeat["IP"] = WiFi.localIP().toString(); rootHeartbeat["rssid"] = getWifiQuality(); rootHeartbeat["load"] = getSystemLoadAverage(); rootHeartbeat["uptime"] = _getUptime(); @@ -1616,10 +1644,7 @@ void MyESP::heartbeatCheck(bool force) { rootHeartbeat["tcpdrops"] = _getSystemDropoutCounter(); rootHeartbeat["mqttpublishfails"] = _mqtt_publish_fails; - char data[300] = {0}; - serializeJson(doc, data, sizeof(data)); - - (void)mqttPublish(MQTT_TOPIC_HEARTBEAT, data, false); // send to MQTT with retain off + mqttPublish(MQTT_TOPIC_HEARTBEAT, doc, false); // send to MQTT with retain off } } @@ -1632,13 +1657,13 @@ void MyESP::heartbeatPrint() { uint32_t total_memory = _getInitialFreeHeap(); uint32_t free_memory = ESP.getFreeHeap(); - myDebug("[%d] uptime:%d bytesfree:%d (%2u%%), load:%d, dropouts:%d", - i++, - _getUptime(), - free_memory, - 100 * free_memory / total_memory, - getSystemLoadAverage(), - _getSystemDropoutCounter() + myDebug_P(PSTR("[%d] uptime:%d bytesfree:%d (%2u%%), load:%d, dropouts:%d"), + i++, + _getUptime(), + free_memory, + 100 * free_memory / total_memory, + getSystemLoadAverage(), + _getSystemDropoutCounter() ); } @@ -2418,7 +2443,7 @@ void MyESP::writeLogEvent(const uint8_t type, const char * msg) { // Handles WebSocket Events void MyESP::_onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len) { if (type == WS_EVT_ERROR) { - myDebug("[WEB] WebSocket[%s][%u] error(%u): %s\r\n", server->url(), client->id(), *((uint16_t *)arg), (char *)data); + myDebug_P(PSTR("[WEB] WebSocket[%s][%u] error(%u): %s\r\n"), server->url(), client->id(), *((uint16_t *)arg), (char *)data); } else if (type == WS_EVT_DATA) { AwsFrameInfo * info = (AwsFrameInfo *)arg; uint64_t index = info->index; @@ -2550,7 +2575,7 @@ bool MyESP::_fs_sendConfig() { // send custom status via ws void MyESP::_sendCustomStatus() { - DynamicJsonDocument doc(MYESP_JSON_MAXSIZE_LARGE); + DynamicJsonDocument doc(MYESP_JSON_MAXSIZE_MEDIUM); JsonObject root = doc.to(); @@ -2566,7 +2591,7 @@ void MyESP::_sendCustomStatus() { (_web_callback_f)(root); } - char buffer[MYESP_JSON_MAXSIZE_LARGE]; + char buffer[MYESP_JSON_MAXSIZE_MEDIUM]; size_t len = serializeJson(root, buffer); #ifdef MYESP_DEBUG @@ -2582,13 +2607,13 @@ void MyESP::_sendStatus() { uint32_t total_memory = _getInitialFreeHeap(); uint32_t free_memory = ESP.getFreeHeap(); - DynamicJsonDocument doc(MQTT_MAX_PAYLOAD_SIZE_LARGE); + DynamicJsonDocument doc(MYESP_JSON_MAXSIZE_MEDIUM); JsonObject root = doc.to(); root["command"] = "status"; FSInfo fsinfo; if (!SPIFFS.info(fsinfo)) { - myDebug("[SYSTEM] Error getting info on SPIFFS"); + myDebug_P(PSTR("[SYSTEM] Error getting info on SPIFFS")); } else { root["availspiffs"] = (fsinfo.totalBytes - fsinfo.usedBytes) / 1000; root["spiffssize"] = (fsinfo.totalBytes / 1000); @@ -2626,7 +2651,7 @@ void MyESP::_sendStatus() { sprintf(uptime, "%d day%s %d hour%s %d minute%s %d second%s", d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s"); root["uptime"] = uptime; - char buffer[MQTT_MAX_PAYLOAD_SIZE_LARGE]; + char buffer[MYESP_JSON_MAXSIZE_MEDIUM]; size_t len = serializeJson(root, buffer); _ws->textAll(buffer, len); @@ -2783,13 +2808,13 @@ void MyESP::_printHeap(const char * prefix) { uint32_t total_memory = _getInitialFreeHeap(); uint32_t free_memory = ESP.getFreeHeap(); - myDebug("%s Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)", - prefix, - total_memory, - total_memory - free_memory, - 100 * (total_memory - free_memory) / total_memory, - free_memory, - 100 * free_memory / total_memory); + myDebug_P(PSTR("%s Free Heap: %d bytes initially | %d bytes used (%2u%%) | %d bytes free (%2u%%)"), + prefix, + total_memory, + total_memory - free_memory, + 100 * (total_memory - free_memory) / total_memory, + free_memory, + 100 * free_memory / total_memory); } // send UTC time via ws @@ -2959,7 +2984,7 @@ void MyESP::loop() { } if (_formatreq) { - myDebug("[SYSTEM] Factory reset initiated. Please wait. System will automatically restart when complete..."); + myDebug_P(PSTR("[SYSTEM] Factory reset initiated. Please wait. System will automatically restart when complete...")); SPIFFS.end(); _ws->enable(false); SPIFFS.format(); @@ -2969,7 +2994,7 @@ void MyESP::loop() { if (_shouldRestart) { writeLogEvent(MYESP_SYSLOG_INFO, "System is restarting"); - myDebug("[SYSTEM] Restarting..."); + myDebug_P(PSTR("[SYSTEM] Restarting...")); _deferredReset(500, CUSTOM_RESET_TERMINAL); ESP.restart(); } @@ -2977,6 +3002,4 @@ void MyESP::loop() { delay(MYESP_DELAY); // some time to WiFi and everything else to catch up, calls yield, and also prevent overheating } - - MyESP myESP; diff --git a/src/MyESP.h b/src/MyESP.h index faa404cbd..c8b755db6 100644 --- a/src/MyESP.h +++ b/src/MyESP.h @@ -96,22 +96,18 @@ extern struct rst_info resetInfo; #define MQTT_QOS 0 // default qos 0 #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 -#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 +#define MQTT_QUEUE_MAX_SIZE 50 // Size of the MQTT queue +#define MQTT_PUBLISH_WAIT 750 // time in ms before sending MQTT messages +#define MQTT_PUBLISH_MAX_RETRY 4 // max retries for giving up on publishing +#define MYESP_JSON_MAXSIZE_LARGE 2000 // for large Dynamic json files - https://arduinojson.org/v6/assistant/ +#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files - https://arduinojson.org/v6/assistant/ +#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents - https://arduinojson.org/v6/assistant/ // Internal MQTT events #define MQTT_CONNECT_EVENT 0 #define MQTT_DISCONNECT_EVENT 1 #define MQTT_MESSAGE_EVENT 2 -#define MYESP_JSON_MAXSIZE_LARGE 2000 // for large Dynamic json files -#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files -#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents - #define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on #define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off @@ -213,9 +209,9 @@ struct RtcmemData { static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); -#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute) -#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot -#define MYESP_HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins) +#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 min) +#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot +#define MYESP_HEARTBEAT_INTERVAL 60000 // in milliseconds, how often the MQTT heartbeat is sent (1 min) typedef struct { bool set; // is it a set command? @@ -286,6 +282,8 @@ class MyESP { void mqttUnsubscribe(const char * topic); void mqttPublish(const char * topic, const char * payload); void mqttPublish(const char * topic, const char * payload, bool retain); + void mqttPublish(const char * topic, JsonDocument payload); + void mqttPublish(const char * topic, JsonDocument payload, bool retain); void setMQTT(mqtt_callback_f callback); bool mqttUseNestedJson(); @@ -346,6 +344,7 @@ class MyESP { void _sendStart(); char * _mqttTopic(const char * topic); bool _mqttQueue(const char * topic, const char * payload, bool retain); + bool _mqttQueue(const char * topic, JsonDocument payload, bool retain); void _printMQTTLog(); void _mqttPublishQueue(); void _mqttRemoveLastPublish(); diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 03442ca17..826465bca 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -573,14 +573,14 @@ void publishSensorValues() { // each payload per sensor is 30 bytes so calculate if we have enough space if ((EMSESP_Settings.dallas_sensors * 50) > DS18_MQTT_PAYLOAD_MAXSIZE) { - myDebug("Error: too many Dallas sensors for MQTT payload"); + myDebug_P(PSTR("Error: too many Dallas sensors for MQTT payload")); } StaticJsonDocument doc; JsonObject sensors = doc.to(); - bool hasdata = false; - char buffer[128] = {0}; // temp string buffer + bool hasdata = false; + char buffer[128]; // temp string buffer // if we're not using nested JSON, send each sensor out seperately if (!myESP.mqttUseNestedJson()) { @@ -593,9 +593,7 @@ void publishSensorValues() { 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 + myESP.mqttPublish(topic, doc); // and publish } } if (hasdata) { @@ -604,24 +602,25 @@ void publishSensorValues() { return; // exit } - // see if the sensor values have changed, if so send it on + // group all sensors together - https://github.com/proddy/EMS-ESP/issues/327 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 +#ifdef SENSOR_MQTT_USEID + sensors[ds18.getDeviceID(buffer, i)] = sensorValue; +#else char sensorID[10]; // sensor{1-n} 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] = ds18.getDeviceID(buffer, i); dataSensor[PAYLOAD_EXTERNAL_SENSOR_TEMP] = sensorValue; +#endif } } - char data[DS18_MQTT_PAYLOAD_MAXSIZE] = {0}; - serializeJson(doc, data, sizeof(data)); - myESP.mqttPublish(TOPIC_EXTERNAL_SENSORS, data); + myESP.mqttPublish(TOPIC_EXTERNAL_SENSORS, doc); if (hasdata) { myDebugLog("Publishing external sensor data via MQTT"); @@ -630,10 +629,11 @@ void publishSensorValues() { // 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(); + const size_t capacity = JSON_OBJECT_SIZE(34); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ + DynamicJsonDocument doc(capacity); + JsonObject rootBoiler = doc.to(); + + char s[20]; // for formatting strings if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { rootBoiler["wWComfort"] = "Hot"; @@ -643,126 +643,153 @@ void publishEMSValues_boiler() { rootBoiler["wWComfort"] = "Intelligent"; } - if (EMS_Boiler.wWSelTemp != EMS_VALUE_INT_NOTSET) + if (EMS_Boiler.wWSelTemp != EMS_VALUE_INT_NOTSET) { rootBoiler["wWSelTemp"] = EMS_Boiler.wWSelTemp; - if (EMS_Boiler.wWDesinfectTemp != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.wWDesinfectTemp != EMS_VALUE_INT_NOTSET) { rootBoiler["wWDesinfectionTemp"] = EMS_Boiler.wWDesinfectTemp; - if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET) { rootBoiler["selFlowTemp"] = EMS_Boiler.selFlowTemp; - if (EMS_Boiler.selBurnPow != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.selBurnPow != EMS_VALUE_INT_NOTSET) { rootBoiler["selBurnPow"] = EMS_Boiler.selBurnPow; - if (EMS_Boiler.curBurnPow != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.curBurnPow != EMS_VALUE_INT_NOTSET) { rootBoiler["curBurnPow"] = EMS_Boiler.curBurnPow; - if (EMS_Boiler.pumpMod != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.pumpMod != EMS_VALUE_INT_NOTSET) { rootBoiler["pumpMod"] = EMS_Boiler.pumpMod; - if (EMS_Boiler.wWCircPump != EMS_VALUE_BOOL_NOTSET) + } + if (EMS_Boiler.wWCircPump != EMS_VALUE_BOOL_NOTSET) { rootBoiler["wWCircPump"] = EMS_Boiler.wWCircPump; - - if (EMS_Boiler.extTemp > EMS_VALUE_SHORT_NOTSET) + } + if (EMS_Boiler.extTemp > EMS_VALUE_SHORT_NOTSET) { rootBoiler["outdoorTemp"] = (float)EMS_Boiler.extTemp / 10; - if (EMS_Boiler.wWCurTmp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.wWCurTmp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["wWCurTmp"] = (float)EMS_Boiler.wWCurTmp / 10; - if (EMS_Boiler.wWCurFlow != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.wWCurFlow != EMS_VALUE_INT_NOTSET) { rootBoiler["wWCurFlow"] = (float)EMS_Boiler.wWCurFlow / 10; - if (EMS_Boiler.curFlowTemp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.curFlowTemp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["curFlowTemp"] = (float)EMS_Boiler.curFlowTemp / 10; - if (EMS_Boiler.retTemp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.retTemp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["retTemp"] = (float)EMS_Boiler.retTemp / 10; - if (EMS_Boiler.switchTemp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.switchTemp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["switchTemp"] = (float)EMS_Boiler.switchTemp / 10; - if (EMS_Boiler.sysPress != EMS_VALUE_INT_NOTSET) + } + if (EMS_Boiler.sysPress != EMS_VALUE_INT_NOTSET) { rootBoiler["sysPress"] = (float)EMS_Boiler.sysPress / 10; - if (EMS_Boiler.boilTemp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.boilTemp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["boilTemp"] = (float)EMS_Boiler.boilTemp / 10; - if (EMS_Boiler.exhaustTemp < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_Boiler.exhaustTemp < EMS_VALUE_USHORT_NOTSET) { rootBoiler["exhaustTemp"] = (float)EMS_Boiler.exhaustTemp / 10; - if (EMS_Boiler.wWActivated != EMS_VALUE_BOOL_NOTSET) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + if (abs(EMS_Boiler.wWStarts) != EMS_VALUE_LONG_NOTSET) { rootBoiler["wWStarts"] = (float)EMS_Boiler.wWStarts; - if (abs(EMS_Boiler.wWWorkM) != EMS_VALUE_LONG_NOTSET) + } + if (abs(EMS_Boiler.wWWorkM) != EMS_VALUE_LONG_NOTSET) { rootBoiler["wWWorkM"] = (float)EMS_Boiler.wWWorkM; - if (abs(EMS_Boiler.UBAuptime) != EMS_VALUE_LONG_NOTSET) + } + if (abs(EMS_Boiler.UBAuptime) != EMS_VALUE_LONG_NOTSET) { rootBoiler["UBAuptime"] = (float)EMS_Boiler.UBAuptime; - - if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) + } + if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) { rootBoiler["burnStarts"] = (float)EMS_Boiler.burnStarts; - if (abs(EMS_Boiler.burnWorkMin) != EMS_VALUE_LONG_NOTSET) + } + if (abs(EMS_Boiler.burnWorkMin) != EMS_VALUE_LONG_NOTSET) { rootBoiler["burnWorkMin"] = (float)EMS_Boiler.burnWorkMin; - if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET) + } + 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); + myESP.mqttPublish(TOPIC_BOILER_DATA, doc); // 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(); + StaticJsonDocument doc; + JsonObject rootThermostat = doc.to(); + JsonObject dataThermostat; + + bool has_data = false; 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(); + uint8_t model = ems_getThermostatModel(); // fetch model flags + has_data = true; + + if (myESP.mqttUseNestedJson()) { + // create nested json for each HC + char hc[10]; // hc{1-4} + strlcpy(hc, THERMOSTAT_HC, sizeof(hc)); + char s[20]; // for formatting strings + strlcat(hc, _int_to_char(s, thermostat->hc), sizeof(hc)); + dataThermostat = rootThermostat.createNestedObject(hc); + } else { + dataThermostat = rootThermostat; + } // different logic depending on thermostat types if (model == EMS_DEVICE_FLAG_EASY) { @@ -808,26 +835,46 @@ void publishEMSValues_thermostat() { } else if (thermoMode == EMS_THERMOSTAT_MODE_NIGHT) { dataThermostat[THERMOSTAT_MODE] = "night"; } + + + // if its not nested, send immediately + if (!myESP.mqttUseNestedJson()) { + char topic[30]; + char s[20]; // for formatting strings + strlcpy(topic, TOPIC_THERMOSTAT_DATA, sizeof(topic)); + strlcat(topic, _int_to_char(s, thermostat->hc), sizeof(topic)); // append hc to topic + char data[MYESP_JSON_MAXSIZE_MEDIUM]; + serializeJson(doc, data); + myESP.mqttPublish(topic, data); + } } } - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing thermostat data via MQTT"); - myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); + // if we're using nested json, send all in one go + if (myESP.mqttUseNestedJson() && has_data) { + char data[MYESP_JSON_MAXSIZE_MEDIUM]; + serializeJson(doc, data); + myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); + } + + if (has_data) { + myDebugLog("Publishing thermostat data via MQTT"); + } } // publish mixing data +// only sending if we have an active hc void publishEMSValues_mixing() { - char s[20] = {0}; // for formatting strings - StaticJsonDocument doc; - char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; - JsonObject rootMixing = doc.to(); + char s[20]; // for formatting strings + StaticJsonDocument doc; + JsonObject rootMixing = doc.to(); + bool has_data = false; 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) { + has_data = true; char hc[10]; // hc{1-4} strlcpy(hc, MIXING_HC, sizeof(hc)); strlcat(hc, _int_to_char(s, mixingHC->hc), sizeof(hc)); @@ -845,8 +892,9 @@ void publishEMSValues_mixing() { 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) { + has_data = true; char wwc[10]; // wwc{1-2} strlcpy(wwc, MIXING_WWC, sizeof(wwc)); strlcat(wwc, _int_to_char(s, mixingWWC->wwc), sizeof(wwc)); @@ -860,72 +908,69 @@ void publishEMSValues_mixing() { } } - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing mixing data via MQTT"); - myESP.mqttPublish(TOPIC_MIXING_DATA, data); + if (has_data) { + myDebugLog("Publishing mixing data via MQTT"); + myESP.mqttPublish(TOPIC_MIXING_DATA, doc); + } } // For SM10 and SM100/SM200 Solar Modules void publishEMSValues_solar() { - StaticJsonDocument doc; - char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; - JsonObject rootSM = doc.to(); + StaticJsonDocument doc; + JsonObject rootSM = doc.to(); - if (EMS_SolarModule.collectorTemp > EMS_VALUE_SHORT_NOTSET) + if (EMS_SolarModule.collectorTemp > EMS_VALUE_SHORT_NOTSET) { rootSM[SM_COLLECTORTEMP] = (float)EMS_SolarModule.collectorTemp / 10; - - if (EMS_SolarModule.bottomTemp > EMS_VALUE_SHORT_NOTSET) + } + if (EMS_SolarModule.bottomTemp > EMS_VALUE_SHORT_NOTSET) { rootSM[SM_BOTTOMTEMP] = (float)EMS_SolarModule.bottomTemp / 10; - - if (EMS_SolarModule.pumpModulation != EMS_VALUE_INT_NOTSET) + } + if (EMS_SolarModule.pumpModulation != EMS_VALUE_INT_NOTSET) { rootSM[SM_PUMPMODULATION] = EMS_SolarModule.pumpModulation; - + } if (EMS_SolarModule.pump != EMS_VALUE_BOOL_NOTSET) { - char s[20] = {0}; // for formatting strings + char s[20]; 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) + if (EMS_SolarModule.EnergyLastHour < EMS_VALUE_USHORT_NOTSET) { rootSM[SM_ENERGYLASTHOUR] = (float)EMS_SolarModule.EnergyLastHour / 10; - - if (EMS_SolarModule.EnergyToday < EMS_VALUE_USHORT_NOTSET) + } + if (EMS_SolarModule.EnergyToday < EMS_VALUE_USHORT_NOTSET) { rootSM[SM_ENERGYTODAY] = EMS_SolarModule.EnergyToday; - - if (EMS_SolarModule.EnergyTotal < EMS_VALUE_USHORT_NOTSET) + } + 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); + myDebugLog("Publishing solar module data via MQTT"); + myESP.mqttPublish(TOPIC_SM_DATA, doc); } // handle HeatPump void publishEMSValues_heatpump() { - StaticJsonDocument doc; - char data[MQTT_MAX_PAYLOAD_SIZE] = {0}; - JsonObject rootHP = doc.to(); + StaticJsonDocument doc; + JsonObject rootHP = doc.to(); - if (EMS_HeatPump.HPModulation != EMS_VALUE_INT_NOTSET) + if (EMS_HeatPump.HPModulation != EMS_VALUE_INT_NOTSET) { rootHP[HP_PUMPMODULATION] = EMS_HeatPump.HPModulation; - - if (EMS_HeatPump.HPSpeed != EMS_VALUE_INT_NOTSET) + } + 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); + myDebugLog("Publishing peat pump data via MQTT"); + myESP.mqttPublish(TOPIC_HP_DATA, doc); } // Publish shower data void do_publishShowerData() { - 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"; + 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}; @@ -938,13 +983,8 @@ void do_publishShowerData() { rootShower[TOPIC_SHOWER_DURATION] = s; } - char data[300] = {0}; - serializeJson(doc, data, sizeof(data)); - myDebugLog("Publishing shower data via MQTT"); - - // Publish MQTT forcing retain to be off - myESP.mqttPublish(TOPIC_SHOWER_DATA, data, false); + myESP.mqttPublish(TOPIC_SHOWER_DATA, doc, false); // Publish MQTT forcing retain to be off } // send values via MQTT @@ -955,16 +995,18 @@ void publishEMSValues(bool force) { 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 } + return; // XXX + + 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_getMixingModuleEnabled() && (ems_Device_has_flags(EMS_DEVICE_UPDATE_FLAG_MIXING) || force)) { publishEMSValues_mixing(); ems_Device_remove_flags(EMS_DEVICE_UPDATE_FLAG_MIXING); // unset flag @@ -1073,8 +1115,6 @@ bool LoadSaveCallback(MYESP_FSACTION_t action, JsonObject settings) { return false; } - // serializeJsonPretty(settings, Serial); // for debugging - EMSESP_Settings.led = settings["led"]; EMSESP_Settings.led_gpio = settings["led_gpio"] | EMSESP_LED_GPIO; EMSESP_Settings.dallas_gpio = settings["dallas_gpio"] | EMSESP_DALLAS_GPIO; @@ -1372,7 +1412,7 @@ void saveEMSDevices() { strlcpy(EMSESP_Settings.known_devices, s, sizeof(s)); - myDebug("The device IDs %s%s%swill be automatically scanned when EMS-ESP boots up.", COLOR_BOLD_ON, EMSESP_Settings.known_devices, COLOR_BOLD_OFF); + myDebug_P(PSTR("The device IDs %s%s%swill be automatically scanned when EMS-ESP boots up."), COLOR_BOLD_ON, EMSESP_Settings.known_devices, COLOR_BOLD_OFF); myESP.saveSettings(); } diff --git a/src/version.h b/src/version.h index e64fc27fb..fb2102c46 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.9.5b39" +#define APP_VERSION "1.9.5b40"