From 0588bfd418ad0da3d7fc36fb8e8193240dfcb968 Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Sat, 15 Mar 2025 07:27:26 +0100 Subject: [PATCH] 15min prices WIP --- lib/PriceService/include/EntsoeA44Parser.h | 13 +- lib/PriceService/include/PriceService.h | 12 +- lib/PriceService/include/PricesContainer.h | 29 +++- lib/PriceService/src/EntsoeA44Parser.cpp | 66 ++++----- lib/PriceService/src/PriceService.cpp | 162 ++++++++++----------- 5 files changed, 140 insertions(+), 142 deletions(-) diff --git a/lib/PriceService/include/EntsoeA44Parser.h b/lib/PriceService/include/EntsoeA44Parser.h index 30bf0da6..cd3fec91 100644 --- a/lib/PriceService/include/EntsoeA44Parser.h +++ b/lib/PriceService/include/EntsoeA44Parser.h @@ -15,28 +15,23 @@ #define DOCPOS_MEASUREMENTUNIT 2 #define DOCPOS_POSITION 3 #define DOCPOS_AMOUNT 4 +#define DOCPOS_RESOLUTION 5 class EntsoeA44Parser: public Stream { public: - EntsoeA44Parser(); + EntsoeA44Parser(PricesContainer *container); virtual ~EntsoeA44Parser(); - char* getCurrency(); - char* getMeasurementUnit(); - float getPoint(uint8_t position); - int available(); int read(); int peek(); void flush(); size_t write(const uint8_t *buffer, size_t size); size_t write(uint8_t); - void get(PricesContainer*); private: - char currency[4]; - char measurementUnit[4]; - float points[25]; + PricesContainer *container; + float multiplier = 1.0; char buf[64]; uint8_t pos = 0; diff --git a/lib/PriceService/include/PriceService.h b/lib/PriceService/include/PriceService.h index af720b21..679de2ed 100644 --- a/lib/PriceService/include/PriceService.h +++ b/lib/PriceService/include/PriceService.h @@ -57,10 +57,12 @@ struct PriceConfig { uint8_t end_dayofmonth; }; -struct PricePart { - char name[32]; - char description[32]; - uint32_t value; +struct AmsPriceV2Header { + char source[4]; + char currency[4]; + uint8_t resolutionInMinutes; + uint8_t hours; + uint8_t numberOfPoints; }; class PriceService { @@ -86,8 +88,6 @@ public: void setPriceConfig(uint8_t index, PriceConfig &priceConfig); void cropPriceConfig(uint8_t size); - PricePart getPricePart(uint8_t index); - int16_t getLastError(); bool load(); diff --git a/lib/PriceService/include/PricesContainer.h b/lib/PriceService/include/PricesContainer.h index 0bd35bde..b46e56d9 100644 --- a/lib/PriceService/include/PricesContainer.h +++ b/lib/PriceService/include/PricesContainer.h @@ -9,10 +9,31 @@ #define PRICE_NO_VALUE -127 -struct PricesContainer { - char currency[4]; - char measurementUnit[4]; - int32_t points[25]; +class PricesContainer { +public: + PricesContainer(char* source); + + void setup(uint8_t resolutionInMinutes, uint8_t hoursThisDay); + + char* getSource(); + void setCurrency(char* currency); + char* getCurrency(); + + uint8_t getResolutionInMinutes(); + uint8_t getHours(); + uint8_t getNumberOfPoints(); + + void setPrice(uint8_t point, int32_t value); + void setPrice(uint8_t point, float value); + bool hasPrice(uint8_t point); + float getPrice(uint8_t point); // int32_t / 10_000 + +private: char source[4]; + char currency[4]; + uint8_t resolutionInMinutes; + uint8_t hours; + uint8_t numberOfPoints; + int32_t *points; }; #endif diff --git a/lib/PriceService/src/EntsoeA44Parser.cpp b/lib/PriceService/src/EntsoeA44Parser.cpp index e3096e31..d0e90be7 100644 --- a/lib/PriceService/src/EntsoeA44Parser.cpp +++ b/lib/PriceService/src/EntsoeA44Parser.cpp @@ -7,27 +7,14 @@ #include "EntsoeA44Parser.h" #include "HardwareSerial.h" -EntsoeA44Parser::EntsoeA44Parser() { - for(int i = 0; i < 25; i++) points[i] = PRICE_NO_VALUE; +EntsoeA44Parser::EntsoeA44Parser(PricesContainer *container) { + this->container = container; } EntsoeA44Parser::~EntsoeA44Parser() { } -char* EntsoeA44Parser::getCurrency() { - return currency; -} - -char* EntsoeA44Parser::getMeasurementUnit() { - return measurementUnit; -} - -float EntsoeA44Parser::getPoint(uint8_t position) { - if(position >= 25) return PRICE_NO_VALUE; - return points[position]; -} - int EntsoeA44Parser::available() { return 0; } @@ -57,7 +44,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) { buf[pos++] = byte; if(pos == 3) { buf[pos++] = '\0'; - memcpy(currency, buf, pos); + container->setCurrency(buf); docPos = DOCPOS_SEEK; pos = 0; } @@ -65,7 +52,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) { buf[pos++] = byte; if(pos == 3) { buf[pos++] = '\0'; - memcpy(measurementUnit, buf, pos); + if(strcmp_P(buf, PSTR("MWH"))) multiplier = 0.001; docPos = DOCPOS_SEEK; pos = 0; } @@ -73,7 +60,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) { if(byte == '<') { buf[pos] = '\0'; long pn = String(buf).toInt() - 1; - if(pn < 25) { + if(pn < container->getNumberOfPoints()) { pointNum = pn; } docPos = DOCPOS_SEEK; @@ -85,8 +72,25 @@ size_t EntsoeA44Parser::write(uint8_t byte) { if(byte == '<') { buf[pos] = '\0'; float val = String(buf).toFloat(); - for(uint8_t i = pointNum; i < 25; i++) { - points[i] = val; + for(uint8_t i = pointNum; i < container->getNumberOfPoints(); i++) { + container->setPrice(i, val * multiplier); + } + docPos = DOCPOS_SEEK; + pos = 0; + } else { + buf[pos++] = byte; + } + } else if(docPos == DOCPOS_RESOLUTION) { + if(byte == '<') { + buf[pos] = '\0'; + + // This happens if there are two time series in the XML. We are only interrested in the first one, so we ignore the rest of the document + if(container->hasPrice(0)) return 1; + + if(strcmp_P(buf, PSTR("PT15M"))) { + container->setup(15, 24); + } else if(strcmp_P(buf, PSTR("PT60M"))) { + container->setup(60, 24); } docPos = DOCPOS_SEEK; pos = 0; @@ -101,15 +105,17 @@ size_t EntsoeA44Parser::write(uint8_t byte) { } else if(byte == '>') { buf[pos++] = byte; buf[pos] = '\0'; - if(strcmp(buf, "") == 0) { + if(strcmp_P(buf, PSTR("")) == 0) { docPos = DOCPOS_CURRENCY; - } else if(strcmp(buf, "") == 0) { + } else if(strcmp(buf, PSTR("")) == 0) { docPos = DOCPOS_MEASUREMENTUNIT; - } else if(strcmp(buf, "") == 0) { + } else if(strcmp(buf, PSTR("")) == 0) { docPos = DOCPOS_POSITION; pointNum = 0xFF; - } else if(strcmp(buf, "") == 0) { + } else if(strcmp(buf, PSTR("")) == 0) { docPos = DOCPOS_AMOUNT; + } else if(strcmp(buf, PSTR("")) == 0) { + docPos = DOCPOS_RESOLUTION; } pos = 0; } else { @@ -118,15 +124,3 @@ size_t EntsoeA44Parser::write(uint8_t byte) { } return 1; } - -void EntsoeA44Parser::get(PricesContainer* container) { - memset(container, 0, sizeof(*container)); - - strcpy(container->currency, currency); - strcpy(container->measurementUnit, measurementUnit); - strcpy(container->source, "EOE"); - - for(uint8_t i = 0; i < 25; i++) { - container->points[i] = points[i] == PRICE_NO_VALUE ? PRICE_NO_VALUE : points[i] * 10000; - } -} \ No newline at end of file diff --git a/lib/PriceService/src/PriceService.cpp b/lib/PriceService/src/PriceService.cpp index bf3275cf..3bfa466a 100644 --- a/lib/PriceService/src/PriceService.cpp +++ b/lib/PriceService/src/PriceService.cpp @@ -86,15 +86,15 @@ char* PriceService::getArea() { char* PriceService::getSource() { if(this->today != NULL && this->tomorrow != NULL) { - if(strcmp(this->today->source, this->tomorrow->source) == 0) { - return this->today->source; + if(strcmp(this->today->getSource(), this->tomorrow->getSource()) == 0) { + return this->today->getSource(); } else { return "MIX"; } } else if(today != NULL) { - return this->today->source; + return this->today->getSource(); } else if(tomorrow != NULL) { - return this->tomorrow->source; + return this->tomorrow->getSource(); } return ""; } @@ -193,33 +193,19 @@ float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t h if(pos >= hoursTomorrow) return PRICE_NO_VALUE; if(tomorrow == NULL) return PRICE_NO_VALUE; - if(tomorrow->points[pos] == PRICE_NO_VALUE) + if(!tomorrow->hasPrice(pos)) return PRICE_NO_VALUE; - value = tomorrow->points[pos] / 10000.0; - if(strcmp(tomorrow->measurementUnit, "KWH") == 0) { - // Multiplier is 1 - } else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) { - multiplier *= 0.001; - } else { - return PRICE_NO_VALUE; - } - float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, time(nullptr)); + value = tomorrow->getPrice(pos); + float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency, time(nullptr)); if(mult == 0) return PRICE_NO_VALUE; multiplier *= mult; } else if(pos >= 0) { if(today == NULL) return PRICE_NO_VALUE; - if(today->points[pos] == PRICE_NO_VALUE) + if(!today->hasPrice(pos)) return PRICE_NO_VALUE; - value = today->points[pos] / 10000.0; - if(strcmp(today->measurementUnit, "KWH") == 0) { - // Multiplier is 1 - } else if(strcmp(today->measurementUnit, "MWH") == 0) { - multiplier *= 0.001; - } else { - return PRICE_NO_VALUE; - } - float mult = getCurrencyMultiplier(today->currency, config->currency, time(nullptr)); + value = today->getPrice(pos); + float mult = getCurrencyMultiplier(today->getCurrency(), config->currency, time(nullptr)); if(mult == 0) return PRICE_NO_VALUE; multiplier *= mult; } @@ -336,17 +322,17 @@ bool PriceService::retrieve(const char* url, Stream* doc) { nextFetchDelayMinutes = 2; } #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf(http->errorToString(status).c_str()); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf(http->errorToString(status).c_str()); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::DEBUG)) -#endif -debugger->printf(http->getString().c_str()); + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf(http->getString().c_str()); http->end(); return false; @@ -393,18 +379,18 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time } if(currencyMultiplier != 0) { #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::DEBUG)) -#endif -debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier); + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier); tmElements_t tm; breakTime(t, tm); lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60); this->currencyMultiplier = currencyMultiplier; } else { #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::WARNING)) -#endif -debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n")); + if (debugger->isActive(RemoteDebug::WARNING)) + #endif + debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n")); lastCurrencyFetch = now + (SECS_PER_HOUR * 1000); if(this->currencyMultiplier == 1) return 0; } @@ -435,19 +421,19 @@ PricesContainer* PriceService::fetchPrices(time_t t) { #endif #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::INFO)) -#endif -debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); + if (debugger->isActive(RemoteDebug::INFO)) + #endif + debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::DEBUG)) -#endif -debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); - EntsoeA44Parser a44; - if(retrieve(buf, &a44) && a44.getPoint(0) != PRICE_NO_VALUE) { - PricesContainer* ret = new PricesContainer(); - a44.get(ret); + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); + PricesContainer* ret = new PricesContainer("EOE"); + EntsoeA44Parser a44(ret); + if(retrieve(buf, &a44) && ret->hasPrice(0)) { return ret; } else { + delete ret; return NULL; } } else if(hub) { @@ -455,7 +441,7 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); breakTime(tz->toLocal(t), tm); String data; - snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d?currency=%s"), + snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price-v2/%s/%d/%d/%d?currency=%s"), config->area, tm.Year+1970, tm.Month, @@ -463,13 +449,13 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); config->currency ); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::INFO)) -#endif -debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); + if (debugger->isActive(RemoteDebug::INFO)) + #endif + debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::DEBUG)) -#endif -debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); #if defined(ESP8266) WiFiClient client; client.setTimeout(5000); @@ -496,13 +482,15 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); GCMParser gcm(key, auth); int8_t gcmRet = gcm.parse(content, ctx); if(gcmRet > 0) { - PricesContainer* ret = new PricesContainer(); - for(uint8_t i = 0; i < 25; i++) { - ret->points[i] = PRICE_NO_VALUE; - } - memcpy(ret, content+gcmRet, sizeof(*ret)); - for(uint8_t i = 0; i < 25; i++) { - ret->points[i] = ntohl(ret->points[i]); + AmsPriceV2Header* header = (AmsPriceV2Header*) (content-gcmRet); + + PricesContainer* ret = new PricesContainer(header->source); + ret->setup(header->resolutionInMinutes, header->hours); + 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]); } lastError = 0; nextFetchDelayMinutes = 1; @@ -511,9 +499,9 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); lastError = gcmRet; nextFetchDelayMinutes = 60; #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet); } } else { lastError = status; @@ -525,20 +513,20 @@ debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n nextFetchDelayMinutes = 5; } #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -{ + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + { debugger->printf(http->errorToString(status).c_str()); debugger->println(); } #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::DEBUG)) -#endif -debugger->printf(http->getString().c_str()); + if (debugger->isActive(RemoteDebug::DEBUG)) + #endif + debugger->printf(http->getString().c_str()); http->end(); } @@ -575,16 +563,16 @@ void PriceService::cropPriceConfig(uint8_t size) { bool PriceService::save() { if(!LittleFS.begin()) { #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); return false; } #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::INFO)) -#endif -debugger->printf_P(PSTR("(PriceService) Saving price config\n")); + if (debugger->isActive(RemoteDebug::INFO)) + #endif + debugger->printf_P(PSTR("(PriceService) Saving price config\n")); PriceConfig pc; File file = LittleFS.open(FILE_PRICE_CONF, "w"); @@ -607,18 +595,18 @@ debugger->printf_P(PSTR("(PriceService) Saving price config\n")); bool PriceService::load() { if(!LittleFS.begin()) { #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::ERROR)) -#endif -debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); + if (debugger->isActive(RemoteDebug::ERROR)) + #endif + debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); return false; } if(!LittleFS.exists(FILE_PRICE_CONF)) { return false; } #if defined(AMS_REMOTE_DEBUG) -if (debugger->isActive(RemoteDebug::INFO)) -#endif -debugger->printf_P(PSTR("(PriceService) Loading price config\n")); + if (debugger->isActive(RemoteDebug::INFO)) + #endif + debugger->printf_P(PSTR("(PriceService) Loading price config\n")); this->priceConfig.clear();