diff --git a/src/AmsDataStorage.cpp b/src/AmsDataStorage.cpp index 26ca0b60..2b235f4e 100644 --- a/src/AmsDataStorage.cpp +++ b/src/AmsDataStorage.cpp @@ -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); -} diff --git a/src/AmsDataStorage.h b/src/AmsDataStorage.h index 1aec8738..68fd1235 100644 --- a/src/AmsDataStorage.h +++ b/src/AmsDataStorage.h @@ -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 diff --git a/src/AmsToMqttBridge.h b/src/AmsToMqttBridge.h index b359cab7..2f1a0075 100644 --- a/src/AmsToMqttBridge.h +++ b/src/AmsToMqttBridge.h @@ -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 diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index b6b4a979..f8c14966 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -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 diff --git a/src/IEC6205675.cpp b/src/IEC6205675.cpp index 8bc2e769..18a3d869 100644 --- a/src/IEC6205675.cpp +++ b/src/IEC6205675.cpp @@ -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; diff --git a/src/entsoe/EntsoeApi.cpp b/src/entsoe/EntsoeApi.cpp index e519ebd1..475351e9 100644 --- a/src/entsoe/EntsoeApi.cpp +++ b/src/entsoe/EntsoeApi.cpp @@ -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); diff --git a/src/entsoe/EntsoeApi.h b/src/entsoe/EntsoeApi.h index 3744f649..ca94ca2c 100644 --- a/src/entsoe/EntsoeApi.h +++ b/src/entsoe/EntsoeApi.h @@ -18,6 +18,7 @@ public: bool loop(); char* getToken(); + char* getCurrency(); float getValueForHour(uint8_t); float getValueForHour(time_t, uint8_t); diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index 9095e914..b5f29c98 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -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"); diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index d7dec960..ba4d952f 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -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(); diff --git a/web/application.js b/web/application.js index 55af61ca..2edf1729 100644 --- a/web/application.js +++ b/web/application.js @@ -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); diff --git a/web/data.json b/web/data.json index 3995443b..e23702b9 100644 --- a/web/data.json +++ b/web/data.json @@ -29,5 +29,6 @@ "hm" : %d, "wm" : %d, "mm" : %d, - "me" : %d + "me" : %d, + "p" : %s } \ No newline at end of file diff --git a/web/energyprice.json b/web/energyprice.json new file mode 100644 index 00000000..9231ba9c --- /dev/null +++ b/web/energyprice.json @@ -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 +} diff --git a/web/entsoe.html b/web/entsoe.html index 92a31566..43b254c9 100644 --- a/web/entsoe.html +++ b/web/entsoe.html @@ -19,7 +19,7 @@