mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-20 01:55:03 +00:00
2.0 development
This commit is contained in:
parent
ab101c8622
commit
76f8e2c343
@ -23,186 +23,141 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
if(now < EPOCH_2021_01_01) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) New time is: %d\n", now);
|
||||
debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %d\n", now);
|
||||
}
|
||||
}
|
||||
if(now-day.lastMeterReadTime < 3590) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
if(now-day.lastMeterReadTime < 3595) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) It is only %d seconds since last update, ignoring\n", (now-day.lastMeterReadTime));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
|
||||
if(tm.Minute > 5) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Already %d minutes into the hour, ignoring\n", tm.Minute);
|
||||
// Update day plot
|
||||
if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
|
||||
day.activeImport = data->getActiveImportCounter() * 1000;
|
||||
day.activeExport = data->getActiveExportCounter() * 1000;
|
||||
day.lastMeterReadTime = now;
|
||||
for(int i = 0; i<24; i++) {
|
||||
setHour(i, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if(now - day.lastMeterReadTime < 4000) {
|
||||
tmElements_t tm;
|
||||
breakTime(now - 3600, tm);
|
||||
int16_t val = (((data->getActiveImportCounter() * 1000) - day.activeImport) - ((data->getActiveExportCounter() * 1000) - day.activeExport));
|
||||
setHour(tm.Hour, val);
|
||||
|
||||
int16_t val = (day.activeImport == 0 ? 0 : ((data->getActiveImportCounter()*1000) - day.activeImport) - ((data->getActiveExportCounter()*1000) - day.activeExport)) / 10;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for hour %d: %d", tm.Hour, val);
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for hour %d: %d\n", tm.Hour, val);
|
||||
}
|
||||
|
||||
if(tm.Hour == 1) {
|
||||
day.h00 = val;
|
||||
} else if(tm.Hour == 2) {
|
||||
day.h01 = val;
|
||||
} else if(tm.Hour == 3) {
|
||||
day.h02 = val;
|
||||
} else if(tm.Hour == 4) {
|
||||
day.h03 = val;
|
||||
} else if(tm.Hour == 5) {
|
||||
day.h04 = val;
|
||||
} else if(tm.Hour == 6) {
|
||||
day.h05 = val;
|
||||
} else if(tm.Hour == 7) {
|
||||
day.h06 = val;
|
||||
} else if(tm.Hour == 8) {
|
||||
day.h07 = val;
|
||||
} else if(tm.Hour == 9) {
|
||||
day.h08 = val;
|
||||
} else if(tm.Hour == 10) {
|
||||
day.h09 = val;
|
||||
} else if(tm.Hour == 11) {
|
||||
day.h10 = val;
|
||||
} else if(tm.Hour == 12) {
|
||||
day.h11 = val;
|
||||
} else if(tm.Hour == 13) {
|
||||
day.h12 = val;
|
||||
} else if(tm.Hour == 14) {
|
||||
day.h13 = val;
|
||||
} else if(tm.Hour == 15) {
|
||||
day.h14 = val;
|
||||
} else if(tm.Hour == 16) {
|
||||
day.h15 = val;
|
||||
} else if(tm.Hour == 17) {
|
||||
day.h16 = val;
|
||||
} else if(tm.Hour == 18) {
|
||||
day.h17 = val;
|
||||
} else if(tm.Hour == 19) {
|
||||
day.h18 = val;
|
||||
} else if(tm.Hour == 20) {
|
||||
day.h19 = val;
|
||||
} else if(tm.Hour == 21) {
|
||||
day.h20 = val;
|
||||
} else if(tm.Hour == 22) {
|
||||
day.h21 = val;
|
||||
} else if(tm.Hour == 23) {
|
||||
day.h22 = val;
|
||||
} else if(tm.Hour == 0) {
|
||||
day.h23 = val;
|
||||
day.activeImport = data->getActiveImportCounter() * 1000;
|
||||
day.activeExport = data->getActiveExportCounter() * 1000;
|
||||
day.lastMeterReadTime = now;
|
||||
} else {
|
||||
float mins = (now - day.lastMeterReadTime) / 60.0;
|
||||
uint16_t im = ((data->getActiveImportCounter() * 1000) - day.activeImport);
|
||||
uint16_t ex = ((data->getActiveExportCounter() * 1000) - day.activeExport);
|
||||
float ipm = im / mins;
|
||||
float epm = ex / mins;
|
||||
|
||||
while(now - day.lastMeterReadTime > 3590) {
|
||||
time_t cur = day.lastMeterReadTime + 3600;
|
||||
tmElements_t tm;
|
||||
breakTime(cur, tm);
|
||||
uint8_t minutes = 60 - tm.Minute;
|
||||
float val = ((ipm * minutes) - (epm * minutes));
|
||||
setHour(tm.Hour-1, val);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for hour %d: %d\n", tm.Hour, val);
|
||||
}
|
||||
|
||||
day.activeImport += ipm * minutes;
|
||||
day.activeExport += epm * minutes;
|
||||
day.lastMeterReadTime += 60 * minutes;
|
||||
}
|
||||
}
|
||||
day.activeImport = data->getActiveImportCounter()*1000;
|
||||
day.activeExport = data->getActiveExportCounter()*1000;
|
||||
day.lastMeterReadTime = now;
|
||||
|
||||
// Update month plot
|
||||
tmElements_t tm;
|
||||
if(tz != NULL) {
|
||||
time_t local = tz->toLocal(now);
|
||||
breakTime(local, tm);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
}
|
||||
|
||||
if(tm.Hour == 0) {
|
||||
val = (month.activeImport == 0 ? 0 : ((data->getActiveImportCounter()*1000) - month.activeImport) - ((data->getActiveExportCounter()*1000) - month.activeExport)) / 10;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for day %d: %d", tm.Day, val);
|
||||
}
|
||||
|
||||
if(tm.Day == 1) {
|
||||
time_t yesterday = now-3600;
|
||||
breakTime(yesterday, tm);
|
||||
if(tm.Day == 29) {
|
||||
month.d28 = val;
|
||||
} else if(tm.Day == 30) {
|
||||
month.d29 = val;
|
||||
} else if(tm.Day == 31) {
|
||||
month.d30 = val;
|
||||
if(tm.Hour == 0 && now-month.lastMeterReadTime > 86300) {
|
||||
if(month.activeImport == 0 || now - month.lastMeterReadTime > 2678400) {
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
month.lastMeterReadTime = now;
|
||||
for(int i = 0; i<31; i++) {
|
||||
setDay(i, 0);
|
||||
}
|
||||
} else if(now - month.lastMeterReadTime < 87000) {
|
||||
int32_t val = (month.activeImport == 0 ? 0 : ((data->getActiveImportCounter() * 1000) - month.activeImport) - ((data->getActiveExportCounter() * 1000) - month.activeExport));
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Usage for day %d: %d\n", tm.Day, val);
|
||||
}
|
||||
|
||||
time_t yesterday = now - 3600;
|
||||
breakTime(yesterday, tm);
|
||||
setDay(tm.Day, val);
|
||||
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
month.lastMeterReadTime = now;
|
||||
} else {
|
||||
float hrs = (now - month.lastMeterReadTime) / 3600.0;
|
||||
uint16_t im = ((data->getActiveImportCounter() * 1000) - month.activeImport);
|
||||
uint16_t ex = ((data->getActiveExportCounter() * 1000) - month.activeExport);
|
||||
float iph = im / hrs;
|
||||
float eph = ex / hrs;
|
||||
|
||||
while(now - month.lastMeterReadTime > 86000) {
|
||||
time_t cur = month.lastMeterReadTime + 86400;
|
||||
tmElements_t tm;
|
||||
breakTime(cur, tm);
|
||||
uint8_t hours = 24 - tm.Hour;
|
||||
float val = ((iph * hours) - (eph * hours));
|
||||
setDay(tm.Day-1, val);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Estimated usage for day %d: %d\n", tm.Day, val);
|
||||
}
|
||||
|
||||
month.activeImport += iph * hours;
|
||||
month.activeExport += eph * hours;
|
||||
month.lastMeterReadTime += 24 * hours;
|
||||
}
|
||||
} else if(tm.Day == 2) {
|
||||
month.d01 = val;
|
||||
} else if(tm.Day == 3) {
|
||||
month.d02 = val;
|
||||
} else if(tm.Day == 4) {
|
||||
month.d03 = val;
|
||||
} else if(tm.Day == 5) {
|
||||
month.d04 = val;
|
||||
} else if(tm.Day == 6) {
|
||||
month.d05 = val;
|
||||
} else if(tm.Day == 7) {
|
||||
month.d06 = val;
|
||||
} else if(tm.Day == 8) {
|
||||
month.d07 = val;
|
||||
} else if(tm.Day == 9) {
|
||||
month.d08 = val;
|
||||
} else if(tm.Day == 10) {
|
||||
month.d09 = val;
|
||||
} else if(tm.Day == 11) {
|
||||
month.d10 = val;
|
||||
} else if(tm.Day == 12) {
|
||||
month.d11 = val;
|
||||
} else if(tm.Day == 13) {
|
||||
month.d12 = val;
|
||||
} else if(tm.Day == 14) {
|
||||
month.d13 = val;
|
||||
} else if(tm.Day == 15) {
|
||||
month.d14 = val;
|
||||
} else if(tm.Day == 16) {
|
||||
month.d15 = val;
|
||||
} else if(tm.Day == 17) {
|
||||
month.d16 = val;
|
||||
} else if(tm.Day == 18) {
|
||||
month.d17 = val;
|
||||
} else if(tm.Day == 19) {
|
||||
month.d18 = val;
|
||||
} else if(tm.Day == 20) {
|
||||
month.d19 = val;
|
||||
} else if(tm.Day == 21) {
|
||||
month.d20 = val;
|
||||
} else if(tm.Day == 22) {
|
||||
month.d21 = val;
|
||||
} else if(tm.Day == 23) {
|
||||
month.d22 = val;
|
||||
} else if(tm.Day == 24) {
|
||||
month.d23 = val;
|
||||
} else if(tm.Day == 25) {
|
||||
month.d24 = val;
|
||||
} else if(tm.Day == 26) {
|
||||
month.d25 = val;
|
||||
} else if(tm.Day == 27) {
|
||||
month.d26 = val;
|
||||
} else if(tm.Day == 28) {
|
||||
month.d27 = val;
|
||||
} else if(tm.Day == 29) {
|
||||
month.d28 = val;
|
||||
} else if(tm.Day == 30) {
|
||||
month.d29 = val;
|
||||
} else if(tm.Day == 31) {
|
||||
month.d30 = val;
|
||||
}
|
||||
month.activeImport = data->getActiveImportCounter()*1000;
|
||||
month.activeExport = data->getActiveExportCounter()*1000;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DayDataPoints AmsDataStorage::getDayDataPoints() {
|
||||
return day;
|
||||
void AmsDataStorage::setHour(uint8_t hour, int16_t val) {
|
||||
day.points[hour] = val / 10;
|
||||
}
|
||||
|
||||
MonthDataPoints AmsDataStorage::getMonthDataPoints() {
|
||||
return month;
|
||||
int16_t AmsDataStorage::getHour(uint8_t hour) {
|
||||
return day.points[hour] * 10;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDay(uint8_t day, int32_t val) {
|
||||
month.points[day-1] = val / 10;
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getDay(uint8_t day) {
|
||||
return (month.points[day-1] * 10);
|
||||
}
|
||||
|
||||
bool AmsDataStorage::load(AmsData* meterState) {
|
||||
if(!LittleFS.begin()) {
|
||||
printE("Unable to load LittleFS");
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf("(AmsDataStorage) Unable to load LittleFS\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ret = false;
|
||||
@ -228,16 +183,6 @@ bool AmsDataStorage::load(AmsData* meterState) {
|
||||
MonthDataPoints* month = (MonthDataPoints*) buf;
|
||||
file.close();
|
||||
|
||||
if(month->version == 3) { // dev-1.6
|
||||
month->d25 = month->d26;
|
||||
month->d26 = month->d27;
|
||||
month->d27 = month->d28;
|
||||
month->d28 = month->d29;
|
||||
month->d29 = month->d30;
|
||||
month->d30 = month->d31;
|
||||
month->version = 4;
|
||||
}
|
||||
|
||||
if(month->version == 4) {
|
||||
memcpy(&this->month, month, sizeof(this->month));
|
||||
ret = true;
|
||||
@ -253,7 +198,9 @@ bool AmsDataStorage::load(AmsData* meterState) {
|
||||
|
||||
bool AmsDataStorage::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
printE("Unable to load LittleFS");
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf("(AmsDataStorage) Unable to load LittleFS\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
@ -278,31 +225,3 @@ bool AmsDataStorage::save() {
|
||||
LittleFS.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AmsDataStorage::printD(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printI(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printW(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void AmsDataStorage::printE(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(AmsDataStorage)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@ -9,30 +9,7 @@
|
||||
|
||||
struct DayDataPoints {
|
||||
uint8_t version;
|
||||
int16_t h00;
|
||||
int16_t h01;
|
||||
int16_t h02;
|
||||
int16_t h03;
|
||||
int16_t h04;
|
||||
int16_t h05;
|
||||
int16_t h06;
|
||||
int16_t h07;
|
||||
int16_t h08;
|
||||
int16_t h09;
|
||||
int16_t h10;
|
||||
int16_t h11;
|
||||
int16_t h12;
|
||||
int16_t h13;
|
||||
int16_t h14;
|
||||
int16_t h15;
|
||||
int16_t h16;
|
||||
int16_t h17;
|
||||
int16_t h18;
|
||||
int16_t h19;
|
||||
int16_t h20;
|
||||
int16_t h21;
|
||||
int16_t h22;
|
||||
int16_t h23;
|
||||
int16_t points[24];
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
@ -40,37 +17,7 @@ struct DayDataPoints {
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
int16_t d01;
|
||||
int16_t d02;
|
||||
int16_t d03;
|
||||
int16_t d04;
|
||||
int16_t d05;
|
||||
int16_t d06;
|
||||
int16_t d07;
|
||||
int16_t d08;
|
||||
int16_t d09;
|
||||
int16_t d10;
|
||||
int16_t d11;
|
||||
int16_t d12;
|
||||
int16_t d13;
|
||||
int16_t d14;
|
||||
int16_t d15;
|
||||
int16_t d16;
|
||||
int16_t d17;
|
||||
int16_t d18;
|
||||
int16_t d19;
|
||||
int16_t d20;
|
||||
int16_t d21;
|
||||
int16_t d22;
|
||||
int16_t d23;
|
||||
int16_t d24;
|
||||
int16_t d25;
|
||||
int16_t d26;
|
||||
int16_t d27;
|
||||
int16_t d28;
|
||||
int16_t d29;
|
||||
int16_t d30;
|
||||
int16_t d31;
|
||||
int16_t points[31];
|
||||
time_t lastMeterReadTime;
|
||||
uint32_t activeImport;
|
||||
uint32_t activeExport;
|
||||
@ -81,8 +28,8 @@ public:
|
||||
AmsDataStorage(RemoteDebug*);
|
||||
void setTimezone(Timezone*);
|
||||
bool update(AmsData*);
|
||||
DayDataPoints getDayDataPoints();
|
||||
MonthDataPoints getMonthDataPoints();
|
||||
int16_t getHour(uint8_t);
|
||||
int32_t getDay(uint8_t);
|
||||
bool load(AmsData*);
|
||||
bool save();
|
||||
|
||||
@ -91,11 +38,8 @@ private:
|
||||
DayDataPoints day;
|
||||
MonthDataPoints month;
|
||||
RemoteDebug* debugger;
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printI(String fmt, ...);
|
||||
void printW(String fmt, ...);
|
||||
void printE(String fmt, ...);
|
||||
void setHour(uint8_t, int16_t);
|
||||
void setDay(uint8_t, int32_t);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#ifndef _AMSTOMQTTBRIDGE_H
|
||||
#define _AMSTOMQTTBRIDGE_H
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000;
|
||||
#define WIFI_CONNECTION_TIMEOUT 25000;
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
|
||||
@ -58,7 +58,9 @@ Timezone* tz;
|
||||
|
||||
AmsWebServer ws(&Debug, &hw);
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
MQTTClient *mqtt = NULL;
|
||||
WiFiClient *mqttClient = NULL;
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
|
||||
Stream *hanSerial;
|
||||
@ -74,6 +76,8 @@ bool ntpEnabled = false;
|
||||
|
||||
AmsDataStorage ds(&Debug);
|
||||
|
||||
uint8_t wifiReconnectCount = 0;
|
||||
|
||||
void setup() {
|
||||
WiFiConfig wifi;
|
||||
Serial.begin(115200);
|
||||
@ -299,7 +303,7 @@ void setup() {
|
||||
swapWifiMode();
|
||||
}
|
||||
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &mqtt);
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds);
|
||||
}
|
||||
|
||||
int buttonTimer = 0;
|
||||
@ -344,7 +348,7 @@ void loop() {
|
||||
hw.updateTemperatures();
|
||||
lastTemperatureRead = now;
|
||||
|
||||
if(mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt.connected() && !topic.isEmpty()) {
|
||||
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
||||
mqttHandler->publishTemperatures(&config, &hw);
|
||||
}
|
||||
debugD("Used %d ms to update temperature", millis()-start);
|
||||
@ -357,6 +361,7 @@ void loop() {
|
||||
Debug.stop();
|
||||
WiFi_connect();
|
||||
} else {
|
||||
wifiReconnectCount = 0;
|
||||
if(!wifiConnected) {
|
||||
wifiConnected = true;
|
||||
|
||||
@ -420,18 +425,21 @@ void loop() {
|
||||
errorBlink();
|
||||
}
|
||||
|
||||
if (mqttEnabled || config.isMqttChanged()) {
|
||||
mqtt.loop();
|
||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||
if(!mqtt.connected() || config.isMqttChanged()) {
|
||||
if (mqttEnabled) {
|
||||
if(mqtt != NULL) {
|
||||
mqtt->loop();
|
||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||
}
|
||||
if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) {
|
||||
MQTT_connect();
|
||||
}
|
||||
} else if(mqtt.connected()) {
|
||||
mqtt.disconnect();
|
||||
} else if(mqtt != NULL && mqtt->connected()) {
|
||||
mqttClient->stop();
|
||||
mqtt->disconnect();
|
||||
}
|
||||
|
||||
if(eapi != NULL && ntpEnabled) {
|
||||
if(eapi->loop() && mqttHandler != NULL && mqtt.connected()) {
|
||||
if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
}
|
||||
}
|
||||
@ -591,7 +599,7 @@ void errorBlink() {
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(mqttEnabled && mqtt.lastError() != 0) {
|
||||
if(mqttEnabled && mqtt != NULL && mqtt->lastError() != 0) {
|
||||
hw.ledBlink(LED_RED, 2); // If MQTT error, blink twice
|
||||
return;
|
||||
}
|
||||
@ -678,6 +686,7 @@ void readHanPort() {
|
||||
if(currentMeterType == 1) {
|
||||
while(hanSerial->available()) {
|
||||
buf[len++] = hanSerial->read();
|
||||
delay(1);
|
||||
}
|
||||
if(len > 0) {
|
||||
int pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
@ -698,7 +707,7 @@ void readHanPort() {
|
||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||
}
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
debugD("Frame dump:");
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(buf, 0, len);
|
||||
if(hc != NULL) {
|
||||
debugD("System title:");
|
||||
@ -739,7 +748,7 @@ void readHanPort() {
|
||||
if(data.getListType() > 0) {
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
hw.ledBlink(LED_INTERNAL, 1);
|
||||
if(mqttEnabled && mqttHandler != NULL) {
|
||||
if(mqttEnabled && mqttHandler != NULL && mqtt != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState)) {
|
||||
if(data.getListType() == 3 && eapi != NULL) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
@ -762,8 +771,10 @@ void readHanPort() {
|
||||
}
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
if(mqtt != NULL) {
|
||||
mqtt->loop();
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
if(ds.update(&data)) {
|
||||
@ -803,6 +814,45 @@ void WiFi_connect() {
|
||||
lastWifiRetry = millis();
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
if(wifiReconnectCount > 3) {
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Disconnecting from WiFi");
|
||||
if(mqtt != NULL) {
|
||||
mqtt->disconnect();
|
||||
mqtt->loop();
|
||||
yield();
|
||||
delete mqtt;
|
||||
mqtt = NULL;
|
||||
ws.setMqtt(NULL);
|
||||
}
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
|
||||
MDNS.end();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
WiFi.enableAP(false);
|
||||
yield();
|
||||
wifiTimeout = 5000;
|
||||
return;
|
||||
}
|
||||
wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
|
||||
WiFiConfig wifi;
|
||||
if(!config.getWiFiConfig(wifi) || strlen(wifi.ssid) == 0) {
|
||||
swapWifiMode();
|
||||
@ -811,11 +861,8 @@ void WiFi_connect() {
|
||||
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Connecting to WiFi network: %s", wifi.ssid);
|
||||
|
||||
MDNS.end();
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
wifiReconnectCount++;
|
||||
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
if(strlen(wifi.ip) > 0) {
|
||||
IPAddress ip, gw, sn(255,255,255,0), dns1, dns2;
|
||||
@ -827,23 +874,25 @@ void WiFi_connect() {
|
||||
WiFi.config(ip, gw, sn, dns1, dns2);
|
||||
} else {
|
||||
#if defined(ESP32)
|
||||
// Changed from INADDR_NONE to INADDR_ANY for last ESP32-Arduino version
|
||||
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY); // Workaround to make DHCP hostname work for ESP32. See: https://github.com/espressif/arduino-esp32/issues/2537
|
||||
// This trick does not work anymore...
|
||||
// WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); // Workaround to make DHCP hostname work for ESP32. See: https://github.com/espressif/arduino-esp32/issues/2537
|
||||
#endif
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
if(strlen(wifi.hostname) > 0) {
|
||||
#if defined(ESP8266)
|
||||
WiFi.hostname(wifi.hostname);
|
||||
#elif defined(ESP32)
|
||||
WiFi.setHostname(wifi.hostname);
|
||||
#endif
|
||||
}
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.persistent(true);
|
||||
if(WiFi.begin(wifi.ssid, wifi.psk)) {
|
||||
yield();
|
||||
} else {
|
||||
if (Debug.isActive(RemoteDebug::ERROR)) debugI("Unable to enable WiFi");
|
||||
}
|
||||
#endif
|
||||
#if defined(ESP32)
|
||||
if(strlen(wifi.hostname) > 0) {
|
||||
WiFi.setHostname(wifi.hostname);
|
||||
}
|
||||
#endif
|
||||
WiFi.begin(wifi.ssid, wifi.psk);
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
@ -856,24 +905,29 @@ void MQTT_connect() {
|
||||
config.ackMqttChange();
|
||||
return;
|
||||
}
|
||||
if(millis() - lastMqttRetry < (mqtt.lastError() == 0 || config.isMqttChanged() ? 5000 : 30000)) {
|
||||
if(mqtt != NULL) {
|
||||
if(millis() - lastMqttRetry < (mqtt->lastError() == 0 || config.isMqttChanged() ? 5000 : 30000)) {
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
lastMqttRetry = millis();
|
||||
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugD("Disconnecting MQTT before connecting");
|
||||
}
|
||||
|
||||
mqtt->disconnect();
|
||||
yield();
|
||||
return;
|
||||
} else {
|
||||
mqtt = new MQTTClient(512);
|
||||
ws.setMqtt(mqtt);
|
||||
}
|
||||
lastMqttRetry = millis();
|
||||
|
||||
mqttEnabled = true;
|
||||
ws.setMqttEnabled(true);
|
||||
payloadFormat = mqttConfig.payloadFormat;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugD("Disconnecting MQTT before connecting");
|
||||
}
|
||||
|
||||
mqtt.disconnect();
|
||||
yield();
|
||||
|
||||
if(mqttHandler != NULL) {
|
||||
delete mqttHandler;
|
||||
mqttHandler = NULL;
|
||||
@ -881,27 +935,26 @@ void MQTT_connect() {
|
||||
|
||||
switch(mqttConfig.payloadFormat) {
|
||||
case 0:
|
||||
mqttHandler = new JsonMqttHandler(&mqtt, mqttConfig.clientId, mqttConfig.publishTopic, &hw);
|
||||
mqttHandler = new JsonMqttHandler(mqtt, mqttConfig.clientId, mqttConfig.publishTopic, &hw);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
mqttHandler = new RawMqttHandler(&mqtt, mqttConfig.publishTopic, mqttConfig.payloadFormat == 2);
|
||||
mqttHandler = new RawMqttHandler(mqtt, mqttConfig.publishTopic, mqttConfig.payloadFormat == 2);
|
||||
break;
|
||||
case 3:
|
||||
DomoticzConfig domo;
|
||||
config.getDomoticzConfig(domo);
|
||||
mqttHandler = new DomoticzMqttHandler(&mqtt, domo);
|
||||
mqttHandler = new DomoticzMqttHandler(mqtt, domo);
|
||||
break;
|
||||
}
|
||||
|
||||
WiFiClientSecure *secureClient = NULL;
|
||||
Client *client = NULL;
|
||||
if(mqttConfig.ssl) {
|
||||
debugI("MQTT SSL is configured");
|
||||
|
||||
secureClient = new WiFiClientSecure();
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
secureClient->setBufferSizes(512, 512);
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
#endif
|
||||
|
||||
if(LittleFS.begin()) {
|
||||
@ -917,9 +970,9 @@ void MQTT_connect() {
|
||||
char caStr[MAX_PEM_SIZE];
|
||||
file.readBytes(caStr, file.size());
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(caStr);
|
||||
secureClient->setTrustAnchors(serverTrustedCA);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
secureClient->loadCACert(file, file.size());
|
||||
mqttSecureClient->loadCACert(file, file.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -933,46 +986,46 @@ void MQTT_connect() {
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
file.readBytes(keyStr, file.size());
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(keyStr);
|
||||
secureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
#elif defined(ESP32)
|
||||
debugI("Found MQTT certificate file");
|
||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||
secureClient->loadCertificate(file, file.size());
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
|
||||
debugI("Found MQTT key file");
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
secureClient->loadPrivateKey(file, file.size());
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
#endif
|
||||
}
|
||||
LittleFS.end();
|
||||
}
|
||||
client = secureClient;
|
||||
} else {
|
||||
client = new WiFiClient();
|
||||
mqttClient = mqttSecureClient;
|
||||
} else if(mqttClient == NULL) {
|
||||
mqttClient = new WiFiClient();
|
||||
}
|
||||
|
||||
if(Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugI("Connecting to MQTT %s:%d", mqttConfig.host, mqttConfig.port);
|
||||
}
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *client);
|
||||
mqtt->begin(mqttConfig.host, mqttConfig.port, *mqttClient);
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(secureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
debugD("Setting NTP time %i for secure MQTT connection", epoch);
|
||||
secureClient->setX509Time(epoch);
|
||||
}
|
||||
if(mqttSecureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
debugD("Setting NTP time %i for secure MQTT connection", epoch);
|
||||
mqttSecureClient->setX509Time(epoch);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
|
||||
config.ackMqttChange();
|
||||
|
||||
// Subscribe to the chosen MQTT topic, if set in configuration
|
||||
if (strlen(mqttConfig.subscribeTopic) > 0) {
|
||||
mqtt.subscribe(mqttConfig.subscribeTopic);
|
||||
mqtt->subscribe(mqttConfig.subscribeTopic);
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI(" Subscribing to [%s]\r\n", mqttConfig.subscribeTopic);
|
||||
}
|
||||
|
||||
@ -981,11 +1034,11 @@ void MQTT_connect() {
|
||||
}
|
||||
} else {
|
||||
if (Debug.isActive(RemoteDebug::ERROR)) {
|
||||
debugE("Failed to connect to MQTT: %d", mqtt.lastError());
|
||||
debugE("Failed to connect to MQTT: %d", mqtt->lastError());
|
||||
#if defined(ESP8266)
|
||||
if(secureClient) {
|
||||
if(mqttSecureClient) {
|
||||
char buf[64];
|
||||
secureClient->getLastSSLError(buf,64);
|
||||
mqttSecureClient->getLastSSLError(buf,64);
|
||||
Debug.println(buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -143,7 +143,13 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
}
|
||||
}
|
||||
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
}
|
||||
|
||||
u32 = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
@ -264,14 +270,9 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
|
||||
CosemData* meterTs = findObis(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
this->meterTimestamp = tz.toUTC(ts);
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
|
||||
@ -32,6 +32,10 @@ char* EntsoeApi::getToken() {
|
||||
return this->config->token;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getCurrency() {
|
||||
return this->config->currency;
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(uint8_t hour) {
|
||||
time_t cur = time(nullptr);
|
||||
return getValueForHour(cur, hour);
|
||||
|
||||
@ -18,6 +18,7 @@ public:
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
float getValueForHour(uint8_t);
|
||||
float getValueForHour(time_t, uint8_t);
|
||||
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
#include "root/lowmem_html.h"
|
||||
#include "root/dayplot_json.h"
|
||||
#include "root/monthplot_json.h"
|
||||
#include "root/energyprice_json.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
@ -51,13 +52,12 @@ AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) {
|
||||
this->hw = hw;
|
||||
}
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds, MQTTClient* mqtt) {
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds) {
|
||||
this->config = config;
|
||||
this->gpioConfig = gpioConfig;
|
||||
this->meterConfig = meterConfig;
|
||||
this->meterState = meterState;
|
||||
this->ds = ds;
|
||||
this->mqtt = mqtt;
|
||||
|
||||
char jsuri[32];
|
||||
snprintf(jsuri, 32, "/application-%s.js", VERSION);
|
||||
@ -80,6 +80,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this));
|
||||
server.on("/dayplot.json", HTTP_GET, std::bind(&AmsWebServer::dayplotJson, this));
|
||||
server.on("/monthplot.json", HTTP_GET, std::bind(&AmsWebServer::monthplotJson, this));
|
||||
server.on("/energyprice.json", HTTP_GET, std::bind(&AmsWebServer::energyPriceJson, this));
|
||||
|
||||
server.on("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this));
|
||||
|
||||
@ -115,6 +116,10 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
mqttEnabled = strlen(mqttConfig.host) > 0;
|
||||
}
|
||||
|
||||
void AmsWebServer::setMqtt(MQTTClient* mqtt) {
|
||||
this->mqtt = mqtt;
|
||||
}
|
||||
|
||||
void AmsWebServer::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
@ -618,10 +623,10 @@ void AmsWebServer::configEntsoeHtml() {
|
||||
html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : "");
|
||||
html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("{ecNOK}", strcmp(entsoe.area, "NOK") == 0 ? "selected" : "");
|
||||
html.replace("{ecSEK}", strcmp(entsoe.area, "SEK") == 0 ? "selected" : "");
|
||||
html.replace("{ecDKK}", strcmp(entsoe.area, "DKK") == 0 ? "selected" : "");
|
||||
html.replace("{ecEUR}", strcmp(entsoe.area, "EUR") == 0 ? "selected" : "");
|
||||
html.replace("{ecNOK}", strcmp(entsoe.currency, "NOK") == 0 ? "selected" : "");
|
||||
html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : "");
|
||||
html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : "");
|
||||
html.replace("{ecEUR}", strcmp(entsoe.currency, "EUR") == 0 ? "selected" : "");
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, "text/html", HEAD_HTML);
|
||||
@ -730,14 +735,18 @@ void AmsWebServer::dataJson() {
|
||||
uint8_t mqttStatus;
|
||||
if(!mqttEnabled) {
|
||||
mqttStatus = 0;
|
||||
} else if(mqtt->connected()) {
|
||||
} else if(mqtt != NULL && mqtt->connected()) {
|
||||
mqttStatus = 1;
|
||||
} else if(mqtt->lastError() == 0) {
|
||||
} else if(mqtt != NULL && mqtt->lastError() == 0) {
|
||||
mqttStatus = 2;
|
||||
} else {
|
||||
mqttStatus = 3;
|
||||
}
|
||||
|
||||
float price = ENTSOE_NO_VALUE;
|
||||
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
||||
price = eapi->getValueForHour(0);
|
||||
|
||||
char json[340];
|
||||
snprintf_P(json, sizeof(json), DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
@ -770,7 +779,8 @@ void AmsWebServer::dataJson() {
|
||||
hanStatus,
|
||||
wifiStatus,
|
||||
mqttStatus,
|
||||
(int) mqtt->lastError()
|
||||
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
||||
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str()
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
@ -784,34 +794,32 @@ void AmsWebServer::dataJson() {
|
||||
void AmsWebServer::dayplotJson() {
|
||||
printD("Serving /dayplot.json over http...");
|
||||
|
||||
DayDataPoints d = ds->getDayDataPoints();
|
||||
|
||||
char json[384];
|
||||
snprintf_P(json, sizeof(json), DAYPLOT_JSON,
|
||||
d.h00 / 100.0,
|
||||
d.h01 / 100.0,
|
||||
d.h02 / 100.0,
|
||||
d.h03 / 100.0,
|
||||
d.h04 / 100.0,
|
||||
d.h05 / 100.0,
|
||||
d.h06 / 100.0,
|
||||
d.h07 / 100.0,
|
||||
d.h08 / 100.0,
|
||||
d.h09 / 100.0,
|
||||
d.h10 / 100.0,
|
||||
d.h11 / 100.0,
|
||||
d.h12 / 100.0,
|
||||
d.h13 / 100.0,
|
||||
d.h14 / 100.0,
|
||||
d.h15 / 100.0,
|
||||
d.h16 / 100.0,
|
||||
d.h17 / 100.0,
|
||||
d.h18 / 100.0,
|
||||
d.h19 / 100.0,
|
||||
d.h20 / 100.0,
|
||||
d.h21 / 100.0,
|
||||
d.h22 / 100.0,
|
||||
d.h23 / 100.0
|
||||
ds->getHour(0) / 1000.0,
|
||||
ds->getHour(1) / 1000.0,
|
||||
ds->getHour(2) / 1000.0,
|
||||
ds->getHour(3) / 1000.0,
|
||||
ds->getHour(4) / 1000.0,
|
||||
ds->getHour(5) / 1000.0,
|
||||
ds->getHour(6) / 1000.0,
|
||||
ds->getHour(7) / 1000.0,
|
||||
ds->getHour(8) / 1000.0,
|
||||
ds->getHour(9) / 1000.0,
|
||||
ds->getHour(10) / 1000.0,
|
||||
ds->getHour(11) / 1000.0,
|
||||
ds->getHour(12) / 1000.0,
|
||||
ds->getHour(13) / 1000.0,
|
||||
ds->getHour(14) / 1000.0,
|
||||
ds->getHour(15) / 1000.0,
|
||||
ds->getHour(16) / 1000.0,
|
||||
ds->getHour(17) / 1000.0,
|
||||
ds->getHour(18) / 1000.0,
|
||||
ds->getHour(19) / 1000.0,
|
||||
ds->getHour(20) / 1000.0,
|
||||
ds->getHour(21) / 1000.0,
|
||||
ds->getHour(22) / 1000.0,
|
||||
ds->getHour(23) / 1000.0
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
@ -825,41 +833,96 @@ void AmsWebServer::dayplotJson() {
|
||||
void AmsWebServer::monthplotJson() {
|
||||
printD("Serving /monthplot.json over http...");
|
||||
|
||||
MonthDataPoints m = ds->getMonthDataPoints();
|
||||
|
||||
char json[512];
|
||||
snprintf_P(json, sizeof(json), MONTHPLOT_JSON,
|
||||
m.d01 / 100.0,
|
||||
m.d02 / 100.0,
|
||||
m.d03 / 100.0,
|
||||
m.d04 / 100.0,
|
||||
m.d05 / 100.0,
|
||||
m.d06 / 100.0,
|
||||
m.d07 / 100.0,
|
||||
m.d08 / 100.0,
|
||||
m.d09 / 100.0,
|
||||
m.d10 / 100.0,
|
||||
m.d11 / 100.0,
|
||||
m.d12 / 100.0,
|
||||
m.d13 / 100.0,
|
||||
m.d14 / 100.0,
|
||||
m.d15 / 100.0,
|
||||
m.d16 / 100.0,
|
||||
m.d17 / 100.0,
|
||||
m.d18 / 100.0,
|
||||
m.d19 / 100.0,
|
||||
m.d20 / 100.0,
|
||||
m.d21 / 100.0,
|
||||
m.d22 / 100.0,
|
||||
m.d23 / 100.0,
|
||||
m.d24 / 100.0,
|
||||
m.d25 / 100.0,
|
||||
m.d26 / 100.0,
|
||||
m.d27 / 100.0,
|
||||
m.d28 / 100.0,
|
||||
m.d29 / 100.0,
|
||||
m.d30 / 100.0,
|
||||
m.d31 / 100.0
|
||||
ds->getDay(1) / 1000.0,
|
||||
ds->getDay(2) / 1000.0,
|
||||
ds->getDay(3) / 1000.0,
|
||||
ds->getDay(4) / 1000.0,
|
||||
ds->getDay(5) / 1000.0,
|
||||
ds->getDay(6) / 1000.0,
|
||||
ds->getDay(7) / 1000.0,
|
||||
ds->getDay(8) / 1000.0,
|
||||
ds->getDay(9) / 1000.0,
|
||||
ds->getDay(10) / 1000.0,
|
||||
ds->getDay(11) / 1000.0,
|
||||
ds->getDay(12) / 1000.0,
|
||||
ds->getDay(13) / 1000.0,
|
||||
ds->getDay(14) / 1000.0,
|
||||
ds->getDay(15) / 1000.0,
|
||||
ds->getDay(16) / 1000.0,
|
||||
ds->getDay(17) / 1000.0,
|
||||
ds->getDay(18) / 1000.0,
|
||||
ds->getDay(19) / 1000.0,
|
||||
ds->getDay(20) / 1000.0,
|
||||
ds->getDay(21) / 1000.0,
|
||||
ds->getDay(22) / 1000.0,
|
||||
ds->getDay(23) / 1000.0,
|
||||
ds->getDay(24) / 1000.0,
|
||||
ds->getDay(25) / 1000.0,
|
||||
ds->getDay(26) / 1000.0,
|
||||
ds->getDay(27) / 1000.0,
|
||||
ds->getDay(28) / 1000.0,
|
||||
ds->getDay(29) / 1000.0,
|
||||
ds->getDay(30) / 1000.0,
|
||||
ds->getDay(31) / 1000.0
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
server.setContentLength(strlen(json));
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void AmsWebServer::energyPriceJson() {
|
||||
printD("Serving /energyprice.json over http...");
|
||||
|
||||
float prices[36];
|
||||
for(int i = 0; i < 36; i++) {
|
||||
prices[i] = eapi == NULL ? ENTSOE_NO_VALUE : eapi->getValueForHour(i);
|
||||
}
|
||||
|
||||
char json[768];
|
||||
snprintf_P(json, sizeof(json), ENERGYPRICE_JSON,
|
||||
eapi == NULL ? "" : eapi->getCurrency(),
|
||||
prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 2).c_str(),
|
||||
prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 2).c_str(),
|
||||
prices[2] == ENTSOE_NO_VALUE ? "null" : String(prices[2], 2).c_str(),
|
||||
prices[3] == ENTSOE_NO_VALUE ? "null" : String(prices[3], 2).c_str(),
|
||||
prices[4] == ENTSOE_NO_VALUE ? "null" : String(prices[4], 2).c_str(),
|
||||
prices[5] == ENTSOE_NO_VALUE ? "null" : String(prices[5], 2).c_str(),
|
||||
prices[6] == ENTSOE_NO_VALUE ? "null" : String(prices[6], 2).c_str(),
|
||||
prices[7] == ENTSOE_NO_VALUE ? "null" : String(prices[7], 2).c_str(),
|
||||
prices[8] == ENTSOE_NO_VALUE ? "null" : String(prices[8], 2).c_str(),
|
||||
prices[9] == ENTSOE_NO_VALUE ? "null" : String(prices[9], 2).c_str(),
|
||||
prices[10] == ENTSOE_NO_VALUE ? "null" : String(prices[10], 2).c_str(),
|
||||
prices[11] == ENTSOE_NO_VALUE ? "null" : String(prices[11], 2).c_str(),
|
||||
prices[12] == ENTSOE_NO_VALUE ? "null" : String(prices[12], 2).c_str(),
|
||||
prices[13] == ENTSOE_NO_VALUE ? "null" : String(prices[13], 2).c_str(),
|
||||
prices[14] == ENTSOE_NO_VALUE ? "null" : String(prices[14], 2).c_str(),
|
||||
prices[15] == ENTSOE_NO_VALUE ? "null" : String(prices[15], 2).c_str(),
|
||||
prices[16] == ENTSOE_NO_VALUE ? "null" : String(prices[16], 2).c_str(),
|
||||
prices[17] == ENTSOE_NO_VALUE ? "null" : String(prices[17], 2).c_str(),
|
||||
prices[18] == ENTSOE_NO_VALUE ? "null" : String(prices[18], 2).c_str(),
|
||||
prices[19] == ENTSOE_NO_VALUE ? "null" : String(prices[19], 2).c_str(),
|
||||
prices[20] == ENTSOE_NO_VALUE ? "null" : String(prices[20], 2).c_str(),
|
||||
prices[21] == ENTSOE_NO_VALUE ? "null" : String(prices[21], 2).c_str(),
|
||||
prices[22] == ENTSOE_NO_VALUE ? "null" : String(prices[22], 2).c_str(),
|
||||
prices[23] == ENTSOE_NO_VALUE ? "null" : String(prices[23], 2).c_str(),
|
||||
prices[24] == ENTSOE_NO_VALUE ? "null" : String(prices[24], 2).c_str(),
|
||||
prices[25] == ENTSOE_NO_VALUE ? "null" : String(prices[25], 2).c_str(),
|
||||
prices[26] == ENTSOE_NO_VALUE ? "null" : String(prices[26], 2).c_str(),
|
||||
prices[27] == ENTSOE_NO_VALUE ? "null" : String(prices[27], 2).c_str(),
|
||||
prices[28] == ENTSOE_NO_VALUE ? "null" : String(prices[28], 2).c_str(),
|
||||
prices[29] == ENTSOE_NO_VALUE ? "null" : String(prices[29], 2).c_str(),
|
||||
prices[30] == ENTSOE_NO_VALUE ? "null" : String(prices[30], 2).c_str(),
|
||||
prices[31] == ENTSOE_NO_VALUE ? "null" : String(prices[31], 2).c_str(),
|
||||
prices[32] == ENTSOE_NO_VALUE ? "null" : String(prices[32], 2).c_str(),
|
||||
prices[33] == ENTSOE_NO_VALUE ? "null" : String(prices[33], 2).c_str(),
|
||||
prices[34] == ENTSOE_NO_VALUE ? "null" : String(prices[34], 2).c_str(),
|
||||
prices[35] == ENTSOE_NO_VALUE ? "null" : String(prices[35], 2).c_str()
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
|
||||
@ -30,8 +30,9 @@
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(RemoteDebug* Debug, HwTools* hw);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage* ds, MQTTClient*);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*);
|
||||
void loop();
|
||||
void setMqtt(MQTTClient* mqtt);
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
void setEntsoeApi(EntsoeApi* eapi);
|
||||
@ -49,7 +50,7 @@ private:
|
||||
WebConfig webConfig;
|
||||
AmsData* meterState;
|
||||
AmsDataStorage* ds;
|
||||
MQTTClient* mqtt;
|
||||
MQTTClient* mqtt = NULL;
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
@ -82,6 +83,7 @@ private:
|
||||
void dataJson();
|
||||
void dayplotJson();
|
||||
void monthplotJson();
|
||||
void energyPriceJson();
|
||||
|
||||
void handleSetup();
|
||||
void handleSave();
|
||||
|
||||
@ -1,11 +1,29 @@
|
||||
var nextVersion;
|
||||
var im, em;
|
||||
|
||||
// Price plot
|
||||
var pp;
|
||||
var pa;
|
||||
var po = {
|
||||
title: 'Future energy price',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
bar: { groupWidth: '90%' },
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
viewWindowMode: 'maximized'
|
||||
},
|
||||
tooltip: { trigger: 'none'},
|
||||
enableInteractivity: false,
|
||||
};
|
||||
var pl = null; // Last price
|
||||
|
||||
// Day plot
|
||||
var ep;
|
||||
var ea;
|
||||
var eo = {
|
||||
title: 'Last 24 hours',
|
||||
title: 'Energy use last 24 hours',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
@ -23,7 +41,7 @@ var eo = {
|
||||
var mp;
|
||||
var ma;
|
||||
var mo = {
|
||||
title: 'Last month',
|
||||
title: 'Energy use last month',
|
||||
titleTextStyle: {
|
||||
fontSize: 14
|
||||
},
|
||||
@ -340,6 +358,7 @@ var zeropad = function(num) {
|
||||
}
|
||||
|
||||
var setupChart = function() {
|
||||
pp = new google.visualization.ColumnChart(document.getElementById('pp'));
|
||||
ep = new google.visualization.ColumnChart(document.getElementById('ep'));
|
||||
mp = new google.visualization.ColumnChart(document.getElementById('mp'));
|
||||
vp = new google.visualization.ColumnChart(document.getElementById('vp'));
|
||||
@ -347,10 +366,14 @@ var setupChart = function() {
|
||||
ip = new google.visualization.PieChart(document.getElementById('ip'));
|
||||
xp = new google.visualization.PieChart(document.getElementById('xp'));
|
||||
fetch();
|
||||
drawEnergy();
|
||||
drawDay();
|
||||
drawMonth();
|
||||
};
|
||||
|
||||
var redraw = function() {
|
||||
if(pl != null) {
|
||||
pp.draw(pa, po);
|
||||
}
|
||||
ep.draw(ea, eo);
|
||||
mp.draw(ma, mo);
|
||||
vp.draw(va, vo);
|
||||
@ -359,7 +382,41 @@ var redraw = function() {
|
||||
xp.draw(xa, xo);
|
||||
};
|
||||
|
||||
var drawEnergy = function() {
|
||||
var drawPrices = function() {
|
||||
$('#ppc').show();
|
||||
$.ajax({
|
||||
url: '/energyprice.json',
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour',json.currency + '/kWh', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
var min = 0;
|
||||
var h = 0;
|
||||
var d = json["20"] == null ? 2 : 1;
|
||||
for(var i = hour; i<24; i++) {
|
||||
var val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
data[r++] = [zeropad((i+offset)%24), val, "color: #6f42c1;opacity: 0.9;", val == null ? "" : val.toFixed(d)];
|
||||
Math.min(0, val);
|
||||
};
|
||||
for(var i = 0; i < 24; i++) {
|
||||
var val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
data[r++] = [zeropad((i+offset)%24), val, "color: #6f42c1;opacity: 0.9;", val == null ? "" : val.toFixed(d)];
|
||||
Math.min(0, val);
|
||||
};
|
||||
pa = google.visualization.arrayToDataTable(data);
|
||||
po.vAxis.title = json.currency;
|
||||
if(min == 0)
|
||||
po.vAxis.minValue = 0;
|
||||
pp.draw(pa, po);
|
||||
});
|
||||
}
|
||||
|
||||
var drawDay = function() {
|
||||
$.ajax({
|
||||
url: '/dayplot.json',
|
||||
timeout: 30000,
|
||||
@ -384,7 +441,12 @@ var drawEnergy = function() {
|
||||
if(min == 0)
|
||||
eo.vAxis.minValue = 0;
|
||||
ep.draw(ea, eo);
|
||||
|
||||
setTimeout(drawDay, (61-moment().minute())*60000);
|
||||
});
|
||||
};
|
||||
|
||||
var drawMonth = function() {
|
||||
$.ajax({
|
||||
url: '/monthplot.json',
|
||||
timeout: 30000,
|
||||
@ -393,9 +455,9 @@ var drawEnergy = function() {
|
||||
data = [['Day','kWh', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var day = moment().date();
|
||||
var start = moment().subtract(1, 'months').endOf('month').date();
|
||||
var eom = moment().subtract(1, 'months').endOf('month').date();
|
||||
var min = 0;
|
||||
for(var i = day; i<=start; i++) {
|
||||
for(var i = day; i<=eom; i++) {
|
||||
var val = json["d"+zeropad(i)];
|
||||
data[r++] = [zeropad((i)), val, "color: #6f42c1;opacity: 0.9;", val.toFixed(0)];
|
||||
Math.min(0, val);
|
||||
@ -409,6 +471,8 @@ var drawEnergy = function() {
|
||||
if(min == 0)
|
||||
mo.vAxis.minValue = 0;
|
||||
mp.draw(ma, mo);
|
||||
|
||||
setTimeout(drawMonth, (24-moment().hour())*60000);
|
||||
});
|
||||
};
|
||||
|
||||
@ -609,6 +673,12 @@ var fetch = function() {
|
||||
$('.jt').html("N/A");
|
||||
}
|
||||
setTimeout(fetch, interval);
|
||||
|
||||
var price = parseFloat(json.p);
|
||||
if(price && price != pl) {
|
||||
pl = price;
|
||||
drawPrices();
|
||||
}
|
||||
}).fail(function(x, text, error) {
|
||||
console.log("Failed request");
|
||||
console.log(text);
|
||||
|
||||
@ -29,5 +29,6 @@
|
||||
"hm" : %d,
|
||||
"wm" : %d,
|
||||
"mm" : %d,
|
||||
"me" : %d
|
||||
"me" : %d,
|
||||
"p" : %s
|
||||
}
|
||||
39
web/energyprice.json
Normal file
39
web/energyprice.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"currency" : "%s",
|
||||
"00" : %s,
|
||||
"01" : %s,
|
||||
"02" : %s,
|
||||
"03" : %s,
|
||||
"04" : %s,
|
||||
"05" : %s,
|
||||
"06" : %s,
|
||||
"07" : %s,
|
||||
"08" : %s,
|
||||
"09" : %s,
|
||||
"10" : %s,
|
||||
"11" : %s,
|
||||
"12" : %s,
|
||||
"13" : %s,
|
||||
"14" : %s,
|
||||
"15" : %s,
|
||||
"16" : %s,
|
||||
"17" : %s,
|
||||
"18" : %s,
|
||||
"19" : %s,
|
||||
"20" : %s,
|
||||
"21" : %s,
|
||||
"22" : %s,
|
||||
"23" : %s,
|
||||
"24" : %s,
|
||||
"25" : %s,
|
||||
"26" : %s,
|
||||
"27" : %s,
|
||||
"28" : %s,
|
||||
"29" : %s,
|
||||
"30" : %s,
|
||||
"31" : %s,
|
||||
"32" : %s,
|
||||
"33" : %s,
|
||||
"34" : %s,
|
||||
"35" : %s
|
||||
}
|
||||
@ -19,7 +19,7 @@
|
||||
<select name="ea" class="form-control">
|
||||
<optgroup label="Norway">
|
||||
<option value="10YNO-1--------2" {eaNo1}>NO1</option>
|
||||
<option value="10YNO-2--------T" {eaNo1}>NO2</option>
|
||||
<option value="10YNO-2--------T" {eaNo2}>NO2</option>
|
||||
<option value="10YNO-3--------J" {eaNo3}>NO3</option>
|
||||
<option value="10YNO-4--------9" {eaNo4}>NO4</option>
|
||||
<option value="10Y1001A1001A48H" {eaNo5}>NO5</option>
|
||||
|
||||
@ -53,9 +53,6 @@
|
||||
<li class="nav-item">
|
||||
<a id="temp-link" class="nav-link" href="/temperature">Temp<span class="d-none d-sm-inline">erature</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a id="price-link" class="nav-link" href="/price"><span class="d-none d-sm-inline">Energy</span> Price</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
|
||||
170
web/index.html
170
web/index.html
@ -19,101 +19,97 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="ip" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="ipo">{P}</span>
|
||||
<span class="ipoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">{ti}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row ric" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jic">{tPI}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="ip" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="ipo">{P}</span>
|
||||
<span class="ipoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">{ti}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-6 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="xp" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="xpo">{PO}</span>
|
||||
<span class="xpoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">Export</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row rec" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jec">{tPO}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 mb-3" style="display: {dn};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<h5 class="text-center">Reactive</h5>
|
||||
<div class="row rri">
|
||||
<div class="col-12 font-weight-bold">Instant</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jri">{Q}</span> var</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jre">{QO}</span> var</div>
|
||||
</div>
|
||||
<div class="row rric">
|
||||
<div class="col-12 font-weight-bold">Total</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jric">{tQI}</span> kvarh</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jrec">{tQO}</span> kvarh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Instant reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jric">{tQI}</span> kvarh</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jrec">{tQO}</span> kvarh</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row ric" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jic">{tPI}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="vp" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center overlay-plot">
|
||||
<div id="xp" class="plot1"></div>
|
||||
<span class="plot-overlay">
|
||||
<span class="xpo">{PO}</span>
|
||||
<span class="xpoa">W</span>
|
||||
<br/>
|
||||
<span class="pol">Export</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="ap" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Total reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jric">{tQI}</span> kvarh</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jrec">{tQO}</span> kvarh</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row rec" style="display: {da};">
|
||||
<div class="col-12 text-right"><span class="jec">{tPO}</span> kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="vp" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<div class="bg-white rounded shadow p-3">
|
||||
<div class="text-center">
|
||||
<div id="ap" class="plot2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3" style="display: {dn};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<h5 class="text-center">Reactive</h5>
|
||||
<div class="row rri">
|
||||
<div class="col-12 font-weight-bold">Instant</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jri">{Q}</span> VAr</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jre">{QO}</span> VAr</div>
|
||||
</div>
|
||||
<div class="row rric">
|
||||
<div class="col-12 font-weight-bold">Total</div>
|
||||
<div class="col-4">In</div>
|
||||
<div class="col-8 text-right"><span class="jric">{tQI}</span> kVArh</div>
|
||||
<div class="col-4">Out</div>
|
||||
<div class="col-8 text-right"><span class="jrec">{tQO}</span> kVArh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Instant reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jri">{Q}</span> VAr</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jre">{QO}</span> VAr</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3" style="display: {de};">
|
||||
<div class="bg-white rounded shadow p-3" style="display: {da};">
|
||||
<div class="row rrec">
|
||||
<div class="col-sm-4 font-weight-bold">Total reactive</div>
|
||||
<div class="col-4 col-sm-1">In</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jric">{tQI}</span> kVArh</div>
|
||||
<div class="col-4 col-sm-1">Out</div>
|
||||
<div class="col-8 col-sm-3 text-right"><span class="jrec">{tQO}</span> kVArh</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ppc" class="col-xl-12 mb-3" style="display: none;">
|
||||
<div class="bg-white rounded shadow" id="pp" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<div class="bg-white rounded shadow" id="ep" style="width: 100%; height: 224px;"></div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user