From 7a358a20b802694fc5fb3f8bb6fee05bd2eb98d1 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Mon, 29 Dec 2025 10:05:25 +0100 Subject: [PATCH] Nullable timestamps for HA JSON --- .../include/HomeAssistantMqttHandler.h | 1 + lib/HomeAssistantMqttHandler/json/ha1.json | 2 +- lib/HomeAssistantMqttHandler/json/ha2.json | 4 +- lib/HomeAssistantMqttHandler/json/ha3.json | 2 +- lib/HomeAssistantMqttHandler/json/ha4.json | 2 +- .../src/HomeAssistantMqttHandler.cpp | 131 +++++++----------- lib/MeterCommunicators/src/IEC6205675.cpp | 3 - 7 files changed, 59 insertions(+), 86 deletions(-) diff --git a/lib/HomeAssistantMqttHandler/include/HomeAssistantMqttHandler.h b/lib/HomeAssistantMqttHandler/include/HomeAssistantMqttHandler.h index 53f08f65..29152146 100644 --- a/lib/HomeAssistantMqttHandler/include/HomeAssistantMqttHandler.h +++ b/lib/HomeAssistantMqttHandler/include/HomeAssistantMqttHandler.h @@ -79,6 +79,7 @@ private: void publishPriceSensors(PriceService* ps); void publishSystemSensors(); void publishThresholdSensors(); + void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen); String boardTypeToString(uint8_t b) { switch(b) { diff --git a/lib/HomeAssistantMqttHandler/json/ha1.json b/lib/HomeAssistantMqttHandler/json/ha1.json index d90fe48d..9a05973f 100644 --- a/lib/HomeAssistantMqttHandler/json/ha1.json +++ b/lib/HomeAssistantMqttHandler/json/ha1.json @@ -1,4 +1,4 @@ { "P" : %lu, - "t" : "%s" + "t" : %s } diff --git a/lib/HomeAssistantMqttHandler/json/ha2.json b/lib/HomeAssistantMqttHandler/json/ha2.json index e0c6ba9a..f293637a 100644 --- a/lib/HomeAssistantMqttHandler/json/ha2.json +++ b/lib/HomeAssistantMqttHandler/json/ha2.json @@ -3,6 +3,6 @@ "tPO" : %.3f, "tQI" : %.3f, "tQO" : %.3f, - "rtc" : "%s", - "t" : "%s" + "rtc" : %s, + "t" : %s } diff --git a/lib/HomeAssistantMqttHandler/json/ha3.json b/lib/HomeAssistantMqttHandler/json/ha3.json index aa5dc318..f52f6510 100644 --- a/lib/HomeAssistantMqttHandler/json/ha3.json +++ b/lib/HomeAssistantMqttHandler/json/ha3.json @@ -12,5 +12,5 @@ "U1" : %.2f, "U2" : %.2f, "U3" : %.2f, - "t" : "%s" + "t" : %s } diff --git a/lib/HomeAssistantMqttHandler/json/ha4.json b/lib/HomeAssistantMqttHandler/json/ha4.json index f68fc60e..1663c1dd 100644 --- a/lib/HomeAssistantMqttHandler/json/ha4.json +++ b/lib/HomeAssistantMqttHandler/json/ha4.json @@ -28,5 +28,5 @@ "tPO1" : %.3f, "tPO2" : %.3f, "tPO3" : %.3f, - "t" : "%s" + "t" : %s } diff --git a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp index 070f2ed1..79fb9778 100644 --- a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp +++ b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp @@ -28,7 +28,8 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag); sensorNamePrefix = String(json); } else { - deviceName = F("AMS reader"); + snprintf_P(json, 128, PSTR("AMS reader")); + deviceName = String(json); sensorNamePrefix = ""; } deviceModel = boardTypeToString(boardType); @@ -52,20 +53,18 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config deviceUrl = String(json); } - if(strlen(config.discoveryPrefix) > 0) { - snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix); - statusTopic = String(json); - - snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix); - sensorTopic = String(json); - - snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix); - updateTopic = String(json); - } else { - statusTopic = F("homeassistant/status"); - sensorTopic = F("homeassistant/sensor"); - updateTopic = F("homeassistant/update"); + if(strlen(config.discoveryPrefix) == 0) { + snprintf_P(config.discoveryPrefix, 64, PSTR("homeassistant")); } + + snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix); + statusTopic = String(json); + + snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix); + sensorTopic = String(json); + + snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix); + updateTopic = String(json); strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str()); } @@ -134,12 +133,7 @@ bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) publishList1Sensors(); char pt[24]; - memset(pt, 0, 24); - if(data->getPackageTimestamp() > 0) { - tmElements_t tm; - breakTime(data->getPackageTimestamp(), tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt)); snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower(), pt); return mqtt.publish(pubTopic + "/power", json); @@ -150,12 +144,7 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) if(data->getActiveExportPower() > 0) publishList2ExportSensors(); char pt[24]; - memset(pt, 0, 24); - if(data->getPackageTimestamp() > 0) { - tmElements_t tm; - breakTime(data->getPackageTimestamp(), tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt)); snprintf_P(json, BufferSize, HA3_JSON, data->getListId().c_str(), @@ -181,20 +170,11 @@ bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors(); char mt[24]; - memset(mt, 0, 24); - if(data->getMeterTimestamp() > 0) { - tmElements_t tm; - breakTime(data->getMeterTimestamp(), tm); - sprintf_P(mt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(data->getMeterTimestamp(), mt, sizeof(mt)); char pt[24]; memset(pt, 0, 24); - if(data->getPackageTimestamp() > 0) { - tmElements_t tm; - breakTime(data->getPackageTimestamp(), tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt)); snprintf_P(json, BufferSize, HA2_JSON, data->getActiveImportCounter(), @@ -212,12 +192,7 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors(); char pt[24]; - memset(pt, 0, 24); - if(data->getPackageTimestamp() > 0) { - tmElements_t tm; - breakTime(data->getPackageTimestamp(), tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt)); snprintf_P(json, BufferSize, HA4_JSON, data->getListId().c_str(), @@ -307,13 +282,8 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* time_t now = time(nullptr); char pt[24]; - memset(pt, 0, 24); - if(now > 0) { - tmElements_t tm; - breakTime(now, tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } - pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt); + toJsonIsoTimestamp(now, pt, sizeof(pt)); + pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt); json[pos++] = '}'; json[pos] = '\0'; @@ -343,13 +313,8 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT time_t now = time(nullptr); char pt[24]; - memset(pt, 0, 24); - if(now > 0) { - tmElements_t tm; - breakTime(now, tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } - pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt); + toJsonIsoTimestamp(now, pt, sizeof(pt)); + pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt); pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}")); bool ret = mqtt.publish(pubTopic + "/temperatures", json); @@ -421,25 +386,34 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { memset(ts1hr, 0, 24); if(min1hrIdx > -1) { time_t ts = now + (SECS_PER_HOUR * min1hrIdx); - tmElements_t tm; + tmElements_t tm; breakTime(ts, tm); - sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour); + tm.Minute = 0; + tm.Second = 0; + ts = makeTime(tm); + toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr)); } char ts3hr[24]; memset(ts3hr, 0, 24); if(min3hrIdx > -1) { time_t ts = now + (SECS_PER_HOUR * min3hrIdx); - tmElements_t tm; + tmElements_t tm; breakTime(ts, tm); - sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour); + tm.Minute = 0; + tm.Second = 0; + ts = makeTime(tm); + toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr)); } char ts6hr[24]; memset(ts6hr, 0, 24); if(min6hrIdx > -1) { time_t ts = now + (SECS_PER_HOUR * min6hrIdx); - tmElements_t tm; + tmElements_t tm; breakTime(ts, tm); - sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour); + tm.Minute = 0; + tm.Second = 0; + ts = makeTime(tm); + toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr)); } uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str()); @@ -468,7 +442,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { } pos--; - pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"), + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":%s,\"cheapest3hr\":%s,\"cheapest6hr\":%s}"), min == INT16_MAX ? 0.0 : min, max == INT16_MIN ? 0.0 : max, ts1hr, @@ -477,13 +451,8 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { ); char pt[24]; - memset(pt, 0, 24); - if(now > 0) { - tmElements_t tm; - breakTime(now, tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } - pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt); + toJsonIsoTimestamp(now, pt, sizeof(pt)); + pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt); json[pos++] = '}'; json[pos] = '\0'; @@ -502,14 +471,9 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, Ener time_t now = time(nullptr); char pt[24]; - memset(pt, 0, 24); - if(now > 0) { - tmElements_t tm; - breakTime(now, tm); - sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - } + toJsonIsoTimestamp(now, pt, sizeof(pt)); - snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":\"%s\"}"), + snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":%s}"), WiFi.macAddress().c_str(), mqttConfig.clientId, (uint32_t) (millis64()/1000), @@ -902,3 +866,14 @@ void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) { } } } + +void HomeAssistantMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) { + memset(buf, 0, buflen); + if(t > 0) { + tmElements_t tm; + breakTime(t, tm); + snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); + } else { + snprintf_P(buf, buflen, PSTR("null")); + } +} diff --git a/lib/MeterCommunicators/src/IEC6205675.cpp b/lib/MeterCommunicators/src/IEC6205675.cpp index 7cdb1683..a03fa2c1 100644 --- a/lib/MeterCommunicators/src/IEC6205675.cpp +++ b/lib/MeterCommunicators/src/IEC6205675.cpp @@ -27,14 +27,11 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC // Kaifa special case... if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) { - this->packageTimestamp = this->packageTimestamp > 0 && tz != NULL ? tz->toUTC(this->packageTimestamp) : 0; listType = 1; meterType = AmsTypeKaifa; activeImportPower = ntohl(data->dlu.data); lastUpdateMillis = millis64(); } else if(data->base.type == CosemTypeOctetString) { - this->packageTimestamp = this->packageTimestamp > 0 && tz != NULL ? tz->toUTC(this->packageTimestamp) : 0; - memcpy(str, data->oct.data, data->oct.length); str[data->oct.length] = 0x00; String listId = String(str);