From eb592451186a02270e404b1b77014d8896058a7f Mon Sep 17 00:00:00 2001 From: Gunnar Skjold Date: Sat, 27 Nov 2021 20:16:26 +0100 Subject: [PATCH] Added usage plots and ADC reading for Vcc --- src/AmsConfiguration.cpp | 106 ++++++--- src/AmsConfiguration.h | 26 ++- src/AmsDataStorage.cpp | 308 ++++++++++++++++++++++++++ src/AmsDataStorage.h | 101 +++++++++ src/AmsStorage.h | 3 + src/AmsToMqttBridge.h | 2 - src/AmsToMqttBridge.ino | 46 +++- src/HwTools.cpp | 147 ++++++++++++- src/HwTools.h | 13 ++ src/IEC6205675.cpp | 26 ++- src/ams/hdlc.cpp | 9 +- src/ams/hdlc.h | 2 + src/entsoe/EntsoeApi.cpp | 4 +- src/web/AmsWebServer.cpp | 136 ++++++++++-- src/web/AmsWebServer.h | 7 +- web/application.js | 465 ++++++++++++++++++++++++++++++--------- web/dayplot.json | 26 +++ web/firmware.html | 34 +++ web/foot.html | 12 +- web/gaugemeter.js | 276 ----------------------- web/gpio.html | 20 +- web/head32.html | 67 +++--- web/head8266.html | 67 +++--- web/index.html | 97 +++----- web/meter.html | 2 + web/monthplot.json | 33 +++ web/upload.html | 2 +- 27 files changed, 1422 insertions(+), 615 deletions(-) create mode 100644 src/AmsDataStorage.cpp create mode 100644 src/AmsDataStorage.h create mode 100644 web/dayplot.json create mode 100644 web/firmware.html delete mode 100644 web/gaugemeter.js create mode 100644 web/monthplot.json diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index b6ffac5c..008edf93 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -2,7 +2,7 @@ bool AmsConfiguration::getSystemConfig(SystemConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_SYSTEM_START, config); EEPROM.end(); return true; @@ -12,7 +12,7 @@ bool AmsConfiguration::getSystemConfig(SystemConfig& config) { } bool AmsConfiguration::setSystemConfig(SystemConfig& config) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_SYSTEM_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -21,7 +21,7 @@ bool AmsConfiguration::setSystemConfig(SystemConfig& config) { bool AmsConfiguration::getWiFiConfig(WiFiConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_WIFI_START, config); EEPROM.end(); return true; @@ -45,7 +45,7 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) { } else { wifiChanged = true; } - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_WIFI_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -85,7 +85,7 @@ void AmsConfiguration::ackWifiChange() { bool AmsConfiguration::getMqttConfig(MqttConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_MQTT_START, config); EEPROM.end(); return true; @@ -110,7 +110,7 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) { } else { mqttChanged = true; } - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_MQTT_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -143,7 +143,7 @@ void AmsConfiguration::ackMqttChange() { bool AmsConfiguration::getWebConfig(WebConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_WEB_START, config); EEPROM.end(); return true; @@ -154,7 +154,7 @@ bool AmsConfiguration::getWebConfig(WebConfig& config) { } bool AmsConfiguration::setWebConfig(WebConfig& config) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_WEB_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -169,7 +169,7 @@ void AmsConfiguration::clearAuth(WebConfig& config) { bool AmsConfiguration::getMeterConfig(MeterConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_METER_START, config); EEPROM.end(); return true; @@ -193,7 +193,7 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) { } else { meterChanged = true; } - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_METER_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -221,7 +221,7 @@ void AmsConfiguration::ackMeterChanged() { bool AmsConfiguration::getDebugConfig(DebugConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_DEBUG_START, config); EEPROM.end(); return true; @@ -232,7 +232,7 @@ bool AmsConfiguration::getDebugConfig(DebugConfig& config) { } bool AmsConfiguration::setDebugConfig(DebugConfig& config) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_DEBUG_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -247,7 +247,7 @@ void AmsConfiguration::clearDebug(DebugConfig& config) { bool AmsConfiguration::getDomoticzConfig(DomoticzConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_DOMOTICZ_START, config); EEPROM.end(); return true; @@ -269,7 +269,7 @@ bool AmsConfiguration::setDomoticzConfig(DomoticzConfig& config) { domoChanged = true; } mqttChanged = domoChanged; - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_DOMOTICZ_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -310,7 +310,7 @@ bool AmsConfiguration::pinUsed(uint8_t pin, GpioConfig& config) { bool AmsConfiguration::getGpioConfig(GpioConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_GPIO_START, config); EEPROM.end(); return true; @@ -366,7 +366,7 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) { if(config.apPin >= 0) pinMode(config.apPin, INPUT_PULLUP); - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_GPIO_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -388,11 +388,13 @@ void AmsConfiguration::clearGpio(GpioConfig& config) { config.vccOffset = 0; config.vccMultiplier = 1000; config.vccBootLimit = 0; + config.vccResistorGnd = 0; + config.vccResistorVcc = 0; } bool AmsConfiguration::getNtpConfig(NtpConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_NTP_START, config); EEPROM.end(); return true; @@ -419,7 +421,7 @@ bool AmsConfiguration::setNtpConfig(NtpConfig& config) { } else { ntpChanged = true; } - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_NTP_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -444,7 +446,7 @@ void AmsConfiguration::clearNtp(NtpConfig& config) { bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) { if(hasConfig()) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_ENTSOE_START, config); EEPROM.end(); return true; @@ -463,7 +465,7 @@ bool AmsConfiguration::setEntsoeConfig(EntsoeConfig& config) { } else { entsoeChanged = true; } - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(CONFIG_ENTSOE_START, config); bool ret = EEPROM.commit(); EEPROM.end(); @@ -486,7 +488,7 @@ void AmsConfiguration::ackEntsoeChange() { } void AmsConfiguration::clear() { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); MeterConfig meter; clearMeter(meter); EEPROM.put(CONFIG_METER_START, meter); @@ -522,7 +524,7 @@ void AmsConfiguration::clear() { bool AmsConfiguration::hasConfig() { if(configVersion == 0) { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS); EEPROM.end(); } @@ -553,6 +555,14 @@ bool AmsConfiguration::hasConfig() { configVersion = 0; return false; } + case 88: + configVersion = -1; // Prevent loop + if(relocateConfig88()) { + configVersion = 89; + } else { + configVersion = 0; + return false; + } case EEPROM_CHECK_SUM: return true; default: @@ -567,7 +577,7 @@ int AmsConfiguration::getConfigVersion() { } void AmsConfiguration::loadTempSensors() { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); TempSensorConfig* tempSensors[32]; int address = EEPROM_TEMP_CONFIG_ADDRESS; int c = 0; @@ -606,7 +616,7 @@ void AmsConfiguration::saveTempSensors() { bool AmsConfiguration::loadConfig83(int address) { ConfigObject83 c; - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(address, c); EntsoeConfig entsoe {"", "", "", 1000}; @@ -644,7 +654,9 @@ bool AmsConfiguration::loadConfig83(int address) { c.vccPin, c.vccOffset, c.vccMultiplier, - c.vccBootLimit + c.vccBootLimit, + 0, + 0 }; EEPROM.put(CONFIG_GPIO_START, gpio); @@ -715,7 +727,7 @@ bool AmsConfiguration::loadConfig83(int address) { bool AmsConfiguration::relocateConfig86() { MqttConfig86 mqtt86; MqttConfig mqtt; - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_MQTT_START_86, mqtt86); strcpy(mqtt.host, mqtt86.host); mqtt.port = mqtt86.port; @@ -736,7 +748,7 @@ bool AmsConfiguration::relocateConfig86() { bool AmsConfiguration::relocateConfig87() { MeterConfig87 meter87; MeterConfig meter; - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.get(CONFIG_METER_START_87, meter87); if(meter87.type < 5) { meter.baud = 2400; @@ -757,8 +769,38 @@ bool AmsConfiguration::relocateConfig87() { return ret; } +bool AmsConfiguration::relocateConfig88() { + GpioConfig88 gpio88; + EEPROM.begin(SPI_FLASH_SEC_SIZE); + EEPROM.get(CONFIG_GPIO_START_88, gpio88); + + GpioConfig gpio { + gpio88.hanPin, + gpio88.apPin, + gpio88.ledPin, + gpio88.ledInverted, + gpio88.ledPinRed, + gpio88.ledPinGreen, + gpio88.ledPinBlue, + gpio88.ledRgbInverted, + gpio88.tempSensorPin, + gpio88.tempAnalogSensorPin, + gpio88.vccPin, + gpio88.vccOffset, + gpio88.vccMultiplier, + gpio88.vccBootLimit, + 0, + 0 + }; + EEPROM.put(CONFIG_GPIO_START, gpio); + EEPROM.put(EEPROM_CONFIG_ADDRESS, 89); + bool ret = EEPROM.commit(); + EEPROM.end(); + return ret; +} + bool AmsConfiguration::save() { - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM); saveTempSensors(); bool success = EEPROM.commit(); @@ -938,9 +980,17 @@ void AmsConfiguration::print(Print* debugger) debugger->printf("Temperature pin: %i\r\n", gpio.tempSensorPin); debugger->printf("Temp analog pin: %i\r\n", gpio.tempAnalogSensorPin); debugger->printf("Vcc pin: %i\r\n", gpio.vccPin); + if(gpio.vccMultiplier > 0) { debugger->printf("Vcc multiplier: %f\r\n", gpio.vccMultiplier / 1000.0); + } + if(gpio.vccOffset > 0) { debugger->printf("Vcc offset: %f\r\n", gpio.vccOffset / 100.0); + } + if(gpio.vccBootLimit > 0) { debugger->printf("Vcc boot limit: %f\r\n", gpio.vccBootLimit / 10.0); + } + debugger->printf("GND resistor: %i\r\n", gpio.vccResistorGnd); + debugger->printf("Vcc resistor: %i\r\n", gpio.vccResistorVcc); debugger->println(""); delay(10); Serial.flush(); diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index 09cb5a3b..0a138796 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -3,17 +3,16 @@ #include #include "Arduino.h" -#define EEPROM_SIZE 1024 * 3 -#define EEPROM_CHECK_SUM 88 // Used to check if config is stored. Change if structure changes +#define EEPROM_CHECK_SUM 89 // Used to check if config is stored. Change if structure changes #define EEPROM_CONFIG_ADDRESS 0 #define EEPROM_TEMP_CONFIG_ADDRESS 2048 #define CONFIG_SYSTEM_START 8 #define CONFIG_WIFI_START 16 #define CONFIG_METER_START 224 +#define CONFIG_GPIO_START 266 #define CONFIG_WEB_START 648 #define CONFIG_DEBUG_START 824 -#define CONFIG_GPIO_START 832 #define CONFIG_DOMOTICZ_START 856 #define CONFIG_NTP_START 872 #define CONFIG_ENTSOE_START 944 @@ -21,6 +20,7 @@ #define CONFIG_MQTT_START_86 224 #define CONFIG_METER_START_87 784 +#define CONFIG_GPIO_START_88 832 struct SystemConfig { @@ -111,6 +111,25 @@ struct GpioConfig { int16_t vccOffset; uint16_t vccMultiplier; uint8_t vccBootLimit; + uint16_t vccResistorGnd; + uint16_t vccResistorVcc; +}; // 20 + +struct GpioConfig88 { + uint8_t hanPin; + uint8_t apPin; + uint8_t ledPin; + bool ledInverted; + uint8_t ledPinRed; + uint8_t ledPinGreen; + uint8_t ledPinBlue; + bool ledRgbInverted; + uint8_t tempSensorPin; + uint8_t tempAnalogSensorPin; + uint8_t vccPin; + int16_t vccOffset; + uint16_t vccMultiplier; + uint8_t vccBootLimit; }; // 16 struct DomoticzConfig { @@ -295,6 +314,7 @@ private: bool loadConfig83(int address); bool relocateConfig86(); bool relocateConfig87(); + bool relocateConfig88(); // dev 1.6 int readString(int pAddress, char* pString[]); int readInt(int pAddress, int *pValue); diff --git a/src/AmsDataStorage.cpp b/src/AmsDataStorage.cpp new file mode 100644 index 00000000..26ca0b60 --- /dev/null +++ b/src/AmsDataStorage.cpp @@ -0,0 +1,308 @@ +#include "AmsDataStorage.h" +#include +#include "EEPROM.h" +#include "LittleFS.h" +#include "AmsStorage.h" + +AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) { + day.version = 3; + month.version = 4; + this->debugger = debugger; +} + +void AmsDataStorage::setTimezone(Timezone* tz) { + this->tz = tz; +} + +bool AmsDataStorage::update(AmsData* data) { + if(data->getListType() != 3) return false; + time_t now = time(nullptr); + if(debugger->isActive(RemoteDebug::DEBUG)) { + debugger->printf("(AmsDataStorage) Time is: %d\n", now); + } + if(now < EPOCH_2021_01_01) { + now = data->getMeterTimestamp(); + if(debugger->isActive(RemoteDebug::DEBUG)) { + debugger->printf("(AmsDataStorage) New time is: %d\n", now); + } + } + if(now-day.lastMeterReadTime < 3590) { + if(debugger->isActive(RemoteDebug::INFO)) { + 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); + } + return false; + } + + 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(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; + + // Update month plot + if(tz != NULL) { + time_t local = tz->toLocal(now); + breakTime(local, 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; + } + } 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; +} + +MonthDataPoints AmsDataStorage::getMonthDataPoints() { + return month; +} + +bool AmsDataStorage::load(AmsData* meterState) { + if(!LittleFS.begin()) { + printE("Unable to load LittleFS"); + return false; + } + bool ret = false; + if(LittleFS.exists(FILE_DAYPLOT)) { + File file = LittleFS.open(FILE_DAYPLOT, "r"); + char buf[file.size()]; + file.readBytes(buf, file.size()); + DayDataPoints* day = (DayDataPoints*) buf; + file.close(); + + if(day->version == 3) { + memcpy(&this->day, day, sizeof(this->day)); + ret = true; + } else { + ret = false; + } + } + + if(LittleFS.exists(FILE_MONTHPLOT)) { + File file = LittleFS.open(FILE_MONTHPLOT, "r"); + char buf[file.size()]; + file.readBytes(buf, file.size()); + 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; + } else { + ret = false; + } + } + + LittleFS.end(); + + return ret; +} + +bool AmsDataStorage::save() { + if(!LittleFS.begin()) { + printE("Unable to load LittleFS"); + return false; + } + { + File file = LittleFS.open(FILE_DAYPLOT, "w"); + char buf[sizeof(day)]; + memcpy(buf, &day, sizeof(day)); + for(int i = 0; i < sizeof(day); i++) { + file.write(buf[i]); + } + file.close(); + } + { + File file = LittleFS.open(FILE_MONTHPLOT, "w"); + char buf[sizeof(month)]; + memcpy(buf, &month, sizeof(month)); + for(int i = 0; i < sizeof(month); i++) { + file.write(buf[i]); + } + file.close(); + } + + 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 new file mode 100644 index 00000000..1aec8738 --- /dev/null +++ b/src/AmsDataStorage.h @@ -0,0 +1,101 @@ +#ifndef _AMSDATASTORAGE_H +#define _AMSDATASTORAGE_H +#include "Arduino.h" +#include "AmsData.h" +#include "RemoteDebug.h" +#include "Timezone.h" + +#define EPOCH_2021_01_01 1609459200 + +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; + time_t lastMeterReadTime; + uint32_t activeImport; + uint32_t activeExport; +}; // 37 bytes + +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; + time_t lastMeterReadTime; + uint32_t activeImport; + uint32_t activeExport; +}; // 75 bytes + +class AmsDataStorage { +public: + AmsDataStorage(RemoteDebug*); + void setTimezone(Timezone*); + bool update(AmsData*); + DayDataPoints getDayDataPoints(); + MonthDataPoints getMonthDataPoints(); + bool load(AmsData*); + bool save(); + +private: + Timezone* tz; + DayDataPoints day; + MonthDataPoints month; + RemoteDebug* debugger; + + void printD(String fmt, ...); + void printI(String fmt, ...); + void printW(String fmt, ...); + void printE(String fmt, ...); +}; + +#endif diff --git a/src/AmsStorage.h b/src/AmsStorage.h index 51194c87..f0815d8a 100644 --- a/src/AmsStorage.h +++ b/src/AmsStorage.h @@ -7,4 +7,7 @@ #define FILE_MQTT_CERT "/mqtt-cert.pem" #define FILE_MQTT_KEY "/mqtt-key.pem" +#define FILE_DAYPLOT "/dayplot.bin" +#define FILE_MONTHPLOT "/monthplot.bin" + #endif diff --git a/src/AmsToMqttBridge.h b/src/AmsToMqttBridge.h index c3f15c8c..b359cab7 100644 --- a/src/AmsToMqttBridge.h +++ b/src/AmsToMqttBridge.h @@ -5,8 +5,6 @@ #define INVALID_BUTTON_PIN 0xFFFFFFFF -#define EPOCH_2021_01_01 1609459200 - #define MAX_PEM_SIZE 4096 #include diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index ae1b7fdc..51bb38d5 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -18,14 +18,11 @@ #include "AmsToMqttBridge.h" #include "AmsStorage.h" +#include "AmsDataStorage.h" #include #include #include -#if defined(ESP8266) -ADC_MODE(ADC_VCC); -#endif - #include "HwTools.h" #include "entsoe/EntsoeApi.h" @@ -74,6 +71,8 @@ String topic = "ams"; AmsData meterState; bool ntpEnabled = false; +AmsDataStorage ds(&Debug); + void setup() { WiFiConfig wifi; Serial.begin(115200); @@ -143,7 +142,7 @@ void setup() { SerialConfig serialConfig; #elif defined(ESP32) uint32_t serialConfig; - #endif; + #endif switch(meterConfig.parity) { case 2: serialConfig = SERIAL_7N1; @@ -186,10 +185,10 @@ void setup() { } float vccBootLimit = gpioConfig.vccBootLimit == 0 ? 0 : gpioConfig.vccBootLimit / 10.0; - if(vccBootLimit > 0 && (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH)) { // Skip if user is holding AP button while booting (HIGH = button is released) + if(vccBootLimit > 2.5 && vccBootLimit < 3.3 && (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH)) { // Skip if user is holding AP button while booting (HIGH = button is released) if (vcc < vccBootLimit) { if(Debug.isActive(RemoteDebug::INFO)) { - debugI("Voltage is too low, sleeping"); + Debug.printf("(setup) Voltage is too low (%.2f < %.2f), sleeping\n", vcc, vccBootLimit); Serial.flush(); } ESP.deepSleep(10000000); //Deep sleep to allow output cap to charge up @@ -214,7 +213,7 @@ void setup() { if(hasFs) { bool flashed = false; if(LittleFS.exists(FILE_FIRMWARE)) { - if (digitalRead(gpioConfig.apPin) == HIGH) { + if (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH) { if(Debug.isActive(RemoteDebug::INFO)) debugI("Found firmware"); #if defined(ESP8266) WiFi.setSleepMode(WIFI_LIGHT_SLEEP); @@ -286,7 +285,10 @@ void setup() { TimeChangeRule dst = {"DST", Last, Sun, Mar, 2, (ntp.offset + ntp.summerOffset) / 6}; tz = new Timezone(dst, std); ws.setTimezone(tz); + ds.setTimezone(tz); } + + ds.load(&meterState); } else { if(Debug.isActive(RemoteDebug::INFO)) { debugI("No configuration, booting AP"); @@ -294,7 +296,7 @@ void setup() { swapWifiMode(); } - ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &mqtt); + ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &mqtt); } int buttonTimer = 0; @@ -472,7 +474,7 @@ void loop() { } void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert) { - debugI("Setting up HAN on pin %d with baud %d and parity %d", pin, baud, parityOrdinal); + if(Debug.isActive(RemoteDebug::INFO)) Debug.printf("(setupHanPort) Setting up HAN on pin %d with baud %d and parity %d\n", pin, baud, parityOrdinal); HardwareSerial *hwSerial = NULL; if(pin == 3) { @@ -630,6 +632,7 @@ void mqttMessageReceived(String &topic, String &payload) // Ideas could be to query for values or to initiate OTA firmware update } +int len = 0; uint8_t buf[BUF_SIZE]; HDLCConfig* hc = NULL; int currentMeterType = -1; @@ -648,12 +651,22 @@ void readHanPort() { hanSerial->readBytes(buf, BUF_SIZE); return; } - CosemDateTime timestamp; + CosemDateTime timestamp = {0}; AmsData data; if(currentMeterType == 1) { - size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI + while(hanSerial->available()) { + buf[len++] = hanSerial->read(); + } if(len > 0) { int pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp); + if(pos == HDLC_FRAME_INCOMPLETE) { + if(len >= BUF_SIZE) { + hanSerial->readBytes(buf, BUF_SIZE); + len = 0; + debugI("Buffer overflow, resetting"); + } + return; + } if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) { hc = new HDLCConfig(); memcpy(hc->encryption_key, meterConfig.encryptionKey, 16); @@ -673,6 +686,7 @@ void readHanPort() { debugPrint(hc->authentication_tag, 0, 8); } } + len = 0; if(pos >= 0) { debugI("Valid HDLC, start at %d", pos); data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), timestamp); @@ -726,6 +740,14 @@ void readHanPort() { mqtt.loop(); delay(10); } + + if(ds.update(&data)) { + debugI("Saving day plot"); + ds.save(); + } else if(data.getListType() == 3) { + debugE("Unable to update day plot"); + } + meterState.apply(data); } } diff --git a/src/HwTools.cpp b/src/HwTools.cpp index 0a61dd73..14f363d0 100644 --- a/src/HwTools.cpp +++ b/src/HwTools.cpp @@ -1,4 +1,7 @@ #include "HwTools.h" +#if defined(ESP8266) +ADC_MODE(ADC_VCC); +#endif void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) { this->config = config; @@ -14,18 +17,35 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) { config->tempSensorPin = 0xFF; } + if(config->vccPin > 0 && config->vccPin < 40) { + getAdcChannel(config->vccPin, voltAdc); + if(voltAdc.unit != 0xFF) { + #if defined(ESP32) + if(voltAdc.unit == ADC_UNIT_1) { + voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t)); + esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar); + adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_6); + } else if(voltAdc.unit == ADC_UNIT_2) { + voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t)); + esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar); + adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6); + } + #endif + } else { + pinMode(config->vccPin, INPUT); + } + } else { + voltAdc.unit = 0xFF; + voltAdc.channel = 0xFF; + config->vccPin = 0xFF; + } + if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) { pinMode(config->tempAnalogSensorPin, INPUT); } else { config->tempAnalogSensorPin = 0xFF; } - if(config->vccPin > 0 && config->vccPin < 40) { - pinMode(config->vccPin, INPUT); - } else { - config->vccPin = 0xFF; - } - if(config->ledPin > 0 && config->ledPin < 40) { pinMode(config->ledPin, OUTPUT); ledOff(LED_INTERNAL); @@ -55,19 +75,128 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) { } } +void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) { + config.unit = 0xFF; + config.channel = 0xFF; + #if defined(ESP32) + switch(pin) { + case ADC1_CHANNEL_0_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_0; + break; + case ADC1_CHANNEL_1_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_1; + break; + case ADC1_CHANNEL_2_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_2; + break; + case ADC1_CHANNEL_3_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_3; + break; + case ADC1_CHANNEL_4_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_4; + break; + case ADC1_CHANNEL_5_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_5; + break; + case ADC1_CHANNEL_6_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_6; + break; + case ADC1_CHANNEL_7_GPIO_NUM: + config.unit = ADC_UNIT_1; + config.channel = ADC1_CHANNEL_7; + break; + case ADC2_CHANNEL_0_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_0; + break; + case ADC2_CHANNEL_1_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_1; + break; + case ADC2_CHANNEL_2_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_2; + break; + case ADC2_CHANNEL_3_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_3; + break; + case ADC2_CHANNEL_4_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_4; + break; + case ADC2_CHANNEL_5_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_5; + break; + case ADC2_CHANNEL_6_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_6; + break; + case ADC2_CHANNEL_7_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_7; + break; + case ADC2_CHANNEL_8_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_8; + break; + case ADC2_CHANNEL_9_GPIO_NUM: + config.unit = ADC_UNIT_2; + config.channel = ADC2_CHANNEL_9; + break; + } + #endif +} + double HwTools::getVcc() { double volts = 0.0; if(config->vccPin != 0xFF) { #if defined(ESP8266) - volts = (analogRead(config->vccPin) / 1024.0) * 3.3; + uint32_t x = 0; + for (int i = 0; i < 10; i++) { + x += analogRead(config->vccPin); + } + volts = x / 10240; #elif defined(ESP32) - volts = (analogRead(config->vccPin) / 4095.0) * 3.3; + if(voltAdc.unit != 0xFF) { + uint32_t x = 0; + for (int i = 0; i < 10; i++) { + if(voltAdc.unit == ADC_UNIT_1) { + x += adc1_get_raw((adc1_channel_t) voltAdc.channel); + } else if(voltAdc.unit == ADC_UNIT_2) { + int v = 0; + adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v); + x += v; + } + } + x = x / 10; + uint32_t voltage = esp_adc_cal_raw_to_voltage(x, voltAdcChar); + volts = voltage / 1000.0; + } else { + uint32_t x = 0; + for (int i = 0; i < 10; i++) { + x += analogRead(config->vccPin); + } + volts = x / 40950; + } #endif } else { #if defined(ESP8266) - volts = ((double) ESP.getVcc()) / 1024.0; + volts = ESP.getVcc() / 1024.0; #endif } + if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) { + volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd); + } + float vccOffset = config->vccOffset / 100.0; float vccMultiplier = config->vccMultiplier / 1000.0; diff --git a/src/HwTools.h b/src/HwTools.h index 456bc72b..b3096ba6 100644 --- a/src/HwTools.h +++ b/src/HwTools.h @@ -7,6 +7,9 @@ #include #elif defined(ESP32) #include +#include +#include +#include #endif #include @@ -26,6 +29,11 @@ struct TempSensorData { bool changed; }; +struct AdcConfig { + uint8_t unit; + uint8_t channel; +}; + class HwTools { public: void setup(GpioConfig*, AmsConfiguration*); @@ -43,6 +51,10 @@ public: HwTools() {}; private: + AdcConfig voltAdc, tempAdc; + #if defined(ESP32) + esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar; + #endif GpioConfig* config; AmsConfiguration* amsConf; bool tempSensorInit; @@ -53,6 +65,7 @@ private: bool writeLedPin(uint8_t color, uint8_t state); bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]); + void getAdcChannel(uint8_t pin, AdcConfig&); }; #endif diff --git a/src/IEC6205675.cpp b/src/IEC6205675.cpp index c3bf06fa..cd5c68ef 100644 --- a/src/IEC6205675.cpp +++ b/src/IEC6205675.cpp @@ -1,5 +1,6 @@ #include "IEC6205675.h" #include "lwip/def.h" +#include "Timezone.h" IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packageTimestamp) { uint32_t u32; @@ -261,9 +262,21 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag } } - time_t ts = getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d))); - if(ts > 0) { - meterTimestamp = ts; + 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 = tz.toUTC(ts); + this->meterTimestamp = tz.toUTC(ts); + Serial.printf("\nKamstrup/Aidon time: %d\n", meterTimestamp); + } else { + meterTimestamp = ts; + } } u32 = getUnsignedNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d))); @@ -425,7 +438,6 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) case CosemTypeOctetString: { if(item->oct.length == 0x0C) { AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item; - //Serial.printf("\nYear: %d, Month: %d, Day: %d, Hour: %d, Minutes %d, Second: %d, Deviation: %d\n", ntohs(ts->dt.year), ts->dt.month, ts->dt.dayOfMonth, ts->dt.hour, ts->dt.minute, ts->dt.second, ntohs(ts->dt.deviation)); return getTimestamp(ts->dt); } } @@ -436,13 +448,17 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) time_t IEC6205675::getTimestamp(CosemDateTime timestamp) { tmElements_t tm; - tm.Year = ntohs(timestamp.year) - 1970; + uint16_t year = ntohs(timestamp.year); + if(year < 1970) return 0; + tm.Year = year - 1970; tm.Month = timestamp.month; tm.Day = timestamp.dayOfMonth; tm.Hour = timestamp.hour; tm.Minute = timestamp.minute; tm.Second = timestamp.second; + Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status); + time_t time = makeTime(tm); int16_t deviation = ntohs(timestamp.deviation); if(deviation >= -720 && deviation <= 720) { diff --git a/src/ams/hdlc.cpp b/src/ams/hdlc.cpp index c52bd009..85e48afb 100644 --- a/src/ams/hdlc.cpp +++ b/src/ams/hdlc.cpp @@ -15,13 +15,15 @@ void mbus_hexdump(const uint8_t* buf, int len) { printf("]\n"); } -int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config, CosemDateTime* timestamp) { +int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) { //mbus_hexdump(d, len); HDLCHeader* h = (HDLCHeader*) d; // Length field (11 lsb of format) - len = (ntohs(h->format) & 0x7FF) + 2; + int len = (ntohs(h->format) & 0x7FF) + 2; + if(len > length) + return -4; HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f); @@ -81,11 +83,12 @@ int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config, CosemDateTime* } ptr += 2 + dateTime->base.length; } else if(dateTime->base.type == CosemTypeNull) { + timestamp = 0; ptr++; } else if(dateTime->base.type == CosemTypeDateTime) { memcpy(timestamp, ptr, dateTime->base.length); } else if(dateTime->base.type == 0x0C) { // Kamstrup bug... - memcpy(timestamp, ptr, dateTime->base.length); + memcpy(timestamp, ptr, 0x0C); ptr += 13; } else { return -99; diff --git a/src/ams/hdlc.h b/src/ams/hdlc.h index f87df6af..c4447450 100644 --- a/src/ams/hdlc.h +++ b/src/ams/hdlc.h @@ -5,6 +5,8 @@ #include #define HDLC_FLAG 0x7E +#define HDLC_BOUNDRY_FLAG_MISSING -1 +#define HDLC_FRAME_INCOMPLETE -4 #define HDLC_ENCRYPTION_CONFIG_MISSING -90 struct HDLCConfig { diff --git a/src/entsoe/EntsoeApi.cpp b/src/entsoe/EntsoeApi.cpp index 821e3d65..e519ebd1 100644 --- a/src/entsoe/EntsoeApi.cpp +++ b/src/entsoe/EntsoeApi.cpp @@ -179,9 +179,9 @@ bool EntsoeApi::retrieve(const char* url, Stream* doc) { client.setBufferSizes(bufSize, bufSize); } */ - - client.setInsecure(); #endif + + client.setInsecure(); HTTPClient https; https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index 3a6ee43d..6d03a73c 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -30,9 +30,9 @@ #include "root/restart_html.h" #include "root/restartwait_html.h" #include "root/boot_css.h" -#include "root/gaugemeter_js.h" #include "root/github_svg.h" #include "root/upload_html.h" +#include "root/firmware_html.h" #include "root/delete_html.h" #include "root/reset_html.h" #include "root/temperature_html.h" @@ -41,6 +41,8 @@ #include "root/data_json.h" #include "root/tempsensor_json.h" #include "root/lowmem_html.h" +#include "root/dayplot_json.h" +#include "root/monthplot_json.h" #include "base64.h" @@ -49,11 +51,12 @@ AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) { this->hw = hw; } -void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, MQTTClient* mqtt) { +void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds, MQTTClient* mqtt) { this->config = config; this->gpioConfig = gpioConfig; this->meterConfig = meterConfig; this->meterState = meterState; + this->ds = ds; this->mqtt = mqtt; char jsuri[32]; @@ -73,9 +76,10 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter server.on("/domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this)); server.on("/entsoe",HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this)); server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this)); - server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this)); server.on("/github.svg", HTTP_GET, std::bind(&AmsWebServer::githubSvg, this)); 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("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this)); @@ -660,13 +664,6 @@ void AmsWebServer::bootCss() { server.send_P(200, "text/css", BOOT_CSS); } -void AmsWebServer::gaugemeterJs() { - printD("Serving /gaugemeter.js over http..."); - - server.sendHeader("Cache-Control", "public, max-age=3600"); - server.send_P(200, "application/javascript", GAUGEMETER_JS); -} - void AmsWebServer::githubSvg() { printD("Serving /github.svg over http..."); @@ -782,6 +779,95 @@ void AmsWebServer::dataJson() { server.send(200, "application/json", json); } +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 + ); + + 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::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 + ); + + 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::handleSetup() { printD("Handling setup method from http"); @@ -796,9 +882,6 @@ void AmsWebServer::handleSetup() { config->clear(); - config->clearGpio(*gpioConfig); - config->clearMeter(*meterConfig); - switch(sys.boardType) { case 0: // roarfred gpioConfig->hanPin = 3; @@ -922,10 +1005,6 @@ void AmsWebServer::handleSetup() { printD("Unable to set web config"); success = false; } - if(!config->setMeterConfig(*meterConfig)) { - printD("Unable to set meter config"); - success = false; - } if(!config->setGpioConfig(*gpioConfig)) { printD("Unable to set GPIO config"); success = false; @@ -1066,6 +1145,8 @@ void AmsWebServer::handleSave() { gpioConfig->vccOffset = server.hasArg("vccOffset") && !server.arg("vccOffset").isEmpty() ? server.arg("vccOffset").toFloat() * 100 : 0; gpioConfig->vccMultiplier = server.hasArg("vccMultiplier") && !server.arg("vccMultiplier").isEmpty() ? server.arg("vccMultiplier").toFloat() * 1000 : 1000; gpioConfig->vccBootLimit = server.hasArg("vccBootLimit") && !server.arg("vccBootLimit").isEmpty() ? server.arg("vccBootLimit").toFloat() * 10 : 0; + gpioConfig->vccResistorGnd = server.hasArg("vccResistorGnd") && !server.arg("vccResistorGnd").isEmpty() ? server.arg("vccResistorGnd").toInt() : 0; + gpioConfig->vccResistorVcc = server.hasArg("vccResistorVcc") && !server.arg("vccResistorVcc").isEmpty() ? server.arg("vccResistorVcc").toInt() : 0; config->setGpioConfig(*gpioConfig); } @@ -1201,6 +1282,9 @@ void AmsWebServer::configGpioHtml() { html.replace("${config.vccMultiplier}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : ""); html.replace("${config.vccBootLimit}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : ""); + html.replace("${config.vccResistorGnd}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : ""); + html.replace("${config.vccResistorVcc}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : ""); + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); @@ -1372,7 +1456,21 @@ void AmsWebServer::firmwareHtml() { if(!checkSecurity(1)) return; - uploadHtml("Firmware", "/firmware", "system"); + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + + String html = String((const __FlashStringHelper*) FIRMWARE_HTML); + + #if defined(ESP8266) + html.replace("{chipset}", "ESP8266"); + #elif defined(ESP32) + html.replace("{chipset}", "ESP32"); + #endif + + server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); + server.send_P(200, "text/html", HEAD_HTML); + server.sendContent(html); + server.sendContent_P(FOOT_HTML); } void AmsWebServer::firmwareUpload() { @@ -1519,7 +1617,7 @@ void AmsWebServer::restartWaitHtml() { yield(); if(performRestart) { - LittleFS.end(); + ds->save(); printI("Rebooting"); delay(1000); #if defined(ESP8266) diff --git a/src/web/AmsWebServer.h b/src/web/AmsWebServer.h index d1080533..d7dec960 100644 --- a/src/web/AmsWebServer.h +++ b/src/web/AmsWebServer.h @@ -8,6 +8,7 @@ #include "AmsConfiguration.h" #include "HwTools.h" #include "AmsData.h" +#include "AmsDataStorage.h" #include "Uptime.h" #include "RemoteDebug.h" #include "entsoe/EntsoeApi.h" @@ -29,7 +30,7 @@ class AmsWebServer { public: AmsWebServer(RemoteDebug* Debug, HwTools* hw); - void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, MQTTClient*); + void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage* ds, MQTTClient*); void loop(); void setTimezone(Timezone* tz); void setMqttEnabled(bool); @@ -47,6 +48,7 @@ private: MeterConfig* meterConfig; WebConfig webConfig; AmsData* meterState; + AmsDataStorage* ds; MQTTClient* mqtt; bool uploading = false; File file; @@ -76,9 +78,10 @@ private: void configGpioHtml(); void configDebugHtml(); void bootCss(); - void gaugemeterJs(); void githubSvg(); void dataJson(); + void dayplotJson(); + void monthplotJson(); void handleSetup(); void handleSave(); diff --git a/web/application.js b/web/application.js index a1087e8b..2f399fc4 100644 --- a/web/application.js +++ b/web/application.js @@ -1,46 +1,132 @@ var nextVersion; -var im, em, vm, am; -$(function() { - im = $("#im"); - if(im && im.gaugeMeter) { - im.gaugeMeter({ - percent: 0, - text: "-", - append: "W" - }); - } - - em = $("#em"); - if(em && em.gaugeMeter) { - em.gaugeMeter({ - percent: 0, - text: "-", - append: "W" - }); - } - - vm = $("#vm"); - if(vm && vm.gaugeMeter) { - vm.gaugeMeter({ - percent: 0, - text: "-", - append: "V" - }); - } - - am = $("#am"); - if(am && am.gaugeMeter) { - am.gaugeMeter({ - percent: 0, - text: "-", - append: "A" - }); - } +var im, em; - var meters = $('.SimpleMeter'); +// Day plot +var ep; +var ea; +var eo = { + title: 'Last 24 hours', + curveType: 'function', + legend: { position: 'none' }, + vAxis: { + viewWindowMode: 'maximized' + } +}; + +// Month plot +var mp; +var ma; +var mo = { + title: 'Last month', + curveType: 'function', + legend: { position: 'none' }, + vAxis: { + viewWindowMode: 'maximized' + } +}; + +// Voltage plot +var vp; +var va; +var vo = { + title: 'Phase voltage', + titleTextStyle: { + fontSize: 14 + }, + bar: { groupWidth: '90%' }, + vAxis: { + minValue: 200, + maxValue: 260, + ticks: [ + { v: 207, f: '-10%'}, + { v: 230, f: '230V'}, + { v: 253, f: '+10%'} + ] + }, + legend: { position: 'none' }, + tooltip: { trigger: 'none'}, + enableInteractivity: false, +}; + +// Amperage plot +var ap; +var aa; +var ao = { + title: 'Phase current', + titleTextStyle: { + fontSize: 14 + }, + bar: { groupWidth: '90%' }, + vAxis: { + minValue: 0, + maxValue: 100, + ticks: [ + { v: 25, f: '25%'}, + { v: 50, f: '50%'}, + { v: 75, f: '75%'}, + { v: 100, f: '100%'} + ] + }, + legend: { position: 'none' }, + tooltip: { trigger: 'none'}, + enableInteractivity: false, +}; + +// Import plot +var ip; +var ia; +var io = { + legend: 'none', + pieHole: 0.6, + pieSliceText: 'none', + pieStartAngle: 216, + slices: { + 0: { color: 'green' }, + 1: { color: '#eee' }, + 2: { color: 'transparent' } + }, + legend: { position: 'none' }, + tooltip: { trigger: 'none'}, + enableInteractivity: false, + chartArea: { + left: 0, + top: 0, + width: '100%', + height: '100%' + } +}; + +// Export plot +var xp; +var xa; +var xo = { + legend: 'none', + pieHole: 0.6, + pieSliceText: 'none', + pieStartAngle: 216, + slices: { + 0: { color: 'green' }, + 1: { color: '#eee' }, + 2: { color: 'transparent' } + }, + legend: { position: 'none' }, + tooltip: { trigger: 'none'}, + enableInteractivity: false, + chartArea: { + left: 0, + top: 0, + width: '100%', + height: '100%' + } +}; + +$(function() { + var meters = $('.plot1'); if(meters.length > 0) { - fetch(); + // Chart + google.charts.load('current', {'packages':['corechart']}); + google.charts.setOnLoadCallback(setupChart); } // For mqtt @@ -97,6 +183,9 @@ $(function() { var fileName = $(this).val(); $(this).next('.custom-file-label').html(fileName); }) + $('.upload-form').on('submit', function(i, form) { + $('#loading-indicator').show(); + }); // For NTP $('#n').on('change', function() { @@ -135,8 +224,9 @@ $(function() { } // Check for software upgrade - var swv = $('#swVersion') - if(meters.length > 0 && swv.length == 1 && swv.text() != "SNAPSHOT") { + var swv = $('#swVersion'); + var fwl = $('#fwLink'); + if((meters.length > 0 || fwl.length > 0) && swv.length == 1) { var v = swv.text().substring(1).split('.'); var v_major = parseInt(v[0]); var v_minor = parseInt(v[1]); @@ -145,48 +235,66 @@ $(function() { url: swv.data('url'), dataType: 'json' }).done(function(releases) { - releases.reverse(); - var me; - var next_patch; - var next_minor; - var next_major; - $.each(releases, function(i, release) { - var ver2 = release.tag_name; - var v2 = ver2.substring(1).split('.'); - var v2_major = parseInt(v2[0]); - var v2_minor = parseInt(v2[1]); - var v2_patch = parseInt(v2[2]); + if(!swv.text().match("^v\d{1,2}\.\d{1,2}\.\d{1,2}$")) { + nextVersion = releases[0]; + } else { + releases.reverse(); + var next_patch; + var next_minor; + var next_major; + $.each(releases, function(i, release) { + var ver2 = release.tag_name; + var v2 = ver2.substring(1).split('.'); + var v2_major = parseInt(v2[0]); + var v2_minor = parseInt(v2[1]); + var v2_patch = parseInt(v2[2]); - if(v2_major == v_major) { - if(v2_minor == v_minor) { - if(v2_patch > v_patch) { - next_patch = release; + if(v2_major == v_major) { + if(v2_minor == v_minor) { + if(v2_patch > v_patch) { + next_patch = release; + } + } else if(v2_minor == v_minor+1) { + next_minor = release; } - } else if(v2_minor == v_minor+1) { - next_minor = release; - } - } else if(v2_major == v_major+1) { - if(next_major) { - var mv = next_major.tag_name.substring(1).split('.'); - var mv_major = parseInt(mv[0]); - var mv_minor = parseInt(mv[1]); - var mv_patch = parseInt(mv[2]); - if(v2_minor == mv_minor) { + } else if(v2_major == v_major+1) { + if(next_major) { + var mv = next_major.tag_name.substring(1).split('.'); + var mv_major = parseInt(mv[0]); + var mv_minor = parseInt(mv[1]); + var mv_patch = parseInt(mv[2]); + if(v2_minor == mv_minor) { + next_major = release; + } + } else { next_major = release; } - } else { - next_major = release; } + }); + if(next_minor) { + nextVersion = next_minor; + } else if(next_major) { + nextVersion = next_major; + } else if(next_patch) { + nextVersion = next_patch; } - }); - if(next_minor) { - nextVersion = next_minor; - } else if(next_major) { - nextVersion = next_major; - } else if(next_patch) { - nextVersion = next_patch; + } if(nextVersion) { + if(fwl.length > 0) { + var chipset = fwl.data('chipset').toLowerCase(); + $.each(releases, function(i, release) { + if(release.tag_name == nextVersion.tag_name) { + $.each(release.assets, function(i, asset) { + if(asset.name.includes(chipset) && !asset.name.includes("partitions")) { + fwl.prop('href', asset.browser_download_url); + $('#fwDownload').show(); + return false; + } + }); + } + }); + }; $('#newVersionTag').text(nextVersion.tag_name); $('#newVersionUrl').prop('href', nextVersion.html_url); $('#newVersion').removeClass('d-none'); @@ -201,6 +309,97 @@ $(function() { } }); +var resizeTO; +$( window ).resize(function() { + if(resizeTO) clearTimeout(resizeTO); + resizeTO = setTimeout(function() { + $(this).trigger('resizeEnd'); + }, 250); +}); + +$(window).on('resizeEnd', function() { + redraw(); +}); + +var zeropad = function(num) { + num = num.toString(); + while (num.length < 2) num = "0" + num; + return num; +} + +var setupChart = function() { + ep = new google.visualization.LineChart(document.getElementById('ep')); + mp = new google.visualization.LineChart(document.getElementById('mp')); + vp = new google.visualization.ColumnChart(document.getElementById('vp')); + ap = new google.visualization.ColumnChart(document.getElementById('ap')); + ip = new google.visualization.PieChart(document.getElementById('ip')); + xp = new google.visualization.PieChart(document.getElementById('xp')); + fetch(); + drawEnergy(); +}; + +var redraw = function() { + ep.draw(ea, eo); + mp.draw(ma, mo); + vp.draw(va, vo); + ap.draw(aa, ao); + ip.draw(ia, io); + xp.draw(xa, xo); +}; + +var drawEnergy = function() { + $.ajax({ + url: '/dayplot.json', + timeout: 30000, + dataType: 'json', + }).done(function(json) { + data = [['Hour','Value']]; + var r = 1; + var hour = moment.utc().hours(); + var offset = moment().utcOffset()/60; + var min = 0; + for(var i = hour; i<24; i++) { + var val = json["h"+zeropad(i)]; + data[r++] = [zeropad((i+offset)%24), val]; + Math.min(0, val); + }; + for(var i = 0; i < hour; i++) { + var val = json["h"+zeropad(i)]; + data[r++] = [zeropad((i+offset)%24), val]; + Math.min(0, val); + }; + ea = google.visualization.arrayToDataTable(data); + if(min == 0) + eo.vAxis.minValue = 0; + ep.draw(ea, eo); + }); + $.ajax({ + url: '/monthplot.json', + timeout: 30000, + dataType: 'json', + }).done(function(json) { + data = [['Day','Value']]; + var r = 1; + var day = moment().date(); + var start = moment().subtract(1, 'months').endOf('month').date(); + var min = 0; + for(var i = day; i<=start; i++) { + var val = json["d"+zeropad(i)]; + data[r++] = [zeropad((i)), val]; + Math.min(0, val); + } + for(var i = 1; i < day; i++) { + var val = json["d"+zeropad(i)]; + data[r++] = [zeropad((i)), val]; + Math.min(0, val); + } + ma = google.visualization.arrayToDataTable(data); + if(min == 0) + mo.vAxis.minValue = 0; + mp.draw(ma, mo); + }); +}; + var setStatus = function(id, sid) { var item = $('#'+id); item.removeClass('d-none'); @@ -220,6 +419,26 @@ var setStatus = function(id, sid) { item.addClass('badge badge-' + status); }; +var voltcol = function(pct) { + if(pct > 85) return '#d90000'; + else if(pct > 75) return'#e32100'; + else if(pct > 70) return '#ffb800'; + else if(pct > 65) return '#dcd800'; + else if(pct > 35) return '#32d900'; + else if(pct > 25) return '#dcd800'; + else if(pct > 20) return '#ffb800'; + else if(pct > 15) return'#e32100'; + else return '#d90000'; +}; + +var ampcol = function(pct) { + if(pct > 85) return '#d90000'; + else if(pct > 75) return'#e32100'; + else if(pct > 70) return '#ffb800'; + else if(pct > 65) return '#dcd800'; + else return '#32d900'; +}; + var interval = 5000; var fetch = function() { $.ajax({ @@ -231,8 +450,7 @@ var fetch = function() { $(".SimpleMeter").hide(); im.show(); em.show(); - vm.show(); - am.show(); + } for(var id in json) { @@ -265,7 +483,7 @@ var fetch = function() { setStatus("mqtt", json.mm); - if(im && im.gaugeMeter) { + if(ip) { var v = parseInt(json.i); var pct = (v*100)/parseInt(json.im); var append = "W"; @@ -273,14 +491,20 @@ var fetch = function() { v = (v/1000).toFixed(1); append = "kW"; } - im.gaugeMeter({ - percent: pct, - text: v, - append: append - }); + $('.ipo').html(v); + $('.ipoa').html(append); + var arr = [ + ['Slice', 'Value'], + ['', (pct*2.88)], + ['', ((100-pct)*2.88)], + ['', 72], + ]; + io.slices[0].color = ampcol(pct); + ia = google.visualization.arrayToDataTable(arr); + ip.draw(ia, io); } - if(em && em.gaugeMeter && json.om) { + if(xp) { var v = parseInt(json.e); var pct = (v*100)/(parseInt(json.om)*1000); var append = "W"; @@ -288,52 +512,78 @@ var fetch = function() { v = (v/1000).toFixed(1); append = "kW"; } - em.gaugeMeter({ - percent: pct, - text: v, - append: append - }); + $('.epo').html(v); + $('.epoa').html(append); + var arr = [ + ['Slice', 'Value'], + ['', (pct*2.88)], + ['', ((100-pct)*2.88)], + ['', 72], + ]; + xo.slices[0].color = ampcol(pct); + xa = google.visualization.arrayToDataTable(arr); + xp.draw(xa, xo); } - if(vm && vm.gaugeMeter) { + if(vp) { var c = 0; var t = 0; + var r = 1; + var arr = [['Phase', 'Voltage', { role: 'style' }, { role: 'annotation' }]]; if(json.u1) { - t += parseFloat(json.u1); + var u1 = parseFloat(json.u1); + t += u1; c++; + var pct = (Math.max(parseFloat(json.u1)-195.5, 1)*100/69); + arr[r++] = ['L1', u1, voltcol(pct), u1 + "V"]; } if(json.u2) { - t += parseFloat(json.u2); + var u2 = parseFloat(json.u2); + t += u2; c++; + var pct = (Math.max(parseFloat(json.u2)-195.5, 1)*100/69); + arr[r++] = ['L2', u2, voltcol(pct), u2 + "V"]; } if(json.u3) { - t += parseFloat(json.u3); + var u3 = parseFloat(json.u3); + t += u3; c++; + var pct = (Math.max(parseFloat(json.u3)-195.5, 1)*100/69); + arr[r++] = ['L3', u3, voltcol(pct), u3 + "V"]; } v = t/c; - var pct = (Math.max(v-207, 1)*100/46); - vm.gaugeMeter({ - percent: pct, - text: v.toFixed(1) - }); + if(v > 0) { + va = google.visualization.arrayToDataTable(arr); + vp.draw(va, vo); + } } - if(am && am.gaugeMeter && json.mf) { + if(ap && json.mf) { var a = 0; + var r = 1; + var arr = [['Phase', 'Amperage', { role: 'style' }, { role: 'annotation' }]]; if(json.i1) { - a = Math.max(a, parseFloat(json.i1)); + var i1 = parseFloat(json.i1); + a = Math.max(a, i1); + var pct = (parseFloat(json.i1)/parseInt(json.mf))*100; + arr[r++] = ['L1', pct, ampcol(pct), i1 + "A"]; } if(json.i2) { - a = Math.max(a, parseFloat(json.i2)); + var i2 = parseFloat(json.i2); + a = Math.max(a, i2); + var pct = (parseFloat(json.i2)/parseInt(json.mf))*100; + arr[r++] = ['L2', pct, ampcol(pct), i2 + "A"]; } if(json.i3) { - a = Math.max(a, parseFloat(json.i3)); + var i3 = parseFloat(json.i3); + a = Math.max(a, i3); + var pct = (parseFloat(json.i3)/parseInt(json.mf))*100; + arr[r++] = ['L3', pct, ampcol(pct), i3 + "A"]; + } + if(a > 0) { + aa = google.visualization.arrayToDataTable(arr); + ap.draw(aa, ao); } - var pct = (a*100)/parseInt(json.mf); - am.gaugeMeter({ - percent: pct, - text: a.toFixed(1) - }); } if(json.me) { @@ -363,6 +613,7 @@ var fetch = function() { var upgrade = function() { if(nextVersion) { if(confirm("Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) { + $('#loading-indicator').show(); window.location.href="/upgrade?version=" + nextVersion.tag_name; } } diff --git a/web/dayplot.json b/web/dayplot.json new file mode 100644 index 00000000..8cb01f9a --- /dev/null +++ b/web/dayplot.json @@ -0,0 +1,26 @@ +{ + "h00" : %.2f, + "h01" : %.2f, + "h02" : %.2f, + "h03" : %.2f, + "h04" : %.2f, + "h05" : %.2f, + "h06" : %.2f, + "h07" : %.2f, + "h08" : %.2f, + "h09" : %.2f, + "h10" : %.2f, + "h11" : %.2f, + "h12" : %.2f, + "h13" : %.2f, + "h14" : %.2f, + "h15" : %.2f, + "h16" : %.2f, + "h17" : %.2f, + "h18" : %.2f, + "h19" : %.2f, + "h20" : %.2f, + "h21" : %.2f, + "h22" : %.2f, + "h23" : %.2f +} diff --git a/web/firmware.html b/web/firmware.html new file mode 100644 index 00000000..0ed23db2 --- /dev/null +++ b/web/firmware.html @@ -0,0 +1,34 @@ +
+ WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit. +
+
+ Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit. + +
+ +
+
+
+
+
+
+ Upload +
+
+ + +
+
+
+
+
+
+
+
+ Back +
+
+ +
+
+
diff --git a/web/foot.html b/web/foot.html index 6055b86c..bfc3312c 100644 --- a/web/foot.html +++ b/web/foot.html @@ -1,12 +1,20 @@ -
New version ! view or upgrade
+
New version ! + view + or upgrade +
+ - + diff --git a/web/gaugemeter.js b/web/gaugemeter.js deleted file mode 100644 index cf16e6a7..00000000 --- a/web/gaugemeter.js +++ /dev/null @@ -1,276 +0,0 @@ -/* - * AshAlom Gauge Meter. Version 2.0.0 - * Copyright AshAlom.com All rights reserved. - * https://github.com/AshAlom/GaugeMeter <- Deleted! - * https://github.com/githubsrinath/GaugeMeter <- Backup original. - * - * Original created by Dr Ash Alom - * - * This is a bug fixed and modified version of the AshAlom Gauge Meter. - * Copyright 2018 Michael Wolf (Mictronics) - * https://github.com/mictronics/GaugeMeter - * - */ -!function ($) { - $.fn.gaugeMeter = function (t) { - var defaults = $.extend({ - id: "", - percent: 0, - used: null, - min: null, - total: null, - size: 100, - prepend: "", - append: "", - theme: "Red-Gold-Green", - color: "", - back: "RGBa(0,0,0,.06)", - width: 3, - style: "Full", - stripe: "0", - animationstep: 1, - animate_gauge_colors: false, - animate_text_colors: false, - label: "", - label_color: "Black", - text: "", - text_size: 0.22, - fill: "", - showvalue: false - }, t); - return this.each(function () { - - function getThemeColor(e) { - var t = "#2C94E0"; - return e || (e = 1e-14), - "Red-Gold-Green" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#e32100"), e > 20 && (t = "#f35100"), e > 30 && (t = "#ff8700"), e > 40 && (t = "#ffb800"), e > 50 && (t = "#ffd900"), e > 60 && (t = "#dcd800"), e > 70 && (t = "#a6d900"), e > 80 && (t = "#69d900"), e > 90 && (t = "#32d900")), - "Green-Gold-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#69d900"), e > 20 && (t = "#a6d900"), e > 30 && (t = "#dcd800"), e > 40 && (t = "#ffd900"), e > 50 && (t = "#ffb800"), e > 60 && (t = "#ff8700"), e > 70 && (t = "#f35100"), e > 80 && (t = "#e32100"), e > 90 && (t = "#d90000")), - "Green-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#41c900"), e > 20 && (t = "#56b300"), e > 30 && (t = "#6f9900"), e > 40 && (t = "#8a7b00"), e > 50 && (t = "#a75e00"), e > 60 && (t = "#c24000"), e > 70 && (t = "#db2600"), e > 80 && (t = "#f01000"), e > 90 && (t = "#ff0000")), - "Red-Green" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#f01000"), e > 20 && (t = "#db2600"), e > 30 && (t = "#c24000"), e > 40 && (t = "#a75e00"), e > 50 && (t = "#8a7b00"), e > 60 && (t = "#6f9900"), e > 70 && (t = "#56b300"), e > 80 && (t = "#41c900"), e > 90 && (t = "#32d900")), - "DarkBlue-LightBlue" === option.theme && (e > 0 && (t = "#2c94e0"), e > 10 && (t = "#2b96e1"), e > 20 && (t = "#2b99e4"), e > 30 && (t = "#2a9ce7"), e > 40 && (t = "#28a0e9"), e > 50 && (t = "#26a4ed"), e > 60 && (t = "#25a8f0"), e > 70 && (t = "#24acf3"), e > 80 && (t = "#23aff5"), e > 90 && (t = "#21b2f7")), - "LightBlue-DarkBlue" === option.theme && (e > 0 && (t = "#21b2f7"), e > 10 && (t = "#23aff5"), e > 20 && (t = "#24acf3"), e > 30 && (t = "#25a8f0"), e > 40 && (t = "#26a4ed"), e > 50 && (t = "#28a0e9"), e > 60 && (t = "#2a9ce7"), e > 70 && (t = "#2b99e4"), e > 80 && (t = "#2b96e1"), e > 90 && (t = "#2c94e0")), - "DarkRed-LightRed" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#dc0000"), e > 20 && (t = "#e00000"), e > 30 && (t = "#e40000"), e > 40 && (t = "#ea0000"), e > 50 && (t = "#ee0000"), e > 60 && (t = "#f30000"), e > 70 && (t = "#f90000"), e > 80 && (t = "#fc0000"), e > 90 && (t = "#ff0000")), - "LightRed-DarkRed" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#fc0000"), e > 20 && (t = "#f90000"), e > 30 && (t = "#f30000"), e > 40 && (t = "#ee0000"), e > 50 && (t = "#ea0000"), e > 60 && (t = "#e40000"), e > 70 && (t = "#e00000"), e > 80 && (t = "#dc0000"), e > 90 && (t = "#d90000")), - "DarkGreen-LightGreen" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#33db00"), e > 20 && (t = "#34df00"), e > 30 && (t = "#34e200"), e > 40 && (t = "#36e700"), e > 50 && (t = "#37ec00"), e > 60 && (t = "#38f100"), e > 70 && (t = "#38f600"), e > 80 && (t = "#39f900"), e > 90 && (t = "#3afc00")), - "LightGreen-DarkGreen" === option.theme && (e > 0 && (t = "#3afc00"), e > 10 && (t = "#39f900"), e > 20 && (t = "#38f600"), e > 30 && (t = "#38f100"), e > 40 && (t = "#37ec00"), e > 50 && (t = "#36e700"), e > 60 && (t = "#34e200"), e > 70 && (t = "#34df00"), e > 80 && (t = "#33db00"), e > 90 && (t = "#32d900")), - "DarkGold-LightGold" === option.theme && (e > 0 && (t = "#ffb800"), e > 10 && (t = "#ffba00"), e > 20 && (t = "#ffbd00"), e > 30 && (t = "#ffc200"), e > 40 && (t = "#ffc600"), e > 50 && (t = "#ffcb00"), e > 60 && (t = "#ffcf00"), e > 70 && (t = "#ffd400"), e > 80 && (t = "#ffd600"), e > 90 && (t = "#ffd900")), - "LightGold-DarkGold" === option.theme && (e > 0 && (t = "#ffd900"), e > 10 && (t = "#ffd600"), e > 20 && (t = "#ffd400"), e > 30 && (t = "#ffcf00"), e > 40 && (t = "#ffcb00"), e > 50 && (t = "#ffc600"), e > 60 && (t = "#ffc200"), e > 70 && (t = "#ffbd00"), e > 80 && (t = "#ffba00"), e > 90 && (t = "#ffb800")), - "Voltage" === option.theme && (e <= 0 && (t = "#d90000"), e > 0 && (t = "#e32100"), e > 10 && (t = "#ffb800"), e > 20 && (t = "#dcd800"), e > 30 && (t = "#32d900"), e > 40 && (t = "#32d900"), e > 50 && (t = "#32d900"), e > 60 && (t = "#32d900"), e > 70 && (t = "#dcd800"), e > 80 && (t = "#ffb800"), e > 90 && (t = "#e32100"), e >= 100 && (t = "#d90000")), - "White" === option.theme && (t = "#fff"), - "Black" === option.theme && (t = "#000"), - t; - }; - /* The label below gauge. */ - function createLabel(t, a) { - if(t.children("b").length === 0){ - $("").appendTo(t).html(option.label).css({ - "line-height": option.size + 5 * a + "px", - color: option.label_color - }); - } - }; - /* Prepend and append text, the gauge text or percentage value. */ - function createSpanTag(t) { - var fgcolor = ""; - if (option.animate_text_colors === true){ - fgcolor = option.fgcolor; - } - var child = t.children("span"); - if(child.length !== 0){ - child.html(r).css({color: fgcolor}); - return; - } - if(option.text_size <= 0.0 || Number.isNaN(option.text_size)){ - option.text_size = 0.22; - } - if(option.text_size > 0.5){ - option.text_size = 0.5; - } - $("").appendTo(t).html(r).css({ - "line-height": option.size + "px", - "font-size": option.text_size * option.size + "px", - color: fgcolor - }); - }; - /* Get data attributes as options from div tag. Fall back to defaults when not exists. */ - function getDataAttr(t) { - $.each(dataAttr, function (index, element) { - if(t.data(element) !== undefined && t.data(element) !== null){ - option[element] = t.data(element); - } else { - option[element] = $(defaults).attr(element); - } - - if(element === "fill"){ - s = option[element]; - } - - if((element === "size" || - element === "width" || - element === "animationstep" || - element === "stripe" - ) && !Number.isInteger(option[element])){ - option[element] = parseInt(option[element]); - } - - if(element === "text_size"){ - option[element] = parseFloat(option[element]); - } - }); - }; - /* Draws the gauge. */ - function drawGauge(a) { - if(M < 0) M = 0; - if(M > 100) M = 100; - var lw = option.width < 1 || isNaN(option.width) ? option.size / 20 : option.width; - g.clearRect(0, 0, b.width, b.height); - g.beginPath(); - g.arc(m, v, x, G, k, !1); - if(s){ - g.fillStyle = option.fill; - g.fill(); - } - g.lineWidth = lw; - g.strokeStyle = option.back; - option.stripe > parseInt(0) ? g.setLineDash([option.stripe], 1) : g.lineCap = "round"; - g.stroke(); - g.beginPath(); - g.arc(m, v, x, -I, P * a - I, !1); - g.lineWidth = lw; - g.strokeStyle = option.fgcolor; - g.stroke(); - c > M && (M += z, requestAnimationFrame(function(){ - drawGauge(Math.min(M, c) / 100); - }, p)); - }; - - $(this).attr("data-id", $(this).attr("id")); - var r, - dataAttr = ["percent", - "used", - "min", - "total", - "size", - "prepend", - "append", - "theme", - "color", - "back", - "width", - "style", - "stripe", - "animationstep", - "animate_gauge_colors", - "animate_text_colors", - "label", - "label_color", - "text", - "text_size", - "fill", - "showvalue"], - option = {}, - c = 0, - p = $(this), - s = false; - p.addClass("gaugeMeter"); - getDataAttr(p); - - if(Number.isInteger(option.used) && Number.isInteger(option.total)){ - var u = option.used; - var t = option.total; - if(Number.isInteger(option.min)) { - if(option.min < 0) { - t -= option.min; - u -= option.min; - } - } - c = u / (t / 100); - } else { - if(Number.isInteger(option.percent)){ - c = option.percent; - } else { - c = parseInt(defaults.percent); - } - } - if(c < 0) c = 0; - if(c > 100) c = 100; - - if( option.text !== "" && option.text !== null && option.text !== undefined){ - if(option.append !== "" && option.append !== null && option.append !== undefined){ - r = option.text + "" + option.append + ""; - } else { - r = option.text; - } - if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){ - r = "" + option.prepend + "" + r; - } - } else { - if(defaults.showvalue === true || option.showvalue === true){ - r = option.used; - } else { - r = c.toString(); - } - if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){ - r = "" + option.prepend + "" + r; - } - - if(option.append !== "" && option.append !== null && option.append !== undefined){ - r = r + "" + option.append + ""; - } - } - - option.fgcolor = getThemeColor(c); - if(option.color !== "" && option.color !== null && option.color !== undefined){ - option.fgcolor = option.color; - } - - if(option.animate_gauge_colors === true){ - option.fgcolor = getThemeColor(c); - } - createSpanTag(p); - - if(option.style !== "" && option.style !== null && option.style !== undefined){ - createLabel(p, option.size / 13); - } - - $(this).width(option.size + "px"); - - var b = $("").attr({width: option.size, height: option.size}).get(0), - g = b.getContext("2d"), - m = b.width / 2, - v = b.height / 2, - _ = 360 * option.percent, - x = (_ * (Math.PI / 180), b.width / 2.5), - k = 2.3 * Math.PI, - G = 0, - M = 0 === option.animationstep ? c : 0, - z = Math.max(option.animationstep, 0), - P = 2 * Math.PI, - I = Math.PI / 2, - R = option.style; - var child = $(this).children("canvas"); - if(child.length !== 0){ - /* Replace existing canvas when new percentage was written. */ - child.replaceWith(b); - } else { - /* Initially create canvas. */ - $(b).appendTo($(this)); - } - - if ("Semi" === R){ - k = 2 * Math.PI; - G = 3.13; - P = 1 * Math.PI; - I = Math.PI / .996; - } - if ("Arch" === R){ - k = 2.195 * Math.PI; - G = 1, G = 655.99999; - P = 1.4 * Math.PI; - I = Math.PI / .8335; - } - drawGauge(M / 100); - }); - }; -} -(jQuery); diff --git a/web/gpio.html b/web/gpio.html index 4ee95616..494667c9 100644 --- a/web/gpio.html +++ b/web/gpio.html @@ -60,6 +60,24 @@ +
+
+ GND resistor +
+ +
+ +
+
+
+
+ Vcc resistor +
+ +
+ +
+
Multiplier @@ -70,7 +88,7 @@
Offset
- +
diff --git a/web/head32.html b/web/head32.html index 33c826bf..4948d8e5 100644 --- a/web/head32.html +++ b/web/head32.html @@ -6,51 +6,40 @@ diff --git a/web/head8266.html b/web/head8266.html index 7a3ed964..d0d6181a 100644 --- a/web/head8266.html +++ b/web/head8266.html @@ -6,51 +6,40 @@ diff --git a/web/index.html b/web/index.html index 18b73357..f592339a 100644 --- a/web/index.html +++ b/web/index.html @@ -1,4 +1,4 @@ -
+
Up {cs}
@@ -21,51 +21,39 @@
-
+
-
-
- {P} W -
- +
+
+ + {P} + W +
+ {ti} +
{tPI} kWh
-
+
-
-
- {PO} W -
- +
+
+ + {PO} + W +
+ Export +
{tPO} kWh
-
+
Reactive
@@ -76,7 +64,7 @@
-
+
In
@@ -88,48 +76,26 @@
-
+
- -
-
-
{U1}V
-
{U2}V
-
{U3}V
+
-
+
- -
-
-
{I1}A
-
{I2}A
-
{I3}A
+
+
+
+
+
+
+
MQTT communication error (-)
MQTT failed to connect
@@ -139,3 +105,4 @@
MQTT lost connection
+ diff --git a/web/meter.html b/web/meter.html index de89eb88..9942e872 100644 --- a/web/meter.html +++ b/web/meter.html @@ -80,6 +80,8 @@