15min prices WIP

This commit is contained in:
Gunnar Skjold
2025-03-15 07:27:26 +01:00
parent a7324d828a
commit 0588bfd418
5 changed files with 140 additions and 142 deletions

View File

@@ -15,28 +15,23 @@
#define DOCPOS_MEASUREMENTUNIT 2 #define DOCPOS_MEASUREMENTUNIT 2
#define DOCPOS_POSITION 3 #define DOCPOS_POSITION 3
#define DOCPOS_AMOUNT 4 #define DOCPOS_AMOUNT 4
#define DOCPOS_RESOLUTION 5
class EntsoeA44Parser: public Stream { class EntsoeA44Parser: public Stream {
public: public:
EntsoeA44Parser(); EntsoeA44Parser(PricesContainer *container);
virtual ~EntsoeA44Parser(); virtual ~EntsoeA44Parser();
char* getCurrency();
char* getMeasurementUnit();
float getPoint(uint8_t position);
int available(); int available();
int read(); int read();
int peek(); int peek();
void flush(); void flush();
size_t write(const uint8_t *buffer, size_t size); size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t); size_t write(uint8_t);
void get(PricesContainer*);
private: private:
char currency[4]; PricesContainer *container;
char measurementUnit[4]; float multiplier = 1.0;
float points[25];
char buf[64]; char buf[64];
uint8_t pos = 0; uint8_t pos = 0;

View File

@@ -57,10 +57,12 @@ struct PriceConfig {
uint8_t end_dayofmonth; uint8_t end_dayofmonth;
}; };
struct PricePart { struct AmsPriceV2Header {
char name[32]; char source[4];
char description[32]; char currency[4];
uint32_t value; uint8_t resolutionInMinutes;
uint8_t hours;
uint8_t numberOfPoints;
}; };
class PriceService { class PriceService {
@@ -86,8 +88,6 @@ public:
void setPriceConfig(uint8_t index, PriceConfig &priceConfig); void setPriceConfig(uint8_t index, PriceConfig &priceConfig);
void cropPriceConfig(uint8_t size); void cropPriceConfig(uint8_t size);
PricePart getPricePart(uint8_t index);
int16_t getLastError(); int16_t getLastError();
bool load(); bool load();

View File

@@ -9,10 +9,31 @@
#define PRICE_NO_VALUE -127 #define PRICE_NO_VALUE -127
struct PricesContainer { class PricesContainer {
char currency[4]; public:
char measurementUnit[4]; PricesContainer(char* source);
int32_t points[25];
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 source[4];
char currency[4];
uint8_t resolutionInMinutes;
uint8_t hours;
uint8_t numberOfPoints;
int32_t *points;
}; };
#endif #endif

View File

@@ -7,27 +7,14 @@
#include "EntsoeA44Parser.h" #include "EntsoeA44Parser.h"
#include "HardwareSerial.h" #include "HardwareSerial.h"
EntsoeA44Parser::EntsoeA44Parser() { EntsoeA44Parser::EntsoeA44Parser(PricesContainer *container) {
for(int i = 0; i < 25; i++) points[i] = PRICE_NO_VALUE; this->container = container;
} }
EntsoeA44Parser::~EntsoeA44Parser() { 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() { int EntsoeA44Parser::available() {
return 0; return 0;
} }
@@ -57,7 +44,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
buf[pos++] = byte; buf[pos++] = byte;
if(pos == 3) { if(pos == 3) {
buf[pos++] = '\0'; buf[pos++] = '\0';
memcpy(currency, buf, pos); container->setCurrency(buf);
docPos = DOCPOS_SEEK; docPos = DOCPOS_SEEK;
pos = 0; pos = 0;
} }
@@ -65,7 +52,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
buf[pos++] = byte; buf[pos++] = byte;
if(pos == 3) { if(pos == 3) {
buf[pos++] = '\0'; buf[pos++] = '\0';
memcpy(measurementUnit, buf, pos); if(strcmp_P(buf, PSTR("MWH"))) multiplier = 0.001;
docPos = DOCPOS_SEEK; docPos = DOCPOS_SEEK;
pos = 0; pos = 0;
} }
@@ -73,7 +60,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
if(byte == '<') { if(byte == '<') {
buf[pos] = '\0'; buf[pos] = '\0';
long pn = String(buf).toInt() - 1; long pn = String(buf).toInt() - 1;
if(pn < 25) { if(pn < container->getNumberOfPoints()) {
pointNum = pn; pointNum = pn;
} }
docPos = DOCPOS_SEEK; docPos = DOCPOS_SEEK;
@@ -85,8 +72,25 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
if(byte == '<') { if(byte == '<') {
buf[pos] = '\0'; buf[pos] = '\0';
float val = String(buf).toFloat(); float val = String(buf).toFloat();
for(uint8_t i = pointNum; i < 25; i++) { for(uint8_t i = pointNum; i < container->getNumberOfPoints(); i++) {
points[i] = val; 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; docPos = DOCPOS_SEEK;
pos = 0; pos = 0;
@@ -101,15 +105,17 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} else if(byte == '>') { } else if(byte == '>') {
buf[pos++] = byte; buf[pos++] = byte;
buf[pos] = '\0'; buf[pos] = '\0';
if(strcmp(buf, "<currency_Unit.name>") == 0) { if(strcmp_P(buf, PSTR("<currency_Unit.name>")) == 0) {
docPos = DOCPOS_CURRENCY; docPos = DOCPOS_CURRENCY;
} else if(strcmp(buf, "<price_Measure_Unit.name>") == 0) { } else if(strcmp(buf, PSTR("<price_Measure_Unit.name>")) == 0) {
docPos = DOCPOS_MEASUREMENTUNIT; docPos = DOCPOS_MEASUREMENTUNIT;
} else if(strcmp(buf, "<position>") == 0) { } else if(strcmp(buf, PSTR("<position>")) == 0) {
docPos = DOCPOS_POSITION; docPos = DOCPOS_POSITION;
pointNum = 0xFF; pointNum = 0xFF;
} else if(strcmp(buf, "<price.amount>") == 0) { } else if(strcmp(buf, PSTR("<price.amount>")) == 0) {
docPos = DOCPOS_AMOUNT; docPos = DOCPOS_AMOUNT;
} else if(strcmp(buf, PSTR("<resolution>")) == 0) {
docPos = DOCPOS_RESOLUTION;
} }
pos = 0; pos = 0;
} else { } else {
@@ -118,15 +124,3 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} }
return 1; 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;
}
}

View File

@@ -86,15 +86,15 @@ char* PriceService::getArea() {
char* PriceService::getSource() { char* PriceService::getSource() {
if(this->today != NULL && this->tomorrow != NULL) { if(this->today != NULL && this->tomorrow != NULL) {
if(strcmp(this->today->source, this->tomorrow->source) == 0) { if(strcmp(this->today->getSource(), this->tomorrow->getSource()) == 0) {
return this->today->source; return this->today->getSource();
} else { } else {
return "MIX"; return "MIX";
} }
} else if(today != NULL) { } else if(today != NULL) {
return this->today->source; return this->today->getSource();
} else if(tomorrow != NULL) { } else if(tomorrow != NULL) {
return this->tomorrow->source; return this->tomorrow->getSource();
} }
return ""; 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(pos >= hoursTomorrow) return PRICE_NO_VALUE;
if(tomorrow == NULL) if(tomorrow == NULL)
return PRICE_NO_VALUE; return PRICE_NO_VALUE;
if(tomorrow->points[pos] == PRICE_NO_VALUE) if(!tomorrow->hasPrice(pos))
return PRICE_NO_VALUE; return PRICE_NO_VALUE;
value = tomorrow->points[pos] / 10000.0; value = tomorrow->getPrice(pos);
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) { float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency, time(nullptr));
// 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));
if(mult == 0) return PRICE_NO_VALUE; if(mult == 0) return PRICE_NO_VALUE;
multiplier *= mult; multiplier *= mult;
} else if(pos >= 0) { } else if(pos >= 0) {
if(today == NULL) if(today == NULL)
return PRICE_NO_VALUE; return PRICE_NO_VALUE;
if(today->points[pos] == PRICE_NO_VALUE) if(!today->hasPrice(pos))
return PRICE_NO_VALUE; return PRICE_NO_VALUE;
value = today->points[pos] / 10000.0; value = today->getPrice(pos);
if(strcmp(today->measurementUnit, "KWH") == 0) { float mult = getCurrencyMultiplier(today->getCurrency(), config->currency, time(nullptr));
// 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));
if(mult == 0) return PRICE_NO_VALUE; if(mult == 0) return PRICE_NO_VALUE;
multiplier *= mult; multiplier *= mult;
} }
@@ -336,17 +322,17 @@ bool PriceService::retrieve(const char* url, Stream* doc) {
nextFetchDelayMinutes = 2; nextFetchDelayMinutes = 2;
} }
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf(http->errorToString(status).c_str()); debugger->printf(http->errorToString(status).c_str());
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG)) if (debugger->isActive(RemoteDebug::DEBUG))
#endif #endif
debugger->printf(http->getString().c_str()); debugger->printf(http->getString().c_str());
http->end(); http->end();
return false; return false;
@@ -393,18 +379,18 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
} }
if(currencyMultiplier != 0) { if(currencyMultiplier != 0) {
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG)) if (debugger->isActive(RemoteDebug::DEBUG))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier); debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
tmElements_t tm; tmElements_t tm;
breakTime(t, tm); breakTime(t, tm);
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60); lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60);
this->currencyMultiplier = currencyMultiplier; this->currencyMultiplier = currencyMultiplier;
} else { } else {
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING)) if (debugger->isActive(RemoteDebug::WARNING))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n")); debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
lastCurrencyFetch = now + (SECS_PER_HOUR * 1000); lastCurrencyFetch = now + (SECS_PER_HOUR * 1000);
if(this->currencyMultiplier == 1) return 0; if(this->currencyMultiplier == 1) return 0;
} }
@@ -435,19 +421,19 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
#endif #endif
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO)) if (debugger->isActive(RemoteDebug::INFO))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); 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 defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG)) if (debugger->isActive(RemoteDebug::DEBUG))
#endif #endif
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
EntsoeA44Parser a44; PricesContainer* ret = new PricesContainer("EOE");
if(retrieve(buf, &a44) && a44.getPoint(0) != PRICE_NO_VALUE) { EntsoeA44Parser a44(ret);
PricesContainer* ret = new PricesContainer(); if(retrieve(buf, &a44) && ret->hasPrice(0)) {
a44.get(ret);
return ret; return ret;
} else { } else {
delete ret;
return NULL; return NULL;
} }
} else if(hub) { } else if(hub) {
@@ -455,7 +441,7 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
breakTime(tz->toLocal(t), tm); breakTime(tz->toLocal(t), tm);
String data; 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, config->area,
tm.Year+1970, tm.Year+1970,
tm.Month, tm.Month,
@@ -463,13 +449,13 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
config->currency config->currency
); );
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO)) if (debugger->isActive(RemoteDebug::INFO))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970); 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 defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG)) if (debugger->isActive(RemoteDebug::DEBUG))
#endif #endif
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf); debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
#if defined(ESP8266) #if defined(ESP8266)
WiFiClient client; WiFiClient client;
client.setTimeout(5000); client.setTimeout(5000);
@@ -496,13 +482,15 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
GCMParser gcm(key, auth); GCMParser gcm(key, auth);
int8_t gcmRet = gcm.parse(content, ctx); int8_t gcmRet = gcm.parse(content, ctx);
if(gcmRet > 0) { if(gcmRet > 0) {
PricesContainer* ret = new PricesContainer(); AmsPriceV2Header* header = (AmsPriceV2Header*) (content-gcmRet);
for(uint8_t i = 0; i < 25; i++) {
ret->points[i] = PRICE_NO_VALUE; PricesContainer* ret = new PricesContainer(header->source);
} ret->setup(header->resolutionInMinutes, header->hours);
memcpy(ret, content+gcmRet, sizeof(*ret)); ret->setCurrency(header->currency);
for(uint8_t i = 0; i < 25; i++) { int32_t* points = (int32_t*) &header[1];
ret->points[i] = ntohl(ret->points[i]);
for(uint8_t i = 0; i < header->numberOfPoints; i++) {
ret->setPrice(i, points[i]);
} }
lastError = 0; lastError = 0;
nextFetchDelayMinutes = 1; nextFetchDelayMinutes = 1;
@@ -511,9 +499,9 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
lastError = gcmRet; lastError = gcmRet;
nextFetchDelayMinutes = 60; nextFetchDelayMinutes = 60;
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet); debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
} }
} else { } else {
lastError = status; lastError = status;
@@ -525,20 +513,20 @@ debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n
nextFetchDelayMinutes = 5; nextFetchDelayMinutes = 5;
} }
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status); debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
{ {
debugger->printf(http->errorToString(status).c_str()); debugger->printf(http->errorToString(status).c_str());
debugger->println(); debugger->println();
} }
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG)) if (debugger->isActive(RemoteDebug::DEBUG))
#endif #endif
debugger->printf(http->getString().c_str()); debugger->printf(http->getString().c_str());
http->end(); http->end();
} }
@@ -575,16 +563,16 @@ void PriceService::cropPriceConfig(uint8_t size) {
bool PriceService::save() { bool PriceService::save() {
if(!LittleFS.begin()) { if(!LittleFS.begin()) {
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
return false; return false;
} }
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO)) if (debugger->isActive(RemoteDebug::INFO))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Saving price config\n")); debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
PriceConfig pc; PriceConfig pc;
File file = LittleFS.open(FILE_PRICE_CONF, "w"); File file = LittleFS.open(FILE_PRICE_CONF, "w");
@@ -607,18 +595,18 @@ debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
bool PriceService::load() { bool PriceService::load() {
if(!LittleFS.begin()) { if(!LittleFS.begin()) {
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR)) if (debugger->isActive(RemoteDebug::ERROR))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n")); debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
return false; return false;
} }
if(!LittleFS.exists(FILE_PRICE_CONF)) { if(!LittleFS.exists(FILE_PRICE_CONF)) {
return false; return false;
} }
#if defined(AMS_REMOTE_DEBUG) #if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO)) if (debugger->isActive(RemoteDebug::INFO))
#endif #endif
debugger->printf_P(PSTR("(PriceService) Loading price config\n")); debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
this->priceConfig.clear(); this->priceConfig.clear();