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_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;

View File

@@ -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();

View File

@@ -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

View File

@@ -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, "<currency_Unit.name>") == 0) {
if(strcmp_P(buf, PSTR("<currency_Unit.name>")) == 0) {
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;
} else if(strcmp(buf, "<position>") == 0) {
} else if(strcmp(buf, PSTR("<position>")) == 0) {
docPos = DOCPOS_POSITION;
pointNum = 0xFF;
} else if(strcmp(buf, "<price.amount>") == 0) {
} else if(strcmp(buf, PSTR("<price.amount>")) == 0) {
docPos = DOCPOS_AMOUNT;
} else if(strcmp(buf, PSTR("<resolution>")) == 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;
}
}

View File

@@ -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();