mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 21:15:30 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e97554514 | ||
|
|
28a9d6746b | ||
|
|
7ea4fe881c | ||
|
|
c4eaf8184b | ||
|
|
312972f77d | ||
|
|
27b9058af5 | ||
|
|
bc4d61098c | ||
|
|
b2de6472cf | ||
|
|
6c3ddc57b5 | ||
|
|
2ff7044c85 | ||
|
|
0145be851e | ||
|
|
6b0d540f39 | ||
|
|
2218ac4e8a | ||
|
|
33bd3da310 | ||
|
|
a239e1a63d | ||
|
|
8a809ec128 | ||
|
|
b48a0f13fe | ||
|
|
b07ed075f4 | ||
|
|
0927cab8e2 | ||
|
|
eed35b7bbc | ||
|
|
e34da5fd83 | ||
|
|
148fb14c93 | ||
|
|
000cfd8697 | ||
|
|
1ef5703971 | ||
|
|
cda3b80b35 | ||
|
|
c7b8090634 | ||
|
|
63a8d79b95 | ||
|
|
02ae3fc7f5 | ||
|
|
2dcc874592 | ||
|
|
d4d9d2224f | ||
|
|
902e43979b | ||
|
|
8e54f23367 | ||
|
|
ab7128c53a | ||
|
|
6563700df4 | ||
|
|
b6f630b134 | ||
|
|
65a09dcecf | ||
|
|
71d261bf34 | ||
|
|
9f4f5b4620 | ||
|
|
92692c6eaf | ||
|
|
e4114c3e74 | ||
|
|
feffbb53a3 | ||
|
|
e1b2554af2 | ||
|
|
627a50ab50 | ||
|
|
a20d007b45 | ||
|
|
64840e13f0 | ||
|
|
72f1d59338 | ||
|
|
07ed425320 | ||
|
|
2e75e1c4dc | ||
|
|
e6df68481f | ||
|
|
dc25c147b9 | ||
|
|
6a76144566 | ||
|
|
95d3008a66 | ||
|
|
04cd6ac387 | ||
|
|
584c7b1154 | ||
|
|
493c4b4337 | ||
|
|
488c969858 | ||
|
|
8b0d4185d3 | ||
|
|
608470c5f7 | ||
|
|
68b3415a6c | ||
|
|
d8f3ae8b07 | ||
|
|
1a92cd1978 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,7 +7,7 @@
|
||||
.vscode
|
||||
.pio
|
||||
platformio-user.ini
|
||||
/src/version.h
|
||||
/lib/AmsConfiguration/include/version.h
|
||||
/src/web/root
|
||||
/src/AmsToMqttBridge.ino.cpp
|
||||
/test
|
||||
@@ -15,3 +15,6 @@ platformio-user.ini
|
||||
/sdkconfig
|
||||
/.tmp
|
||||
/*.zip
|
||||
node_modules
|
||||
/gui/dist
|
||||
/scripts/*dev
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 96 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 100 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
@@ -29,7 +30,11 @@
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
}; // 1
|
||||
bool vendorConfigured;
|
||||
bool userConfigured;
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[2];
|
||||
}; // 6
|
||||
|
||||
struct WiFiConfig91 {
|
||||
char ssid[32];
|
||||
@@ -55,7 +60,9 @@ struct WiFiConfig {
|
||||
bool mdns;
|
||||
uint8_t power;
|
||||
uint8_t sleep;
|
||||
}; // 211
|
||||
uint8_t mode;
|
||||
bool autoreboot;
|
||||
}; // 213
|
||||
|
||||
struct MqttConfig86 {
|
||||
char host[128];
|
||||
@@ -165,6 +172,13 @@ struct DomoticzConfig {
|
||||
}; // 10
|
||||
|
||||
struct NtpConfig {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
char server[64];
|
||||
char timezone[32];
|
||||
}; // 98
|
||||
|
||||
struct NtpConfig96 {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
int16_t offset;
|
||||
@@ -177,6 +191,7 @@ struct EntsoeConfig {
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t multiplier;
|
||||
bool enabled;
|
||||
}; // 62
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
@@ -280,14 +295,13 @@ private:
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
bool relocateConfig86(); // 1.5.0
|
||||
bool relocateConfig87(); // 1.5.4
|
||||
bool relocateConfig90(); // 2.0.0
|
||||
bool relocateConfig91(); // 2.0.2
|
||||
bool relocateConfig92(); // 2.0.3
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.4
|
||||
bool relocateConfig95(); // 2.1.13
|
||||
bool relocateConfig94(); // 2.1.0
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
86
lib/AmsConfiguration/include/Timezones.h
Normal file
86
lib/AmsConfiguration/include/Timezones.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <Timezone.h>
|
||||
|
||||
#define JULY1970 15634800
|
||||
|
||||
TimeChangeRule TC_GMT = {"GMT", Last, Sun, Jan, 0, 0};
|
||||
TimeChangeRule TC_WET = {"WET", Last, Sun, Oct, 2, 0};
|
||||
TimeChangeRule TC_WEST = {"WEST", Last, Sun, Mar, 1, 60};
|
||||
TimeChangeRule TC_CET = {"CET", Last, Sun, Oct, 3, 60};
|
||||
TimeChangeRule TC_CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule TC_EET = {"EET", Last, Sun, Oct, 4, 120};
|
||||
TimeChangeRule TC_EEST = {"EEST", Last, Sun, Mar, 3, 180};
|
||||
|
||||
Timezone GMT = Timezone(TC_GMT);
|
||||
Timezone WesterEuropean = Timezone(TC_WET, TC_WEST);
|
||||
Timezone CentralEuropean = Timezone(TC_CET, TC_CEST);
|
||||
Timezone EasternEuropean = Timezone(TC_EET, TC_EEST);
|
||||
|
||||
Timezone* resolveTimezone(char* name) {
|
||||
if(strncmp_P(name, PSTR("Europe/"), 7) == 0) {
|
||||
if(strncmp_P(name+7, PSTR("Amsterdam"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Athens"), 6) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Belfast"), 7) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Berlin"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Bratislava"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Brussels"), 8) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Bucharest"), 9) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Budapest"), 8) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Copenhagen"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Dublin"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Helsinki"), 8) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Lisbon"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Ljubljana"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("London"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Luxembourg"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Madrid"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Malta"), 5) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Nicosia"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Oslo"), 4) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Paris"), 5) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Podgorica"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Prague"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Riga"), 4) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Rome"), 4) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Sofia"), 5) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Stockholm"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Tallinn"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Vienna"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Vilnius"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Warsaw"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Zagreb"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Zurich"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
}
|
||||
return &GMT;
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
|
||||
EEPROM.get(CONFIG_SYSTEM_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
config.boardType = 0xFF;
|
||||
config.vendorConfigured = false;
|
||||
config.userConfigured = false;
|
||||
config.dataCollectionConsent = 0;
|
||||
strcpy(config.country, "");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +54,8 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
wifiChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
wifiChanged |= config.power != existing.power;
|
||||
wifiChanged |= config.sleep != existing.sleep;
|
||||
wifiChanged |= config.mode != existing.mode;
|
||||
wifiChanged |= config.autoreboot != existing.autoreboot;
|
||||
} else {
|
||||
wifiChanged = true;
|
||||
}
|
||||
@@ -210,8 +218,8 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearMeter(MeterConfig& config) {
|
||||
config.baud = 2400;
|
||||
config.parity = 11; // 8E1
|
||||
config.baud = 0;
|
||||
config.parity = 0;
|
||||
config.invert = false;
|
||||
config.distributionSystem = 0;
|
||||
config.mainFuse = 0;
|
||||
@@ -430,9 +438,8 @@ bool AmsConfiguration::setNtpConfig(NtpConfig& config) {
|
||||
}
|
||||
}
|
||||
ntpChanged |= config.dhcp != existing.dhcp;
|
||||
ntpChanged |= config.offset != existing.offset;
|
||||
ntpChanged |= config.summerOffset != existing.summerOffset;
|
||||
ntpChanged |= strcmp(config.server, existing.server) != 0;
|
||||
ntpChanged |= strcmp(config.timezone, existing.timezone) != 0;
|
||||
} else {
|
||||
ntpChanged = true;
|
||||
}
|
||||
@@ -454,9 +461,8 @@ void AmsConfiguration::ackNtpChange() {
|
||||
void AmsConfiguration::clearNtp(NtpConfig& config) {
|
||||
config.enable = true;
|
||||
config.dhcp = true;
|
||||
config.offset = 360;
|
||||
config.summerOffset = 360;
|
||||
strcpy(config.server, "pool.ntp.org");
|
||||
strcpy(config.timezone, "Europe/Oslo");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
|
||||
@@ -480,6 +486,7 @@ bool AmsConfiguration::setEntsoeConfig(EntsoeConfig& config) {
|
||||
entsoeChanged |= strcmp(config.area, existing.area) != 0;
|
||||
entsoeChanged |= strcmp(config.currency, existing.currency) != 0;
|
||||
entsoeChanged |= config.multiplier != existing.multiplier;
|
||||
entsoeChanged |= config.enabled != existing.enabled;
|
||||
} else {
|
||||
entsoeChanged = true;
|
||||
}
|
||||
@@ -568,6 +575,14 @@ void AmsConfiguration::ackEnergyAccountingChange() {
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
sys.userConfigured = false;
|
||||
sys.dataCollectionConsent = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
MeterConfig meter;
|
||||
clearMeter(meter);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
@@ -600,7 +615,11 @@ void AmsConfiguration::clear() {
|
||||
clearEnergyAccountingConfig(eac);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, -1);
|
||||
DebugConfig debug;
|
||||
clearDebug(debug);
|
||||
EEPROM.put(CONFIG_DEBUG_START, debug);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
}
|
||||
@@ -619,22 +638,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
}
|
||||
} else {
|
||||
switch(configVersion) {
|
||||
case 86:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig86()) {
|
||||
configVersion = 87;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 87:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig87()) {
|
||||
configVersion = 88;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 90:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig90()) {
|
||||
@@ -683,6 +686,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 96:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig96()) {
|
||||
configVersion = 100;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -735,51 +746,6 @@ void AmsConfiguration::saveTempSensors() {
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig86() {
|
||||
MqttConfig86 mqtt86;
|
||||
MqttConfig mqtt;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START_86, mqtt86);
|
||||
strcpy(mqtt.host, mqtt86.host);
|
||||
mqtt.port = mqtt86.port;
|
||||
strcpy(mqtt.clientId, mqtt86.clientId);
|
||||
strcpy(mqtt.publishTopic, mqtt86.publishTopic);
|
||||
strcpy(mqtt.subscribeTopic, mqtt86.subscribeTopic);
|
||||
strcpy(mqtt.username, mqtt86.username);
|
||||
strcpy(mqtt.password, mqtt86.password);
|
||||
mqtt.payloadFormat = mqtt86.payloadFormat;
|
||||
mqtt.ssl = mqtt86.ssl;
|
||||
EEPROM.put(CONFIG_MQTT_START, mqtt);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 87);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig87() {
|
||||
MeterConfig87 meter87 = {0,0,0,0,0,0,0};
|
||||
MeterConfig meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_87, meter87);
|
||||
if(meter87.type < 5) {
|
||||
meter.baud = 2400;
|
||||
meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11;
|
||||
meter.invert = false;
|
||||
} else {
|
||||
meter.baud = 115200;
|
||||
meter.parity = 3;
|
||||
meter.invert = meter87.type == 6;
|
||||
}
|
||||
meter.distributionSystem = meter87.distributionSystem;
|
||||
meter.mainFuse = meter87.mainFuse;
|
||||
meter.productionCapacity = meter87.productionCapacity;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 88);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig90() {
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@@ -877,6 +843,75 @@ bool AmsConfiguration::relocateConfig95() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig96() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
#if defined(ESP8266)
|
||||
MeterConfig meter;
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
GpioConfig gpio;
|
||||
EEPROM.get(CONFIG_GPIO_START, gpio);
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 3: // Pow UART0 -- Now Pow-K UART0
|
||||
case 4: // Pow GPIO12 -- Now Pow-U UART0
|
||||
case 5: // Pow-K+ -- Now also Pow-K GPIO12
|
||||
case 7: // Pow-U+ -- Now also Pow-U GPIO12
|
||||
if(meter.baud == 2400 && meter.parity == 3) { // 3 == 8N1, assuming Pow-K
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 3;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 5;
|
||||
}
|
||||
} else { // Assuming Pow-U
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 4;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 7;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
sys.vendorConfigured = true;
|
||||
sys.userConfigured = true;
|
||||
sys.dataCollectionConsent = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
WiFiConfig wifi;
|
||||
EEPROM.get(CONFIG_WIFI_START, wifi);
|
||||
wifi.mode = 1; // WIFI_STA
|
||||
wifi.autoreboot = true;
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
|
||||
NtpConfig ntp;
|
||||
NtpConfig96 ntp96;
|
||||
EEPROM.get(CONFIG_NTP_START, ntp96);
|
||||
ntp.enable = ntp96.enable;
|
||||
ntp.dhcp = ntp96.dhcp;
|
||||
if(ntp96.offset == 360 && ntp96.summerOffset == 360) {
|
||||
strcpy(ntp.timezone, "Europe/Oslo");
|
||||
} else {
|
||||
strcpy(ntp.timezone, "GMT");
|
||||
}
|
||||
strcpy(ntp.server, ntp96.server);
|
||||
EEPROM.put(CONFIG_NTP_START, ntp);
|
||||
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.get(CONFIG_ENTSOE_START, entsoe);
|
||||
entsoe.enabled = strlen(entsoe.token) > 0;
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 100);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
@@ -1080,8 +1115,7 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->println("--NTP configuration--");
|
||||
debugger->printf("Enabled: %s\r\n", ntp.enable ? "Yes" : "No");
|
||||
if(ntp.enable) {
|
||||
debugger->printf("Offset: %i\r\n", ntp.offset);
|
||||
debugger->printf("Summer offset: %i\r\n", ntp.summerOffset);
|
||||
debugger->printf("Timezone: %s\r\n", ntp.timezone);
|
||||
debugger->printf("Server: %s\r\n", ntp.server);
|
||||
debugger->printf("DHCP: %s\r\n", ntp.dhcp ? "Yes" : "No");
|
||||
}
|
||||
@@ -1092,12 +1126,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);
|
||||
@@ -70,6 +70,9 @@ public:
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
|
||||
int8_t getLastError();
|
||||
void setLastError(int8_t);
|
||||
|
||||
protected:
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
unsigned long lastList2 = 0;
|
||||
@@ -84,6 +87,9 @@ protected:
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false;
|
||||
|
||||
int8_t lastError = 0x00;
|
||||
uint8_t lastErrorCount = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -214,3 +214,16 @@ bool AmsData::isThreePhase() {
|
||||
bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
int8_t AmsData::getLastError() {
|
||||
return lastErrorCount > 3 ? lastError : 0;
|
||||
}
|
||||
|
||||
void AmsData::setLastError(int8_t lastError) {
|
||||
this->lastError = lastError;
|
||||
if(lastError == 0) {
|
||||
lastErrorCount = 0;
|
||||
} else {
|
||||
lastErrorCount++;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %lld\n", (int64_t) now);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %lu\n", (int32_t) now);
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
|
||||
return false;
|
||||
@@ -30,18 +30,18 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
if(data->getMeterTimestamp() > BUILD_EPOCH) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %lld\n", (int64_t) now);
|
||||
debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %lu\n", (int32_t) now);
|
||||
}
|
||||
} else if(data->getPackageTimestamp() > BUILD_EPOCH) {
|
||||
now = data->getPackageTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Using package timestamp, which is: %lld\n", (int64_t) now);
|
||||
debugger->printf("(AmsDataStorage) Using package timestamp, which is: %lu\n", (int32_t) now);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(now < BUILD_EPOCH) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Invalid time: %lld\n", (int64_t) now);
|
||||
debugger->printf("(AmsDataStorage) Invalid time: %lu\n", (int32_t) now);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
return true;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last day update: %lld\n", (int64_t) day.lastMeterReadTime);
|
||||
debugger->printf("(AmsDataStorage) Last day update: %lu\n", (int32_t) day.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
@@ -86,7 +86,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
month.lastMeterReadTime = now;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month update: %lld\n", (int64_t) month.lastMeterReadTime);
|
||||
debugger->printf("(AmsDataStorage) Last month update: %lu\n", (int32_t) month.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
@@ -156,7 +156,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
setHourExport(last.Hour, exp);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lld)\n", last.Hour, imp, exp, (int64_t) cur);
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lu)\n", last.Hour, imp, exp, (int32_t) cur);
|
||||
}
|
||||
|
||||
day.activeImport += imp;
|
||||
@@ -199,7 +199,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lld\n", (int64_t) month.lastMeterReadTime);
|
||||
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu\n", (int32_t) month.lastMeterReadTime);
|
||||
}
|
||||
|
||||
float hrs = (now - month.lastMeterReadTime) / 3600.0;
|
||||
@@ -224,7 +224,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
setDayExport(last.Day, exp);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lld)\n", last.Day, imp, exp, (int64_t) cur);
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lu)\n", last.Day, imp, exp, (int32_t) cur);
|
||||
}
|
||||
|
||||
month.activeImport += imp;
|
||||
@@ -383,11 +383,11 @@ bool AmsDataStorage::isDayHappy() {
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lu < %lu\n", (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lu - %lu > 3600\n", (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
@@ -411,11 +411,11 @@ bool AmsDataStorage::isMonthHappy() {
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu < %lu\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-month.lastMeterReadTime > 86400) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu - %lu > 3600\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
@@ -40,17 +40,20 @@
|
||||
"h" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"d" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"m" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
}
|
||||
},
|
||||
"c" : %u
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -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>
|
||||
@@ -35,6 +35,7 @@
|
||||
<span class="input-group-text">Baud rate</span>
|
||||
</div>
|
||||
<select class="form-control sd" name="b">
|
||||
<option value="300" {b300}>300</option>
|
||||
<option value="2400" {b2400}>2400</option>
|
||||
<option value="4800" {b4800}>4800</option>
|
||||
<option value="9600" {b9600}>9600</option>
|
||||
@@ -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/>
|
||||
1
lib/ClassicUi/include/.gitignore
vendored
Normal file
1
lib/ClassicUi/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
root/*.h
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "EnergyAccounting.h"
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
@@ -89,7 +89,7 @@ private:
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void configDomoticzHtml();
|
||||
void configEntsoeHtml();
|
||||
void configPriceApiHtml();
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
@@ -25,8 +25,8 @@ except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "web"
|
||||
srcroot = "src/web/root"
|
||||
webroot = "lib/ClassicUi/html"
|
||||
srcroot = "lib/ClassicUi/include/root"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
@@ -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));
|
||||
@@ -347,6 +347,7 @@ void AmsWebServer::configMeterHtml() {
|
||||
html.replace(F("{mod}"), meterState->getMeterModel());
|
||||
html.replace(F("{mid}"), meterState->getMeterId());
|
||||
html.replace(F("{b}"), String(meterConfig->baud));
|
||||
html.replace(F("{b300}"), meterConfig->baud == 300 ? F("selected") : F(""));
|
||||
html.replace(F("{b2400}"), meterConfig->baud == 2400 ? F("selected") : F(""));
|
||||
html.replace(F("{b4800}"), meterConfig->baud == 4800 ? F("selected") : F(""));
|
||||
html.replace(F("{b9600}"), meterConfig->baud == 9600 ? F("selected") : F(""));
|
||||
@@ -543,8 +544,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 +553,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 +727,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 = "";
|
||||
@@ -732,7 +735,7 @@ void AmsWebServer::dataJson() {
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i));
|
||||
peaks += String(ea->getPeak(i).value / 100.0);
|
||||
}
|
||||
|
||||
snprintf_P(buf, BufferSize, DATA_JSON,
|
||||
@@ -776,12 +779,15 @@ void AmsWebServer::dataJson() {
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getIncomeThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getIncomeToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth(),
|
||||
(uint32_t) time(nullptr)
|
||||
);
|
||||
|
||||
@@ -1264,6 +1270,7 @@ void AmsWebServer::handleSave() {
|
||||
if(server.hasArg(F("h")) && !server.arg(F("h")).isEmpty()) {
|
||||
strcpy(wifi.hostname, server.arg(F("h")).c_str());
|
||||
}
|
||||
wifi.mdns = server.arg(F("m")) == F("true");
|
||||
wifi.power = server.arg(F("w")).toFloat() * 10;
|
||||
wifi.sleep = server.arg(F("z")).toInt();
|
||||
config->setWiFiConfig(wifi);
|
||||
@@ -2304,9 +2311,9 @@ void AmsWebServer::configFileDownload() {
|
||||
|
||||
if(ds != NULL) {
|
||||
DayDataPoints day = ds->getDayData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lld %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lu %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
|
||||
day.version,
|
||||
(int64_t) day.lastMeterReadTime,
|
||||
(int32_t) day.lastMeterReadTime,
|
||||
day.activeImport,
|
||||
ds->getHourImport(0),
|
||||
ds->getHourImport(1),
|
||||
@@ -2366,9 +2373,9 @@ void AmsWebServer::configFileDownload() {
|
||||
}
|
||||
|
||||
MonthDataPoints month = ds->getMonthData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lld %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lu %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
|
||||
month.version,
|
||||
(int64_t) month.lastMeterReadTime,
|
||||
(int32_t) month.lastMeterReadTime,
|
||||
month.activeImport,
|
||||
ds->getDayImport(1),
|
||||
ds->getDayImport(2),
|
||||
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
Normal file
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
json/*.h
|
||||
76
lib/DomoticzMqttHandler/scripts/generate_includes.py
Normal file
76
lib/DomoticzMqttHandler/scripts/generate_includes.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/DomoticzMqttHandler/json"
|
||||
srcroot = "lib/DomoticzMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "web/root/domoticz_json.h"
|
||||
#include "json/domoticz_json.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
bool ret = false;
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
@@ -12,6 +12,18 @@ struct EnergyAccountingPeak {
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
uint16_t incomeYesterday;
|
||||
uint16_t incomeThisMonth;
|
||||
uint16_t incomeLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData4 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
@@ -55,9 +67,15 @@ public:
|
||||
double getCostThisMonth();
|
||||
uint16_t getCostLastMonth();
|
||||
|
||||
double getIncomeThisHour();
|
||||
double getIncomeToday();
|
||||
double getIncomeYesterday();
|
||||
double getIncomeThisMonth();
|
||||
uint16_t getIncomeLastMonth();
|
||||
|
||||
float getMonthMax();
|
||||
uint8_t getCurrentThreshold();
|
||||
float getPeak(uint8_t);
|
||||
EnergyAccountingPeak getPeak(uint8_t);
|
||||
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
@@ -72,7 +90,7 @@ private:
|
||||
Timezone *tz = NULL;
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
double use, costHour, costDay;
|
||||
double produce;
|
||||
double produce, incomeHour, incomeDay;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
void calcDayCost();
|
||||
@@ -42,11 +42,12 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(!init) {
|
||||
currentHour = local.Hour;
|
||||
currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lu\n", (int32_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n");
|
||||
data = { 4, local.Month,
|
||||
0, 0, 0,
|
||||
data = { 5, local.Month,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
@@ -58,23 +59,27 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
debugger->printf("(EnergyAccounting) Peak hour from day %d: %d\n", data.peaks[i].day, data.peaks[i].value*10);
|
||||
}
|
||||
debugger->printf("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
|
||||
debugger->printf("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n", data.incomeYesterday / 10.0, data.incomeThisMonth, data.incomeLastMonth);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lu\n", (int32_t) now);
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour);
|
||||
|
||||
tmElements_t oneHrAgo;
|
||||
tmElements_t oneHrAgo, oneHrAgoLocal;
|
||||
breakTime(now-3600, oneHrAgo);
|
||||
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
|
||||
ret |= updateMax(val, local.Day);
|
||||
|
||||
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day);
|
||||
|
||||
currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
@@ -82,13 +87,18 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
use = 0;
|
||||
produce = 0;
|
||||
costHour = 0;
|
||||
currentHour = local.Hour;
|
||||
incomeHour = 0;
|
||||
|
||||
if(local.Day != currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", local.Day);
|
||||
data.costYesterday = costDay * 10;
|
||||
data.costThisMonth += costDay;
|
||||
costDay = 0;
|
||||
|
||||
data.incomeYesterday = incomeDay * 10;
|
||||
data.incomeThisMonth += incomeDay;
|
||||
incomeDay = 0;
|
||||
|
||||
currentDay = local.Day;
|
||||
ret = true;
|
||||
}
|
||||
@@ -97,6 +107,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", local.Month);
|
||||
data.costLastMonth = data.costThisMonth;
|
||||
data.costThisMonth = 0;
|
||||
data.incomeLastMonth = data.incomeThisMonth;
|
||||
data.incomeThisMonth = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
data.peaks[i] = { 0, 0 };
|
||||
}
|
||||
@@ -124,6 +136,13 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe);
|
||||
produce += kwhe;
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
float price = eapi->getValueForHour(0);
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", income / 100.0, eapi->getCurrency());
|
||||
incomeHour += income;
|
||||
incomeDay += income;
|
||||
}
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
@@ -141,13 +160,19 @@ void EnergyAccounting::calcDayCost() {
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(initPrice) costDay = 0;
|
||||
if(initPrice) {
|
||||
costDay = 0;
|
||||
incomeDay = 0;
|
||||
}
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
float price = eapi->getValueForHour(i - currentHour);
|
||||
float price = eapi->getValueForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
costDay += price * (wh / 1000.0);
|
||||
|
||||
wh = ds->getHourExport(utc.Hour);
|
||||
incomeDay += price * (wh / 1000.0);
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
@@ -161,9 +186,10 @@ double EnergyAccounting::getUseToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t utc;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getUseThisHour();
|
||||
@@ -226,6 +252,26 @@ uint16_t EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getIncomeThisHour() {
|
||||
return incomeHour;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getIncomeToday() {
|
||||
return incomeDay;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getIncomeYesterday() {
|
||||
return data.incomeYesterday / 10.0;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getIncomeThisMonth() {
|
||||
return data.incomeThisMonth + getIncomeToday();
|
||||
}
|
||||
|
||||
uint16_t EnergyAccounting::getIncomeLastMonth() {
|
||||
return data.incomeLastMonth;
|
||||
}
|
||||
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
if(config == NULL)
|
||||
return 0;
|
||||
@@ -261,8 +307,8 @@ float EnergyAccounting::getMonthMax() {
|
||||
return maxHour > 0 ? maxHour / count / 100.0 : 0.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPeak(uint8_t num) {
|
||||
if(num < 1 || num > 5) return 0.0;
|
||||
EnergyAccountingPeak EnergyAccounting::getPeak(uint8_t num) {
|
||||
if(num < 1 || num > 5) return EnergyAccountingPeak({0,0});
|
||||
|
||||
uint8_t count = 0;
|
||||
bool included[5] = { false, false, false, false, false };
|
||||
@@ -288,10 +334,10 @@ float EnergyAccounting::getPeak(uint8_t num) {
|
||||
if(!included[i]) continue;
|
||||
pos++;
|
||||
if(pos == num) {
|
||||
return data.peaks[i].value / 100.0;
|
||||
return data.peaks[i];
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
return EnergyAccountingPeak({0,0});
|
||||
}
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
@@ -309,14 +355,27 @@ bool EnergyAccounting::load() {
|
||||
file.readBytes(buf, file.size());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Data version %d\n", buf[0]);
|
||||
if(buf[0] == 4) {
|
||||
if(buf[0] == 5) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else if(buf[0] == 4) {
|
||||
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
|
||||
this->data = { 5, data->month,
|
||||
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
|
||||
0,0,0, // Income from production
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else if(buf[0] == 3) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
this->data = { 4, data->month,
|
||||
this->data = { 5, data->month,
|
||||
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
|
||||
0,0,0, // Income from production
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
@@ -325,8 +384,9 @@ bool EnergyAccounting::load() {
|
||||
};
|
||||
ret = true;
|
||||
} else {
|
||||
data = { 4, 0,
|
||||
0, 0, 0,
|
||||
data = { 5, 0,
|
||||
0, 0, 0, // Cost
|
||||
0,0,0, // Income from production
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
@@ -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>
|
||||
@@ -25,12 +25,16 @@ public:
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
char* getArea();
|
||||
float getValueForHour(int8_t);
|
||||
float getValueForHour(time_t, int8_t);
|
||||
|
||||
int16_t getLastError();
|
||||
|
||||
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)
|
||||
@@ -38,20 +42,28 @@ 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;
|
||||
|
||||
int16_t lastError = 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
|
||||
8
lib/EntsoePriceApi/include/PricesContainer.h
Normal file
8
lib/EntsoePriceApi/include/PricesContainer.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
int32_t points[24];
|
||||
};
|
||||
#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 "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() {
|
||||
@@ -41,6 +63,10 @@ char* EntsoeApi::getCurrency() {
|
||||
return this->config->currency;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getArea() {
|
||||
return this->config->area;
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(int8_t hour) {
|
||||
time_t cur = time(nullptr);
|
||||
return getValueForHour(cur, hour);
|
||||
@@ -60,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;
|
||||
}
|
||||
@@ -86,15 +120,24 @@ 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(!config->enabled)
|
||||
return false;
|
||||
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);
|
||||
@@ -110,7 +153,7 @@ bool EntsoeApi::loop() {
|
||||
currentDay = tm.Day;
|
||||
return false;
|
||||
} else if(now > midnightMillis && currentDay != tm.Day) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lld\n", t);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
today = tomorrow;
|
||||
@@ -120,39 +163,15 @@ 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;
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
today = fetchPrices(t);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
today = NULL;
|
||||
return false;
|
||||
}
|
||||
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
|
||||
@@ -161,49 +180,23 @@ bool EntsoeApi::loop() {
|
||||
&& midnightMillis - now < tomorrowFetchMillis
|
||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
|
||||
) {
|
||||
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;
|
||||
try {
|
||||
breakTime(t+SECS_PER_DAY, tm); // Break UTC tomorrow to find UTC midnight
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
tomorrow = NULL;
|
||||
return false;
|
||||
}
|
||||
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/" + String(VERSION));
|
||||
#if defined(ESP32)
|
||||
if(https.begin(url)) {
|
||||
if(http.begin(url)) {
|
||||
printD("Connection established");
|
||||
|
||||
#if defined(ESP32)
|
||||
@@ -212,7 +205,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();
|
||||
@@ -222,15 +215,17 @@ 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();
|
||||
lastError = 0;
|
||||
return true;
|
||||
} else {
|
||||
lastError = status;
|
||||
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 {
|
||||
@@ -282,6 +277,108 @@ 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();
|
||||
lastError = 0;
|
||||
} else {
|
||||
lastError = status;
|
||||
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]);
|
||||
}
|
||||
lastError = 0;
|
||||
return ret;
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
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);
|
||||
@@ -295,3 +392,23 @@ 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("");
|
||||
}
|
||||
|
||||
int16_t EntsoeApi::getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
1
lib/HomeAssistantMqttHandler/include/.gitignore
vendored
Normal file
1
lib/HomeAssistantMqttHandler/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
json/*.h
|
||||
76
lib/HomeAssistantMqttHandler/scripts/generate_includes.py
Normal file
76
lib/HomeAssistantMqttHandler/scripts/generate_includes.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/HomeAssistantMqttHandler/json"
|
||||
srcroot = "lib/HomeAssistantMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "version.h"
|
||||
#include "web/root/ha1_json.h"
|
||||
#include "web/root/ha2_json.h"
|
||||
#include "web/root/ha3_json.h"
|
||||
#include "web/root/ha4_json.h"
|
||||
#include "web/root/jsonsys_json.h"
|
||||
#include "web/root/jsonprices_json.h"
|
||||
#include "web/root/hadiscover_json.h"
|
||||
#include "web/root/realtime_json.h"
|
||||
#include "json/ha1_json.h"
|
||||
#include "json/ha2_json.h"
|
||||
#include "json/ha3_json.h"
|
||||
#include "json/ha4_json.h"
|
||||
#include "json/jsonsys_json.h"
|
||||
#include "json/jsonprices_json.h"
|
||||
#include "json/hadiscover_json.h"
|
||||
#include "json/realtime_json.h"
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
@@ -86,7 +86,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i), 2);
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
ea->getMonthMax(),
|
||||
@@ -371,9 +371,9 @@ bool HwTools::ledOff(uint8_t color) {
|
||||
bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
|
||||
for(int i = 0; i < blink; i++) {
|
||||
if(!ledOn(color)) return false;
|
||||
delay(50);
|
||||
delay(75);
|
||||
ledOff(color);
|
||||
delay(200);
|
||||
if(i != blink) delay(75);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
1
lib/JsonMqttHandler/include/.gitignore
vendored
Normal file
1
lib/JsonMqttHandler/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
json/*.h
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user