mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-25 20:06:08 +00:00
Merge branch 'hub-prices' into dev-v2.2
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<a class="dropdown-item" href="/mqtt">MQTT</a>
|
||||
<a class="dropdown-item" href="/web">Web</a>
|
||||
<a class="dropdown-item" href="/ntp">NTP</a>
|
||||
<a class="dropdown-item d-none ssl-capable" href="/entsoe">ENTSO-E API</a>
|
||||
<a class="dropdown-item" href="/priceapi">Price API</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="https://github.com/gskjold/AmsToMqttBridge/wiki" target="_blank">Documentation</a>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,31 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="ec" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>ENTSO-E API</h6>
|
||||
<h6>Price API</h6>
|
||||
<div class="row">
|
||||
<div class="col-xl-4 col-lg-6 col-md-8">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Token</span>
|
||||
</div>
|
||||
<input type="text" name="et" class="form-control" value="{et}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Region</span>
|
||||
</div>
|
||||
<select name="ea" class="form-control">
|
||||
<option value="">None</option>
|
||||
<optgroup label="Norway">
|
||||
<option value="10YNO-1--------2" {eaNo1}>NO1</option>
|
||||
<option value="10YNO-2--------T" {eaNo2}>NO2</option>
|
||||
<option value="10YNO-3--------J" {eaNo3}>NO3</option>
|
||||
<option value="10YNO-4--------9" {eaNo4}>NO4</option>
|
||||
<option value="10Y1001A1001A48H" {eaNo5}>NO5</option>
|
||||
<option value="10YNO-1--------2" {no1}>NO1</option>
|
||||
<option value="10YNO-2--------T" {no2}>NO2</option>
|
||||
<option value="10YNO-3--------J" {no3}>NO3</option>
|
||||
<option value="10YNO-4--------9" {no4}>NO4</option>
|
||||
<option value="10Y1001A1001A48H" {no5}>NO5</option>
|
||||
</optgroup>
|
||||
<optgroup label="Sweden">
|
||||
<option value="10Y1001A1001A44P" {eaSe1}>SE1</option>
|
||||
<option value="10Y1001A1001A45N" {eaSe2}>SE2</option>
|
||||
<option value="10Y1001A1001A46L" {eaSe3}>SE3</option>
|
||||
<option value="10Y1001A1001A47J" {eaSe4}>SE4</option>
|
||||
<option value="10Y1001A1001A44P" {se1}>SE1</option>
|
||||
<option value="10Y1001A1001A45N" {se2}>SE2</option>
|
||||
<option value="10Y1001A1001A46L" {se3}>SE3</option>
|
||||
<option value="10Y1001A1001A47J" {se4}>SE4</option>
|
||||
</optgroup>
|
||||
<optgroup label="Denmark">
|
||||
<option value="10YDK-1--------W" {eaDk1}>DK1</option>
|
||||
<option value="10YDK-2--------M" {eaDk2}>DK2</option>
|
||||
<option value="10YDK-1--------W" {dk1}>DK1</option>
|
||||
<option value="10YDK-2--------M" {dk2}>DK2</option>
|
||||
</optgroup>
|
||||
<option value="10YAT-APG------L" {at}>Austria</option>
|
||||
<option value="10YBE----------2" {be}>Belgium</option>
|
||||
@@ -56,10 +49,10 @@
|
||||
<span class="input-group-text">Currency</span>
|
||||
</div>
|
||||
<select name="ecu" class="form-control">
|
||||
<option value="NOK" {ecNOK}>NOK</option>
|
||||
<option value="SEK" {ecSEK}>SEK</option>
|
||||
<option value="DKK" {ecDKK}>DKK</option>
|
||||
<option value="EUR" {ecEUR}>EUR</option>
|
||||
<option value="NOK" {nok}>NOK</option>
|
||||
<option value="SEK" {sek}>SEK</option>
|
||||
<option value="DKK" {dkk}>DKK</option>
|
||||
<option value="EUR" {eur}>EUR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,6 +64,14 @@
|
||||
<input name="em" type="number" min="0.001" max="1000" step="0.001" class="form-control" value="{em}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-6 col-md-8 {dt}">
|
||||
<div class="m-2 input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">ENTSO-E token</span>
|
||||
</div>
|
||||
<input type="text" name="et" class="form-control" value="{et}" placeholder="Optional"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
@@ -89,7 +89,7 @@ private:
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void configDomoticzHtml();
|
||||
void configEntsoeHtml();
|
||||
void configPriceApiHtml();
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 <ESP8266HTTPClient.h>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "DnbCurrParser.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "ams/GcmParser.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#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("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user