From a6da4501df5017d8a4a6002023593bb139a5963a Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Thu, 18 Sep 2025 13:28:26 +0200 Subject: [PATCH] Adjustments on MQTT handlers after switching to 15min prices --- .../include/HomeAssistantStatic.h | 16 ++--- .../src/HomeAssistantMqttHandler.cpp | 71 ++++++++++++------- lib/JsonMqttHandler/src/JsonMqttHandler.cpp | 41 ++++++++--- lib/PriceService/include/PriceService.h | 3 +- lib/PriceService/src/PriceService.cpp | 29 +++++--- lib/RawMqttHandler/src/RawMqttHandler.cpp | 17 +++-- 6 files changed, 120 insertions(+), 57 deletions(-) diff --git a/lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h b/lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h index 2a5236c2..17bc05be 100644 --- a/lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h +++ b/lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h @@ -105,16 +105,16 @@ const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGM const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""}; const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", ""}; -const uint8_t PriceSensorCount PROGMEM = 5; +const uint8_t PriceSensorCount PROGMEM = 6; const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = { - {"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", ""}, - {"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", ""}, - {"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr",4000, "", "timestamp", ""}, - {"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr",4000, "", "timestamp", ""}, - {"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr",4000, "", "timestamp", ""} + {"Import price", "/prices", "prices.import.current", 4000, "", "monetary", ""}, + {"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", ""}, + {"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", ""}, + {"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr", 4000, "", "timestamp", ""}, + {"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr", 4000, "", "timestamp", ""}, + {"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr", 4000, "", "timestamp", ""} }; - -const HomeAssistantSensor PriceSensor PROGMEM = {"Current %s price", "/prices", "prices.%s[%d]", 4000, "", "monetary", "total"}; +const HomeAssistantSensor ExportPriceSensor PROGMEM = {"Export price", "/prices", "prices.export.current", 4000, "", "monetary", ""}; const uint8_t SystemSensorCount PROGMEM = 3; const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = { diff --git a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp index 2672ecfb..9f13d527 100644 --- a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp +++ b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp @@ -442,7 +442,10 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour); } - uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str()); + uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":{\"current\":%.4f,\"all\":["), + WiFi.macAddress().c_str(), + ps->getCurrentPrice(PRICE_DIRECTION_IMPORT) + ); uint8_t numberOfPoints = ps->getNumberOfPointsAvailable(); for(int i = 0; i < numberOfPoints; i++) { float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i); @@ -453,7 +456,8 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { } } if(rteInit && ps->isExportPricesDifferentFromImport()) { - pos += snprintf_P(json+pos-1, BufferSize-pos, PSTR("],\"export\":[")); + pos--; + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("]},\"export\":{\"current\":%.4f,\"all\":["), ps->getCurrentPrice(PRICE_DIRECTION_EXPORT)); for(int i = 0; i < numberOfPoints; i++) { float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i); if(val == PRICE_NO_VALUE) { @@ -464,7 +468,8 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { } } - pos += snprintf_P(json+pos-1, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"), + pos--; + 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, @@ -697,35 +702,47 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) { pInit = true; } if(!prInit && ps->hasPrice()) { - char path[strlen(PriceSensor.path)+1]; - snprintf(path, strlen(PriceSensor.path)+1, PriceSensor.path, "import", 0); - HomeAssistantSensor sensor = { - "Current import price", - PriceSensor.topic, - PriceSensor.path, - PriceSensor.ttl, - uom.c_str(), - PriceSensor.devcl, - PriceSensor.stacl - }; - publishSensor(sensor); + for(uint8_t i = 0; i < ps->getNumberOfPointsAvailable(); i++) { + char name[32]; + snprintf_P(name, 32, PSTR("Import price point %02d"), i); + char path[32]; + snprintf_P(path, 32, PSTR("prices.import.all[%d]"), i); + HomeAssistantSensor sensor = { + name, + "/prices", + path, + ps->getResolutionInMinutes() * 60 + 30, + uom.c_str(), + "monetary", + "" + }; + publishSensor(sensor); + } prInit = true; } if(rteInit && !preInit && ps->isExportPricesDifferentFromImport()) { - char path[strlen(PriceSensor.path)+1]; - snprintf(path, strlen(PriceSensor.path)+1, PriceSensor.path, "export", 0); - HomeAssistantSensor sensor = { - "Current export price", - PriceSensor.topic, - PriceSensor.path, - PriceSensor.ttl, - uom.c_str(), - PriceSensor.devcl, - PriceSensor.stacl - }; + HomeAssistantSensor sensor = ExportPriceSensor; + sensor.uom = uom.c_str(); publishSensor(sensor); - prInit = true; + + for(uint8_t i = 0; i < ps->getNumberOfPointsAvailable(); i++) { + char name[32]; + snprintf_P(name, 32, PSTR("Export price point %02d"), i); + char path[32]; + snprintf_P(path, 32, PSTR("prices.export.all[%d]"), i); + HomeAssistantSensor sensor = { + name, + "/prices", + path, + ps->getResolutionInMinutes() * 60 + 30, + uom.c_str(), + "monetary", + "" + }; + publishSensor(sensor); + } + preInit = true; } } diff --git a/lib/JsonMqttHandler/src/JsonMqttHandler.cpp b/lib/JsonMqttHandler/src/JsonMqttHandler.cpp index 710882da..2e7430c2 100644 --- a/lib/JsonMqttHandler/src/JsonMqttHandler.cpp +++ b/lib/JsonMqttHandler/src/JsonMqttHandler.cpp @@ -365,10 +365,10 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) { char pf[4]; uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\","), WiFi.macAddress().c_str()); + uint8_t numberOfPoints = ps->getNumberOfPointsAvailable(); if(mqttConfig.payloadFormat != 6) { memset(pf, 0, 4); - pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"prices\":{\"import\":[")); - uint8_t numberOfPoints = ps->getNumberOfPointsAvailable(); + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"prices\":{\"import\":{\"current\":%.4f,\"all\":["), ps->getCurrentPrice(PRICE_DIRECTION_IMPORT)); for(int i = 0; i < numberOfPoints; i++) { float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i); if(val == PRICE_NO_VALUE) { @@ -378,7 +378,8 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) { } } if(hasExport && ps->isExportPricesDifferentFromImport()) { - pos += snprintf_P(json+pos-1, BufferSize-pos, PSTR("],\"export\":[")); + pos--; + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("]},\"export\":{\"current\":%.4f,\"all\":["), ps->getCurrentPrice(PRICE_DIRECTION_EXPORT)); for(int i = 0; i < numberOfPoints; i++) { float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i); if(val == PRICE_NO_VALUE) { @@ -388,14 +389,38 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) { } } } - pos += snprintf_P(json+pos-1, BufferSize-pos, PSTR("],")); + pos--; + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("]},")); } else { strcpy_P(pf, PSTR("pr_")); - for(uint8_t i = 0;i < 38; i++) { - if(values[i] == PRICE_NO_VALUE) { - pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":null,"), pf, i); + float val = ps->getCurrentPrice(PRICE_DIRECTION_IMPORT); + if(val == PRICE_NO_VALUE) { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%sc\":null,"), pf); + } else { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%sc\":%.4f,"), pf, val); + } + for(uint8_t i = 0;i < numberOfPoints; i++) { + val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i); + if(val == PRICE_NO_VALUE) { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%02d\":null,"), pf, i); } else { - pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":%.4f,"), pf, i, values[i]); + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%02d\":%.4f,"), pf, i, val); + } + } + if(hasExport && ps->isExportPricesDifferentFromImport()) { + float val = ps->getCurrentPrice(PRICE_DIRECTION_EXPORT); + if(val == PRICE_NO_VALUE) { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%sec\":null,"), pf); + } else { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%sec\":%.4f,"), pf, val); + } + for(uint8_t i = 0;i < numberOfPoints; i++) { + val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i); + if(val == PRICE_NO_VALUE) { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%se%02d\":null,"), pf, i); + } else { + pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%se%02d\":%.4f,"), pf, i, val); + } } } } diff --git a/lib/PriceService/include/PriceService.h b/lib/PriceService/include/PriceService.h index 5f60bf91..f75c2953 100644 --- a/lib/PriceService/include/PriceService.h +++ b/lib/PriceService/include/PriceService.h @@ -109,7 +109,7 @@ private: PriceServiceConfig* config = NULL; HTTPClient* http = NULL; - uint8_t currentDay = 0, currentHour = 0; + uint8_t currentDay = 0, currentPricePoint = 0; uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices uint8_t nextFetchDelayMinutes = 15; uint64_t lastTodayFetch = 0; @@ -140,5 +140,6 @@ private: bool timeIsInPeriod(tmElements_t tm, PriceConfig pc); float getFixedPrice(uint8_t direction, int8_t hour); float getEnergyPricePoint(uint8_t direction, uint8_t point); + uint8_t getCurrentPricePointIndex(); }; #endif diff --git a/lib/PriceService/src/PriceService.cpp b/lib/PriceService/src/PriceService.cpp index 8067a735..be630b0e 100644 --- a/lib/PriceService/src/PriceService.cpp +++ b/lib/PriceService/src/PriceService.cpp @@ -168,7 +168,7 @@ float PriceService::getCurrentPrice(uint8_t direction) { time_t ts = time(nullptr); tmElements_t tm; breakTime(tz->toLocal(ts), tm); - uint8_t pos = ((tm.Hour * 60) + tm.Minute) / today->getResolutionInMinutes(); + uint8_t pos = getCurrentPricePointIndex(); return getPricePoint(direction, pos); } @@ -307,7 +307,7 @@ bool PriceService::loop() { #endif debugger->printf_P(PSTR("(PriceService) Day init\n")); currentDay = tm.Day; - currentHour = tm.Hour; + currentPricePoint = getCurrentPricePointIndex(); } if(currentDay != tm.Day) { @@ -321,14 +321,14 @@ bool PriceService::loop() { tomorrow = NULL; } currentDay = tm.Day; - currentHour = tm.Hour; + currentPricePoint = getCurrentPricePointIndex(); return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices. - } else if(currentHour != tm.Hour) { + } else if(currentPricePoint != getCurrentPricePointIndex()) { #if defined(AMS_REMOTE_DEBUG) if (debugger->isActive(RemoteDebug::INFO)) #endif - debugger->printf_P(PSTR("(PriceService) Hour reset\n")); - currentHour = tm.Hour; + debugger->printf_P(PSTR("(PriceService) Price point reset\n")); + currentPricePoint = getCurrentPricePointIndex(); return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices. } @@ -348,6 +348,7 @@ bool PriceService::loop() { } today = NULL; } + currentPricePoint = getCurrentPricePointIndex(); return today != NULL && !readyToFetchForTomorrow; // Only trigger MQTT publish if we have todays prices and we are not immediately ready to fetch price for tomorrow. } @@ -364,6 +365,7 @@ bool PriceService::loop() { } tomorrow = NULL; } + currentPricePoint = getCurrentPricePointIndex(); return tomorrow != NULL; } @@ -582,7 +584,8 @@ PricesContainer* PriceService::fetchPrices(time_t t) { int32_t* points = (int32_t*) &header[1]; for(uint8_t i = 0; i < header->numberOfPoints; i++) { - float value = ntohl(points[i]) / 10000.0; + int32_t intval = ntohl(points[i]); + float value = intval / 10000.0; #if defined(AMS_REMOTE_DEBUG) if (debugger->isActive(RemoteDebug::VERBOSE)) #endif @@ -591,7 +594,8 @@ PricesContainer* PriceService::fetchPrices(time_t t) { } if(header->differentExportPrices) { for(uint8_t i = 0; i < header->numberOfPoints; i++) { - float value = ntohl(points[i]) / 10000.0; + int32_t intval = ntohl(points[i]); + float value = intval / 10000.0; #if defined(AMS_REMOTE_DEBUG) if (debugger->isActive(RemoteDebug::VERBOSE)) #endif @@ -761,4 +765,13 @@ bool PriceService::timeIsInPeriod(tmElements_t tm, PriceConfig pc) { } return makeTime(tms) <= makeTime(tm) && makeTime(tme) >= makeTime(tm); +} + +uint8_t PriceService::getCurrentPricePointIndex() { + if(today == NULL) return 0; + + time_t ts = time(nullptr); + tmElements_t tm; + breakTime(tz->toLocal(ts), tm); + return ((tm.Hour * 60) + tm.Minute) / today->getResolutionInMinutes(); } \ No newline at end of file diff --git a/lib/RawMqttHandler/src/RawMqttHandler.cpp b/lib/RawMqttHandler/src/RawMqttHandler.cpp index fd3e3f37..306156bc 100644 --- a/lib/RawMqttHandler/src/RawMqttHandler.cpp +++ b/lib/RawMqttHandler/src/RawMqttHandler.cpp @@ -317,24 +317,31 @@ bool RawMqttHandler::publishPrices(PriceService* ps) { sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); } - uint8_t numberOfPoints = ps->getNumberOfPointsAvailable(); + mqtt.publish(topic + "/price/import/current", String(ps->getCurrentPrice(PRICE_DIRECTION_IMPORT), 4), true, 0); + mqtt.loop(); + if(hasExport && ps->isExportPricesDifferentFromImport()) { + mqtt.publish(topic + "/price/export/current", String(ps->getCurrentPrice(PRICE_DIRECTION_EXPORT), 4), true, 0); + mqtt.loop(); + } + + uint8_t numberOfPoints = ps->getNumberOfPointsAvailable(); for(int i = 0; i < numberOfPoints; i++) { float importVal = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i); if(importVal == PRICE_NO_VALUE) { - mqtt.publish(topic + "/price/import/" + String(i), "", true, 0); + mqtt.publish(topic + "/price/import/all/" + String(i), "", true, 0); mqtt.loop(); } else { - mqtt.publish(topic + "/price/import/" + String(i), String(importVal, 4), true, 0); + mqtt.publish(topic + "/price/import/all/" + String(i), String(importVal, 4), true, 0); mqtt.loop(); } if(hasExport && ps->isExportPricesDifferentFromImport()) { float exportVal = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i); if(exportVal == PRICE_NO_VALUE) { - mqtt.publish(topic + "/price/export/" + String(i), "", true, 0); + mqtt.publish(topic + "/price/export/all/" + String(i), "", true, 0); mqtt.loop(); } else { - mqtt.publish(topic + "/price/export/" + String(i), String(exportVal, 4), true, 0); + mqtt.publish(topic + "/price/export/all/" + String(i), String(exportVal, 4), true, 0); mqtt.loop(); } }