Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
751a0edca7 | ||
|
|
a8e62e086c | ||
|
|
35eb69bebb | ||
|
|
5c7c0699b2 | ||
|
|
7e31a60000 | ||
|
|
793bc877fc | ||
|
|
5ab6de21dc | ||
|
|
db5e242ad8 | ||
|
|
5be921a342 | ||
|
|
dd87d70876 | ||
|
|
52992f09ee | ||
|
|
6aa02d54c8 | ||
|
|
9dbf9137c7 | ||
|
|
c0a9a01b41 | ||
|
|
7b203b196f | ||
|
|
50c06e2cfe | ||
|
|
e4d4753181 | ||
|
|
04daf551fb | ||
|
|
4b51f0f235 | ||
|
|
5e4ccca663 | ||
|
|
6b0ec39759 |
@@ -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
|
||||||
|
|||||||
BIN
doc/Germany/Anleitung_M-Bus_Protokoll_V1.0.pdf
Normal file
BIN
doc/M-Bus_DOC48.PDF
Normal file
BIN
doc/omnipower.technical.description.pdf
Normal file
51
frames/Kamstrup-1p.raw
Normal 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
|
After Width: | Height: | Size: 136 KiB |
BIN
images/dayplot.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
images/future-energy-price.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/main-header.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
images/monthplot.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
images/real-time-calculation.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
images/sensor-displays.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
images/status-bar.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/tempsensors.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -17,11 +17,12 @@ extra_scripts =
|
|||||||
scripts/makeweb.py
|
scripts/makeweb.py
|
||||||
build_flags =
|
build_flags =
|
||||||
-D WEBSOCKET_DISABLED=1
|
-D WEBSOCKET_DISABLED=1
|
||||||
|
build_unflags = -fno-exceptions
|
||||||
|
|
||||||
|
|
||||||
[env:esp32]
|
[env:esp32]
|
||||||
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
|
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
|
||||||
board = esp32dev
|
board = esp32doit-devkit-v1
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
lib_ignore = ${common.lib_ignore}
|
lib_ignore = ${common.lib_ignore}
|
||||||
@@ -30,3 +31,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
|
||||||
|
|||||||
@@ -451,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;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ protected:
|
|||||||
String listId, meterId, meterModel;
|
String listId, meterId, meterModel;
|
||||||
time_t meterTimestamp = 0;
|
time_t meterTimestamp = 0;
|
||||||
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
double l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||||
float activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
float activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||||
bool threePhase = false, twoPhase = false, counterEstimated = false;
|
bool threePhase = false, twoPhase = false, counterEstimated = false;
|
||||||
|
|||||||
@@ -253,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ ADC_MODE(ADC_VCC);
|
|||||||
#if defined(ESP32)
|
#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"
|
||||||
@@ -137,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);
|
||||||
@@ -331,7 +333,11 @@ unsigned long lastErrorBlink = 0;
|
|||||||
int lastError = 0;
|
int lastError = 0;
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
Debug.handle();
|
try {
|
||||||
|
Debug.handle();
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
Serial.printf("Exception in Debug loop (%s)\n", e.what());
|
||||||
|
}
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
if(gpioConfig.apPin != 0xFF) {
|
if(gpioConfig.apPin != 0xFF) {
|
||||||
if (digitalRead(gpioConfig.apPin) == LOW) {
|
if (digitalRead(gpioConfig.apPin) == LOW) {
|
||||||
@@ -361,7 +367,11 @@ void loop() {
|
|||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
wifiConnected = false;
|
wifiConnected = false;
|
||||||
Debug.stop();
|
Debug.stop();
|
||||||
WiFi_connect();
|
try {
|
||||||
|
WiFi_connect();
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception in WiFi connect (%s)", e.what());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
wifiReconnectCount = 0;
|
wifiReconnectCount = 0;
|
||||||
if(!wifiConnected) {
|
if(!wifiConnected) {
|
||||||
@@ -375,10 +385,14 @@ void loop() {
|
|||||||
}
|
}
|
||||||
DebugConfig debug;
|
DebugConfig debug;
|
||||||
if(config.getDebugConfig(debug)) {
|
if(config.getDebugConfig(debug)) {
|
||||||
Debug.begin(wifi.hostname, (uint8_t) debug.level);
|
try {
|
||||||
Debug.setSerialEnabled(debug.serial);
|
Debug.begin(wifi.hostname, (uint8_t) debug.level);
|
||||||
if(!debug.telnet) {
|
Debug.setSerialEnabled(debug.serial);
|
||||||
Debug.stop();
|
if(!debug.telnet) {
|
||||||
|
Debug.stop();
|
||||||
|
}
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
Serial.printf("Exception in Debug setup (%s)\n", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||||
@@ -436,32 +450,46 @@ 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
|
||||||
|
try {
|
||||||
|
ws.loop();
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception in Web server loop (%s)", e.what());
|
||||||
}
|
}
|
||||||
ws.loop();
|
|
||||||
}
|
}
|
||||||
if(mqtt != NULL) { // Run loop regardless, to let MQTT do its work.
|
if(mqtt != NULL) {
|
||||||
mqtt->loop();
|
try {
|
||||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
mqtt->loop();
|
||||||
|
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception in MQTT loop (%s)", e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(dnsServer != NULL) {
|
if(dnsServer != NULL) {
|
||||||
@@ -484,17 +512,29 @@ void loop() {
|
|||||||
hc = NULL;
|
hc = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(readHanPort() || now - meterState.getLastUpdateMillis() > 30000) {
|
try {
|
||||||
if(now - lastTemperatureRead > 15000) {
|
if(readHanPort() || now - meterState.getLastUpdateMillis() > 30000) {
|
||||||
unsigned long start = millis();
|
if(now - lastTemperatureRead > 15000) {
|
||||||
hw.updateTemperatures();
|
try {
|
||||||
lastTemperatureRead = now;
|
unsigned long start = millis();
|
||||||
|
hw.updateTemperatures();
|
||||||
|
lastTemperatureRead = now;
|
||||||
|
|
||||||
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
try {
|
||||||
mqttHandler->publishTemperatures(&config, &hw);
|
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
||||||
|
mqttHandler->publishTemperatures(&config, &hw);
|
||||||
|
}
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while publishing temperatures to MQTT (%s)", e.what());
|
||||||
|
}
|
||||||
|
debugD("Used %d ms to update temperature", millis()-start);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while updating temperatures (%s)", e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
debugD("Used %d ms to update temperature", millis()-start);
|
|
||||||
}
|
}
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception in readHanPort (%s)", e.what());
|
||||||
}
|
}
|
||||||
delay(1); // Needed for auto modem sleep
|
delay(1); // Needed for auto modem sleep
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
@@ -555,8 +595,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)
|
||||||
@@ -700,8 +742,11 @@ bool readHanPort() {
|
|||||||
int pos = HDLC_FRAME_INCOMPLETE;
|
int pos = HDLC_FRAME_INCOMPLETE;
|
||||||
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, ×tamp);
|
try {
|
||||||
delay(1);
|
pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while parsing validating HDLC (%s)", e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
if(len >= BUF_SIZE) {
|
if(len >= BUF_SIZE) {
|
||||||
@@ -751,7 +796,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:");
|
||||||
@@ -764,8 +809,12 @@ 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);
|
try {
|
||||||
|
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), meterConfig.distributionSystem, timestamp, hc);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while parsing IEC62056-7-5 (%s)", e.what());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if(Debug.isActive(RemoteDebug::WARNING)) {
|
if(Debug.isActive(RemoteDebug::WARNING)) {
|
||||||
switch(pos) {
|
switch(pos) {
|
||||||
@@ -818,7 +867,11 @@ bool readHanPort() {
|
|||||||
}
|
}
|
||||||
} else if(currentMeterType == 2) {
|
} else if(currentMeterType == 2) {
|
||||||
String payload = hanSerial->readString();
|
String payload = hanSerial->readString();
|
||||||
data = IEC6205621(payload);
|
try {
|
||||||
|
data = IEC6205621(payload);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while parsing IEC62056-21 (%s)", e.what());
|
||||||
|
}
|
||||||
if(data.getListType() == 0) {
|
if(data.getListType() == 0) {
|
||||||
currentMeterType = 1;
|
currentMeterType = 1;
|
||||||
return false;
|
return false;
|
||||||
@@ -834,17 +887,29 @@ bool readHanPort() {
|
|||||||
if(!hw.ledBlink(LED_GREEN, 1))
|
if(!hw.ledBlink(LED_GREEN, 1))
|
||||||
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)) {
|
try {
|
||||||
if(data.getListType() == 3 && eapi != NULL) {
|
if(mqttHandler->publish(&data, &meterState)) {
|
||||||
mqttHandler->publishPrices(eapi);
|
if(data.getListType() == 3 && eapi != NULL) {
|
||||||
|
try {
|
||||||
|
mqttHandler->publishPrices(eapi);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while publishing prices to MQTT (%s)", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(data.getListType() >= 2) {
|
||||||
|
try {
|
||||||
|
mqttHandler->publishSystem(&hw);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while publishing system info to MQTT (%s)", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(data.getListType() >= 2) {
|
if(mqtt != NULL) {
|
||||||
mqttHandler->publishSystem(&hw);
|
mqtt->loop();
|
||||||
|
delay(10);
|
||||||
}
|
}
|
||||||
}
|
} catch(const std::exception& e) {
|
||||||
if(mqtt != NULL) {
|
debugE("Exception while publishing AMS data to MQTT (%s)", e.what());
|
||||||
mqtt->loop();
|
|
||||||
delay(10);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +1034,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...
|
||||||
@@ -1017,7 +1084,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,7 +1183,11 @@ void MQTT_connect() {
|
|||||||
config.ackMqttChange();
|
config.ackMqttChange();
|
||||||
|
|
||||||
if(mqttHandler != NULL) {
|
if(mqttHandler != NULL) {
|
||||||
mqttHandler->publishSystem(&hw);
|
try {
|
||||||
|
mqttHandler->publishSystem(&hw);
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
debugE("Exception while publishing system info to MQTT (%s)", e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Debug.isActive(RemoteDebug::ERROR)) {
|
if (Debug.isActive(RemoteDebug::ERROR)) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -344,7 +352,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
|
|||||||
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.0) {
|
if(activeExportPower == 0.0) {
|
||||||
l2current = max((float) 0.0, l2current);
|
l2current = max((double) 0.0, l2current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
|
} else if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
|
||||||
@@ -380,7 +388,8 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -423,7 +432,8 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -471,9 +481,8 @@ double IEC6205675::getNumber(CosemData* item) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CosemTypeLongSigned: {
|
case CosemTypeLongSigned: {
|
||||||
uint16_t u16 = ntohs(item->lu.data); // ntohs only works for uint16 ?
|
int16_t i16 = ntohs(item->ls.data);
|
||||||
int16_t i16 = u16; // Cast to int16 before use?
|
ret = i16;
|
||||||
ret = i16; // Who knows, got to try it all...
|
|
||||||
pos += 3;
|
pos += 3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -4,21 +4,23 @@
|
|||||||
#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) {
|
||||||
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 +28,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 +64,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 +76,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 +86,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
|
||||||
@@ -121,21 +127,31 @@ bool EntsoeApi::loop() {
|
|||||||
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", url);
|
||||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||||
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
|
if(retrieve(url, 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);
|
||||||
@@ -151,23 +167,29 @@ bool EntsoeApi::loop() {
|
|||||||
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", url);
|
||||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||||
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
|
if(retrieve(url, 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,40 @@ 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;
|
||||||
|
|
||||||
|
char url[256];
|
||||||
|
DnbCurrParser p;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
ESP.wdtFeed();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
snprintf(url, sizeof(url), "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", url);
|
||||||
|
if(retrieve(url, &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(url, sizeof(url), "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", url);
|
||||||
|
if(retrieve(url, &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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(data->getListType() == 1) {
|
if(data->getListType() == 1) {
|
||||||
char json[192];
|
char json[256];
|
||||||
snprintf_P(json, sizeof(json), JSON1_JSON,
|
snprintf_P(json, sizeof(json), JSON1_JSON,
|
||||||
WiFi.macAddress().c_str(),
|
WiFi.macAddress().c_str(),
|
||||||
clientId.c_str(),
|
clientId.c_str(),
|
||||||
@@ -26,7 +26,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];
|
char json[512];
|
||||||
snprintf_P(json, sizeof(json), JSON2_JSON,
|
snprintf_P(json, sizeof(json), JSON2_JSON,
|
||||||
WiFi.macAddress().c_str(),
|
WiFi.macAddress().c_str(),
|
||||||
clientId.c_str(),
|
clientId.c_str(),
|
||||||
@@ -52,7 +52,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];
|
char json[768];
|
||||||
snprintf_P(json, sizeof(json), JSON3_JSON,
|
snprintf_P(json, sizeof(json), JSON3_JSON,
|
||||||
WiFi.macAddress().c_str(),
|
WiFi.macAddress().c_str(),
|
||||||
clientId.c_str(),
|
clientId.c_str(),
|
||||||
|
|||||||
@@ -458,9 +458,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) {
|
||||||
@@ -498,12 +495,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" : "");
|
||||||
|
|
||||||
@@ -748,7 +754,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];
|
char json[512];
|
||||||
snprintf_P(json, sizeof(json), 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,
|
||||||
@@ -782,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");
|
||||||
@@ -1030,7 +1037,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;
|
||||||
@@ -1118,6 +1124,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()) {
|
||||||
@@ -1564,6 +1571,7 @@ void AmsWebServer::firmwareUpload() {
|
|||||||
server.send(500, "text/plain", "500: couldn't create file");
|
server.send(500, "text/plain", "500: couldn't create file");
|
||||||
} else {
|
} else {
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
|
esp_task_wdt_delete(NULL);
|
||||||
esp_task_wdt_deinit();
|
esp_task_wdt_deinit();
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
ESP.wdtDisable();
|
ESP.wdtDisable();
|
||||||
@@ -1591,6 +1599,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)
|
||||||
@@ -1609,6 +1618,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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
After Width: | Height: | Size: 136 KiB |