mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 13:05:31 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd87d70876 | ||
|
|
52992f09ee | ||
|
|
6aa02d54c8 | ||
|
|
9dbf9137c7 | ||
|
|
c0a9a01b41 | ||
|
|
7b203b196f | ||
|
|
50c06e2cfe | ||
|
|
e4d4753181 | ||
|
|
04daf551fb | ||
|
|
4b51f0f235 | ||
|
|
5e4ccca663 | ||
|
|
6b0ec39759 | ||
|
|
1f7d845a32 | ||
|
|
6de5b719f3 |
@@ -232,6 +232,8 @@ bool AmsConfiguration::getDebugConfig(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.put(CONFIG_DEBUG_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
@@ -449,6 +451,9 @@ bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENTSOE_START, config);
|
||||
EEPROM.end();
|
||||
if(strlen(config.token) != 0 && strlen(config.token) != 36) {
|
||||
clearEntsoe(config);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -198,6 +198,9 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
float iph = im / 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)) {
|
||||
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;
|
||||
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) {
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
ADC_MODE(ADC_VCC);
|
||||
#else if defined(ESP32)
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
#define WDT_TIMEOUT 10
|
||||
#define WDT_TIMEOUT 30
|
||||
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include "AmsStorage.h"
|
||||
@@ -135,12 +137,14 @@ void setup() {
|
||||
hw.ledBlink(LED_GREEN, 1);
|
||||
hw.ledBlink(LED_BLUE, 1);
|
||||
|
||||
#if defined(ESP32)
|
||||
EntsoeConfig entsoe;
|
||||
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
|
||||
eapi = new EntsoeApi(&Debug);
|
||||
eapi->setup(entsoe);
|
||||
ws.setEntsoeApi(eapi);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool shared = false;
|
||||
config.getMeterConfig(meterConfig);
|
||||
@@ -434,6 +438,7 @@ void loop() {
|
||||
mqtt->disconnect();
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
if(eapi != NULL && ntpEnabled) {
|
||||
if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
@@ -451,10 +456,11 @@ void loop() {
|
||||
} else if(eapi != NULL) {
|
||||
delete eapi;
|
||||
eapi = NULL;
|
||||
ws.setEntsoeApi(eapi);
|
||||
ws.setEntsoeApi(NULL);
|
||||
}
|
||||
config.ackEntsoeChange();
|
||||
}
|
||||
#endif
|
||||
ws.loop();
|
||||
}
|
||||
if(mqtt != NULL) { // Run loop regardless, to let MQTT do its work.
|
||||
@@ -553,8 +559,10 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
|
||||
#if defined(ESP32)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
hwSerial->setRxBufferSize(768);
|
||||
#else
|
||||
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
|
||||
hwSerial->setRxBufferSize(768);
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
@@ -699,7 +707,6 @@ bool readHanPort() {
|
||||
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||
buf[len++] = hanSerial->read();
|
||||
pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
delay(1);
|
||||
}
|
||||
if(len > 0) {
|
||||
if(len >= BUF_SIZE) {
|
||||
@@ -749,7 +756,7 @@ bool readHanPort() {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(buf, 0, len);
|
||||
}
|
||||
if(hc != NULL && Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
if(hc != NULL && Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("System title:");
|
||||
debugPrint(hc->system_title, 0, 8);
|
||||
debugD("Initialization vector:");
|
||||
@@ -762,7 +769,7 @@ bool readHanPort() {
|
||||
len = 0;
|
||||
while(hanSerial->available()) hanSerial->read();
|
||||
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);
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::WARNING)) {
|
||||
@@ -967,7 +974,9 @@ void WiFi_connect() {
|
||||
} else if(dns1.toString().isEmpty()) {
|
||||
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 {
|
||||
#if defined(ESP32)
|
||||
// This trick does not work anymore...
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "Timezone.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distributionSystem, CosemDateTime packageTimestamp, HDLCConfig* hc) {
|
||||
uint32_t ui;
|
||||
double val;
|
||||
char str[64];
|
||||
|
||||
@@ -13,8 +12,8 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
|
||||
|
||||
this->packageTimestamp = getTimestamp(packageTimestamp);
|
||||
|
||||
ui = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(ui == 0xFFFFFFFF) {
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(val == NOVALUE) {
|
||||
CosemData* data = getCosemDataAt(1, ((char *) (d)));
|
||||
|
||||
// Kaifa special case...
|
||||
@@ -137,7 +136,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
|
||||
// Kaifa end
|
||||
} else {
|
||||
listType = 1;
|
||||
activeImportPower = ui;
|
||||
activeImportPower = val;
|
||||
|
||||
meterType = AmsTypeUnknown;
|
||||
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;
|
||||
}
|
||||
|
||||
ui = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
if(ui > 0) {
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
listId = String(str);
|
||||
}
|
||||
|
||||
ui = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
|
||||
if(ui != 0xFFFFFFFF) {
|
||||
activeExportPower = ui;
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
|
||||
if(val != NOVALUE) {
|
||||
activeExportPower = val;
|
||||
}
|
||||
|
||||
ui = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
|
||||
if(ui != 0xFFFFFFFF) {
|
||||
reactiveImportPower = ui;
|
||||
val = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
|
||||
if(val != NOVALUE) {
|
||||
reactiveImportPower = val;
|
||||
}
|
||||
|
||||
ui = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
|
||||
if(ui != 0xFFFFFFFF) {
|
||||
reactiveExportPower = ui;
|
||||
val = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
|
||||
if(val != NOVALUE) {
|
||||
reactiveExportPower = val;
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l1voltage = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l2voltage = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l3voltage = val;
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l1current = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l2current = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 2;
|
||||
l3current = val;
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 3;
|
||||
activeImportCounter = val / 1000.0;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 3;
|
||||
activeExportCounter = val / 1000.0;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 3;
|
||||
reactiveImportCounter = val / 1000.0;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
listType = 3;
|
||||
reactiveExportCounter = val / 1000.0;
|
||||
}
|
||||
|
||||
ui = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
|
||||
if(ui > 0) {
|
||||
str_len = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterModel = String(str);
|
||||
} else {
|
||||
ui = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
|
||||
if(ui > 0) {
|
||||
str_len = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterModel = String(str);
|
||||
}
|
||||
}
|
||||
|
||||
ui = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
|
||||
if(ui > 0) {
|
||||
str_len = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterId = String(str);
|
||||
} else {
|
||||
ui = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
|
||||
if(ui > 0) {
|
||||
str_len = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
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)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
powerFactor = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
l1PowerFactor = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
l2PowerFactor = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d)));
|
||||
if(val != 0xFFFFFFFF) {
|
||||
if(val != NOVALUE) {
|
||||
l3PowerFactor = val;
|
||||
}
|
||||
|
||||
@@ -335,14 +335,15 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distribution
|
||||
}
|
||||
|
||||
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
|
||||
if(distributionSystem == 1) {
|
||||
if(threePhase) {
|
||||
if(l2current == 0.0 && l1current > 0.0 && l3current > 0.0) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
if(activeExportPower == 0) {
|
||||
if(activeExportPower == 0.0) {
|
||||
l2current = max((float) 0.0, l2current);
|
||||
}
|
||||
}
|
||||
@@ -453,31 +454,38 @@ double IEC6205675::getNumber(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
}
|
||||
|
||||
double IEC6205675::getNumber(CosemData* item) {
|
||||
double val = 0xFFFFFFFF;
|
||||
if(item != NULL) {
|
||||
double ret = 0.0;
|
||||
char* pos = ((char*) item);
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongUnsigned:
|
||||
val = ntohs(item->lu.data);
|
||||
case CosemTypeLongUnsigned: {
|
||||
uint16_t u16 = ntohs(item->lu.data);
|
||||
ret = u16;
|
||||
pos += 3;
|
||||
break;
|
||||
case CosemTypeDLongUnsigned:
|
||||
val = ntohl(item->dlu.data);
|
||||
}
|
||||
case CosemTypeDLongUnsigned: {
|
||||
uint32_t u32 = ntohl(item->dlu.data);
|
||||
ret = u32;
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeLongSigned:
|
||||
val = ntohs(item->lu.data);
|
||||
}
|
||||
case CosemTypeLongSigned: {
|
||||
int16_t i16 = ntohs(item->ls.data);
|
||||
ret = i16;
|
||||
pos += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(pos != NULL) {
|
||||
if(*pos++ == 0x02 && *pos++ == 0x02) {
|
||||
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) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "AmsData.h"
|
||||
#include "ams/hdlc.h"
|
||||
|
||||
#define NOVALUE 0xFFFFFFFF
|
||||
|
||||
struct AmsOctetTimestamp {
|
||||
uint8_t type;
|
||||
CosemDateTime dt;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "DnbCurrParser.h"
|
||||
#include "Arduino.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
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) {
|
||||
if(pos >= 64) pos = 0;
|
||||
if(pos == 0) {
|
||||
if(byte == '<') {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(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++) {
|
||||
if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) {
|
||||
pos = i + 11;
|
||||
@@ -49,7 +66,7 @@ size_t DnbCurrParser::write(uint8_t byte) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
value = String(buf+pos).toFloat();
|
||||
value = String(buf+pos).toFloat() / pow(10, scale);
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
size_t write(uint8_t);
|
||||
|
||||
private:
|
||||
uint8_t scale = 0;
|
||||
float value = 1.0;
|
||||
|
||||
char buf[64];
|
||||
|
||||
@@ -14,6 +14,7 @@ char* EntsoeA44Parser::getMeasurementUnit() {
|
||||
}
|
||||
|
||||
float EntsoeA44Parser::getPoint(uint8_t position) {
|
||||
if(position >= 24) return ENTSOE_NO_VALUE;
|
||||
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) {
|
||||
if(pos >= 64) pos = 0;
|
||||
if(docPos == DOCPOS_CURRENCY) {
|
||||
buf[pos++] = byte;
|
||||
if(pos == 3) {
|
||||
|
||||
@@ -4,21 +4,23 @@
|
||||
#include "TimeLib.h"
|
||||
#include "DnbCurrParser.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
||||
debugger = Debug;
|
||||
|
||||
client.setInsecure();
|
||||
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
https.setTimeout(20000);
|
||||
|
||||
// Entso-E uses CET/CEST
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
|
||||
tomorrowFetchMillis = 36000000 + (random(1800) * 1000); // Random between 13:30 and 14:00
|
||||
}
|
||||
|
||||
void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
@@ -26,6 +28,7 @@ void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
this->config = new EntsoeConfig();
|
||||
}
|
||||
memcpy(this->config, &config, sizeof(config));
|
||||
lastCurrencyFetch = 0;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getToken() {
|
||||
@@ -61,8 +64,10 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
multiplier *= getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
|
||||
} else {
|
||||
float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
if(today == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = today->getPoint(pos);
|
||||
@@ -71,7 +76,9 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
@@ -79,7 +86,6 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
|
||||
bool EntsoeApi::loop() {
|
||||
if(strlen(getToken()) == 0)
|
||||
return false;
|
||||
bool ret = false;
|
||||
|
||||
uint64_t now = millis64();
|
||||
if(now < 10000) return false; // Grace period
|
||||
@@ -121,21 +127,31 @@ bool EntsoeApi::loop() {
|
||||
d2.Year+1970, d2.Month, d2.Day, 23, 00,
|
||||
config->area, config->area);
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for today\n");
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
|
||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
|
||||
today = a44;
|
||||
ret = true;
|
||||
return true;
|
||||
} else if(a44 != NULL) {
|
||||
delete a44;
|
||||
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
|
||||
&& midnightMillis - now < 39600000 // Fetch 11hrs before midnight (13:00 CE(S)T)
|
||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 300000) // Retry every 5min
|
||||
&& midnightMillis - now < tomorrowFetchMillis
|
||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
|
||||
) {
|
||||
lastTomorrowFetch = now;
|
||||
time_t e1 = time(nullptr);
|
||||
@@ -151,23 +167,29 @@ bool EntsoeApi::loop() {
|
||||
d2.Year+1970, d2.Month, d2.Day, 23, 00,
|
||||
config->area, config->area);
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for tomorrow\n");
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
|
||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||
if(retrieve(url, a44) && a44->getPoint(0) != ENTSOE_NO_VALUE) {
|
||||
tomorrow = a44;
|
||||
ret = true;
|
||||
return true;
|
||||
} else if(a44 != NULL) {
|
||||
delete a44;
|
||||
tomorrow = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
WiFiClientSecure client;
|
||||
#if defined(ESP8266)
|
||||
// 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.
|
||||
@@ -185,13 +207,15 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
*/
|
||||
#endif
|
||||
|
||||
client.setInsecure();
|
||||
|
||||
HTTPClient https;
|
||||
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
|
||||
if(https.begin(client, url)) {
|
||||
printD("Connection established");
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
/*
|
||||
#if defined(ESP8266)
|
||||
if(!client.getMFLNStatus()) {
|
||||
@@ -204,6 +228,13 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
*/
|
||||
|
||||
int status = https.GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Receiving data");
|
||||
https.writeToStream(doc);
|
||||
@@ -239,18 +270,40 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
||||
return 1.00;
|
||||
|
||||
uint64_t now = millis64();
|
||||
if(lastCurrencyFetch == 0 || now - lastCurrencyFetch > (SECS_PER_HOUR * 1000)) {
|
||||
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();
|
||||
}
|
||||
if(now > lastCurrencyFetch && (now - lastCurrencyFetch) < 900000) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
#include "EntsoeA44Parser.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 SSL_BUF_SIZE 512
|
||||
|
||||
@@ -24,7 +32,10 @@ public:
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
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 lastTodayFetch = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#define HEAD_HTML_LEN HEAD32_HTML_LEN
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
#include "root/foot_html.h"
|
||||
#include "root/index_html.h"
|
||||
#include "root/application_js.h"
|
||||
@@ -322,9 +326,6 @@ void AmsWebServer::indexHtml() {
|
||||
for(int i = 0; i<255; i++) {
|
||||
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.wifiPassword}", wifi.psk);
|
||||
html.replace("${config.wifiStaticIp}", strlen(wifi.ip) > 0 ? "checked" : "");
|
||||
@@ -457,9 +458,6 @@ void AmsWebServer::configMeterHtml() {
|
||||
html.replace("{d" + String(i) + "}", meterConfig->distributionSystem == i ? "selected" : "");
|
||||
}
|
||||
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));
|
||||
|
||||
if(meterConfig->encryptionKey[0] != 0x00) {
|
||||
@@ -497,12 +495,21 @@ void AmsWebServer::configWifiHtml() {
|
||||
|
||||
html.replace("{s}", wifi.ssid);
|
||||
html.replace("{p}", wifi.psk);
|
||||
html.replace("{st}", strlen(wifi.ip) > 0 ? "checked" : "");
|
||||
html.replace("{i}", wifi.ip);
|
||||
html.replace("{g}", wifi.gateway);
|
||||
html.replace("{sn}", wifi.subnet);
|
||||
html.replace("{d1}", wifi.dns1);
|
||||
html.replace("{d2}", wifi.dns2);
|
||||
if(strlen(wifi.ip) > 0) {
|
||||
html.replace("{st}", "checked");
|
||||
html.replace("{i}", wifi.ip);
|
||||
html.replace("{g}", wifi.gateway);
|
||||
html.replace("{sn}", wifi.subnet);
|
||||
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("{m}", wifi.mdns ? "checked" : "");
|
||||
|
||||
@@ -747,7 +754,7 @@ void AmsWebServer::dataJson() {
|
||||
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
||||
price = eapi->getValueForHour(0);
|
||||
|
||||
char json[384];
|
||||
char json[512];
|
||||
snprintf_P(json, sizeof(json), DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
meterConfig->productionCapacity,
|
||||
@@ -781,8 +788,9 @@ void AmsWebServer::dataJson() {
|
||||
mqttStatus,
|
||||
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
||||
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");
|
||||
@@ -1029,7 +1037,6 @@ void AmsWebServer::handleSetup() {
|
||||
break;
|
||||
case 200: // ESP32
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = false;
|
||||
break;
|
||||
@@ -1117,6 +1124,7 @@ void AmsWebServer::handleSave() {
|
||||
meterConfig->distributionSystem = server.arg("d").toInt();
|
||||
meterConfig->mainFuse = server.arg("f").toInt();
|
||||
meterConfig->productionCapacity = server.arg("p").toInt();
|
||||
maxPwr = 0;
|
||||
|
||||
String encryptionKeyHex = server.arg("e");
|
||||
if(!encryptionKeyHex.isEmpty()) {
|
||||
@@ -1561,6 +1569,13 @@ void AmsWebServer::firmwareUpload() {
|
||||
String filename = upload.filename;
|
||||
if(!filename.endsWith(".bin")) {
|
||||
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);
|
||||
@@ -1584,6 +1599,7 @@ void AmsWebServer::firmwareDownload() {
|
||||
printI("Downloading firmware...");
|
||||
HTTPClient httpClient;
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.setTimeout(20000);
|
||||
httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION));
|
||||
|
||||
#if defined(ESP8266)
|
||||
@@ -1602,6 +1618,13 @@ void AmsWebServer::firmwareDownload() {
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Received OK from server");
|
||||
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");
|
||||
file = LittleFS.open(FILE_FIRMWARE, "w");
|
||||
int len = httpClient.writeToStream(&file);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var nextVersion;
|
||||
var im, em;
|
||||
var ds = 0;
|
||||
|
||||
// Price plot
|
||||
var pp;
|
||||
@@ -231,9 +232,9 @@ $(function() {
|
||||
// For wifi
|
||||
$('#st').on('change', function() {
|
||||
if($(this).is(':checked')) {
|
||||
$('#i').show();
|
||||
$('.sip').prop('disabled', false);
|
||||
} else {
|
||||
$('#i').hide();
|
||||
$('.sip').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
$('#st').trigger('change');
|
||||
@@ -597,12 +598,6 @@ var fetch = function() {
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
retrycount = 0;
|
||||
if(im) {
|
||||
$(".SimpleMeter").hide();
|
||||
im.show();
|
||||
em.show();
|
||||
|
||||
}
|
||||
|
||||
for(var id in json) {
|
||||
var str = json[id];
|
||||
@@ -622,6 +617,8 @@ var fetch = function() {
|
||||
$('.ju').html(moment.duration(parseInt(json.u), 'seconds').humanize());
|
||||
}
|
||||
|
||||
ds = parseInt(json.ds);
|
||||
|
||||
var kib = parseInt(json.m)/1000;
|
||||
$('.jm').html(kib.toFixed(1));
|
||||
if(kib > 32) {
|
||||
@@ -677,6 +674,14 @@ var fetch = function() {
|
||||
}
|
||||
|
||||
if(vp) {
|
||||
switch(ds) {
|
||||
case 1:
|
||||
vo.title = 'Voltage between';
|
||||
break;
|
||||
case 2:
|
||||
vo.title = 'Phase voltage';
|
||||
break;
|
||||
}
|
||||
var c = 0;
|
||||
var t = 0;
|
||||
var r = 1;
|
||||
@@ -686,21 +691,21 @@ var fetch = function() {
|
||||
t += u1;
|
||||
c++;
|
||||
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) {
|
||||
var u2 = parseFloat(json.u2);
|
||||
t += u2;
|
||||
c++;
|
||||
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) {
|
||||
var u3 = parseFloat(json.u3);
|
||||
t += u3;
|
||||
c++;
|
||||
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;
|
||||
if(v > 0) {
|
||||
@@ -710,6 +715,14 @@ var fetch = function() {
|
||||
}
|
||||
|
||||
if(ap && json.mf) {
|
||||
switch(ds) {
|
||||
case 1:
|
||||
ao.title = 'Line current';
|
||||
break;
|
||||
case 2:
|
||||
ao.title = 'Phase current';
|
||||
break;
|
||||
}
|
||||
var a = 0;
|
||||
var r = 1;
|
||||
var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]];
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"mm" : %d,
|
||||
"me" : %d,
|
||||
"p" : %s,
|
||||
"c" : %lu,
|
||||
"mt" : %d
|
||||
"mt" : %d,
|
||||
"ds" : %d,
|
||||
"c" : %lu
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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="input-group-prepend">
|
||||
<span class="input-group-text">Distribution system</span>
|
||||
@@ -73,22 +73,15 @@
|
||||
</select>
|
||||
</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="input-group-prepend">
|
||||
<span class="input-group-text">Main fuse</span>
|
||||
</div>
|
||||
<select class="form-control" name="f">
|
||||
<option value="0" {f0}></option>
|
||||
<option value="16" {f16}>16A</option>
|
||||
<option value="20" {f20}>20A</option>
|
||||
<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>
|
||||
<input class="form-control text-right" name="f" type="number" min="5" max="255" step="1" value="{f}"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">A</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
|
||||
@@ -96,7 +89,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Production capacity</span>
|
||||
</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">
|
||||
<span class="input-group-text">kWp</span>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">IP</span>
|
||||
</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 class="col-xl-3 col-lg-4 form-group">
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Subnet</span>
|
||||
</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 class="col-xl-3 col-lg-4 form-group">
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Gateway</span>
|
||||
</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 class="col-xl-4 col-lg-5 form-group">
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 1</span>
|
||||
</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 class="col-xl-4 col-lg-5 form-group">
|
||||
@@ -74,7 +74,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">DNS 2</span>
|
||||
</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>
|
||||
|
||||
BIN
webui2.png
Normal file
BIN
webui2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
Reference in New Issue
Block a user