diff --git a/lib/AmsConfiguration/src/AmsConfiguration.cpp b/lib/AmsConfiguration/src/AmsConfiguration.cpp index a0cb305d..052f87a5 100644 --- a/lib/AmsConfiguration/src/AmsConfiguration.cpp +++ b/lib/AmsConfiguration/src/AmsConfiguration.cpp @@ -1088,12 +1088,12 @@ void AmsConfiguration::print(Print* debugger) EntsoeConfig entsoe; if(getEntsoeConfig(entsoe)) { - debugger->println("--ENTSO-E configuration--"); - debugger->printf("Token: %s\r\n", entsoe.token); - if(strlen(entsoe.token) > 0) { + if(strlen(entsoe.area) > 0) { + debugger->println("--ENTSO-E configuration--"); debugger->printf("Area: %s\r\n", entsoe.area); debugger->printf("Currency: %s\r\n", entsoe.currency); debugger->printf("Multiplier: %f\r\n", entsoe.multiplier / 1000.0); + debugger->printf("Token: %s\r\n", entsoe.token); } debugger->println(""); delay(10); diff --git a/lib/AmsDataStorage/src/AmsDataStorage.cpp b/lib/AmsDataStorage/src/AmsDataStorage.cpp index 091eef04..75bf4b56 100644 --- a/lib/AmsDataStorage/src/AmsDataStorage.cpp +++ b/lib/AmsDataStorage/src/AmsDataStorage.cpp @@ -67,7 +67,7 @@ bool AmsDataStorage::update(AmsData* data) { } tmElements_t last; breakTime(day.lastMeterReadTime, last); - for(int i = last.Hour; i < utc.Hour; i++) { + for(int i = last.Hour; i <= utc.Hour; i++) { if(debugger->isActive(RemoteDebug::VERBOSE)) { debugger->printf("(AmsDataStorage) Clearing hour: %d\n", i); } @@ -90,7 +90,7 @@ bool AmsDataStorage::update(AmsData* data) { } tmElements_t last; breakTime(tz->toLocal(month.lastMeterReadTime), last); - for(int i = last.Day; i < ltz.Day; i++) { + for(int i = last.Day; i <= ltz.Day; i++) { if(debugger->isActive(RemoteDebug::VERBOSE)) { debugger->printf("(AmsDataStorage) Clearing day: %d\n", i); } diff --git a/lib/ClassicUi/html/head.html b/lib/ClassicUi/html/head.html index 6e34c7aa..ef742621 100644 --- a/lib/ClassicUi/html/head.html +++ b/lib/ClassicUi/html/head.html @@ -64,7 +64,7 @@ MQTT Web NTP - ENTSO-E API + Price API Documentation diff --git a/lib/ClassicUi/html/entsoe.html b/lib/ClassicUi/html/priceapi.html similarity index 70% rename from lib/ClassicUi/html/entsoe.html rename to lib/ClassicUi/html/priceapi.html index 44b4847e..60dc4e6f 100644 --- a/lib/ClassicUi/html/entsoe.html +++ b/lib/ClassicUi/html/priceapi.html @@ -1,38 +1,31 @@
-
ENTSO-E API
+
Price API
-
-
-
- Token -
- -
-
Region
- - - - + + + +
@@ -71,6 +64,14 @@
+
+
+
+ ENTSO-E token +
+ +
+

diff --git a/lib/ClassicUi/include/AmsWebServer.h b/lib/ClassicUi/include/AmsWebServer.h index f6cbe053..b6d3decf 100644 --- a/lib/ClassicUi/include/AmsWebServer.h +++ b/lib/ClassicUi/include/AmsWebServer.h @@ -89,7 +89,7 @@ private: void configMqttHtml(); void configWebHtml(); void configDomoticzHtml(); - void configEntsoeHtml(); + void configPriceApiHtml(); void configNtpHtml(); void configGpioHtml(); void configDebugHtml(); diff --git a/lib/ClassicUi/src/AmsWebServer.cpp b/lib/ClassicUi/src/AmsWebServer.cpp index 71da2b5f..6093312f 100644 --- a/lib/ClassicUi/src/AmsWebServer.cpp +++ b/lib/ClassicUi/src/AmsWebServer.cpp @@ -18,7 +18,7 @@ #include "root/mqtt_html.h" #include "root/web_html.h" #include "root/domoticz_html.h" -#include "root/entsoe_html.h" +#include "root/priceapi_html.h" #include "root/ntp_html.h" #include "root/gpio_html.h" #include "root/debugging_html.h" @@ -71,7 +71,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter server.on(F("/mqtt"), HTTP_GET, std::bind(&AmsWebServer::configMqttHtml, this)); server.on(F("/web"), HTTP_GET, std::bind(&AmsWebServer::configWebHtml, this)); server.on(F("/domoticz"),HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this)); - server.on(F("/entsoe"),HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this)); + server.on(F("/priceapi"),HTTP_GET, std::bind(&AmsWebServer::configPriceApiHtml, this)); server.on(F("/thresholds"),HTTP_GET, std::bind(&AmsWebServer::configThresholdsHtml, this)); server.on(F("/boot.css"), HTTP_GET, std::bind(&AmsWebServer::bootCss, this)); server.on(F("/github.svg"), HTTP_GET, std::bind(&AmsWebServer::githubSvg, this)); @@ -543,8 +543,8 @@ void AmsWebServer::configDomoticzHtml() { server.sendContent_P(FOOT_HTML); } -void AmsWebServer::configEntsoeHtml() { - printD(F("Serving /entsoe.html over http...")); +void AmsWebServer::configPriceApiHtml() { + printD(F("Serving /priceapi.html over http...")); if(!checkSecurity(1)) return; @@ -552,52 +552,54 @@ void AmsWebServer::configEntsoeHtml() { EntsoeConfig entsoe; config->getEntsoeConfig(entsoe); - if(ESP.getFreeHeap() > 25000) { - String html = String((const __FlashStringHelper*) ENTSOE_HTML); + String html = String((const __FlashStringHelper*) PRICEAPI_HTML); - html.replace(F("{et}"), entsoe.token); - html.replace(F("{em}"), String(entsoe.multiplier / 1000.0, 3)); - - html.replace(F("{eaNo1}"), strcmp(entsoe.area, "10YNO-1--------2") == 0 ? F("selected") : F("")); - html.replace(F("{eaNo2}"), strcmp(entsoe.area, "10YNO-2--------T") == 0 ? F("selected") : F("")); - html.replace(F("{eaNo3}"), strcmp(entsoe.area, "10YNO-3--------J") == 0 ? F("selected") : F("")); - html.replace(F("{eaNo4}"), strcmp(entsoe.area, "10YNO-4--------9") == 0 ? F("selected") : F("")); - html.replace(F("{eaNo5}"), strcmp(entsoe.area, "10Y1001A1001A48H") == 0 ? F("selected") : F("")); - - html.replace(F("{eaSe1}"), strcmp(entsoe.area, "10Y1001A1001A44P") == 0 ? F("selected") : F("")); - html.replace(F("{eaSe2}"), strcmp(entsoe.area, "10Y1001A1001A45N") == 0 ? F("selected") : F("")); - html.replace(F("{eaSe3}"), strcmp(entsoe.area, "10Y1001A1001A46L") == 0 ? F("selected") : F("")); - html.replace(F("{eaSe4}"), strcmp(entsoe.area, "10Y1001A1001A47J") == 0 ? F("selected") : F("")); - - html.replace(F("{eaDk1}"), strcmp(entsoe.area, "10YDK-1--------W") == 0 ? F("selected") : F("")); - html.replace(F("{eaDk2}"), strcmp(entsoe.area, "10YDK-2--------M") == 0 ? F("selected") : F("")); - - html.replace(F("{at}"), strcmp(entsoe.area, "10YAT-APG------L") == 0 ? F("selected") : F("")); - html.replace(F("{be}"), strcmp(entsoe.area, "10YBE----------2") == 0 ? F("selected") : F("")); - html.replace(F("{cz}"), strcmp(entsoe.area, "10YCZ-CEPS-----N") == 0 ? F("selected") : F("")); - html.replace(F("{ee}"), strcmp(entsoe.area, "10Y1001A1001A39I") == 0 ? F("selected") : F("")); - html.replace(F("{fi}"), strcmp(entsoe.area, "10YFI-1--------U") == 0 ? F("selected") : F("")); - html.replace(F("{fr}"), strcmp(entsoe.area, "10YFR-RTE------C") == 0 ? F("selected") : F("")); - html.replace(F("{de}"), strcmp(entsoe.area, "10Y1001A1001A83F") == 0 ? F("selected") : F("")); - html.replace(F("{gb}"), strcmp(entsoe.area, "10YGB----------A") == 0 ? F("selected") : F("")); - html.replace(F("{lv}"), strcmp(entsoe.area, "10YLV-1001A00074") == 0 ? F("selected") : F("")); - html.replace(F("{lt}"), strcmp(entsoe.area, "10YLT-1001A0008Q") == 0 ? F("selected") : F("")); - html.replace(F("{nl}"), strcmp(entsoe.area, "10YNL----------L") == 0 ? F("selected") : F("")); - html.replace(F("{pl}"), strcmp(entsoe.area, "10YPL-AREA-----S") == 0 ? F("selected") : F("")); - html.replace(F("{ch}"), strcmp(entsoe.area, "10YCH-SWISSGRIDZ") == 0 ? F("selected") : F("")); - - html.replace(F("{ecNOK}"), strcmp(entsoe.currency, "NOK") == 0 ? F("selected") : F("")); - html.replace(F("{ecSEK}"), strcmp(entsoe.currency, "SEK") == 0 ? F("selected") : F("")); - html.replace(F("{ecDKK}"), strcmp(entsoe.currency, "DKK") == 0 ? F("selected") : F("")); - html.replace(F("{ecEUR}"), strcmp(entsoe.currency, "EUR") == 0 ? F("selected") : F("")); - - server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); - server.send_P(200, MIME_HTML, HEAD_HTML); - server.sendContent(html); - server.sendContent_P(FOOT_HTML); + if(ESP.getFreeHeap() > 32000) { + html.replace("{et}", entsoe.token); + html.replace("{dt}", ""); } else { - notFound(); + html.replace("{et}", ""); + html.replace("{dt}", "d-none"); } + html.replace("{em}", String(entsoe.multiplier / 1000.0, 3)); + + html.replace(F("{no1}"), strcmp(entsoe.area, "10YNO-1--------2") == 0 ? "selected" : ""); + html.replace(F("{no2}"), strcmp(entsoe.area, "10YNO-2--------T") == 0 ? "selected" : ""); + html.replace(F("{no3}"), strcmp(entsoe.area, "10YNO-3--------J") == 0 ? "selected" : ""); + html.replace(F("{no4}"), strcmp(entsoe.area, "10YNO-4--------9") == 0 ? "selected" : ""); + html.replace(F("{no5}"), strcmp(entsoe.area, "10Y1001A1001A48H") == 0 ? "selected" : ""); + + html.replace(F("{se1}"), strcmp(entsoe.area, "10Y1001A1001A44P") == 0 ? "selected" : ""); + html.replace(F("{se2}"), strcmp(entsoe.area, "10Y1001A1001A45N") == 0 ? "selected" : ""); + html.replace(F("{se3}"), strcmp(entsoe.area, "10Y1001A1001A46L") == 0 ? "selected" : ""); + html.replace(F("{se4}"), strcmp(entsoe.area, "10Y1001A1001A47J") == 0 ? "selected" : ""); + + html.replace(F("{dk1}"), strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : ""); + html.replace(F("{dk2}"), strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : ""); + + html.replace(F("{at}"), strcmp(entsoe.area, "10YAT-APG------L") == 0 ? F("selected") : F("")); + html.replace(F("{be}"), strcmp(entsoe.area, "10YBE----------2") == 0 ? F("selected") : F("")); + html.replace(F("{cz}"), strcmp(entsoe.area, "10YCZ-CEPS-----N") == 0 ? F("selected") : F("")); + html.replace(F("{ee}"), strcmp(entsoe.area, "10Y1001A1001A39I") == 0 ? F("selected") : F("")); + html.replace(F("{fi}"), strcmp(entsoe.area, "10YFI-1--------U") == 0 ? F("selected") : F("")); + html.replace(F("{fr}"), strcmp(entsoe.area, "10YFR-RTE------C") == 0 ? F("selected") : F("")); + html.replace(F("{de}"), strcmp(entsoe.area, "10Y1001A1001A83F") == 0 ? F("selected") : F("")); + html.replace(F("{gb}"), strcmp(entsoe.area, "10YGB----------A") == 0 ? F("selected") : F("")); + html.replace(F("{lv}"), strcmp(entsoe.area, "10YLV-1001A00074") == 0 ? F("selected") : F("")); + html.replace(F("{lt}"), strcmp(entsoe.area, "10YLT-1001A0008Q") == 0 ? F("selected") : F("")); + html.replace(F("{nl}"), strcmp(entsoe.area, "10YNL----------L") == 0 ? F("selected") : F("")); + html.replace(F("{pl}"), strcmp(entsoe.area, "10YPL-AREA-----S") == 0 ? F("selected") : F("")); + html.replace(F("{ch}"), strcmp(entsoe.area, "10YCH-SWISSGRIDZ") == 0 ? F("selected") : F("")); + + html.replace(F("{nok}"), strcmp(entsoe.currency, "NOK") == 0 ? "selected" : ""); + html.replace(F("{sek}"), strcmp(entsoe.currency, "SEK") == 0 ? "selected" : ""); + html.replace(F("{dkk}"), strcmp(entsoe.currency, "DKK") == 0 ? "selected" : ""); + html.replace(F("{eur}"), strcmp(entsoe.currency, "EUR") == 0 ? "selected" : ""); + + server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); + server.send_P(200, MIME_HTML, HEAD_HTML); + server.sendContent(html); + server.sendContent_P(FOOT_HTML); } void AmsWebServer::configThresholdsHtml() { @@ -724,7 +726,7 @@ void AmsWebServer::dataJson() { } float price = ENTSOE_NO_VALUE; - if(eapi != NULL && strlen(eapi->getToken()) > 0) + if(eapi != NULL) price = eapi->getValueForHour(0); String peaks = ""; diff --git a/lib/EntsoePriceApi/include/EntsoeA44Parser.h b/lib/EntsoePriceApi/include/EntsoeA44Parser.h index 2f538d10..55201a62 100644 --- a/lib/EntsoePriceApi/include/EntsoeA44Parser.h +++ b/lib/EntsoePriceApi/include/EntsoeA44Parser.h @@ -2,6 +2,7 @@ #define _ENTSOEA44PARSER_H #include "Stream.h" +#include "PricesContainer.h" #define DOCPOS_SEEK 0 #define DOCPOS_CURRENCY 1 @@ -26,6 +27,7 @@ public: void flush(); size_t write(const uint8_t *buffer, size_t size); size_t write(uint8_t); + void get(PricesContainer*); private: char currency[4]; diff --git a/lib/EntsoePriceApi/include/EntsoeApi.h b/lib/EntsoePriceApi/include/EntsoeApi.h index 2fbd2129..43f3811c 100644 --- a/lib/EntsoePriceApi/include/EntsoeApi.h +++ b/lib/EntsoePriceApi/include/EntsoeApi.h @@ -4,8 +4,8 @@ #include "TimeLib.h" #include "Timezone.h" #include "RemoteDebug.h" -#include "EntsoeA44Parser.h" #include "AmsConfiguration.h" +#include "EntsoeA44Parser.h" #if defined(ESP8266) #include @@ -32,6 +32,7 @@ public: private: RemoteDebug* debugger; EntsoeConfig* config = NULL; + HTTPClient http; uint8_t currentDay = 0, currentHour = 0; uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T) @@ -39,20 +40,26 @@ private: uint64_t lastTodayFetch = 0; uint64_t lastTomorrowFetch = 0; uint64_t lastCurrencyFetch = 0; - EntsoeA44Parser* today = NULL; - EntsoeA44Parser* tomorrow = NULL; + PricesContainer* today = NULL; + PricesContainer* tomorrow = NULL; Timezone* tz = NULL; static const uint16_t BufferSize = 256; char* buf; + bool hub = false; + uint8_t* key = NULL; + uint8_t* auth = NULL; + float currencyMultiplier = 0; + PricesContainer* fetchPrices(time_t); bool retrieve(const char* url, Stream* doc); float getCurrencyMultiplier(const char* from, const char* to); void printD(String fmt, ...); void printE(String fmt, ...); + void debugPrint(byte *buffer, int start, int length); }; #endif diff --git a/lib/EntsoePriceApi/src/EntsoeA44Parser.cpp b/lib/EntsoePriceApi/src/EntsoeA44Parser.cpp index 9dc41de6..c64cd145 100644 --- a/lib/EntsoePriceApi/src/EntsoeA44Parser.cpp +++ b/lib/EntsoePriceApi/src/EntsoeA44Parser.cpp @@ -106,3 +106,35 @@ size_t EntsoeA44Parser::write(uint8_t byte) { } return 1; } + +void EntsoeA44Parser::get(PricesContainer* container) { + strcpy(container->currency, currency); + strcpy(container->measurementUnit, measurementUnit); + + container->points[0] = points[0] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[0] * 10000; + container->points[1] = points[1] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[1] * 10000; + container->points[2] = points[2] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[2] * 10000; + container->points[3] = points[3] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[3] * 10000; + container->points[4] = points[4] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[4] * 10000; + container->points[5] = points[5] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[5] * 10000; + container->points[6] = points[6] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[6] * 10000; + container->points[7] = points[7] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[7] * 10000; + container->points[8] = points[8] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[8] * 10000; + container->points[9] = points[9] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[9] * 10000; + + container->points[10] = points[10] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[10] * 10000; + container->points[11] = points[11] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[11] * 10000; + container->points[12] = points[12] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[12] * 10000; + container->points[13] = points[13] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[13] * 10000; + container->points[14] = points[14] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[14] * 10000; + container->points[15] = points[15] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[15] * 10000; + container->points[16] = points[16] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[16] * 10000; + container->points[17] = points[17] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[17] * 10000; + container->points[18] = points[18] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[18] * 10000; + container->points[19] = points[19] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[19] * 10000; + + container->points[20] = points[20] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[20] * 10000; + container->points[21] = points[21] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[21] * 10000; + container->points[22] = points[22] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[22] * 10000; + container->points[23] = points[23] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[23] * 10000; +} \ No newline at end of file diff --git a/lib/EntsoePriceApi/src/EntsoeApi.cpp b/lib/EntsoePriceApi/src/EntsoeApi.cpp index b1f3db2f..3f6cc60b 100644 --- a/lib/EntsoePriceApi/src/EntsoeApi.cpp +++ b/lib/EntsoePriceApi/src/EntsoeApi.cpp @@ -5,6 +5,8 @@ #include "DnbCurrParser.h" #include "version.h" +#include "ams/GcmParser.h" + #if defined(ESP32) #include #endif @@ -31,6 +33,26 @@ void EntsoeApi::setup(EntsoeConfig& config) { if(today != NULL) delete today; if(tomorrow != NULL) delete tomorrow; today = tomorrow = NULL; + + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setReuse(false); + http.setTimeout(60000); + http.setUserAgent("ams2mqtt/" + String(VERSION)); + http.useHTTP10(true); + + #if defined(AMS2MQTT_PRICE_KEY) + key = new uint8_t[16] AMS2MQTT_PRICE_KEY; + hub = true; + #else + hub = false; + #endif + #if defined(AMS2MQTT_PRICE_AUTHENTICATION) + auth = new uint8_t[16] AMS2MQTT_PRICE_AUTHENTICATION; + hub = hub && true; + #else + hub = false; + #endif + } char* EntsoeApi::getToken() { @@ -64,25 +86,33 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) { if(pos > 23) { if(tomorrow == NULL) return ENTSOE_NO_VALUE; - value = tomorrow->getPoint(pos-24); - if(value != ENTSOE_NO_VALUE && strcmp(tomorrow->getMeasurementUnit(), "MWH") == 0) { + if(tomorrow->points[pos-24] == ENTSOE_NO_VALUE) + return ENTSOE_NO_VALUE; + value = tomorrow->points[pos-24] / 10000.0; + if(strcmp(tomorrow->measurementUnit, "KWH") == 0) { + // Multiplier is 1 + } else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) { multiplier *= 0.001; } else { return ENTSOE_NO_VALUE; } - float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency); + float mult = getCurrencyMultiplier(tomorrow->currency, config->currency); if(mult == 0) return ENTSOE_NO_VALUE; multiplier *= mult; } else if(pos >= 0) { if(today == NULL) return ENTSOE_NO_VALUE; - value = today->getPoint(pos); - if(value != ENTSOE_NO_VALUE && strcmp(today->getMeasurementUnit(), "MWH") == 0) { + if(today->points[pos] == ENTSOE_NO_VALUE) + return ENTSOE_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 ENTSOE_NO_VALUE; } - float mult = getCurrencyMultiplier(today->getCurrency(), config->currency); + float mult = getCurrencyMultiplier(today->currency, config->currency); if(mult == 0) return ENTSOE_NO_VALUE; multiplier *= mult; } @@ -90,15 +120,22 @@ float EntsoeApi::getValueForHour(time_t cur, int8_t hour) { } bool EntsoeApi::loop() { - if(strlen(getToken()) == 0) - return false; - uint64_t now = millis64(); if(now < 10000) return false; // Grace period time_t t = time(nullptr); if(t < BUILD_EPOCH) return false; + #ifndef AMS2MQTT_PRICE_KEY + if(strlen(getToken()) == 0) { + return false; + } + #endif + if(strlen(config->area) == 0) + return false; + if(strlen(config->currency) == 0) + return false; + bool ret = false; tmElements_t tm; breakTime(tz->toLocal(t), tm); @@ -124,39 +161,10 @@ bool EntsoeApi::loop() { midnightMillis = 0; // Force new midnight millis calculation return true; } else { - breakTime(t, tm); // Break UTC to find UTC midnight if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) { lastTodayFetch = now; - time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight - time_t e2 = e1 + SECS_PER_DAY; - tmElements_t d1, d2; - breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight - breakTime(tz->toUTC(e2), d2); - - snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s", - "https://transparency.entsoe.eu/api", getToken(), - d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00, - d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00, - config->area, config->area); - - #if defined(ESP32) - esp_task_wdt_reset(); - #elif defined(ESP8266) - ESP.wdtFeed(); - #endif - - - if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for today\n"); - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf); - EntsoeA44Parser* a44 = new EntsoeA44Parser(); - if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) { - today = a44; - return true; - } else if(a44 != NULL) { - delete a44; - today = NULL; - return false; - } + today = fetchPrices(t); + return today != NULL; } // Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will @@ -165,49 +173,18 @@ bool EntsoeApi::loop() { && midnightMillis - now < tomorrowFetchMillis && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000) ) { + breakTime(t+SECS_PER_DAY, tm); // Break UTC tomorrow to find UTC midnight lastTomorrowFetch = now; - time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second + (SECS_PER_DAY); - time_t e2 = e1 + SECS_PER_DAY; - tmElements_t d1, d2; - breakTime(tz->toUTC(e1), d1); - breakTime(tz->toUTC(e2), d2); - - snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s", - "https://transparency.entsoe.eu/api", getToken(), - d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00, - d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00, - config->area, config->area); - - #if defined(ESP32) - esp_task_wdt_reset(); - #elif defined(ESP8266) - ESP.wdtFeed(); - #endif - - if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for tomorrow\n"); - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf); - EntsoeA44Parser* a44 = new EntsoeA44Parser(); - if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) { - tomorrow = a44; - return true; - } else if(a44 != NULL) { - delete a44; - tomorrow = NULL; - return false; - } + tomorrow = fetchPrices(t+SECS_PER_DAY); + return tomorrow != NULL; } } return ret; } bool EntsoeApi::retrieve(const char* url, Stream* doc) { - HTTPClient https; - https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - https.setReuse(false); - https.setTimeout(50000); - https.setUserAgent("ams2mqtt"); #if defined(ESP32) - if(https.begin(url)) { + if(http.begin(url)) { printD("Connection established"); #if defined(ESP32) @@ -216,7 +193,7 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) { ESP.wdtFeed(); #endif - int status = https.GET(); + int status = http.GET(); #if defined(ESP32) esp_task_wdt_reset(); @@ -226,15 +203,15 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) { if(status == HTTP_CODE_OK) { printD("Receiving data"); - https.writeToStream(doc); - https.end(); + http.writeToStream(doc); + http.end(); return true; } else { if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status); - printE(https.errorToString(status)); - printD(https.getString()); + printE(http.errorToString(status)); + printD(http.getString()); - https.end(); + http.end(); return false; } } else { @@ -286,6 +263,104 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) { return currencyMultiplier; } +PricesContainer* EntsoeApi::fetchPrices(time_t t) { + tmElements_t tm; + breakTime(t, tm); + if(strlen(getToken()) > 0) { + time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight + time_t e2 = e1 + SECS_PER_DAY; + tmElements_t d1, d2; + breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight + breakTime(tz->toUTC(e2), d2); + + snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s", + "https://transparency.entsoe.eu/api", getToken(), + d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00, + d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00, + config->area, config->area); + + #if defined(ESP32) + esp_task_wdt_reset(); + #elif defined(ESP8266) + ESP.wdtFeed(); + #endif + + if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for %d.%d.%d\n", tm.Day, tm.Month, tm.Year+1970); + if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf); + EntsoeA44Parser a44; + if(retrieve(buf, &a44) && a44.getPoint(0) != ENTSOE_NO_VALUE) { + PricesContainer* ret = new PricesContainer(); + a44.get(ret); + return ret; + } else { + return NULL; + } + } else if(hub) { + String data; + snprintf(buf, BufferSize, "%s/%s/%d/%d/%d?currency=%s", + "http://ams2mqtt.rewiredinvent.no/hub/price", + config->area, + tm.Year+1970, + tm.Month, + tm.Day, + config->currency + ); + #if defined(ESP8266) + WiFiClient client; + client.setTimeout(5000); + if(http.begin(client, buf)) { + #elif defined(ESP32) + if(http.begin(buf)) { + #endif + int status = http.GET(); + + #if defined(ESP32) + esp_task_wdt_reset(); + #elif defined(ESP8266) + ESP.wdtFeed(); + #endif + + if(status == HTTP_CODE_OK) { + printD("Receiving data"); + data = http.getString(); + http.end(); + } else { + if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status); + printE(http.errorToString(status)); + printD(http.getString()); + + http.end(); + } + } + uint8_t* content = (uint8_t*) (data.c_str()); + if(debugger->isActive(RemoteDebug::DEBUG)) { + printD("Received content for prices:"); + debugPrint(content, 0, data.length()); + } + + DataParserContext ctx; + ctx.length = data.length(); + GCMParser gcm(key, auth); + int8_t gcmRet = gcm.parse(content, ctx); + if(debugger->isActive(RemoteDebug::DEBUG)) { + printD("Decrypted content for prices:"); + debugPrint(content, 0, data.length()); + } + if(gcmRet > 0) { + if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Price data starting at: %d\n", gcmRet); + PricesContainer* ret = new PricesContainer(); + memcpy(ret, content+gcmRet, sizeof(*ret)); + for(uint8_t i = 0; i < 24; i++) { + ret->points[i] = ntohl(ret->points[i]); + } + return ret; + } else { + if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Error code while decrypting prices: %d\n", gcmRet); + } + } + return NULL; +} + void EntsoeApi::printD(String fmt, ...) { va_list args; va_start(args, fmt); @@ -299,3 +374,19 @@ void EntsoeApi::printE(String fmt, ...) { if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args); va_end(args); } + +void EntsoeApi::debugPrint(byte *buffer, int start, int length) { + for (int i = start; i < start + length; i++) { + if (buffer[i] < 0x10) + debugger->print("0"); + debugger->print(buffer[i], HEX); + debugger->print(" "); + if ((i - start + 1) % 16 == 0) + debugger->println(""); + else if ((i - start + 1) % 4 == 0) + debugger->print(" "); + + yield(); // Let other get some resources too + } + debugger->println(""); +} diff --git a/platformio.ini b/platformio.ini index 9f61cd4f..83339e4d 100755 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ platform = espressif8266@3.2.0 framework = arduino board = esp12e board_build.ldscript = eagle.flash.4m2m.ld -build_flags = -D WEBSOCKET_DISABLED=1 +build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions lib_ldf_mode = off lib_deps = ESP8266WiFi, ESP8266mDNS, ESP8266WebServer, ESP8266HTTPClient, ESP8266httpUpdate, ${common.lib_deps} lib_ignore = ${common.lib_ignore} diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 0a3e9480..94901139 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -200,15 +200,12 @@ void setup() { hw.ledBlink(LED_GREEN, 1); hw.ledBlink(LED_BLUE, 1); - #if defined(ESP32) EntsoeConfig entsoe; - if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) { + if(config.getEntsoeConfig(entsoe) && strlen(entsoe.area) > 0) { eapi = new EntsoeApi(&Debug); eapi->setup(entsoe); ws.setEntsoeApi(eapi); } - #endif - bool shared = false; config.getMeterConfig(meterConfig); Serial.flush(); @@ -514,7 +511,6 @@ void loop() { mqtt->disconnect(); } - #if defined(ESP32) try { if(eapi != NULL && ntpEnabled) { if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) { @@ -524,7 +520,7 @@ void loop() { if(config.isEntsoeChanged()) { EntsoeConfig entsoe; - if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) { + if(config.getEntsoeConfig(entsoe) && strlen(entsoe.area) > 0) { if(eapi == NULL) { eapi = new EntsoeApi(&Debug); ea.setEapi(eapi); @@ -541,7 +537,6 @@ void loop() { } catch(const std::exception& e) { debugE("Exception in ENTSO-E loop (%s)", e.what()); } - #endif ws.loop(); } if(mqtt != NULL) { diff --git a/src/entsoe/PricesContainer.h b/src/entsoe/PricesContainer.h new file mode 100644 index 00000000..cb6d687a --- /dev/null +++ b/src/entsoe/PricesContainer.h @@ -0,0 +1,8 @@ +#ifndef _PRICESCONTAINER_H +#define _PRICESCONTAINER_H +struct PricesContainer { + char currency[4]; + char measurementUnit[4]; + int32_t points[24]; +}; +#endif