Compare commits

...

25 Commits

Author SHA1 Message Date
Gunnar Skjold
29524c2123 Reducing stack mem footprint 2022-02-10 10:35:57 +01:00
Gunnar Skjold
e2fb5e2673 Removed exception handling 2022-02-08 08:37:54 +01:00
Gunnar Skjold
751a0edca7 160mhz esp32 and some error handling in loop() 2022-02-07 21:18:27 +01:00
Gunnar Skjold
a8e62e086c Divide current another decimal point for Kamstrup CT meters 2022-02-06 19:33:59 +01:00
Gunnar Skjold
35eb69bebb Trying higher buffer for MQTT 2022-02-06 19:28:32 +01:00
Gunnar Skjold
5c7c0699b2 Added images 2022-02-06 11:29:19 +01:00
Gunnar Skjold
7e31a60000 Support null values in the middle of the payload 2022-02-05 19:46:41 +01:00
Gunnar Skjold
793bc877fc Support null values in the middle of the payload 2022-02-05 19:34:44 +01:00
Gunnar Skjold
5ab6de21dc Added some documentation 2022-02-05 19:18:07 +01:00
Gunnar Skjold
db5e242ad8 Various changes 2022-02-04 17:36:30 +01:00
Gunnar Skjold
5be921a342 Various changes 2022-02-04 17:35:47 +01:00
Gunnar Skjold
dd87d70876 Free value on fuse size and production capacity 2022-01-28 18:29:50 +01:00
Gunnar Skjold
52992f09ee Stability adjustments 2022-01-28 17:41:48 +01:00
Gunnar Skjold
6aa02d54c8 Improvements for ENTSO-E 2022-01-23 17:52:10 +01:00
Gunnar Skjold
9dbf9137c7 Merge branch 'master' of github.com:gskjold/AmsToMqttBridge 2022-01-21 17:57:35 +01:00
Gunnar Skjold
c0a9a01b41 Fixed uint problem with current 2022-01-21 17:57:01 +01:00
Gunnar Skjold
7b203b196f Fixed voltage and current labels for esp8266 2022-01-21 17:50:02 +01:00
Gunnar Skjold
50c06e2cfe Removed ap pin from generic ESP32 2022-01-16 19:38:35 +01:00
Gunnar Skjold
e4d4753181 Fixed incorrect labels for IT/TT current 2022-01-16 17:24:57 +01:00
Gunnar Skjold
04daf551fb Fixed incorrect time diff for price fetch 2022-01-16 17:04:58 +01:00
Gunnar Skjold
4b51f0f235 Only update exchange rate once per day 2022-01-16 10:50:08 +01:00
Gunnar Skjold
5e4ccca663 Fetch prices between 13:30 and 14:00 2022-01-16 10:37:08 +01:00
Gunnar Skjold
6b0ec39759 Various updates 2022-01-16 09:58:13 +01:00
Gunnar Skjold
1f7d845a32 Removed debugging 2022-01-14 07:13:43 +01:00
Gunnar Skjold
6de5b719f3 Various changes 2022-01-14 07:12:08 +01:00
42 changed files with 424 additions and 224 deletions

View File

@@ -2,7 +2,7 @@
1.1.2.8.0.255 - Active- Energy 1.1.2.8.0.255 - Active- Energy
1.1.3.8.0.255 - Reactive+ Energy 1.1.3.8.0.255 - Reactive+ Energy
1.1.4.8.0.255 - Reactive- Energy 1.1.4.8.0.255 - Reactive- Energy
1.1.0.0.1.255 - Electricity ID? 1.1.0.0.1.255 - Meter number 1
1.1.1.7.0.255 - Active+ Instantaneous value 1.1.1.7.0.255 - Active+ Instantaneous value
1.1.2.7.0.255 - Active- Instantaneous value 1.1.2.7.0.255 - Active- Instantaneous value
1.1.3.7.0.255 - Reactive+ Instantaneous value 1.1.3.7.0.255 - Reactive+ Instantaneous value

Binary file not shown.

BIN
doc/M-Bus_DOC48.PDF Normal file

Binary file not shown.

Binary file not shown.

51
frames/Kamstrup-1p.raw Normal file
View File

@@ -0,0 +1,51 @@
7E A0 BA 2B 21 13 ED AA E6 E7 00 0F 00 00 00 00
0C 07 E6 02 05 06 0D 00 0A FF 80 00 00
02 19
0A 0E 4B 61 6D 73 74 72 75 70 5F 56 30 30 30 31
09 06 01 01 00 00 05 FF 0A 10 35 37 30 36 35 36 37 32 37 31 35 33 33 32 30 37
09 06 01 01 60 01 01 FF 0A 12 36 38 36 31 31 31 31 42 4E 32 34 32 31 30 31 30 34 30
09 06 01 01 01 07 00 FF 06 00 00 02 68
09 06 01 01 02 07 00 FF 06 00 00 00 00
09 06 01 01 03 07 00 FF 06 00 00 00 53
09 06 01 01 04 07 00 FF 06 00 00 00 00
09 06 01 01 1F 07 00 FF 06 00 00 01 22
00 00 00 00
09 06 01 01 20 07 00 FF 12 00 E2
00 00 00 00
05 D8 7E
7E A0 BA 2B 21 13 ED AA E6 E7 00 0F 00 00 00 00
0C 07 E6 02 05 06 0D 00 14 FF 80 00 00
02 19
0A 0E 4B 61 6D 73 74 72 75 70 5F 56 30 30 30 31
09 06 01 01 00 00 05 FF 0A 10 35 37 30 36 35 36 37 32 37 31 35 33 33 32 30 37
09 06 01 01 60 01 01 FF 0A 12 36 38 36 31 31 31 31 42 4E 32 34 32 31 30 31 30 34 30
09 06 01 01 01 07 00 FF 06 00 00 02 68
09 06 01 01 02 07 00 FF 06 00 00 00 00
09 06 01 01 03 07 00 FF 06 00 00 00 53
09 06 01 01 04 07 00 FF 06 00 00 00 00
09 06 01 01 1F 07 00 FF 06 00 00 01 23
00 00 00 00
09 06 01 01 20 07 00 FF 12 00 E1
00 00 00 00
8E 5E 7E
7E A1 04 2B 21 13 77 6E E6 E7 00 0F 00 00 00 00
0C 07 E6 02 05 06 0D 00 19 FF 80 00 00
02 23 0A 0E 4B 61 6D 73 74 72 75 70 5F 56 30 30 30 31
09 06 01 01 00 00 05 FF 0A 10 35 37 30 36 35 36 37 32 37 31 35 33 33 32 30 37
09 06 01 01 60 01 01 FF 0A 12 36 38 36 31 31 31 31 42 4E 32 34 32 31 30 31 30 34 30
09 06 01 01 01 07 00 FF 06 00 00 02 6B
09 06 01 01 02 07 00 FF 06 00 00 00 00
09 06 01 01 03 07 00 FF 06 00 00 00 54
09 06 01 01 04 07 00 FF 06 00 00 00 00
09 06 01 01 1F 07 00 FF 06 00 00 01 25
00 00 00 00
09 06 01 01 20 07 00 FF 12 00 E1
00 00 00 00
09 06 00 01 01 00 00 FF 09 0C 07 E6 02 05 06 0D 00 19 FF 80 00 00
09 06 01 01 01 08 00 FF 06 00 12 CF 93
09 06 01 01 02 08 00 FF 06 00 00 00 00
09 06 01 01 03 08 00 FF 06 00 00 8C CE
09 06 01 01 04 08 00 FF 06 00 05 E5 04
7F E9 7E

BIN
images/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
images/dayplot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/main-header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/monthplot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/sensor-displays.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/status-bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/tempsensors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -30,3 +30,4 @@ extra_scripts =
scripts/makeweb.py scripts/makeweb.py
build_flags = build_flags =
-D WEBSOCKET_DISABLED=1 -D WEBSOCKET_DISABLED=1
board_build.f_cpu = 160000000L

View File

@@ -232,6 +232,8 @@ bool AmsConfiguration::getDebugConfig(DebugConfig& config) {
} }
bool AmsConfiguration::setDebugConfig(DebugConfig& config) { bool AmsConfiguration::setDebugConfig(DebugConfig& config) {
if(!config.serial && !config.telnet)
config.level = 5; // Force error level when debug is disabled
EEPROM.begin(EEPROM_SIZE); EEPROM.begin(EEPROM_SIZE);
EEPROM.put(CONFIG_DEBUG_START, config); EEPROM.put(CONFIG_DEBUG_START, config);
bool ret = EEPROM.commit(); bool ret = EEPROM.commit();
@@ -449,6 +451,9 @@ bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
EEPROM.begin(EEPROM_SIZE); EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_ENTSOE_START, config); EEPROM.get(CONFIG_ENTSOE_START, config);
EEPROM.end(); EEPROM.end();
if(strlen(config.token) != 0 && strlen(config.token) != 36) {
clearEntsoe(config);
}
return true; return true;
} else { } else {
return false; return false;

View File

@@ -198,6 +198,9 @@ bool AmsDataStorage::update(AmsData* data) {
float iph = im / hrs; float iph = im / hrs;
float eph = ex / hrs; float eph = ex / hrs;
// There is something wacky going on when it ends up here. The total value (im) is way way lower than it should be, which in
// turn causes low values for all estimates. And then when it returns to the normal case above, the value is waaay higher.
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Since last month update, hours: %.1f, import: %d (%.2f/hr), export: %d (%.2f/hr)\n", hrs, im, iph, ex, eph); debugger->printf("(AmsDataStorage) Since last month update, hours: %.1f, import: %d (%.2f/hr), export: %d (%.2f/hr)\n", hrs, im, iph, ex, eph);
} }
@@ -210,7 +213,7 @@ bool AmsDataStorage::update(AmsData* data) {
} }
month.lastMeterReadTime = month.lastMeterReadTime - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; month.lastMeterReadTime = month.lastMeterReadTime - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second;
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu", month.lastMeterReadTime); debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu\n", month.lastMeterReadTime);
} }
if(tz != NULL) { if(tz != NULL) {
@@ -250,7 +253,7 @@ void AmsDataStorage::setHour(uint8_t hour, int32_t val) {
day.points[hour] = val / 10; day.points[hour] = val / 10;
} }
int16_t AmsDataStorage::getHour(uint8_t hour) { int32_t AmsDataStorage::getHour(uint8_t hour) {
if(hour < 0 || hour > 24) return 0; if(hour < 0 || hour > 24) return 0;
return day.points[hour] * 10; return day.points[hour] * 10;
} }

View File

@@ -28,7 +28,7 @@ public:
AmsDataStorage(RemoteDebug*); AmsDataStorage(RemoteDebug*);
void setTimezone(Timezone*); void setTimezone(Timezone*);
bool update(AmsData*); bool update(AmsData*);
int16_t getHour(uint8_t); int32_t getHour(uint8_t);
int32_t getDay(uint8_t); int32_t getDay(uint8_t);
bool load(); bool load();
bool save(); bool save();

View File

@@ -13,10 +13,12 @@
*/ */
#if defined(ESP8266) #if defined(ESP8266)
ADC_MODE(ADC_VCC); ADC_MODE(ADC_VCC);
#else if defined(ESP32) #endif
#if defined(ESP32)
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#endif #endif
#define WDT_TIMEOUT 10 #define WDT_TIMEOUT 60
#include "AmsToMqttBridge.h" #include "AmsToMqttBridge.h"
#include "AmsStorage.h" #include "AmsStorage.h"
@@ -135,12 +137,14 @@ void setup() {
hw.ledBlink(LED_GREEN, 1); hw.ledBlink(LED_GREEN, 1);
hw.ledBlink(LED_BLUE, 1); hw.ledBlink(LED_BLUE, 1);
#if defined(ESP32)
EntsoeConfig entsoe; EntsoeConfig entsoe;
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) { if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
eapi = new EntsoeApi(&Debug); eapi = new EntsoeApi(&Debug);
eapi->setup(entsoe); eapi->setup(entsoe);
ws.setEntsoeApi(eapi); ws.setEntsoeApi(eapi);
} }
#endif
bool shared = false; bool shared = false;
config.getMeterConfig(meterConfig); config.getMeterConfig(meterConfig);
@@ -434,30 +438,36 @@ void loop() {
mqtt->disconnect(); mqtt->disconnect();
} }
if(eapi != NULL && ntpEnabled) { #if defined(ESP32)
if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) { try {
mqttHandler->publishPrices(eapi); if(eapi != NULL && ntpEnabled) {
} if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) {
} mqttHandler->publishPrices(eapi);
if(config.isEntsoeChanged()) {
EntsoeConfig entsoe;
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
if(eapi == NULL) {
eapi = new EntsoeApi(&Debug);
ws.setEntsoeApi(eapi);
} }
eapi->setup(entsoe);
} else if(eapi != NULL) {
delete eapi;
eapi = NULL;
ws.setEntsoeApi(eapi);
} }
config.ackEntsoeChange();
if(config.isEntsoeChanged()) {
EntsoeConfig entsoe;
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
if(eapi == NULL) {
eapi = new EntsoeApi(&Debug);
ws.setEntsoeApi(eapi);
}
eapi->setup(entsoe);
} else if(eapi != NULL) {
delete eapi;
eapi = NULL;
ws.setEntsoeApi(NULL);
}
config.ackEntsoeChange();
}
} catch(const std::exception& e) {
debugE("Exception in ENTSO-E loop (%s)", e.what());
} }
#endif
ws.loop(); ws.loop();
} }
if(mqtt != NULL) { // Run loop regardless, to let MQTT do its work. if(mqtt != NULL) {
mqtt->loop(); mqtt->loop();
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
} }
@@ -553,8 +563,10 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
#if defined(ESP32) #if defined(ESP32)
hwSerial->begin(baud, serialConfig, -1, -1, invert); hwSerial->begin(baud, serialConfig, -1, -1, invert);
hwSerial->setRxBufferSize(768);
#else #else
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert); hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
hwSerial->setRxBufferSize(768);
#endif #endif
#if defined(ESP8266) #if defined(ESP8266)
@@ -674,11 +686,12 @@ void swapWifiMode() {
} }
int len = 0; int len = 0;
uint8_t buf[BUF_SIZE]; uint8_t* buf = NULL;
MbusAssembler* ma = NULL; MbusAssembler* ma = NULL;
int currentMeterType = -1; int currentMeterType = -1;
bool readHanPort() { bool readHanPort() {
if(!hanSerial->available()) return false; if(!hanSerial->available()) return false;
if(buf == NULL) buf = (uint8_t*) malloc(BUF_SIZE);
if(currentMeterType == -1) { if(currentMeterType == -1) {
hanSerial->readBytes(buf, BUF_SIZE); hanSerial->readBytes(buf, BUF_SIZE);
@@ -699,7 +712,6 @@ bool readHanPort() {
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) { while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
buf[len++] = hanSerial->read(); buf[len++] = hanSerial->read();
pos = HDLC_validate((uint8_t *) buf, len, hc, &timestamp); pos = HDLC_validate((uint8_t *) buf, len, hc, &timestamp);
delay(1);
} }
if(len > 0) { if(len > 0) {
if(len >= BUF_SIZE) { if(len >= BUF_SIZE) {
@@ -749,7 +761,7 @@ bool readHanPort() {
debugD("Frame dump (%db):", len); debugD("Frame dump (%db):", len);
debugPrint(buf, 0, len); debugPrint(buf, 0, len);
} }
if(hc != NULL && Debug.isActive(RemoteDebug::DEBUG)) { if(hc != NULL && Debug.isActive(RemoteDebug::VERBOSE)) {
debugD("System title:"); debugD("System title:");
debugPrint(hc->system_title, 0, 8); debugPrint(hc->system_title, 0, 8);
debugD("Initialization vector:"); debugD("Initialization vector:");
@@ -762,7 +774,7 @@ bool readHanPort() {
len = 0; len = 0;
while(hanSerial->available()) hanSerial->read(); while(hanSerial->available()) hanSerial->read();
if(pos > 0) { if(pos > 0) {
debugI("Valid data, start at byte %d", pos); debugD("Valid data, start at byte %d", pos);
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), meterConfig.distributionSystem, timestamp, hc); data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), meterConfig.distributionSystem, timestamp, hc);
} else { } else {
if(Debug.isActive(RemoteDebug::WARNING)) { if(Debug.isActive(RemoteDebug::WARNING)) {
@@ -833,17 +845,19 @@ bool readHanPort() {
hw.ledBlink(LED_INTERNAL, 1); hw.ledBlink(LED_INTERNAL, 1);
if(mqttEnabled && mqttHandler != NULL && mqtt != NULL) { if(mqttEnabled && mqttHandler != NULL && mqtt != NULL) {
if(mqttHandler->publish(&data, &meterState)) { if(mqttHandler->publish(&data, &meterState)) {
mqtt->loop();
delay(10);
if(data.getListType() == 3 && eapi != NULL) { if(data.getListType() == 3 && eapi != NULL) {
mqttHandler->publishPrices(eapi); mqttHandler->publishPrices(eapi);
mqtt->loop();
delay(10);
} }
if(data.getListType() >= 2) { if(data.getListType() >= 2) {
mqttHandler->publishSystem(&hw); mqttHandler->publishSystem(&hw);
mqtt->loop();
delay(10);
} }
} }
if(mqtt != NULL) {
mqtt->loop();
delay(10);
}
} }
time_t now = time(nullptr); time_t now = time(nullptr);
@@ -967,7 +981,9 @@ void WiFi_connect() {
} else if(dns1.toString().isEmpty()) { } else if(dns1.toString().isEmpty()) {
dns2.fromString("208.67.220.220"); // Add OpenDNS as second by default if nothing is configured dns2.fromString("208.67.220.220"); // Add OpenDNS as second by default if nothing is configured
} }
WiFi.config(ip, gw, sn, dns1, dns2); if(!WiFi.config(ip, gw, sn, dns1, dns2)) {
debugE("Static IP configuration is invalid, not using");
}
} else { } else {
#if defined(ESP32) #if defined(ESP32)
// This trick does not work anymore... // This trick does not work anymore...
@@ -1015,7 +1031,7 @@ void MQTT_connect() {
mqtt->disconnect(); mqtt->disconnect();
yield(); yield();
} else { } else {
mqtt = new MQTTClient(512); mqtt = new MQTTClient(1024);
ws.setMqtt(mqtt); ws.setMqtt(mqtt);
} }

View File

@@ -3,7 +3,6 @@
#include "Timezone.h" #include "Timezone.h"
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distributionSystem, CosemDateTime packageTimestamp, HDLCConfig* hc) { IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distributionSystem, CosemDateTime packageTimestamp, HDLCConfig* hc) {
uint32_t ui;
double val; double val;
char str[64]; char str[64];
@@ -13,8 +12,8 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
this->packageTimestamp = getTimestamp(packageTimestamp); this->packageTimestamp = getTimestamp(packageTimestamp);
ui = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d))); val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
if(ui == 0xFFFFFFFF) { if(val == NOVALUE) {
CosemData* data = getCosemDataAt(1, ((char *) (d))); CosemData* data = getCosemDataAt(1, ((char *) (d)));
// Kaifa special case... // Kaifa special case...
@@ -137,7 +136,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
// Kaifa end // Kaifa end
} else { } else {
listType = 1; listType = 1;
activeImportPower = ui; activeImportPower = val;
meterType = AmsTypeUnknown; meterType = AmsTypeUnknown;
CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d); CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d);
@@ -166,95 +165,96 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0; this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
} }
ui = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str); uint8_t str_len = 0;
if(ui > 0) { str_len = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
if(str_len > 0) {
listId = String(str); listId = String(str);
} }
ui = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d))); val = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
if(ui != 0xFFFFFFFF) { if(val != NOVALUE) {
activeExportPower = ui; activeExportPower = val;
} }
ui = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
if(ui != 0xFFFFFFFF) { if(val != NOVALUE) {
reactiveImportPower = ui; reactiveImportPower = val;
} }
ui = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
if(ui != 0xFFFFFFFF) { if(val != NOVALUE) {
reactiveExportPower = ui; reactiveExportPower = val;
} }
val = getNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l1voltage = val; l1voltage = val;
} }
val = getNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l2voltage = val; l2voltage = val;
} }
val = getNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d))); val = getNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l3voltage = val; l3voltage = val;
} }
val = getNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l1current = val; l1current = val;
} }
val = getNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l2current = val; l2current = val;
} }
val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d))); val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 2; listType = 2;
l3current = val; l3current = val;
} }
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 3; listType = 3;
activeImportCounter = val / 1000.0; activeImportCounter = val / 1000.0;
} }
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 3; listType = 3;
activeExportCounter = val / 1000.0; activeExportCounter = val / 1000.0;
} }
val = getNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 3; listType = 3;
reactiveImportCounter = val / 1000.0; reactiveImportCounter = val / 1000.0;
} }
val = getNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d))); val = getNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
listType = 3; listType = 3;
reactiveExportCounter = val / 1000.0; reactiveExportCounter = val / 1000.0;
} }
ui = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str); str_len = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
if(ui > 0) { if(str_len > 0) {
meterModel = String(str); meterModel = String(str);
} else { } else {
ui = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str); str_len = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
if(ui > 0) { if(str_len > 0) {
meterModel = String(str); meterModel = String(str);
} }
} }
ui = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str); str_len = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
if(ui > 0) { if(str_len > 0) {
meterId = String(str); meterId = String(str);
} else { } else {
ui = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str); str_len = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
if(ui > 0) { if(str_len > 0) {
meterId = String(str); meterId = String(str);
} }
} }
@@ -271,19 +271,19 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
} }
val = getNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
powerFactor = val; powerFactor = val;
} }
val = getNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
l1PowerFactor = val; l1PowerFactor = val;
} }
val = getNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
l2PowerFactor = val; l2PowerFactor = val;
} }
val = getNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d))); val = getNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d)));
if(val != 0xFFFFFFFF) { if(val != NOVALUE) {
l3PowerFactor = val; l3PowerFactor = val;
} }
@@ -308,6 +308,14 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
l2PowerFactor /= 100; l2PowerFactor /= 100;
if(l3PowerFactor != 0) if(l3PowerFactor != 0)
l3PowerFactor /= 100; l3PowerFactor /= 100;
int watt = abs((l1voltage * l1current) + (l2voltage * l2current) + (l3voltage * l3current));
if(watt / (activeImportPower + activeExportPower + reactiveImportPower + reactiveExportPower) > 2) {
l1current = l1current != 0 ? l1current / 10 : 0;
l2current = l2current != 0 ? l2current / 10 : 0;
l3current = l3current != 0 ? l3current / 10 : 0;
}
} else if(meterType == AmsTypeSagemcom) { } else if(meterType == AmsTypeSagemcom) {
CosemData* meterTs = getCosemDataAt(1, ((char *) (d))); CosemData* meterTs = getCosemDataAt(1, ((char *) (d)));
if(meterTs != NULL) { if(meterTs != NULL) {
@@ -335,14 +343,15 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
} }
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0; threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0); if(!threePhase)
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
// Special case for Norwegian IT/TT meters that does not report all values // Special case for Norwegian IT/TT meters that does not report all values
if(distributionSystem == 1) { if(distributionSystem == 1) {
if(threePhase) { if(threePhase) {
if(l2current == 0.0 && l1current > 0.0 && l3current > 0.0) { if(l2current == 0.0 && l1current > 0.0 && l3current > 0.0) {
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage; l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
if(activeExportPower == 0) { if(activeExportPower == 0.0) {
l2current = max((float) 0.0, l2current); l2current = max((float) 0.0, l2current);
} }
} }
@@ -379,11 +388,13 @@ CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
pos += 5; pos += 5;
break; break;
case CosemTypeNull: case CosemTypeNull:
return NULL; pos += 1;
break;
default: default:
pos += 2; pos += 2;
} }
i++; i++;
if(pos-ptr > 900) break;
} while(item->base.type != HDLC_FLAG); } while(item->base.type != HDLC_FLAG);
return NULL; return NULL;
} }
@@ -422,10 +433,12 @@ CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr)
pos += 5; pos += 5;
break; break;
case CosemTypeNull: case CosemTypeNull:
return NULL; pos += 1;
break;
default: default:
pos += 2; pos += 2;
} }
if(pos-ptr > 900) break;
} while(item->base.type != HDLC_FLAG); } while(item->base.type != HDLC_FLAG);
return NULL; return NULL;
} }
@@ -453,31 +466,38 @@ double IEC6205675::getNumber(uint8_t* obis, int matchlength, const char* ptr) {
} }
double IEC6205675::getNumber(CosemData* item) { double IEC6205675::getNumber(CosemData* item) {
double val = 0xFFFFFFFF;
if(item != NULL) { if(item != NULL) {
double ret = 0.0;
char* pos = ((char*) item); char* pos = ((char*) item);
switch(item->base.type) { switch(item->base.type) {
case CosemTypeLongUnsigned: case CosemTypeLongUnsigned: {
val = ntohs(item->lu.data); uint16_t u16 = ntohs(item->lu.data);
ret = u16;
pos += 3; pos += 3;
break; break;
case CosemTypeDLongUnsigned: }
val = ntohl(item->dlu.data); case CosemTypeDLongUnsigned: {
uint32_t u32 = ntohl(item->dlu.data);
ret = u32;
pos += 5; pos += 5;
break; break;
case CosemTypeLongSigned: }
val = ntohs(item->lu.data); case CosemTypeLongSigned: {
int16_t i16 = ntohs(item->ls.data);
ret = i16;
pos += 3; pos += 3;
break; break;
}
} }
if(pos != NULL) { if(pos != NULL) {
if(*pos++ == 0x02 && *pos++ == 0x02) { if(*pos++ == 0x02 && *pos++ == 0x02) {
int8_t scale = *++pos; int8_t scale = *++pos;
val *= pow(10, scale); ret *= pow(10, scale);
} }
} }
return ret;
} }
return val; return NOVALUE;
} }
time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) { time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) {

View File

@@ -4,6 +4,8 @@
#include "AmsData.h" #include "AmsData.h"
#include "ams/hdlc.h" #include "ams/hdlc.h"
#define NOVALUE 0xFFFFFFFF
struct AmsOctetTimestamp { struct AmsOctetTimestamp {
uint8_t type; uint8_t type;
CosemDateTime dt; CosemDateTime dt;
@@ -29,26 +31,12 @@ private:
uint8_t AMS_OBIS_METER_ID_2[4] = { 0, 0, 5, 255 }; uint8_t AMS_OBIS_METER_ID_2[4] = { 0, 0, 5, 255 };
uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 }; uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 }; uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 }; uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 }; uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_IMPORT_L1[4] = { 23, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_IMPORT_L2[4] = { 43, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_IMPORT_L3[4] = { 63, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 }; uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_EXPORT_L1[4] = { 24, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_EXPORT_L2[4] = { 44, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_EXPORT_L3[4] = { 64, 7, 0, 255 };
uint8_t AMS_OBIS_CURRENT[4] = { 11, 7, 0, 255 };
uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 }; uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 };
uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 }; uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 };
uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 }; uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 };
uint8_t AMS_OBIS_VOLTAGE[4] = { 12, 7, 0, 255 };
uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 }; uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 };
uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 }; uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 };
uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 }; uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 };

View File

@@ -268,7 +268,7 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
} else if(dateTime->base.type == CosemTypeDateTime) { } else if(dateTime->base.type == CosemTypeDateTime) {
memcpy(timestamp, ptr, dateTime->base.length); memcpy(timestamp, ptr, dateTime->base.length);
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug... } else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
memcpy(timestamp, ptr, 0x0C); memcpy(timestamp, ptr, 13);
ptr += 13; ptr += 13;
} else { } else {
return HDLC_TIMESTAMP_UNKNOWN; return HDLC_TIMESTAMP_UNKNOWN;

View File

@@ -1,4 +1,5 @@
#include "DnbCurrParser.h" #include "DnbCurrParser.h"
#include "Arduino.h"
#include "HardwareSerial.h" #include "HardwareSerial.h"
float DnbCurrParser::getValue() { float DnbCurrParser::getValue() {
@@ -29,13 +30,29 @@ size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
} }
size_t DnbCurrParser::write(uint8_t byte) { size_t DnbCurrParser::write(uint8_t byte) {
if(pos >= 64) pos = 0;
if(pos == 0) { if(pos == 0) {
if(byte == '<') { if(byte == '<') {
buf[pos++] = byte; buf[pos++] = byte;
} }
} else if(byte == '>') { } else if(byte == '>') {
buf[pos++] = byte; buf[pos++] = byte;
if(strncmp(buf, "<Obs", 4) == 0) { if(strncmp(buf, "<Series", 7) == 0) {
for(int i = 0; i < pos; i++) {
if(strncmp(buf+i, "UNIT_MULT=\"", 11) == 0) {
pos = i + 11;
break;
}
}
for(int i = 0; i < 16; i++) {
uint8_t b = buf[pos+i];
if(b == '"') {
buf[pos+i] = '\0';
break;
}
}
scale = String(buf+pos).toInt();
} else if(strncmp(buf, "<Obs", 4) == 0) {
for(int i = 0; i < pos; i++) { for(int i = 0; i < pos; i++) {
if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) { if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) {
pos = i + 11; pos = i + 11;
@@ -49,7 +66,7 @@ size_t DnbCurrParser::write(uint8_t byte) {
break; break;
} }
} }
value = String(buf+pos).toFloat(); value = String(buf+pos).toFloat() / pow(10, scale);
} }
pos = 0; pos = 0;
} else { } else {

View File

@@ -15,6 +15,7 @@ public:
size_t write(uint8_t); size_t write(uint8_t);
private: private:
uint8_t scale = 0;
float value = 1.0; float value = 1.0;
char buf[64]; char buf[64];

View File

@@ -14,6 +14,7 @@ char* EntsoeA44Parser::getMeasurementUnit() {
} }
float EntsoeA44Parser::getPoint(uint8_t position) { float EntsoeA44Parser::getPoint(uint8_t position) {
if(position >= 24) return ENTSOE_NO_VALUE;
return points[position]; return points[position];
} }
@@ -41,6 +42,7 @@ size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
} }
size_t EntsoeA44Parser::write(uint8_t byte) { size_t EntsoeA44Parser::write(uint8_t byte) {
if(pos >= 64) pos = 0;
if(docPos == DOCPOS_CURRENCY) { if(docPos == DOCPOS_CURRENCY) {
buf[pos++] = byte; buf[pos++] = byte;
if(pos == 3) { if(pos == 3) {

View File

@@ -4,21 +4,25 @@
#include "TimeLib.h" #include "TimeLib.h"
#include "DnbCurrParser.h" #include "DnbCurrParser.h"
#if defined(ESP8266) #if defined(ESP32)
#include <ESP8266HTTPClient.h> #include <esp_task_wdt.h>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <HTTPClient.h>
#else
#warning "Unsupported board type"
#endif #endif
EntsoeApi::EntsoeApi(RemoteDebug* Debug) { EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
this->buf = (char*) malloc(BufferSize);
debugger = Debug; debugger = Debug;
client.setInsecure();
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
https.setTimeout(50000);
// Entso-E uses CET/CEST // Entso-E uses CET/CEST
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
tz = new Timezone(CEST, CET); tz = new Timezone(CEST, CET);
tomorrowFetchMillis = 36000000 + (random(1800) * 1000); // Random between 13:30 and 14:00
} }
void EntsoeApi::setup(EntsoeConfig& config) { void EntsoeApi::setup(EntsoeConfig& config) {
@@ -26,6 +30,7 @@ void EntsoeApi::setup(EntsoeConfig& config) {
this->config = new EntsoeConfig(); this->config = new EntsoeConfig();
} }
memcpy(this->config, &config, sizeof(config)); memcpy(this->config, &config, sizeof(config));
lastCurrencyFetch = 0;
} }
char* EntsoeApi::getToken() { char* EntsoeApi::getToken() {
@@ -61,8 +66,10 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
} else { } else {
return ENTSOE_NO_VALUE; return ENTSOE_NO_VALUE;
} }
multiplier *= getCurrencyMultiplier(tomorrow->getCurrency(), config->currency); float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
} else { if(mult == 0) return ENTSOE_NO_VALUE;
multiplier *= mult;
} else if(pos >= 0) {
if(today == NULL) if(today == NULL)
return ENTSOE_NO_VALUE; return ENTSOE_NO_VALUE;
value = today->getPoint(pos); value = today->getPoint(pos);
@@ -71,7 +78,9 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
} else { } else {
return ENTSOE_NO_VALUE; return ENTSOE_NO_VALUE;
} }
multiplier *= getCurrencyMultiplier(today->getCurrency(), config->currency); float mult = getCurrencyMultiplier(today->getCurrency(), config->currency);
if(mult == 0) return ENTSOE_NO_VALUE;
multiplier *= mult;
} }
return value * multiplier; return value * multiplier;
} }
@@ -79,7 +88,6 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
bool EntsoeApi::loop() { bool EntsoeApi::loop() {
if(strlen(getToken()) == 0) if(strlen(getToken()) == 0)
return false; return false;
bool ret = false;
uint64_t now = millis64(); uint64_t now = millis64();
if(now < 10000) return false; // Grace period if(now < 10000) return false; // Grace period
@@ -114,28 +122,37 @@ bool EntsoeApi::loop() {
breakTime(e1, d1); breakTime(e1, d1);
breakTime(e2, d2); breakTime(e2, d2);
char url[256]; 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",
snprintf(url, sizeof(url), "%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(), "https://transparency.entsoe.eu/api", getToken(),
d1.Year+1970, d1.Month, d1.Day, 23, 00, d1.Year+1970, d1.Month, d1.Day, 23, 00,
d2.Year+1970, d2.Month, d2.Day, 23, 00, d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area); 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::INFO)) debugger->printf("(EntsoeApi) Fetching prices for today\n");
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
EntsoeA44Parser* a44 = new EntsoeA44Parser(); EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) { if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
today = a44; today = a44;
ret = true; return true;
} else if(a44 != NULL) { } else if(a44 != NULL) {
delete a44; delete a44;
today = NULL; today = NULL;
return false;
} }
} }
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
// fetch 1 hr after that (with some random delay) and retry every 15 minutes
if(tomorrow == NULL if(tomorrow == NULL
&& midnightMillis - now < 39600000 // Fetch 11hrs before midnight (13:00 CE(S)T) && midnightMillis - now < tomorrowFetchMillis
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 300000) // Retry every 5min && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
) { ) {
lastTomorrowFetch = now; lastTomorrowFetch = now;
time_t e1 = time(nullptr); time_t e1 = time(nullptr);
@@ -144,30 +161,35 @@ bool EntsoeApi::loop() {
breakTime(e1, d1); breakTime(e1, d1);
breakTime(e2, d2); breakTime(e2, d2);
char url[256]; 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",
snprintf(url, sizeof(url), "%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(), "https://transparency.entsoe.eu/api", getToken(),
d1.Year+1970, d1.Month, d1.Day, 23, 00, d1.Year+1970, d1.Month, d1.Day, 23, 00,
d2.Year+1970, d2.Month, d2.Day, 23, 00, d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area); 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::INFO)) debugger->printf("(EntsoeApi) Fetching prices for tomorrow\n");
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
EntsoeA44Parser* a44 = new EntsoeA44Parser(); EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) { if(retrieve(buf, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
tomorrow = a44; tomorrow = a44;
ret = true; return true;
} else if(a44 != NULL) { } else if(a44 != NULL) {
delete a44; delete a44;
tomorrow = NULL; tomorrow = NULL;
return false;
} }
} }
} }
return ret; return false;
} }
bool EntsoeApi::retrieve(const char* url, Stream* doc) { bool EntsoeApi::retrieve(const char* url, Stream* doc) {
WiFiClientSecure client;
#if defined(ESP8266) #if defined(ESP8266)
// https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/bearssl-client-secure-class.html#mfln-or-maximum-fragment-length-negotiation-saving-ram // https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/bearssl-client-secure-class.html#mfln-or-maximum-fragment-length-negotiation-saving-ram
/* Rumor has it that a client cannot request a lower max_fragment_length, so I guess thats why the following does not work. /* Rumor has it that a client cannot request a lower max_fragment_length, so I guess thats why the following does not work.
@@ -185,13 +207,15 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
*/ */
#endif #endif
client.setInsecure();
HTTPClient https;
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
if(https.begin(client, url)) { if(https.begin(client, url)) {
printD("Connection established"); printD("Connection established");
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
/* /*
#if defined(ESP8266) #if defined(ESP8266)
if(!client.getMFLNStatus()) { if(!client.getMFLNStatus()) {
@@ -204,6 +228,13 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
*/ */
int status = https.GET(); int status = https.GET();
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
if(status == HTTP_CODE_OK) { if(status == HTTP_CODE_OK) {
printD("Receiving data"); printD("Receiving data");
https.writeToStream(doc); https.writeToStream(doc);
@@ -239,18 +270,39 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
return 1.00; return 1.00;
uint64_t now = millis64(); uint64_t now = millis64();
if(lastCurrencyFetch == 0 || now - lastCurrencyFetch > (SECS_PER_HOUR * 1000)) { if(now > lastCurrencyFetch && (now - lastCurrencyFetch) < 900000) {
char url[256];
snprintf(url, sizeof(url), "https://data.norges-bank.no/api/data/EXR/M.%s.%s.SP?lastNObservations=1",
from,
to
);
DnbCurrParser p;
if(retrieve(url, &p)) {
currencyMultiplier = p.getValue();
}
lastCurrencyFetch = now; lastCurrencyFetch = now;
DnbCurrParser p;
#if defined(ESP32)
esp_task_wdt_reset();
#elif defined(ESP8266)
ESP.wdtFeed();
#endif
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", from);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", from);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
currencyMultiplier = p.getValue();
if(strncmp(to, "NOK", 3) != 0) {
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", to);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", to);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
if(retrieve(buf, &p)) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
currencyMultiplier /= p.getValue();
} else {
return 0;
}
}
} else {
return 0;
}
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
lastCurrencyFetch = midnightMillis;
} }
return currencyMultiplier; return currencyMultiplier;
} }

View File

@@ -7,6 +7,14 @@
#include "EntsoeA44Parser.h" #include "EntsoeA44Parser.h"
#include "AmsConfiguration.h" #include "AmsConfiguration.h"
#if defined(ESP8266)
#include <ESP8266HTTPClient.h>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <HTTPClient.h>
#else
#warning "Unsupported board type"
#endif
#define ENTSOE_DEFAULT_MULTIPLIER 1.00 #define ENTSOE_DEFAULT_MULTIPLIER 1.00
#define SSL_BUF_SIZE 512 #define SSL_BUF_SIZE 512
@@ -24,7 +32,10 @@ public:
private: private:
RemoteDebug* debugger; RemoteDebug* debugger;
EntsoeConfig* config = NULL; EntsoeConfig* config = NULL;
WiFiClientSecure client;
HTTPClient https;
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
uint64_t midnightMillis = 0; uint64_t midnightMillis = 0;
uint64_t lastTodayFetch = 0; uint64_t lastTodayFetch = 0;
uint64_t lastTomorrowFetch = 0; uint64_t lastTomorrowFetch = 0;
@@ -34,6 +45,9 @@ private:
Timezone* tz = NULL; Timezone* tz = NULL;
static const uint16_t BufferSize = 256;
char* buf;
float currencyMultiplier = ENTSOE_DEFAULT_MULTIPLIER; float currencyMultiplier = ENTSOE_DEFAULT_MULTIPLIER;
bool retrieve(const char* url, Stream* doc); bool retrieve(const char* url, Stream* doc);

View File

@@ -10,8 +10,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
if(energy > 0.0) { if(energy > 0.0) {
char val[16]; char val[16];
snprintf(val, 16, "%.1f;%.1f", (data->getActiveImportPower()/1.0), energy*1000.0); snprintf(val, 16, "%.1f;%.1f", (data->getActiveImportPower()/1.0), energy*1000.0);
char json[192]; snprintf_P(json, BufferSize, DOMOTICZ_JSON,
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.elidx, config.elidx,
val val
); );
@@ -25,8 +24,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
if (config.vl1idx > 0){ if (config.vl1idx > 0){
char val[16]; char val[16];
snprintf(val, 16, "%.2f", data->getL1Voltage()); snprintf(val, 16, "%.2f", data->getL1Voltage());
char json[192]; snprintf_P(json, BufferSize, DOMOTICZ_JSON,
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl1idx, config.vl1idx,
val val
); );
@@ -36,8 +34,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
if (config.vl2idx > 0){ if (config.vl2idx > 0){
char val[16]; char val[16];
snprintf(val, 16, "%.2f", data->getL2Voltage()); snprintf(val, 16, "%.2f", data->getL2Voltage());
char json[192]; snprintf_P(json, BufferSize, DOMOTICZ_JSON,
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl2idx, config.vl2idx,
val val
); );
@@ -47,8 +44,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
if (config.vl3idx > 0){ if (config.vl3idx > 0){
char val[16]; char val[16];
snprintf(val, 16, "%.2f", data->getL3Voltage()); snprintf(val, 16, "%.2f", data->getL3Voltage());
char json[192]; snprintf_P(json, BufferSize, DOMOTICZ_JSON,
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl3idx, config.vl3idx,
val val
); );
@@ -58,8 +54,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
if (config.cl1idx > 0){ if (config.cl1idx > 0){
char val[16]; char val[16];
snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current()); snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current());
char json[192]; snprintf_P(json, BufferSize, DOMOTICZ_JSON,
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.cl1idx, config.cl1idx,
val val
); );

View File

@@ -8,14 +8,19 @@ class DomoticzMqttHandler : public AmsMqttHandler {
public: public:
DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) { DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) {
this->config = config; this->config = config;
this->json = (char*) malloc(BufferSize);
}; };
bool publish(AmsData* data, AmsData* previousState); bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools*);
static const uint16_t BufferSize = 192;
private: private:
DomoticzConfig config; DomoticzConfig config;
int energy = 0.0; int energy = 0.0;
char* json;
}; };
#endif #endif

View File

@@ -13,8 +13,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
return false; return false;
if(data->getListType() == 1) { if(data->getListType() == 1) {
char json[192]; snprintf_P(json, BufferSize, JSON1_JSON,
snprintf_P(json, sizeof(json), JSON1_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
clientId.c_str(), clientId.c_str(),
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
@@ -26,8 +25,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 2) { } else if(data->getListType() == 2) {
char json[384]; snprintf_P(json, BufferSize, JSON2_JSON,
snprintf_P(json, sizeof(json), JSON2_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
clientId.c_str(), clientId.c_str(),
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
@@ -52,8 +50,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else if(data->getListType() == 3) { } else if(data->getListType() == 3) {
if(data->getPowerFactor() == 0) { if(data->getPowerFactor() == 0) {
char json[512]; snprintf_P(json, BufferSize, JSON3_JSON,
snprintf_P(json, sizeof(json), JSON3_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
clientId.c_str(), clientId.c_str(),
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
@@ -82,8 +79,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
); );
return mqtt->publish(topic, json); return mqtt->publish(topic, json);
} else { } else {
char json[768]; snprintf_P(json, BufferSize, JSON3PF_JSON,
snprintf_P(json, sizeof(json), JSON3PF_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
clientId.c_str(), clientId.c_str(),
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),
@@ -229,8 +225,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
} }
char json[384]; snprintf_P(json, BufferSize, JSONPRICES_JSON,
snprintf_P(json, sizeof(json), JSONPRICES_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
values[0], values[0],
values[1], values[1],
@@ -257,8 +252,7 @@ bool JsonMqttHandler::publishSystem(HwTools* hw) {
if(init || topic.isEmpty() || !mqtt->connected()) if(init || topic.isEmpty() || !mqtt->connected())
return false; return false;
char json[192]; snprintf_P(json, BufferSize, JSONSYS_JSON,
snprintf_P(json, sizeof(json), JSONSYS_JSON,
WiFi.macAddress().c_str(), WiFi.macAddress().c_str(),
clientId.c_str(), clientId.c_str(),
(uint32_t) (millis64()/1000), (uint32_t) (millis64()/1000),

View File

@@ -9,16 +9,20 @@ public:
this->clientId = clientId; this->clientId = clientId;
this->topic = String(topic); this->topic = String(topic);
this->hw = hw; this->hw = hw;
this->json = (char*) malloc(BufferSize);
}; };
bool publish(AmsData* data, AmsData* previousState); bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*); bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools*);
static const uint16_t BufferSize = 768;
private: private:
String clientId; String clientId;
String topic; String topic;
HwTools* hw; HwTools* hw;
bool init = false; bool init = false;
char* json;
}; };
#endif #endif

View File

@@ -14,6 +14,8 @@ public:
bool publishPrices(EntsoeApi*); bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*); bool publishSystem(HwTools*);
static const uint16_t BufferSize = 128;
private: private:
String topic; String topic;
bool full; bool full;

View File

@@ -14,6 +14,10 @@
#define HEAD_HTML_LEN HEAD32_HTML_LEN #define HEAD_HTML_LEN HEAD32_HTML_LEN
#endif #endif
#if defined(ESP32)
#include <esp_task_wdt.h>
#endif
#include "root/foot_html.h" #include "root/foot_html.h"
#include "root/index_html.h" #include "root/index_html.h"
#include "root/application_js.h" #include "root/application_js.h"
@@ -50,6 +54,7 @@
AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) { AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) {
this->debugger = Debug; this->debugger = Debug;
this->hw = hw; this->hw = hw;
this->json = (char*) malloc(JsonSize);
} }
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds) { void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds) {
@@ -322,9 +327,6 @@ void AmsWebServer::indexHtml() {
for(int i = 0; i<255; i++) { for(int i = 0; i<255; i++) {
html.replace("${config.boardType" + String(i) + "}", sys.boardType == i ? "selected" : ""); html.replace("${config.boardType" + String(i) + "}", sys.boardType == i ? "selected" : "");
} }
for(int i = 0; i<5; i++) {
html.replace("${config.meterType" + String(i) + "}", sys.boardType == i ? "selected" : "");
}
html.replace("${config.wifiSsid}", wifi.ssid); html.replace("${config.wifiSsid}", wifi.ssid);
html.replace("${config.wifiPassword}", wifi.psk); html.replace("${config.wifiPassword}", wifi.psk);
html.replace("${config.wifiStaticIp}", strlen(wifi.ip) > 0 ? "checked" : ""); html.replace("${config.wifiStaticIp}", strlen(wifi.ip) > 0 ? "checked" : "");
@@ -457,9 +459,6 @@ void AmsWebServer::configMeterHtml() {
html.replace("{d" + String(i) + "}", meterConfig->distributionSystem == i ? "selected" : ""); html.replace("{d" + String(i) + "}", meterConfig->distributionSystem == i ? "selected" : "");
} }
html.replace("{f}", String(meterConfig->mainFuse)); html.replace("{f}", String(meterConfig->mainFuse));
for(int i = 0; i<64; i++) {
html.replace("{f" + String(i) + "}", meterConfig->mainFuse == i ? "selected" : "");
}
html.replace("{p}", String(meterConfig->productionCapacity)); html.replace("{p}", String(meterConfig->productionCapacity));
if(meterConfig->encryptionKey[0] != 0x00) { if(meterConfig->encryptionKey[0] != 0x00) {
@@ -497,12 +496,21 @@ void AmsWebServer::configWifiHtml() {
html.replace("{s}", wifi.ssid); html.replace("{s}", wifi.ssid);
html.replace("{p}", wifi.psk); html.replace("{p}", wifi.psk);
html.replace("{st}", strlen(wifi.ip) > 0 ? "checked" : ""); if(strlen(wifi.ip) > 0) {
html.replace("{i}", wifi.ip); html.replace("{st}", "checked");
html.replace("{g}", wifi.gateway); html.replace("{i}", wifi.ip);
html.replace("{sn}", wifi.subnet); html.replace("{g}", wifi.gateway);
html.replace("{d1}", wifi.dns1); html.replace("{sn}", wifi.subnet);
html.replace("{d2}", wifi.dns2); html.replace("{d1}", wifi.dns1);
html.replace("{d2}", wifi.dns2);
} else {
html.replace("{st}", "");
html.replace("{i}", WiFi.localIP().toString());
html.replace("{g}", WiFi.gatewayIP().toString());
html.replace("{sn}", WiFi.subnetMask().toString());
html.replace("{d1}", WiFi.dnsIP().toString());
html.replace("{d2}", "");
}
html.replace("{h}", wifi.hostname); html.replace("{h}", wifi.hostname);
html.replace("{m}", wifi.mdns ? "checked" : ""); html.replace("{m}", wifi.mdns ? "checked" : "");
@@ -747,8 +755,7 @@ void AmsWebServer::dataJson() {
if(eapi != NULL && strlen(eapi->getToken()) > 0) if(eapi != NULL && strlen(eapi->getToken()) > 0)
price = eapi->getValueForHour(0); price = eapi->getValueForHour(0);
char json[384]; snprintf_P(json, JsonSize, DATA_JSON,
snprintf_P(json, sizeof(json), DATA_JSON,
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr, maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
meterConfig->productionCapacity, meterConfig->productionCapacity,
meterConfig->mainFuse == 0 ? 32 : meterConfig->mainFuse, meterConfig->mainFuse == 0 ? 32 : meterConfig->mainFuse,
@@ -781,8 +788,9 @@ void AmsWebServer::dataJson() {
mqttStatus, mqttStatus,
mqtt == NULL ? 0 : (int) mqtt->lastError(), mqtt == NULL ? 0 : (int) mqtt->lastError(),
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(), price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
time(nullptr), meterState->getMeterType(),
meterState->getMeterType() meterConfig->distributionSystem,
(uint32_t) time(nullptr)
); );
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
@@ -798,8 +806,7 @@ void AmsWebServer::dayplotJson() {
if(ds == NULL) { if(ds == NULL) {
notFound(); notFound();
} else { } else {
char json[384]; snprintf_P(json, JsonSize, DAYPLOT_JSON,
snprintf_P(json, sizeof(json), DAYPLOT_JSON,
ds->getHour(0) / 1000.0, ds->getHour(0) / 1000.0,
ds->getHour(1) / 1000.0, ds->getHour(1) / 1000.0,
ds->getHour(2) / 1000.0, ds->getHour(2) / 1000.0,
@@ -841,8 +848,7 @@ void AmsWebServer::monthplotJson() {
if(ds == NULL) { if(ds == NULL) {
notFound(); notFound();
} else { } else {
char json[512]; snprintf_P(json, JsonSize, MONTHPLOT_JSON,
snprintf_P(json, sizeof(json), MONTHPLOT_JSON,
ds->getDay(1) / 1000.0, ds->getDay(1) / 1000.0,
ds->getDay(2) / 1000.0, ds->getDay(2) / 1000.0,
ds->getDay(3) / 1000.0, ds->getDay(3) / 1000.0,
@@ -893,8 +899,7 @@ void AmsWebServer::energyPriceJson() {
prices[i] = eapi == NULL ? ENTSOE_NO_VALUE : eapi->getValueForHour(i); prices[i] = eapi == NULL ? ENTSOE_NO_VALUE : eapi->getValueForHour(i);
} }
char json[768]; snprintf_P(json, JsonSize, ENERGYPRICE_JSON,
snprintf_P(json, sizeof(json), ENERGYPRICE_JSON,
eapi == NULL ? "" : eapi->getCurrency(), eapi == NULL ? "" : eapi->getCurrency(),
prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 2).c_str(), prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 2).c_str(),
prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 2).c_str(), prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 2).c_str(),
@@ -1029,7 +1034,6 @@ void AmsWebServer::handleSetup() {
break; break;
case 200: // ESP32 case 200: // ESP32
gpioConfig->hanPin = 16; gpioConfig->hanPin = 16;
gpioConfig->apPin = 0;
gpioConfig->ledPin = 2; gpioConfig->ledPin = 2;
gpioConfig->ledInverted = false; gpioConfig->ledInverted = false;
break; break;
@@ -1117,6 +1121,7 @@ void AmsWebServer::handleSave() {
meterConfig->distributionSystem = server.arg("d").toInt(); meterConfig->distributionSystem = server.arg("d").toInt();
meterConfig->mainFuse = server.arg("f").toInt(); meterConfig->mainFuse = server.arg("f").toInt();
meterConfig->productionCapacity = server.arg("p").toInt(); meterConfig->productionCapacity = server.arg("p").toInt();
maxPwr = 0;
String encryptionKeyHex = server.arg("e"); String encryptionKeyHex = server.arg("e");
if(!encryptionKeyHex.isEmpty()) { if(!encryptionKeyHex.isEmpty()) {
@@ -1561,6 +1566,13 @@ void AmsWebServer::firmwareUpload() {
String filename = upload.filename; String filename = upload.filename;
if(!filename.endsWith(".bin")) { if(!filename.endsWith(".bin")) {
server.send(500, "text/plain", "500: couldn't create file"); server.send(500, "text/plain", "500: couldn't create file");
} else {
#if defined(ESP32)
esp_task_wdt_delete(NULL);
esp_task_wdt_deinit();
#elif defined(ESP8266)
ESP.wdtDisable();
#endif
} }
} }
uploadFile(FILE_FIRMWARE); uploadFile(FILE_FIRMWARE);
@@ -1584,6 +1596,7 @@ void AmsWebServer::firmwareDownload() {
printI("Downloading firmware..."); printI("Downloading firmware...");
HTTPClient httpClient; HTTPClient httpClient;
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
httpClient.setTimeout(20000);
httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION)); httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION));
#if defined(ESP8266) #if defined(ESP8266)
@@ -1602,6 +1615,13 @@ void AmsWebServer::firmwareDownload() {
if(status == HTTP_CODE_OK) { if(status == HTTP_CODE_OK) {
printD("Received OK from server"); printD("Received OK from server");
if(LittleFS.begin()) { if(LittleFS.begin()) {
#if defined(ESP32)
esp_task_wdt_delete(NULL);
esp_task_wdt_deinit();
#elif defined(ESP8266)
ESP.wdtDisable();
#endif
printI("Downloading firmware to LittleFS"); printI("Downloading firmware to LittleFS");
file = LittleFS.open(FILE_FIRMWARE, "w"); file = LittleFS.open(FILE_FIRMWARE, "w");
int len = httpClient.writeToStream(&file); int len = httpClient.writeToStream(&file);

View File

@@ -55,6 +55,9 @@ private:
File file; File file;
bool performRestart = false; bool performRestart = false;
static const uint16_t JsonSize = 768;
char* json;
#if defined(ESP8266) #if defined(ESP8266)
ESP8266WebServer server; ESP8266WebServer server;
#elif defined(ESP32) // ARDUINO_ARCH_ESP32 #elif defined(ESP32) // ARDUINO_ARCH_ESP32

View File

@@ -1,5 +1,6 @@
var nextVersion; var nextVersion;
var im, em; var im, em;
var ds = 0;
// Price plot // Price plot
var pp; var pp;
@@ -231,9 +232,9 @@ $(function() {
// For wifi // For wifi
$('#st').on('change', function() { $('#st').on('change', function() {
if($(this).is(':checked')) { if($(this).is(':checked')) {
$('#i').show(); $('.sip').prop('disabled', false);
} else { } else {
$('#i').hide(); $('.sip').prop('disabled', true);
} }
}); });
$('#st').trigger('change'); $('#st').trigger('change');
@@ -597,12 +598,6 @@ var fetch = function() {
dataType: 'json', dataType: 'json',
}).done(function(json) { }).done(function(json) {
retrycount = 0; retrycount = 0;
if(im) {
$(".SimpleMeter").hide();
im.show();
em.show();
}
for(var id in json) { for(var id in json) {
var str = json[id]; var str = json[id];
@@ -622,6 +617,8 @@ var fetch = function() {
$('.ju').html(moment.duration(parseInt(json.u), 'seconds').humanize()); $('.ju').html(moment.duration(parseInt(json.u), 'seconds').humanize());
} }
ds = parseInt(json.ds);
var kib = parseInt(json.m)/1000; var kib = parseInt(json.m)/1000;
$('.jm').html(kib.toFixed(1)); $('.jm').html(kib.toFixed(1));
if(kib > 32) { if(kib > 32) {
@@ -677,6 +674,14 @@ var fetch = function() {
} }
if(vp) { if(vp) {
switch(ds) {
case 1:
vo.title = 'Voltage between';
break;
case 2:
vo.title = 'Phase voltage';
break;
}
var c = 0; var c = 0;
var t = 0; var t = 0;
var r = 1; var r = 1;
@@ -686,21 +691,21 @@ var fetch = function() {
t += u1; t += u1;
c++; c++;
var pct = (Math.max(parseFloat(json.u1)-195.5, 1)*100/69); var pct = (Math.max(parseFloat(json.u1)-195.5, 1)*100/69);
arr[r++] = ['L1', u1, "color: " + voltcol(pct) + ";opacity: 0.9;", u1 + "V"]; arr[r++] = [ds == 1 ? 'L1-L2' : 'L1', u1, "color: " + voltcol(pct) + ";opacity: 0.9;", u1 + "V"];
} }
if(json.u2) { if(json.u2) {
var u2 = parseFloat(json.u2); var u2 = parseFloat(json.u2);
t += u2; t += u2;
c++; c++;
var pct = (Math.max(parseFloat(json.u2)-195.5, 1)*100/69); var pct = (Math.max(parseFloat(json.u2)-195.5, 1)*100/69);
arr[r++] = ['L2', u2, "color: " + voltcol(pct) + ";opacity: 0.9;", u2 + "V"]; arr[r++] = [ds == 1 ? 'L1-L3' : 'L2', u2, "color: " + voltcol(pct) + ";opacity: 0.9;", u2 + "V"];
} }
if(json.u3) { if(json.u3) {
var u3 = parseFloat(json.u3); var u3 = parseFloat(json.u3);
t += u3; t += u3;
c++; c++;
var pct = (Math.max(parseFloat(json.u3)-195.5, 1)*100/69); var pct = (Math.max(parseFloat(json.u3)-195.5, 1)*100/69);
arr[r++] = ['L3', u3, "color: " + voltcol(pct) + ";opacity: 0.9;", u3 + "V"]; arr[r++] = [ds == 1 ? 'L2-L3' : 'L3', u3, "color: " + voltcol(pct) + ";opacity: 0.9;", u3 + "V"];
} }
v = t/c; v = t/c;
if(v > 0) { if(v > 0) {
@@ -710,22 +715,30 @@ var fetch = function() {
} }
if(ap && json.mf) { if(ap && json.mf) {
switch(ds) {
case 1:
ao.title = 'Line current';
break;
case 2:
ao.title = 'Phase current';
break;
}
var a = 0; var a = 0;
var r = 1; var r = 1;
var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]]; var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]];
if(json.i1) { if(json.i1 || json.u1) {
var i1 = parseFloat(json.i1); var i1 = parseFloat(json.i1);
a = Math.max(a, i1); a = Math.max(a, i1);
var pct = (parseFloat(json.i1)/parseInt(json.mf))*100; var pct = (parseFloat(json.i1)/parseInt(json.mf))*100;
arr[r++] = ['L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"]; arr[r++] = ['L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"];
} }
if(json.i2) { if(json.i2 || json.u2) {
var i2 = parseFloat(json.i2); var i2 = parseFloat(json.i2);
a = Math.max(a, i2); a = Math.max(a, i2);
var pct = (parseFloat(json.i2)/parseInt(json.mf))*100; var pct = (parseFloat(json.i2)/parseInt(json.mf))*100;
arr[r++] = ['L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"]; arr[r++] = ['L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"];
} }
if(json.i3) { if(json.i3 || json.u3) {
var i3 = parseFloat(json.i3); var i3 = parseFloat(json.i3);
a = Math.max(a, i3); a = Math.max(a, i3);
var pct = (parseFloat(json.i3)/parseInt(json.mf))*100; var pct = (parseFloat(json.i3)/parseInt(json.mf))*100;

View File

@@ -31,6 +31,7 @@
"mm" : %d, "mm" : %d,
"me" : %d, "me" : %d,
"p" : %s, "p" : %s,
"c" : %lu, "mt" : %d,
"mt" : %d "ds" : %d,
"c" : %lu
} }

View File

@@ -44,8 +44,8 @@
<div class="text-center overlay-plot"> <div class="text-center overlay-plot">
<div id="xp" class="plot1"></div> <div id="xp" class="plot1"></div>
<span class="plot-overlay"> <span class="plot-overlay">
<span class="xpo">{PO}</span> <span class="epo">{PO}</span>
<span class="xpoa">W</span> <span class="epoa">W</span>
<br/> <br/>
<span class="pol">Export</span> <span class="pol">Export</span>
</span> </span>

View File

@@ -61,7 +61,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-6"> <div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
<div class="m-2 input-group input-group-sm"> <div class="m-2 input-group input-group-sm">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Distribution system</span> <span class="input-group-text">Distribution system</span>
@@ -73,22 +73,15 @@
</select> </select>
</div> </div>
</div> </div>
<div class="col-xl-2 col-md-3 col-sm-5"> <div class="col-lg-3 col-md-4 col-sm-5">
<div class="m-2 input-group input-group-sm"> <div class="m-2 input-group input-group-sm">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Main fuse</span> <span class="input-group-text">Main fuse</span>
</div> </div>
<select class="form-control" name="f"> <input class="form-control text-right" name="f" type="number" min="5" max="255" step="1" value="{f}"/>
<option value="0" {f0}></option> <div class="input-group-append">
<option value="16" {f16}>16A</option> <span class="input-group-text">A</span>
<option value="20" {f20}>20A</option> </div>
<option value="25" {f25}>25A</option>
<option value="32" {f32}>32A</option>
<option value="35" {f35}>35A</option>
<option value="40" {f40}>40A</option>
<option value="50" {f50}>50A</option>
<option value="63" {f63}>63A</option>
</select>
</div> </div>
</div> </div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7"> <div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
@@ -96,7 +89,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Production capacity</span> <span class="input-group-text">Production capacity</span>
</div> </div>
<input class="form-control" name="p" type="number" min="0" max="50" value="{p}"/> <input class="form-control text-right" name="p" type="number" min="0" max="255" step="1" value="{p}"/>
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text">kWp</span> <span class="input-group-text">kWp</span>
</div> </div>

View File

@@ -42,7 +42,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">IP</span> <span class="input-group-text">IP</span>
</div> </div>
<input type="text" name="i" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{i}"/> <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> </div>
<div class="col-xl-3 col-lg-4 form-group"> <div class="col-xl-3 col-lg-4 form-group">
@@ -50,7 +50,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Subnet</span> <span class="input-group-text">Subnet</span>
</div> </div>
<input type="text" name="sn" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{sn}"/> <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> </div>
<div class="col-xl-3 col-lg-4 form-group"> <div class="col-xl-3 col-lg-4 form-group">
@@ -58,7 +58,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">Gateway</span> <span class="input-group-text">Gateway</span>
</div> </div>
<input type="text" name="g" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{g}"/> <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> </div>
<div class="col-xl-4 col-lg-5 form-group"> <div class="col-xl-4 col-lg-5 form-group">
@@ -66,7 +66,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">DNS 1</span> <span class="input-group-text">DNS 1</span>
</div> </div>
<input type="text" name="d1" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d1}"/> <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> </div>
<div class="col-xl-4 col-lg-5 form-group"> <div class="col-xl-4 col-lg-5 form-group">
@@ -74,7 +74,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">DNS 2</span> <span class="input-group-text">DNS 2</span>
</div> </div>
<input type="text" name="d2" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d2}"/> <input type="text" name="d2" class="form-control sip" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d2}"/>
</div> </div>
</div> </div>
</div> </div>

BIN
webui2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB