From a4d458144243c1663db4e50f0bbc0665d72e467a Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Thu, 4 Sep 2025 14:06:14 +0200 Subject: [PATCH] Some changes after testing --- lib/EnergyAccounting/src/EnergyAccounting.cpp | 8 +- .../src/HomeAssistantMqttHandler.cpp | 2 +- lib/JsonMqttHandler/src/JsonMqttHandler.cpp | 2 +- lib/PriceService/include/PriceService.h | 14 ++- lib/PriceService/src/PriceService.cpp | 87 +++++++++++++++---- lib/RawMqttHandler/src/RawMqttHandler.cpp | 2 +- lib/SvelteUi/src/AmsWebServer.cpp | 30 ++++--- 7 files changed, 100 insertions(+), 45 deletions(-) diff --git a/lib/EnergyAccounting/src/EnergyAccounting.cpp b/lib/EnergyAccounting/src/EnergyAccounting.cpp index c10563cc..b96dd61f 100644 --- a/lib/EnergyAccounting/src/EnergyAccounting.cpp +++ b/lib/EnergyAccounting/src/EnergyAccounting.cpp @@ -162,7 +162,7 @@ bool EnergyAccounting::update(AmsData* amsData) { float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0; if(kwhi > 0) { realtimeData->use += kwhi; - float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_IMPORT); + float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_IMPORT); if(importPrice != PRICE_NO_VALUE) { float cost = importPrice * kwhi; realtimeData->costHour += cost; @@ -177,7 +177,7 @@ bool EnergyAccounting::update(AmsData* amsData) { float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0; if(kwhe > 0) { realtimeData->produce += kwhe; - float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_EXPORT); + float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_EXPORT); if(exportPrice != PRICE_NO_VALUE) { float income = exportPrice * kwhe; realtimeData->incomeHour += income; @@ -216,13 +216,13 @@ void EnergyAccounting::calcDayCost() { for(uint8_t i = calcFromHour; i < realtimeData->currentHour; i++) { breakTime(now - ((local.Hour - i) * 3600), utc); - float priceIn = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour); + float priceIn = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i - local.Hour); if(priceIn != PRICE_NO_VALUE) { int16_t wh = ds->getHourImport(utc.Hour); realtimeData->costDay += priceIn * (wh / 1000.0); } - float priceOut = ps->getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour); + float priceOut = ps->getPriceForRelativeHour(PRICE_DIRECTION_EXPORT, i - local.Hour); if(priceOut != PRICE_NO_VALUE) { int16_t wh = ds->getHourExport(utc.Hour); realtimeData->incomeDay += priceOut * (wh / 1000.0); diff --git a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp index 6a68b1e8..2672ecfb 100644 --- a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp +++ b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp @@ -373,7 +373,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) { float values[38]; for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE; for(uint8_t i = 0; i < 38; i++) { - float val = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i); + float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i); values[i] = val; if(val == PRICE_NO_VALUE) break; diff --git a/lib/JsonMqttHandler/src/JsonMqttHandler.cpp b/lib/JsonMqttHandler/src/JsonMqttHandler.cpp index cf751daf..710882da 100644 --- a/lib/JsonMqttHandler/src/JsonMqttHandler.cpp +++ b/lib/JsonMqttHandler/src/JsonMqttHandler.cpp @@ -294,7 +294,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) { float values[38]; for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE; for(uint8_t i = 0; i < 38; i++) { - float val = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i); + float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i); values[i] = val; if(val == PRICE_NO_VALUE) break; diff --git a/lib/PriceService/include/PriceService.h b/lib/PriceService/include/PriceService.h index 2e3f8570..5f60bf91 100644 --- a/lib/PriceService/include/PriceService.h +++ b/lib/PriceService/include/PriceService.h @@ -81,17 +81,15 @@ public: uint8_t getResolutionInMinutes(); uint8_t getNumberOfPointsAvailable(); - bool isExportPricesDifferentFromImport() { - return today != NULL && today->isExportPricesDifferentFromImport(); - } + bool isExportPricesDifferentFromImport(); bool hasPrice() { return hasPrice(PRICE_DIRECTION_IMPORT); } - bool hasPrice(uint8_t direction) { return getPrice(direction) != PRICE_NO_VALUE; } + bool hasPrice(uint8_t direction) { return getCurrentPrice(direction) != PRICE_NO_VALUE; } bool hasPricePoint(uint8_t direction, int8_t point) { return getPricePoint(direction, point) != PRICE_NO_VALUE; } - float getPrice(uint8_t direction) { return getPricePoint(direction, 0); } - float getPricePoint(uint8_t direction, int8_t point); - float getPriceForHour(uint8_t direction, int8_t hour); // If not 60min interval, average + float getCurrentPrice(uint8_t direction); + float getPricePoint(uint8_t direction, uint8_t point); + float getPriceForRelativeHour(uint8_t direction, int8_t hour); // If not 60min interval, average std::vector& getPriceConfig(); void setPriceConfig(uint8_t index, PriceConfig &priceConfig); @@ -141,6 +139,6 @@ private: float getCurrencyMultiplier(const char* from, const char* to, time_t t); bool timeIsInPeriod(tmElements_t tm, PriceConfig pc); float getFixedPrice(uint8_t direction, int8_t hour); - float getEnergyPricePoint(uint8_t direction, int8_t point); + float getEnergyPricePoint(uint8_t direction, uint8_t point); }; #endif diff --git a/lib/PriceService/src/PriceService.cpp b/lib/PriceService/src/PriceService.cpp index 1777fa46..f289512f 100644 --- a/lib/PriceService/src/PriceService.cpp +++ b/lib/PriceService/src/PriceService.cpp @@ -118,7 +118,17 @@ uint8_t PriceService::getNumberOfPointsAvailable() { return today->getNumberOfPoints(); } -float PriceService::getPricePoint(uint8_t direction, int8_t point) { +bool PriceService::isExportPricesDifferentFromImport() { + for (uint8_t i = 0; i < priceConfig.size(); i++) { + PriceConfig pc = priceConfig.at(i); + if(pc.direction != PRICE_DIRECTION_BOTH) { + return true; + } + } + return today != NULL && today->isExportPricesDifferentFromImport(); +} + +float PriceService::getPricePoint(uint8_t direction, uint8_t point) { float value = getFixedPrice(direction, point * getResolutionInMinutes() / 60); if(value == PRICE_NO_VALUE) value = getEnergyPricePoint(direction, point); if(value == PRICE_NO_VALUE) return PRICE_NO_VALUE; @@ -126,8 +136,7 @@ float PriceService::getPricePoint(uint8_t direction, int8_t point) { tmElements_t tm; time_t ts = time(nullptr); breakTime(tz->toLocal(ts), tm); - tm.Minute = (tm.Minute / getResolutionInMinutes()) * getResolutionInMinutes(); - tm.Second = 0; + tm.Hour = tm.Minute = tm.Second = 0; breakTime(makeTime(tm) + (point * SECS_PER_MIN * getResolutionInMinutes()), tm); for (uint8_t i = 0; i < priceConfig.size(); i++) { @@ -135,15 +144,17 @@ float PriceService::getPricePoint(uint8_t direction, int8_t point) { if(pc.type == PRICE_TYPE_FIXED) continue; if((pc.direction & direction) != direction) continue; if(!timeIsInPeriod(tm, pc)) continue; + float pcVal = pc.value / 10000.0; + switch(pc.type) { case PRICE_TYPE_ADD: - value += pc.value / 10000.0; + value += pcVal; break; case PRICE_TYPE_SUBTRACT: - value -= pc.value / 10000.0; + value -= pcVal; break; case PRICE_TYPE_PCT: - value += ((pc.value / 10000.0) * value) / 100.0; + value += (pcVal * value) / 100.0; break; } } @@ -151,14 +162,27 @@ float PriceService::getPricePoint(uint8_t direction, int8_t point) { return value; } -float PriceService::getEnergyPricePoint(uint8_t direction, int8_t point) { - float value = PRICE_NO_VALUE; +float PriceService::getCurrentPrice(uint8_t direction) { + if(today == NULL) return PRICE_NO_VALUE; + + time_t ts = time(nullptr); + tmElements_t tm; + breakTime(tz->toLocal(ts), tm); + float pointsPerHour = 60.0 / today->getResolutionInMinutes(); + uint8_t pos = (uint8_t) floor(pointsPerHour * tm.Hour); + + return getPricePoint(direction, pos); +} + +float PriceService::getEnergyPricePoint(uint8_t direction, uint8_t point) { uint8_t pos = point; float multiplier = 1.0; uint8_t numberOfPointsToday = 24; if(today != NULL) { numberOfPointsToday = today->getNumberOfPoints(); } + + float value = PRICE_NO_VALUE; if(pos >= numberOfPointsToday) { pos = pos - numberOfPointsToday; if(tomorrow == NULL) @@ -183,25 +207,41 @@ float PriceService::getEnergyPricePoint(uint8_t direction, int8_t point) { return value == PRICE_NO_VALUE ? PRICE_NO_VALUE : value * multiplier; } -float PriceService::getPriceForHour(uint8_t direction, int8_t hour) { +float PriceService::getPriceForRelativeHour(uint8_t direction, int8_t hour) { float value = getFixedPrice(direction, hour); if(value != PRICE_NO_VALUE) return value; if(today == NULL) return PRICE_NO_VALUE; + time_t ts = time(nullptr); + tmElements_t tm; + + breakTime(tz->toLocal(ts), tm); + tm.Hour = tm.Minute = tm.Second = 0; + time_t startOfDay = makeTime(tm); + + if(makeTime(tm) < startOfDay) { + return PRICE_NO_VALUE; + } + + breakTime(tz->toLocal(ts), tm); + int8_t targetHour = tm.Hour + hour; + if(today->getResolutionInMinutes() == 60) { - return getPricePoint(direction, hour); + return getPricePoint(direction, targetHour); } float valueSum = 0.0f; uint8_t valueCount = 0; - float indexIncrements = 60 / today->getResolutionInMinutes(); - uint8_t priceMapIndexStart = (uint8_t) floor(indexIncrements * hour); - uint8_t priceMapIndexEnd = (uint8_t) ceil(indexIncrements * (hour+1)); + float indexIncrements = 60.0 / today->getResolutionInMinutes(); + uint8_t priceMapIndexStart = (uint8_t) floor(indexIncrements * targetHour); + uint8_t priceMapIndexEnd = (uint8_t) ceil(indexIncrements * (targetHour+1)); + for(uint8_t mi = priceMapIndexStart; mi < priceMapIndexEnd; mi++) { float val = getPricePoint(direction, mi); if(val == PRICE_NO_VALUE) continue; valueSum += val; valueCount++; } + if(valueCount == 0) return PRICE_NO_VALUE; return valueSum / valueCount; } @@ -530,19 +570,34 @@ PricesContainer* PriceService::fetchPrices(time_t t) { GCMParser gcm(key, auth); int8_t gcmRet = gcm.parse(content, ctx); if(gcmRet > 0) { - AmsPriceV2Header* header = (AmsPriceV2Header*) (content-gcmRet); + AmsPriceV2Header* header = (AmsPriceV2Header*) (content+gcmRet); PricesContainer* ret = new PricesContainer(header->source); + #if defined(AMS_REMOTE_DEBUG) + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf_P(PSTR("(PriceService) Setting up price container with pt%dm, %dpts, edi: %d\n"), header->resolutionInMinutes, header->numberOfPoints, header->differentExportPrices); + ret->setup(header->resolutionInMinutes, header->numberOfPoints, header->differentExportPrices); ret->setCurrency(header->currency); int32_t* points = (int32_t*) &header[1]; for(uint8_t i = 0; i < header->numberOfPoints; i++) { - ret->setPrice(i, points[i], PRICE_DIRECTION_IMPORT); + float value = ntohl(points[i]) / 10000.0; + #if defined(AMS_REMOTE_DEBUG) + if (debugger->isActive(RemoteDebug::VERBOSE)) + #endif + debugger->printf_P(PSTR("(PriceService) Import price position and value received: %d :: %.2f\n"), i, value); + ret->setPrice(i, value, PRICE_DIRECTION_IMPORT); } if(header->differentExportPrices) { for(uint8_t i = 0; i < header->numberOfPoints; i++) { - ret->setPrice(i, points[i], PRICE_DIRECTION_EXPORT); + float value = ntohl(points[i]) / 10000.0; + #if defined(AMS_REMOTE_DEBUG) + if (debugger->isActive(RemoteDebug::VERBOSE)) + #endif + debugger->printf_P(PSTR("(PriceService) Export price position and value received: %d :: %.2f\n"), i, value); + ret->setPrice(i, value, PRICE_DIRECTION_EXPORT); } } lastError = 0; diff --git a/lib/RawMqttHandler/src/RawMqttHandler.cpp b/lib/RawMqttHandler/src/RawMqttHandler.cpp index d0338f20..fd3e3f37 100644 --- a/lib/RawMqttHandler/src/RawMqttHandler.cpp +++ b/lib/RawMqttHandler/src/RawMqttHandler.cpp @@ -250,7 +250,7 @@ bool RawMqttHandler::publishPrices(PriceService* ps) { float values[34]; for(int i = 0;i < 34; i++) values[i] = PRICE_NO_VALUE; for(uint8_t i = 0; i < 34; i++) { - float val = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i); + float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i); values[i] = val; if(i > 23) continue; diff --git a/lib/SvelteUi/src/AmsWebServer.cpp b/lib/SvelteUi/src/AmsWebServer.cpp index 08c55af1..82820ae0 100644 --- a/lib/SvelteUi/src/AmsWebServer.cpp +++ b/lib/SvelteUi/src/AmsWebServer.cpp @@ -581,8 +581,8 @@ void AmsWebServer::dataJson() { mqttStatus = 3; } - float price = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_IMPORT); - float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getPrice(PRICE_DIRECTION_EXPORT); + float price = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_IMPORT); + float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_EXPORT); String peaks = ""; for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) { @@ -731,7 +731,7 @@ void AmsWebServer::energyPriceJson() { float prices[36]; for(int i = 0; i < 36; i++) { - prices[i] = ps->getPriceForHour(PRICE_DIRECTION_IMPORT, i); + prices[i] = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i); } uint16_t pos = snprintf_P(buf, BufferSize, PSTR("{\"currency\":\"%s\",\"source\":\"%s\""), @@ -781,7 +781,7 @@ void AmsWebServer::priceJson(uint8_t direction) { prices[i] = ps->getPricePoint(direction, i); } - uint16_t pos = snprintf_P(buf, BufferSize, PSTR("{\"currency\":\"%s\",\"source\":\"%s\",\"\"resolution\":%d,\"direction\":\"%s\",\"importExportPriceDifferent\":%s"), + snprintf_P(buf, BufferSize, PSTR("{\"currency\":\"%s\",\"source\":\"%s\",\"resolution\":%d,\"direction\":\"%s\",\"importExportPriceDifferent\":%s"), ps->getCurrency(), ps->getSource(), ps->getResolutionInMinutes(), @@ -789,22 +789,24 @@ void AmsWebServer::priceJson(uint8_t direction) { ps->isExportPricesDifferentFromImport() ? "true" : "false" ); - for(uint8_t i = 0;i < numberOfPoints; i++) { - if(prices[i] == PRICE_NO_VALUE) { - pos += snprintf_P(buf+pos, BufferSize-pos, PSTR(",\"%02d\":null"), i); - } else { - pos += snprintf_P(buf+pos, BufferSize-pos, PSTR(",\"%02d\":%.4f"), i, prices[i]); - } - } - snprintf_P(buf+pos, BufferSize-pos, PSTR("}")); - addConditionalCloudHeaders(); server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE); server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE); server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF); - server.setContentLength(strlen(buf)); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); server.send(200, MIME_JSON, buf); + + for(uint8_t i = 0;i < numberOfPoints; i++) { + if(prices[i] == PRICE_NO_VALUE) { + snprintf_P(buf, BufferSize, PSTR(",\"%02d\":null"), i); + server.sendContent(buf); + } else { + snprintf_P(buf, BufferSize, PSTR(",\"%02d\":%.4f"), i, prices[i]); + server.sendContent(buf); + } + } + server.sendContent_P(PSTR("}")); } void AmsWebServer::temperatureJson() {