mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 04:57:28 +00:00
Compare commits
137 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 | ||
|
|
538de5ea99 | ||
|
|
042e2bcc85 | ||
|
|
b6f630b134 | ||
|
|
65a09dcecf | ||
|
|
775e5a0881 | ||
|
|
6c0d5fcc09 | ||
|
|
69d8fa9254 | ||
|
|
71d261bf34 | ||
|
|
9f4f5b4620 | ||
|
|
c38c305bab | ||
|
|
bfee2a1d64 | ||
|
|
ec7edae9a1 | ||
|
|
d8e265b7ac | ||
|
|
1987cddab7 | ||
|
|
e18be5f97c | ||
|
|
537597d6d1 | ||
|
|
a89013cec3 | ||
|
|
9c7a0cb7ff | ||
|
|
ade12199b9 | ||
|
|
92692c6eaf | ||
|
|
e4114c3e74 | ||
|
|
39b68aca51 | ||
|
|
feffbb53a3 | ||
|
|
8ac1e034b1 | ||
|
|
f446dff865 | ||
|
|
e1b2554af2 | ||
|
|
627a50ab50 | ||
|
|
a20d007b45 | ||
|
|
64840e13f0 | ||
|
|
34ebe9601a | ||
|
|
72f1d59338 | ||
|
|
07ed425320 | ||
|
|
2e75e1c4dc | ||
|
|
e6df68481f | ||
|
|
fa299198fc | ||
|
|
6d81b0a856 | ||
|
|
dd095da97b | ||
|
|
1b6ce203b7 | ||
|
|
2850be4e48 | ||
|
|
7eca31de84 | ||
|
|
a64f960cc7 | ||
|
|
ce3a47a7e6 | ||
|
|
8395e1dc77 | ||
|
|
39a4761415 | ||
|
|
dc25c147b9 | ||
|
|
867ab9d6c2 | ||
|
|
6a76144566 | ||
|
|
5e03e3d3c2 | ||
|
|
adb5050621 | ||
|
|
026cd25c8c | ||
|
|
06ec97b42a | ||
|
|
b420a0e6f4 | ||
|
|
7cd52d5689 | ||
|
|
ad78ff3082 | ||
|
|
fe7be81f1e | ||
|
|
7674fc2ad0 | ||
|
|
d50181c347 | ||
|
|
8ca771fa5a | ||
|
|
7d557b2679 | ||
|
|
2f0c912388 | ||
|
|
57e6d0fbe3 | ||
|
|
e7c25fafda | ||
|
|
2a4772fe25 | ||
|
|
f1f7408208 | ||
|
|
feb8e5007b | ||
|
|
c4af1ee74f | ||
|
|
992e1b6121 | ||
|
|
95d3008a66 | ||
|
|
04cd6ac387 | ||
|
|
584c7b1154 | ||
|
|
493c4b4337 | ||
|
|
488c969858 | ||
|
|
8b0d4185d3 | ||
|
|
9cc7529934 | ||
|
|
ef8715be6d | ||
|
|
e232b875fa | ||
|
|
f18171fecc | ||
|
|
a3c7a09211 | ||
|
|
a6f3bc3f71 | ||
|
|
44bcd386d1 | ||
|
|
01547f9a52 | ||
|
|
608470c5f7 | ||
|
|
940d38af5c | ||
|
|
4e451c51e1 | ||
|
|
43def1c311 | ||
|
|
2978116207 | ||
|
|
a055465ce0 | ||
|
|
68b3415a6c | ||
|
|
d8f3ae8b07 | ||
|
|
998b986604 | ||
|
|
7799431405 | ||
|
|
1a92cd1978 | ||
|
|
508c14a671 | ||
|
|
4a7ef87269 | ||
|
|
3a4fc707b0 | ||
|
|
5d2e320b07 | ||
|
|
d12613b91a | ||
|
|
e6a02f34ab | ||
|
|
0b2ffbfd77 | ||
|
|
cab6c54ed9 | ||
|
|
313024f273 | ||
|
|
91fc078c5e | ||
|
|
5b9d44a3e9 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
**Relevant firmware information:**
|
||||
- Version: [e.g. 2.1.0]
|
||||
- MQTT: [yes/no]
|
||||
- MQTT payload type: [e.g. JSON]
|
||||
- HAN GPIO: [e.g. GPIO5]
|
||||
- HAN baud and parity: [e.g. 2400 8E1]
|
||||
- Temperature sensors [e.g. 3xDS18B20]
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/support.md
vendored
1
.github/ISSUE_TEMPLATE/support.md
vendored
@@ -20,6 +20,7 @@ A clear and concise description of what the problem is.
|
||||
**Relevant firmware information:**
|
||||
- Version: [e.g. 2.1.0]
|
||||
- MQTT: [yes/no]
|
||||
- MQTT payload type: [e.g. JSON]
|
||||
- HAN GPIO: [e.g. GPIO5]
|
||||
- HAN baud and parity: [e.g. 2400 8E1]
|
||||
- Temperature sensors [e.g. 3xDS18B20]
|
||||
|
||||
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
|
||||
|
||||
@@ -43,3 +43,37 @@ FF // Last byte of OBIS in previous block
|
||||
0600000000 // Accumulated export
|
||||
8BA4
|
||||
7E
|
||||
|
||||
|
||||
|
||||
|
||||
7E A1 23 CE FF 03 13 21 55 E6 E7 00
|
||||
|
||||
0F 00 00 08 E2
|
||||
0C 07 E5 07 13 01 0C 1A 0A FF 80 00 00
|
||||
|
||||
02 0B // 11
|
||||
01 0B // 11
|
||||
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 01 12 00 00
|
||||
02 04 12 00 01 09 06 00 00 60 01 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 00 01 07 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 00 02 07 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 01 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 02 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 05 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 06 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 07 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 08 08 00 FF 0F 02 12 00 00
|
||||
09 06 00 08 19 09 00 FF
|
||||
09 08 34 33 30 39 34 33 35 31
|
||||
06 00 00 00 0B
|
||||
06 00 00 00 00
|
||||
06 00 00 00 10
|
||||
06 00 00 00 04
|
||||
06 00 00 00 00
|
||||
06 00 00 00 08
|
||||
06 00 00 00 00
|
||||
06 00 00 00 01
|
||||
7C 8B
|
||||
7E
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 95 // 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];
|
||||
@@ -54,7 +59,10 @@ struct WiFiConfig {
|
||||
char hostname[32];
|
||||
bool mdns;
|
||||
uint8_t power;
|
||||
}; // 210
|
||||
uint8_t sleep;
|
||||
uint8_t mode;
|
||||
bool autoreboot;
|
||||
}; // 213
|
||||
|
||||
struct MqttConfig86 {
|
||||
char host[128];
|
||||
@@ -87,6 +95,23 @@ struct WebConfig {
|
||||
}; // 129
|
||||
|
||||
struct MeterConfig {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint32_t wattageMultiplier;
|
||||
uint32_t voltageMultiplier;
|
||||
uint32_t amperageMultiplier;
|
||||
uint32_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 50
|
||||
|
||||
struct MeterConfig95 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
@@ -147,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;
|
||||
@@ -159,6 +191,7 @@ struct EntsoeConfig {
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t multiplier;
|
||||
bool enabled;
|
||||
}; // 62
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
@@ -262,13 +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 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;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +30,7 @@ bool AmsConfiguration::getWiFiConfig(WiFiConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_WIFI_START, config);
|
||||
EEPROM.end();
|
||||
if(config.sleep > 2) config.sleep = 1;
|
||||
return true;
|
||||
} else {
|
||||
clearWifi(config);
|
||||
@@ -33,6 +40,7 @@ bool AmsConfiguration::getWiFiConfig(WiFiConfig& config) {
|
||||
|
||||
bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
WiFiConfig existing;
|
||||
if(config.sleep > 2) config.sleep = 1;
|
||||
if(getWiFiConfig(existing)) {
|
||||
wifiChanged |= strcmp(config.ssid, existing.ssid) != 0;
|
||||
wifiChanged |= strcmp(config.psk, existing.psk) != 0;
|
||||
@@ -45,6 +53,9 @@ 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;
|
||||
}
|
||||
@@ -63,11 +74,14 @@ void AmsConfiguration::clearWifi(WiFiConfig& config) {
|
||||
uint16_t chipId;
|
||||
#if defined(ESP32)
|
||||
chipId = ESP.getEfuseMac();
|
||||
config.power = 195;
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
config.power = 205;
|
||||
#endif
|
||||
strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str());
|
||||
config.mdns = true;
|
||||
config.sleep = 0xFF;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearWifiIp(WiFiConfig& config) {
|
||||
@@ -204,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;
|
||||
@@ -424,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;
|
||||
}
|
||||
@@ -448,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) {
|
||||
@@ -474,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;
|
||||
}
|
||||
@@ -508,6 +521,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
if(config.thresholds[9] != 255) {
|
||||
clearEnergyAccountingConfig(config);
|
||||
}
|
||||
if(config.hours > 5) config.hours = 5;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -515,6 +529,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
if(config.hours > 5) config.hours = 5;
|
||||
EnergyAccountingConfig existing;
|
||||
if(getEnergyAccountingConfig(existing)) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
@@ -523,6 +538,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
}
|
||||
}
|
||||
config.thresholds[9] = 255;
|
||||
energyAccountingChanged |= config.hours != existing.hours;
|
||||
} else {
|
||||
energyAccountingChanged = true;
|
||||
}
|
||||
@@ -545,6 +561,7 @@ void AmsConfiguration::clearEnergyAccountingConfig(EnergyAccountingConfig& confi
|
||||
config.thresholds[7] = 100;
|
||||
config.thresholds[8] = 150;
|
||||
config.thresholds[9] = 255;
|
||||
config.hours = 3;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isEnergyAccountingChanged() {
|
||||
@@ -558,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);
|
||||
@@ -590,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();
|
||||
}
|
||||
@@ -609,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()) {
|
||||
@@ -665,6 +678,22 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 95:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig95()) {
|
||||
configVersion = 96;
|
||||
} else {
|
||||
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:
|
||||
@@ -717,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;
|
||||
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);
|
||||
@@ -842,6 +826,92 @@ bool AmsConfiguration::relocateConfig94() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig95() {
|
||||
MeterConfig meter;
|
||||
MeterConfig95 meter95;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
EEPROM.get(CONFIG_METER_START, meter95);
|
||||
meter.wattageMultiplier = meter95.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter95.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter95.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter95.accumulatedMultiplier;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 96);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
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);
|
||||
@@ -1045,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");
|
||||
}
|
||||
@@ -1057,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);
|
||||
@@ -12,6 +12,7 @@ enum AmsType {
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandis = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeLng = 0x0B,
|
||||
AmsTypeCustom = 0x88,
|
||||
AmsTypeUnknown = 0xFF
|
||||
};
|
||||
@@ -53,6 +54,14 @@ public:
|
||||
float getL2PowerFactor();
|
||||
float getL3PowerFactor();
|
||||
|
||||
float getL1ActiveImportPower();
|
||||
float getL2ActiveImportPower();
|
||||
float getL3ActiveImportPower();
|
||||
|
||||
float getL1ActiveExportPower();
|
||||
float getL2ActiveExportPower();
|
||||
float getL3ActiveExportPower();
|
||||
|
||||
double getActiveImportCounter();
|
||||
double getReactiveImportCounter();
|
||||
double getActiveExportCounter();
|
||||
@@ -61,6 +70,9 @@ public:
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
|
||||
int8_t getLastError();
|
||||
void setLastError(int8_t);
|
||||
|
||||
protected:
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
unsigned long lastList2 = 0;
|
||||
@@ -70,9 +82,14 @@ protected:
|
||||
time_t meterTimestamp = 0;
|
||||
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
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
|
||||
@@ -45,6 +45,12 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l1PowerFactor = other.getL1PowerFactor();
|
||||
this->l2PowerFactor = other.getL2PowerFactor();
|
||||
this->l3PowerFactor = other.getL3PowerFactor();
|
||||
this->l1activeImportPower = other.getL1ActiveImportPower();
|
||||
this->l2activeImportPower = other.getL2ActiveImportPower();
|
||||
this->l3activeImportPower = other.getL3ActiveImportPower();
|
||||
this->l1activeExportPower = other.getL1ActiveExportPower();
|
||||
this->l2activeExportPower = other.getL2ActiveExportPower();
|
||||
this->l3activeExportPower = other.getL3ActiveExportPower();
|
||||
case 3:
|
||||
this->meterTimestamp = other.getMeterTimestamp();
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
@@ -161,6 +167,30 @@ float AmsData::getL3PowerFactor() {
|
||||
return this->l3PowerFactor;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveImportPower() {
|
||||
return this->l1activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveImportPower() {
|
||||
return this->l2activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveImportPower() {
|
||||
return this->l3activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveExportPower() {
|
||||
return this->l1activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveExportPower() {
|
||||
return this->l2activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveExportPower() {
|
||||
return this->l3activeExportPower;
|
||||
}
|
||||
|
||||
double AmsData::getActiveImportCounter() {
|
||||
return this->activeImportCounter;
|
||||
}
|
||||
@@ -184,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++;
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,20 @@
|
||||
|
||||
struct DayDataPoints {
|
||||
uint8_t version;
|
||||
int16_t hImport[24];
|
||||
uint16_t hImport[24];
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
int16_t hExport[24];
|
||||
uint16_t hExport[24];
|
||||
}; // 112 bytes
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
int16_t dImport[31];
|
||||
uint16_t dImport[31];
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
int16_t dExport[31];
|
||||
uint16_t dExport[31];
|
||||
}; // 141 bytes
|
||||
|
||||
class AmsDataStorage {
|
||||
@@ -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);
|
||||
@@ -4,7 +4,6 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
uint16_t crcPos = 0;
|
||||
bool reachedEnd = verified;
|
||||
uint8_t lastByte = 0x00;
|
||||
int c = 0;
|
||||
for(int pos = 0; pos < ctx.length; pos++) {
|
||||
uint8_t b = *(buf+pos);
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
@@ -27,7 +27,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
|
||||
int len;
|
||||
int len = 0;
|
||||
int headersize = 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
if(((*ptr) & 0xFF) == 0x81) {
|
||||
@@ -5,8 +5,6 @@
|
||||
int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int len;
|
||||
|
||||
uint8_t flag = *d;
|
||||
|
||||
uint8_t* ptr;
|
||||
if(ctx.length < 3)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
LLCHeader* llc = (LLCHeader*) buf;
|
||||
ctx.length -= 3;
|
||||
return 3;
|
||||
}
|
||||
@@ -5,8 +5,6 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int headersize = 3;
|
||||
int footersize = 1;
|
||||
|
||||
uint8_t flag = *d;
|
||||
|
||||
uint8_t* ptr;
|
||||
|
||||
// https://m-bus.com/documentation-wired/06-application-layer
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
@@ -15,11 +15,12 @@ public:
|
||||
this->mqtt = mqtt;
|
||||
this->json = buf;
|
||||
};
|
||||
virtual ~AmsMqttHandler() {};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*);
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
|
||||
|
||||
protected:
|
||||
MQTTClient* mqtt;
|
||||
@@ -316,6 +316,7 @@ $(function() {
|
||||
url: swv.data('url'),
|
||||
dataType: 'json'
|
||||
}).done(function(releases) {
|
||||
var isnew = false;
|
||||
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
|
||||
releases.reverse();
|
||||
var next_patch;
|
||||
@@ -352,10 +353,13 @@ $(function() {
|
||||
});
|
||||
if(next_minor) {
|
||||
nextVersion = next_minor;
|
||||
isnew = true;
|
||||
} else if(next_major) {
|
||||
nextVersion = next_major;
|
||||
isnew = true;
|
||||
} else if(next_patch) {
|
||||
nextVersion = next_patch;
|
||||
isnew = true;
|
||||
}
|
||||
} else {
|
||||
nextVersion = releases[0];
|
||||
@@ -375,9 +379,11 @@ $(function() {
|
||||
}
|
||||
});
|
||||
};
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
if(isnew) {
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -478,7 +484,7 @@ var drawDay = function() {
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]];
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
@@ -486,13 +492,13 @@ var drawDay = function() {
|
||||
for(var i = hour; i<24; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
for(var i = 0; i < hour; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
ea = google.visualization.arrayToDataTable(data);
|
||||
@@ -511,7 +517,7 @@ var drawMonth = function() {
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]];
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var day = moment().date();
|
||||
var eom = moment().subtract(1, 'months').endOf('month').date();
|
||||
@@ -519,13 +525,13 @@ var drawMonth = function() {
|
||||
for(var i = day; i<=eom; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
for(var i = 1; i < day; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
ma = google.visualization.arrayToDataTable(data);
|
||||
@@ -651,7 +657,7 @@ var fetch = function() {
|
||||
|
||||
if(ip) {
|
||||
var v = parseInt(json.i);
|
||||
var pct = (v*100)/parseInt(json.im);
|
||||
var pct = Math.min((v*100)/parseInt(json.im), 100);
|
||||
var append = "W";
|
||||
if(v > 1000 && !swatt) {
|
||||
v = (v/1000).toFixed(1);
|
||||
@@ -677,7 +683,7 @@ var fetch = function() {
|
||||
$('.rim').hide();
|
||||
if(xp) {
|
||||
var v = parseInt(json.e);
|
||||
var pct = (v*100)/(om*1000);
|
||||
var pct = Math.min((v*100)/(om*1000), 100);
|
||||
var append = "W";
|
||||
if(v > 1000 && !swatt) {
|
||||
v = (v/1000).toFixed(1);
|
||||
@@ -717,21 +723,21 @@ var fetch = function() {
|
||||
var u1 = parseFloat(json.u1);
|
||||
t += u1;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u1)-195.5, 1)*100/69);
|
||||
var pct = Math.min(Math.max(parseFloat(json.u1)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L1-L2' : 'L1', u1, "color: " + voltcol(pct) + ";opacity: 0.9;", u1 + "V"];
|
||||
}
|
||||
if(json.u2) {
|
||||
var u2 = parseFloat(json.u2);
|
||||
t += u2;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u2)-195.5, 1)*100/69);
|
||||
var pct = Math.min(Math.max(parseFloat(json.u2)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L1-L3' : 'L2', u2, "color: " + voltcol(pct) + ";opacity: 0.9;", u2 + "V"];
|
||||
}
|
||||
if(json.u3) {
|
||||
var u3 = parseFloat(json.u3);
|
||||
t += u3;
|
||||
c++;
|
||||
var pct = (Math.max(parseFloat(json.u3)-195.5, 1)*100/69);
|
||||
var pct = Math.min(Math.max(parseFloat(json.u3)-195.5, 1)*100/69, 100);
|
||||
arr[r++] = [ds == 1 ? 'L2-L3' : 'L3', u3, "color: " + voltcol(pct) + ";opacity: 0.9;", u3 + "V"];
|
||||
}
|
||||
v = t/c;
|
||||
@@ -756,19 +762,19 @@ var fetch = function() {
|
||||
if(json.i1 || json.u1) {
|
||||
var i1 = parseFloat(json.i1);
|
||||
dA = true;
|
||||
var pct = (parseFloat(json.i1)/parseInt(json.mf))*100;
|
||||
var pct = Math.min((parseFloat(json.i1)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"];
|
||||
}
|
||||
if(json.i2 || json.u2) {
|
||||
var i2 = parseFloat(json.i2);
|
||||
dA = true;
|
||||
var pct = (parseFloat(json.i2)/parseInt(json.mf))*100;
|
||||
var pct = Math.min((parseFloat(json.i2)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"];
|
||||
}
|
||||
if(json.i3 || json.u3) {
|
||||
var i3 = parseFloat(json.i3);
|
||||
dA = true;
|
||||
var pct = (parseFloat(json.i3)/parseInt(json.mf))*100;
|
||||
var pct = Math.min((parseFloat(json.i3)/parseInt(json.mf))*100, 100);
|
||||
arr[r++] = ['L3', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i3 + "A"];
|
||||
}
|
||||
if(dA) {
|
||||
@@ -790,6 +796,12 @@ var fetch = function() {
|
||||
if(currency) {
|
||||
$('.sp').show();
|
||||
}
|
||||
if(om > 0) {
|
||||
$('.se').removeClass('d-none');
|
||||
$('#eache').html(json.ea.h.p.toFixed(2));
|
||||
$('#eacde').html(json.ea.d.p.toFixed(1));
|
||||
$('#eacme').html(json.ea.m.p.toFixed(0));
|
||||
}
|
||||
}
|
||||
|
||||
if(json.me) {
|
||||
@@ -878,7 +890,7 @@ var fetch = function() {
|
||||
|
||||
var upgrade = function() {
|
||||
if(nextVersion) {
|
||||
if(confirm("Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
if(confirm("WARNING: If you have a M-BUS powered device (Pow-U), please keep USB power connected while upgrading.\n\nAre you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
$('#loading-indicator').show();
|
||||
window.location.href="/upgrade?version=" + nextVersion.tag_name;
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
"v" : %.3f,
|
||||
"r" : %d,
|
||||
"t" : %.2f,
|
||||
"u" : %lu,
|
||||
"m" : %lu,
|
||||
"u" : %u,
|
||||
"m" : %u,
|
||||
"em" : %d,
|
||||
"hm" : %d,
|
||||
"wm" : %d,
|
||||
@@ -35,19 +35,26 @@
|
||||
"ds" : %d,
|
||||
"ea" : {
|
||||
"x" : %.1f,
|
||||
"p" : [ %s ],
|
||||
"t" : %d,
|
||||
"h" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"d" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"m" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
}
|
||||
},
|
||||
"c" : %lu
|
||||
"c" : %u
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
<div class="alert alert-danger">
|
||||
WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
WARNING: Units powered by M-BUS (Pow-U) must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
|
||||
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
When using URL, only a valid ESP OTA server response will be accepted.
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload</span>
|
||||
<span class="input-group-text">Upload file</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
@@ -21,6 +24,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">or</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Use URL</span>
|
||||
</div>
|
||||
<input type="text" name="url" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
@@ -28,7 +44,7 @@
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
<button class="btn btn-primary">Upgrade firmware</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
@@ -1,6 +1,5 @@
|
||||
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>!
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a>
|
||||
<span class="d-none ssl-capable"> or <a href="javascript:upgrade();">upgrade</a></span>
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a> or <a href="javascript:upgrade();">click here to upgrade</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="gpioConfig" value="true"/>
|
||||
<input type="hidden" name="gc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>GPIO settings</h6>
|
||||
<div class="d-flex flex-row flex-wrap">
|
||||
@@ -8,18 +8,18 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">HAN</span>
|
||||
</div>
|
||||
<select name="hanPin" class="form-control">
|
||||
${options.han}
|
||||
<select name="h" class="form-control">
|
||||
${h}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">LED</span>
|
||||
</div>
|
||||
<input name="ledPin" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPin}"/>
|
||||
<input name="l" type="number" min="2" max="${g}" class="form-control" value="${l}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="ledInverted" value="true" ${config.ledInverted}/> inv
|
||||
<input type="checkbox" name="i" value="true" ${i}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -27,12 +27,12 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">RGB</span>
|
||||
</div>
|
||||
<input name="ledPinRed" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinRed}"/>
|
||||
<input name="ledPinGreen" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinGreen}"/>
|
||||
<input name="ledPinBlue" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinBlue}"/>
|
||||
<input name="r" type="number" min="2" max="${g}" class="form-control" value="${r}"/>
|
||||
<input name="e" type="number" min="2" max="${g}" class="form-control" value="${e}"/>
|
||||
<input name="b" type="number" min="2" max="${g}" class="form-control" value="${b}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="ledRgbInverted" value="true" ${config.ledRgbInverted}/> inv
|
||||
<input type="checkbox" name="n" value="true" ${n}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,31 +40,31 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">AP button</span>
|
||||
</div>
|
||||
<input name="apPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.apPin}"/>
|
||||
<input name="a" type="number" min="0" max="${g}" class="form-control" value="${a}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Temperature</span>
|
||||
</div>
|
||||
<input name="tempSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempSensorPin}"/>
|
||||
<input name="t" type="number" min="0" max="${g}" class="form-control" value="${t}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Analog temp</span>
|
||||
</div>
|
||||
<input name="tempAnalogSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempAnalogSensorPin}"/>
|
||||
<input name="m" type="number" min="0" max="${g}" class="form-control" value="${m}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 100px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc</span>
|
||||
</div>
|
||||
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
|
||||
<input name="v" type="number" min="0" max="${g}" class="form-control" value="${v}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">GND resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorGnd" value="${config.vccResistorGnd}" />
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="d" value="${d}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorVcc" value="${config.vccResistorVcc}" />
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="s" value="${s}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
@@ -82,19 +82,19 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Multiplier</span>
|
||||
</div>
|
||||
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="vccMultiplier" value="${config.vccMultiplier}" />
|
||||
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="u" value="${u}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 120px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Offset</span>
|
||||
</div>
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="o" value="${o}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 130px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Boot limit</span>
|
||||
</div>
|
||||
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="vccBootLimit" value="${config.vccBootLimit}" />
|
||||
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="c" value="${c}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,3 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -63,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>
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow pt-3 pb-3" style="font-size: 14px;">
|
||||
<strong class="mr-3 ml-3">Real time calculation</strong><br/>
|
||||
<strong class="mr-3 ml-3">Real time consumption</strong><br/>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
@@ -151,6 +151,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<strong class="mr-3 ml-3 se d-none">Real time production</strong><br/>
|
||||
<div class="row se d-none">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eache"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacde"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="mr-3 ml-3 d-flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-fill text-right">
|
||||
<span id="eacme"></span> kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
86
lib/ClassicUi/html/priceapi.html
Normal file
86
lib/ClassicUi/html/priceapi.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="ec" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>Price API</h6>
|
||||
<div class="row">
|
||||
<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" {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" {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" {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>
|
||||
<option value="10YCZ-CEPS-----N" {cz}>Czech Republic</option>
|
||||
<option value="10Y1001A1001A39I" {ee}>Estonia</option>
|
||||
<option value="10YFI-1--------U" {fi}>Finland</option>
|
||||
<option value="10YFR-RTE------C" {fr}>France</option>
|
||||
<option value="10Y1001A1001A83F" {de}>Germany</option>
|
||||
<option value="10YGB----------A" {gb}>Great Britain</option>
|
||||
<option value="10YLV-1001A00074" {lv}>Latvia</option>
|
||||
<option value="10YLT-1001A0008Q" {lt}>Lithuania</option>
|
||||
<option value="10YNL----------L" {nl}>Netherland</option>
|
||||
<option value="10YPL-AREA-----S" {pl}>Poland</option>
|
||||
<option value="10YCH-SWISSGRIDZ" {ch}>Switzerland</option>
|
||||
</select>
|
||||
</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">Currency</span>
|
||||
</div>
|
||||
<select name="ecu" class="form-control">
|
||||
<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>
|
||||
<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">Multiplier</span>
|
||||
</div>
|
||||
<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/>
|
||||
<div class="row form-group">
|
||||
<div class="col-6">
|
||||
<a href="/" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -18,9 +18,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="my-3 p-3 bg-white rounded shadow {rs}">
|
||||
Device is rebooting. You will be redirected to the main page when it is available again.
|
||||
</div>
|
||||
<div class="alert alert-warning shadow {us}">Firmware upgrade in progress, DO NOT disconnect power from the device. You will be redirected to the main page when firmware upgrade is complete.</div>
|
||||
</main>
|
||||
<script>
|
||||
var tries = 0;
|
||||
@@ -26,6 +26,7 @@
|
||||
<select name="board" class="form-control" required>
|
||||
<option value=""></option>
|
||||
<optgroup label="Custom hardware">
|
||||
<option value="7" ${config.boardType7}>Pow-U+ (ESP32) from amsleser.no</option>
|
||||
<option value="6" ${config.boardType6}>Pow-P1 from amsleser.no</option>
|
||||
<option value="5" ${config.boardType5}>Pow-K+ (ESP32) from amsleser.no</option>
|
||||
<option value="4" ${config.boardType4}>Pow-U or Pow-K from amsleser.no (GPIO12)</option>
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" id="i">
|
||||
<div class="col-xl-3 col-lg-4 form-group">
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">IP</span>
|
||||
@@ -45,7 +45,7 @@
|
||||
<input type="text" name="i" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{i}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 form-group">
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Netmask</span>
|
||||
@@ -53,7 +53,7 @@
|
||||
<input type="text" name="sn" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{sn}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 form-group">
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Gateway</span>
|
||||
@@ -61,7 +61,7 @@
|
||||
<input type="text" name="g" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{g}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-5 form-group">
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 1</span>
|
||||
@@ -69,7 +69,7 @@
|
||||
<input type="text" name="d1" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d1}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4 col-lg-5 form-group">
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 2</span>
|
||||
@@ -89,7 +89,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 form-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Power saving</span>
|
||||
</div>
|
||||
<select name="z" class="form-control">
|
||||
<option value="255">Default</option>
|
||||
<option value="0" {z0}>Off</option>
|
||||
<option value="1" {z1}>Minimum</option>
|
||||
<option value="2" {z2}>Maximum</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
1
lib/ClassicUi/include/.gitignore
vendored
Normal file
1
lib/ClassicUi/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
root/*.h
|
||||
@@ -2,6 +2,7 @@ static const char HEADER_CACHE_CONTROL[] PROGMEM = "Cache-Control";
|
||||
static const char HEADER_PRAGMA[] PROGMEM = "Pragma";
|
||||
static const char HEADER_EXPIRES[] PROGMEM = "Expires";
|
||||
static const char HEADER_AUTHENTICATE[] PROGMEM = "WWW-Authenticate";
|
||||
static const char HEADER_LOCATION[] PROGMEM = "Location";
|
||||
|
||||
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
|
||||
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
|
||||
@@ -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>
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
@@ -58,6 +59,13 @@ private:
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
bool performUpgrade = false;
|
||||
bool rebootForUpgrade = false;
|
||||
#if defined(AMS2MQTT_FIRMWARE_URL)
|
||||
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
|
||||
#else
|
||||
String customFirmwareUrl;
|
||||
#endif
|
||||
|
||||
static const uint16_t BufferSize = 2048;
|
||||
char* buf;
|
||||
@@ -81,7 +89,7 @@ private:
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void configDomoticzHtml();
|
||||
void configEntsoeHtml();
|
||||
void configPriceApiHtml();
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
@@ -101,6 +109,7 @@ private:
|
||||
|
||||
String getSerialSelectOptions(int selected);
|
||||
void firmwareHtml();
|
||||
void firmwarePost();
|
||||
void firmwareUpload();
|
||||
void firmwareDownload();
|
||||
void restartHtml();
|
||||
@@ -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:
|
||||
File diff suppressed because it is too large
Load Diff
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
Normal file
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
json/*.h
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
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;
|
||||
@@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
return 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;
|
||||
@@ -42,16 +54,28 @@ public:
|
||||
bool save();
|
||||
|
||||
double getUseThisHour();
|
||||
double getCostThisHour();
|
||||
double getUseToday();
|
||||
double getUseThisMonth();
|
||||
|
||||
double getProducedThisHour();
|
||||
double getProducedToday();
|
||||
double getProducedThisMonth();
|
||||
|
||||
double getCostThisHour();
|
||||
double getCostToday();
|
||||
double getCostYesterday();
|
||||
double getUseThisMonth();
|
||||
double getCostThisMonth();
|
||||
uint16_t getCostLastMonth();
|
||||
|
||||
double getIncomeThisHour();
|
||||
double getIncomeToday();
|
||||
double getIncomeYesterday();
|
||||
double getIncomeThisMonth();
|
||||
uint16_t getIncomeLastMonth();
|
||||
|
||||
float getMonthMax();
|
||||
uint8_t getCurrentThreshold();
|
||||
EnergyAccountingPeak getPeak(uint8_t);
|
||||
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
@@ -66,6 +90,7 @@ private:
|
||||
Timezone *tz = NULL;
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
double use, costHour, costDay;
|
||||
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
|
||||
@@ -57,37 +58,47 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
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: %d, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -96,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 };
|
||||
}
|
||||
@@ -106,19 +119,31 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
|
||||
float kwh = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
lastUpdateMillis = amsData->getLastUpdateMillis();
|
||||
if(kwh > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh\n", kwh);
|
||||
use += kwh;
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh import\n", kwhi);
|
||||
use += kwhi;
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
float price = eapi->getValueForHour(0);
|
||||
float cost = price * kwh;
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
|
||||
costHour += cost;
|
||||
costDay += cost;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
|
||||
@@ -135,13 +160,19 @@ void EnergyAccounting::calcDayCost() {
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(initPrice) costDay = 0;
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
if(initPrice) {
|
||||
costDay = 0;
|
||||
incomeDay = 0;
|
||||
}
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
float price = eapi->getValueForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
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;
|
||||
}
|
||||
@@ -151,23 +182,60 @@ double EnergyAccounting::getUseThisHour() {
|
||||
return use;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t local, utc;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getUseThisHour();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
float ret = 0;
|
||||
for(int i = 0; i < currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedThisHour() {
|
||||
return produce;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t utc;
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getProducedThisHour();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
float ret = 0;
|
||||
for(int i = 0; i < currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
}
|
||||
|
||||
|
||||
double EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostToday() {
|
||||
return costDay;
|
||||
}
|
||||
@@ -176,21 +244,6 @@ double EnergyAccounting::getCostYesterday() {
|
||||
return data.costYesterday / 10.0;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t tm;
|
||||
if(tz != NULL)
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
else
|
||||
breakTime(now, tm);
|
||||
float ret = 0;
|
||||
for(int i = 0; i < tm.Day; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostThisMonth() {
|
||||
return data.costThisMonth + getCostToday();
|
||||
}
|
||||
@@ -199,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;
|
||||
@@ -210,7 +283,37 @@ float EnergyAccounting::getMonthMax() {
|
||||
uint32_t maxHour = 0.0;
|
||||
bool included[5] = { false, false, false, false, false };
|
||||
|
||||
while(count < config->hours) {
|
||||
for(uint8_t x = 0;x < min((uint8_t) 5, config->hours); x++) {
|
||||
uint8_t maxIdx = 0;
|
||||
uint16_t maxVal = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(included[i]) continue;
|
||||
if(data.peaks[i].day == 0) continue;
|
||||
if(data.peaks[i].value > maxVal) {
|
||||
maxVal = data.peaks[i].value;
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
if(maxVal > 0) {
|
||||
included[maxIdx] = true;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(!included[i]) continue;
|
||||
maxHour += data.peaks[i].value;
|
||||
}
|
||||
return maxHour > 0 ? maxHour / count / 100.0 : 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 };
|
||||
|
||||
for(uint8_t x = 0;x < min((uint8_t) 5, config->hours); x++) {
|
||||
uint8_t maxIdx = 0;
|
||||
uint16_t maxVal = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
@@ -220,17 +323,21 @@ float EnergyAccounting::getMonthMax() {
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
included[maxIdx] = true;
|
||||
count++;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(!included[i]) continue;
|
||||
if(data.peaks[i].day > 0) {
|
||||
maxHour += data.peaks[i].value;
|
||||
if(maxVal > 0) {
|
||||
included[maxIdx] = true;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return maxHour > 0 ? maxHour / count / 100.0 : 0.0;
|
||||
|
||||
uint8_t pos = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(!included[i]) continue;
|
||||
pos++;
|
||||
if(pos == num) {
|
||||
return data.peaks[i];
|
||||
}
|
||||
}
|
||||
return EnergyAccountingPeak({0,0});
|
||||
}
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
@@ -248,13 +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,
|
||||
@@ -263,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
|
||||
@@ -282,7 +404,7 @@ bool EnergyAccounting::load() {
|
||||
this->data.peaks[b].day = b;
|
||||
memcpy(&this->data.peaks[b].value, buf+i, 2);
|
||||
b++;
|
||||
if(b >= config->hours) break;
|
||||
if(b >= config->hours || b >= 5) break;
|
||||
}
|
||||
ret = true;
|
||||
} else if(buf[0] == 1) {
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _ENTSOEA44PARSER_H
|
||||
|
||||
#include "Stream.h"
|
||||
#include "PricesContainer.h"
|
||||
|
||||
#define DOCPOS_SEEK 0
|
||||
#define DOCPOS_CURRENCY 1
|
||||
@@ -14,6 +15,7 @@
|
||||
class EntsoeA44Parser: public Stream {
|
||||
public:
|
||||
EntsoeA44Parser();
|
||||
virtual ~EntsoeA44Parser();
|
||||
|
||||
char* getCurrency();
|
||||
char* getMeasurementUnit();
|
||||
@@ -25,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
|
||||
@@ -22,7 +22,7 @@ void DnbCurrParser::flush() {
|
||||
}
|
||||
|
||||
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
@@ -5,6 +5,10 @@ EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
|
||||
}
|
||||
|
||||
EntsoeA44Parser::~EntsoeA44Parser() {
|
||||
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
@@ -35,7 +39,7 @@ void EntsoeA44Parser::flush() {
|
||||
}
|
||||
|
||||
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
@@ -102,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);
|
||||
@@ -106,7 +149,7 @@ bool EntsoeApi::loop() {
|
||||
if(midnightMillis == 0) {
|
||||
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %llu\n", midnightMillis);
|
||||
currentDay = tm.Day;
|
||||
return false;
|
||||
} else if(now > midnightMillis && currentDay != tm.Day) {
|
||||
@@ -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
|
||||
@@ -13,11 +13,9 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
static const uint8_t sensors = 17;
|
||||
|
||||
String haTopic = "homeassistant/sensor/";
|
||||
|
||||
String haName = "AMS reader";
|
||||
81
lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h
Normal file
81
lib/HomeAssistantMqttHandler/include/HomeAssistantStatic.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef _HOMEASSISTANTSTATIC_H
|
||||
#define _HOMEASSISTANTSTATIC_H
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
struct HomeAssistantSensor {
|
||||
const char* name;
|
||||
const char* topic;
|
||||
const char* path;
|
||||
const char* uom;
|
||||
const char* devcl;
|
||||
const char* stacl;
|
||||
};
|
||||
|
||||
|
||||
const uint8_t HA_SENSOR_COUNT PROGMEM = 60;
|
||||
HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""},
|
||||
{"Temperature", "/state", "temp", "C", "temperature", "\"measurement\""},
|
||||
{"Active import", "/power", "P", "W", "power", "\"measurement\""},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""},
|
||||
{"Reactive import", "/power", "Q", "VAr", "reactive_power", "\"measurement\""},
|
||||
{"Active export", "/power", "PO", "W", "power", "\"measurement\""},
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""},
|
||||
{"Reactive export", "/power", "QO", "VAr", "reactive_power", "\"measurement\""},
|
||||
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
|
||||
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
|
||||
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""},
|
||||
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive import","/energy", "tQI", "kVArh","energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive export","/energy", "tQO", "kVArh","energy", "\"total_increasing\""},
|
||||
{"Power factor", "/power", "PF", "", "power_factor", "\"measurement\""},
|
||||
{"L1 power factor", "/power", "PF1", "", "power_factor", "\"measurement\""},
|
||||
{"L2 power factor", "/power", "PF2", "", "power_factor", "\"measurement\""},
|
||||
{"L3 power factor", "/power", "PF3", "", "power_factor", "\"measurement\""},
|
||||
{"Price current hour", "/prices", "prices['0']", "", "monetary", ""},
|
||||
{"Price next hour", "/prices", "prices['1']", "", "monetary", ""},
|
||||
{"Price in two hour", "/prices", "prices['2']", "", "monetary", ""},
|
||||
{"Price in three hour", "/prices", "prices['3']", "", "monetary", ""},
|
||||
{"Price in four hour", "/prices", "prices['4']", "", "monetary", ""},
|
||||
{"Price in five hour", "/prices", "prices['5']", "", "monetary", ""},
|
||||
{"Price in six hour", "/prices", "prices['6']", "", "monetary", ""},
|
||||
{"Price in seven hour", "/prices", "prices['7']", "", "monetary", ""},
|
||||
{"Price in eight hour", "/prices", "prices['8']", "", "monetary", ""},
|
||||
{"Price in nine hour", "/prices", "prices['9']", "", "monetary", ""},
|
||||
{"Price in ten hour", "/prices", "prices['10']", "", "monetary", ""},
|
||||
{"Price in eleven hour", "/prices", "prices['11']", "", "monetary", ""},
|
||||
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""},
|
||||
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""},
|
||||
{"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""},
|
||||
{"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""},
|
||||
{"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""},
|
||||
{"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""},
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -3,5 +3,5 @@
|
||||
"tPO" : %.2f,
|
||||
"tQI" : %.2f,
|
||||
"tQO" : %.2f,
|
||||
"rtc" : %llu
|
||||
"rtc" : %lu
|
||||
}
|
||||
15
lib/HomeAssistantMqttHandler/json/ha3.json
Normal file
15
lib/HomeAssistantMqttHandler/json/ha3.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
}
|
||||
@@ -3,8 +3,14 @@
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %.2f,
|
||||
"P2" : %.2f,
|
||||
"P3" : %.2f,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %.2f,
|
||||
"PO2" : %.2f,
|
||||
"PO3" : %.2f,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
20
lib/HomeAssistantMqttHandler/json/realtime.json
Normal file
20
lib/HomeAssistantMqttHandler/json/realtime.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"max" : %.1f,
|
||||
"peaks" : [ %s ],
|
||||
"threshold" : %d,
|
||||
"hour" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
},
|
||||
"day" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
},
|
||||
"month" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f
|
||||
}
|
||||
}
|
||||
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,12 +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/jsonsys_json.h"
|
||||
#include "web/root/jsonprices_json.h"
|
||||
#include "web/root/hadiscover_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())
|
||||
@@ -24,6 +26,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
mqtt->publish(topic + "/energy", json);
|
||||
mqtt->loop();
|
||||
}
|
||||
String meterModel = data->getMeterModel();
|
||||
meterModel.replace("\\", "\\\\");
|
||||
@@ -31,8 +34,8 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
snprintf_P(json, BufferSize, HA1_JSON,
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() <= 3) { // publish power counts and volts/amps
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -46,15 +49,62 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getL1ActiveImportPower(),
|
||||
data->getL2ActiveImportPower(),
|
||||
data->getL3ActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
data->getL1ActiveExportPower(),
|
||||
data->getL2ActiveExportPower(),
|
||||
data->getL3ActiveExportPower(),
|
||||
data->getReactiveExportPower(),
|
||||
data->getL1Current(),
|
||||
data->getL2Current(),
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
}
|
||||
return false;
|
||||
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth()
|
||||
);
|
||||
mqtt->publish(topic + "/realtime", json);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
@@ -91,7 +141,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[24];
|
||||
@@ -141,7 +191,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
}
|
||||
|
||||
char ts1hr[21];
|
||||
char ts1hr[24];
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
|
||||
@@ -149,7 +199,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
char ts3hr[24];
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
|
||||
@@ -157,7 +207,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
char ts6hr[24];
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
|
||||
@@ -186,10 +236,10 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
return mqtt->publish(topic + "/prices", json);
|
||||
return mqtt->publish(topic + "/prices", json, true, 0);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected()){
|
||||
sequence = 0;
|
||||
return false;
|
||||
@@ -202,7 +252,8 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature()
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
);
|
||||
mqtt->publish(topic + "/state", json);
|
||||
}
|
||||
@@ -215,27 +266,46 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
#endif
|
||||
String haUrl = "http://" + haUID + ".local/";
|
||||
// Could this be necessary? haUID.replace("-", "_");
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
|
||||
for(int i=0;i<sensors;i++){
|
||||
uint8_t peaks = 0;
|
||||
for(int i=0;i<HA_SENSOR_COUNT;i++) {
|
||||
HomeAssistantSensor sensor = HA_SENSORS[i];
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
String uom = String(sensor.uom);
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
uom = String(eapi->getCurrency());
|
||||
}
|
||||
if(strncmp(sensor.path, "peaks[", 6) == 0) {
|
||||
if(peaks >= peakCount) continue;
|
||||
peaks++;
|
||||
}
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
FPSTR(HA_NAMES[i]),
|
||||
topic.c_str(), FPSTR(HA_TOPICS[i]),
|
||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
||||
FPSTR(HA_UOM[i]),
|
||||
FPSTR(HA_PARAMS[i]),
|
||||
FPSTR(HA_DEVCL[i]),
|
||||
sensor.name,
|
||||
topic.c_str(), sensor.topic,
|
||||
haUID.c_str(), uid.c_str(),
|
||||
haUID.c_str(), uid.c_str(),
|
||||
uom.c_str(),
|
||||
sensor.path,
|
||||
sensor.devcl,
|
||||
haUID.c_str(),
|
||||
haName.c_str(),
|
||||
haModel.c_str(),
|
||||
VERSION,
|
||||
haManuf.c_str(),
|
||||
haUrl.c_str(),
|
||||
strlen_P(HA_STACL[i]) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(HA_STACL[i]) > 0 ? (char *) FPSTR(HA_STACL[i]) : ""
|
||||
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||
);
|
||||
mqtt->publish(haTopic + haUID + "_" + FPSTR(HA_PARAMS[i]) + "/config", json, true, 0);
|
||||
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
}
|
||||
|
||||
autodiscoverInit = true;
|
||||
}
|
||||
if(listType>0) sequence++;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user