diff --git a/src/AmsConfiguration.cpp b/src/AmsConfiguration.cpp index 78067922..77f47586 100644 --- a/src/AmsConfiguration.cpp +++ b/src/AmsConfiguration.cpp @@ -657,6 +657,14 @@ bool AmsConfiguration::hasConfig() { configVersion = 0; return false; } + case 94: + configVersion = -1; // Prevent loop + if(relocateConfig94()) { + configVersion = 95; + } else { + configVersion = 0; + return false; + } case EEPROM_CHECK_SUM: return true; default: @@ -822,6 +830,18 @@ bool AmsConfiguration::relocateConfig93() { return ret; } +bool AmsConfiguration::relocateConfig94() { + EnergyAccountingConfig eac; + EEPROM.begin(EEPROM_SIZE); + EEPROM.get(CONFIG_ENERGYACCOUNTING_START, eac); + eac.hours = 1; + EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac); + EEPROM.put(EEPROM_CONFIG_ADDRESS, 95); + bool ret = EEPROM.commit(); + EEPROM.end(); + return ret; +} + bool AmsConfiguration::save() { EEPROM.begin(EEPROM_SIZE); EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM); diff --git a/src/AmsConfiguration.h b/src/AmsConfiguration.h index 63d30f43..79e34e08 100644 --- a/src/AmsConfiguration.h +++ b/src/AmsConfiguration.h @@ -4,7 +4,7 @@ #include "Arduino.h" #define EEPROM_SIZE 1024*3 -#define EEPROM_CHECK_SUM 94 // Used to check if config is stored. Change if structure changes +#define EEPROM_CHECK_SUM 95 // Used to check if config is stored. Change if structure changes #define EEPROM_CONFIG_ADDRESS 0 #define EEPROM_TEMP_CONFIG_ADDRESS 2048 @@ -161,7 +161,8 @@ struct EntsoeConfig { struct EnergyAccountingConfig { uint8_t thresholds[10]; -}; // 10 + uint8_t hours; +}; // 11 struct TempSensorConfig { uint8_t address[8]; @@ -264,7 +265,8 @@ private: bool relocateConfig90(); // 2.0.0 bool relocateConfig91(); // 2.0.2 bool relocateConfig92(); // 2.0.3 - bool relocateConfig93(); // 2.1 beta + bool relocateConfig93(); // 2.1.0 + bool relocateConfig94(); // 2.1.4 void saveToFs(); bool loadFromFs(uint8_t version); diff --git a/src/AmsDataStorage.cpp b/src/AmsDataStorage.cpp index 79332c47..091eef04 100644 --- a/src/AmsDataStorage.cpp +++ b/src/AmsDataStorage.cpp @@ -383,17 +383,17 @@ bool AmsDataStorage::isDayHappy() { tmElements_t tm, last; if(now < day.lastMeterReadTime) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime); return false; } if(now-day.lastMeterReadTime > 3600) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime); return false; } breakTime(tz->toLocal(now), tm); breakTime(tz->toLocal(day.lastMeterReadTime), last); if(tm.Hour > last.Hour) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %d > %d\n", tm.Hour, last.Hour); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %d > %d\n", tm.Hour, last.Hour); return false; } @@ -411,17 +411,17 @@ bool AmsDataStorage::isMonthHappy() { tmElements_t tm, last; if(now < month.lastMeterReadTime) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime); return false; } if(now-month.lastMeterReadTime > 86400) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime); return false; } breakTime(tz->toLocal(now), tm); breakTime(tz->toLocal(month.lastMeterReadTime), last); if(tm.Day > last.Day) { - if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %d > %d\n", tm.Day, last.Day); + if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %d > %d\n", tm.Day, last.Day); return false; } diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index 3994f5b8..6f12dc46 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -371,7 +371,8 @@ void setup() { config.setEnergyAccountingConfig(*eac); config.ackEnergyAccountingChange(); } - ea.setup(&ds, eapi, eac); + ea.setup(&ds, eac); + ea.setEapi(eapi); ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &ea); #if defined(ESP32) @@ -517,6 +518,7 @@ void loop() { if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) { if(eapi == NULL) { eapi = new EntsoeApi(&Debug); + ea.setEapi(eapi); ws.setEntsoeApi(eapi); } eapi->setup(entsoe); @@ -561,7 +563,7 @@ void loop() { if(config.isEnergyAccountingChanged()) { EnergyAccountingConfig *eac = ea.getConfig(); config.getEnergyAccountingConfig(*eac); - ea.setup(&ds, eapi, eac); + ea.setup(&ds, eac); config.ackEnergyAccountingChange(); } @@ -1020,14 +1022,14 @@ bool readHanPort() { bool saveData = false; if(!ds.isHappy() && now > BUILD_EPOCH) { - debugD("Its time to update data storage"); + debugV("Its time to update data storage"); tmElements_t tm; breakTime(now, tm); if(tm.Minute == 0) { - debugD(" using actual data"); + debugV(" using actual data"); saveData = ds.update(&data); } else if(meterState.getListType() >= 3) { - debugD(" using estimated data"); + debugV(" using estimated data"); saveData = ds.update(&meterState); } if(saveData) { @@ -1672,6 +1674,7 @@ void configFileParse() { } else if(strncmp(buf, "energyaccounting ", 17) == 0) { int i = 0; EnergyAccountingData ead = { 1 }; + uint16_t *maxHours = NULL; char * pch = strtok (buf+17," "); while (pch != NULL) { if(i == 1) { @@ -1679,7 +1682,7 @@ void configFileParse() { ead.month = val; } else if(i == 2) { double val = String(pch).toDouble(); - ead.maxHour = val * 100; + ead.unused = val * 100; } else if(i == 3) { double val = String(pch).toDouble(); ead.costYesterday = val * 100; @@ -1689,11 +1692,22 @@ void configFileParse() { } else if(i == 5) { double val = String(pch).toDouble(); ead.costLastMonth = val * 100; + } else if(i == 6) { + int val = String(pch).toInt(); + if(val > 0) { + maxHours = new uint16_t[val]; + } + } else { + double val = String(pch).toDouble(); + maxHours[i-7] = val * 100; } pch = strtok (NULL, " "); i++; } ea.setData(ead); + if(maxHours != NULL) { + ea.setMaxHours(maxHours); + } } memset(buf, 0, 1024); } diff --git a/src/EnergyAccounting.cpp b/src/EnergyAccounting.cpp index e4e34d21..678d254c 100644 --- a/src/EnergyAccounting.cpp +++ b/src/EnergyAccounting.cpp @@ -8,11 +8,25 @@ EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) { this->debugger = debugger; } -void EnergyAccounting::setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config) { +void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) { this->ds = ds; - this->eapi = eapi; this->config = config; this->currentThresholdIdx = 0; + uint16_t *maxHours = new uint16_t[config->hours]; + for(uint8_t i = 0; i < config->hours; i++) { + maxHours[i] = 0; + } + if(this->maxHours != NULL) { + for(uint8_t i = 0; i < sizeof(this->maxHours)/2 && i < config->hours; i++) { + maxHours[i] = this->maxHours[i]; + } + delete(this->maxHours); + } + this->maxHours = maxHours; +} + +void EnergyAccounting::setEapi(EntsoeApi *eapi) { + this->eapi = eapi; } EnergyAccountingConfig* EnergyAccounting::getConfig() { @@ -24,6 +38,7 @@ void EnergyAccounting::setTimezone(Timezone* tz) { } bool EnergyAccounting::update(AmsData* amsData) { + if(config == NULL) return false; time_t now = time(nullptr); if(now < BUILD_EPOCH) return false; if(tz == NULL) { @@ -38,24 +53,50 @@ bool EnergyAccounting::update(AmsData* amsData) { if(!init) { currentHour = local.Hour; currentDay = local.Day; - if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now); + if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now); if(!load()) { - if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data"); - data = { 1, local.Month, 0, 0, 0, 0 }; - if(calcDayUse()) ret = true; + if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n"); + data = { 2, local.Month, 0, 0, 0, 0 }; + for(uint8_t i = 0; i < config->hours; i++) { + maxHours[i] = 0; + break; + } + } else if(debugger->isActive(RemoteDebug::DEBUG)) { + debugger->printf("(EnergyAccounting) Loaded max calculated from %d hours with highest consumption\n", config->hours); + for(uint8_t i = 0; i < config->hours; i++) { + debugger->printf("(EnergyAccounting) hour %d: %d\n", i+1, maxHours[i]*10); + } + debugger->printf("(EnergyAccounting) Loaded cost yesterday: %d, this month: %d, last month: %d\n", data.costYesterday, data.costThisMonth, data.costLastMonth); } init = true; } if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { - if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now); + if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now); calcDayCost(); } if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour); - if(calcDayUse()) ret = true; + tmElements_t oneHrAgo; + breakTime(now-3600, oneHrAgo); + uint32_t val = ds->getHourImport(oneHrAgo.Hour) / 10; + for(uint8_t i = 0; i < config->hours; i++) { + if(val > maxHours[i]) { + if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Adding new max (%d) which is larger than %d\n", val*10, maxHours[i]*10); + maxHours[i] = val; + ret = true; + break; + } + } + if(debugger->isActive(RemoteDebug::INFO)) { + debugger->printf("(EnergyAccounting) Current max calculated from %d hours with highest consumption\n", config->hours); + for(uint8_t i = 0; i < config->hours; i++) { + debugger->printf("(EnergyAccounting) hour %d: %d\n", i+1, maxHours[i]*10); + } + } + if(local.Hour > 0) { calcDayCost(); } @@ -77,7 +118,9 @@ bool EnergyAccounting::update(AmsData* amsData) { if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", local.Month); data.costLastMonth = data.costThisMonth; data.costThisMonth = 0; - data.maxHour = 0; + for(uint8_t i = 0; i < config->hours; i++) { + maxHours[i] = 0; + } data.month = local.Month; currentThresholdIdx = 0; ret = true; @@ -108,24 +151,6 @@ bool EnergyAccounting::update(AmsData* amsData) { return ret; } -bool EnergyAccounting::calcDayUse() { - time_t now = time(nullptr); - tmElements_t local, utc; - breakTime(tz->toLocal(now), local); - - bool ret = false; - uint8_t lim = local.Day == 1 ? local.Hour : 24; - for(int i = 0; i < lim; i++) { - breakTime(now - ((lim - i) * 3600), utc); - int16_t val = ds->getHourImport(utc.Hour) / 10.0; - if(val > data.maxHour) { - data.maxHour = val; - ret = true; - } - } - return ret; -} - void EnergyAccounting::calcDayCost() { time_t now = time(nullptr); tmElements_t local, utc; @@ -203,7 +228,15 @@ uint8_t EnergyAccounting::getCurrentThreshold() { } float EnergyAccounting::getMonthMax() { - return data.maxHour / 100.0; + uint8_t count = 0; + uint32_t maxHour = 0.0; + for(uint8_t i = 0; i < config->hours; i++) { + if(maxHours[i] > 0) { + maxHour += maxHours[i]; + count++; + } + } + return maxHour > 0 ? maxHour / count / 100.0 : 0.0; } bool EnergyAccounting::load() { @@ -220,14 +253,31 @@ bool EnergyAccounting::load() { char buf[file.size()]; file.readBytes(buf, file.size()); EnergyAccountingData* data = (EnergyAccountingData*) buf; - file.close(); - if(data->version == 1) { + if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Data version %d\n", data->version); + if(data->version == 2) { memcpy(&this->data, data, sizeof(this->data)); + uint8_t b = 0; + for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) { + memcpy(&this->maxHours[b++], buf+i, 2); + if(b > config->hours) break; + } + ret = true; + } else if(data->version == 1) { + memcpy(&this->data, data, sizeof(this->data)); + for(uint8_t i = 0; i < config->hours; i++) { + maxHours[i] = data->unused; + } + data->version = 2; ret = true; } else { + if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(EnergyAccounting) Unknown version\n"); ret = false; } + + file.close(); + } else { + if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(EnergyAccounting) File not found\n"); } LittleFS.end(); @@ -244,9 +294,12 @@ bool EnergyAccounting::save() { } { File file = LittleFS.open(FILE_ENERGYACCOUNTING, "w"); - char buf[sizeof(data)]; + char buf[sizeof(data)+sizeof(this->maxHours)]; memcpy(buf, &data, sizeof(data)); - for(unsigned long i = 0; i < sizeof(data); i++) { + for(uint8_t i = 0; i < config->hours; i++) { + memcpy(buf+sizeof(data)+(i*2), &this->maxHours[i], 2); + } + for(uint8_t i = 0; i < sizeof(buf); i++) { file.write(buf[i]); } file.close(); @@ -263,3 +316,13 @@ EnergyAccountingData EnergyAccounting::getData() { void EnergyAccounting::setData(EnergyAccountingData& data) { this->data = data; } + +uint16_t * EnergyAccounting::getMaxHours() { + return maxHours; +} + +void EnergyAccounting::setMaxHours(uint16_t * maxHours) { + for(uint8_t i = 0; i < config->hours; i++) { + this->maxHours[i] = maxHours[i]; + } +} diff --git a/src/EnergyAccounting.h b/src/EnergyAccounting.h index f873df1b..56edc362 100644 --- a/src/EnergyAccounting.h +++ b/src/EnergyAccounting.h @@ -9,7 +9,7 @@ struct EnergyAccountingData { uint8_t version; uint8_t month; - uint16_t maxHour; + uint16_t unused; uint16_t costYesterday; uint16_t costThisMonth; uint16_t costLastMonth; @@ -18,7 +18,8 @@ struct EnergyAccountingData { class EnergyAccounting { public: EnergyAccounting(RemoteDebug*); - void setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config); + void setup(AmsDataStorage *ds, EnergyAccountingConfig *config); + void setEapi(EntsoeApi *eapi); void setTimezone(Timezone*); EnergyAccountingConfig* getConfig(); bool update(AmsData* amsData); @@ -38,6 +39,8 @@ public: EnergyAccountingData getData(); void setData(EnergyAccountingData&); + uint16_t * getMaxHours(); + void setMaxHours(uint16_t * maxHours); private: RemoteDebug* debugger = NULL; @@ -50,9 +53,9 @@ private: uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0; double use, costHour, costDay; EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 }; + uint16_t *maxHours; bool load(); - bool calcDayUse(); void calcDayCost(); }; diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp index b1afdaa1..7fc340e7 100644 --- a/src/web/AmsWebServer.cpp +++ b/src/web/AmsWebServer.cpp @@ -606,6 +606,7 @@ void AmsWebServer::configThresholdsHtml() { for(int i = 0; i < 9; i++) { html.replace("{t" + String(i) + "}", String(config->thresholds[i])); } + html.replace("{h}", String(config->hours)); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, MIME_HTML, HEAD_HTML); @@ -1353,6 +1354,7 @@ void AmsWebServer::handleSave() { eac.thresholds[6] = server.arg("t6").toInt(); eac.thresholds[7] = server.arg("t7").toInt(); eac.thresholds[8] = server.arg("t8").toInt(); + eac.hours = server.arg("h").toInt(); config->setEnergyAccountingConfig(eac); } @@ -2268,7 +2270,7 @@ void AmsWebServer::configFileDownload() { EnergyAccountingConfig eac; config->getEnergyAccountingConfig(eac); - if(eac.thresholds[9] > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("thresholds %d %d %d %d %d %d %d %d %d %d\n"), + if(eac.thresholds[9] > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("thresholds %d %d %d %d %d %d %d %d %d %d %d\n"), eac.thresholds[0], eac.thresholds[1], eac.thresholds[2], @@ -2278,7 +2280,8 @@ void AmsWebServer::configFileDownload() { eac.thresholds[6], eac.thresholds[7], eac.thresholds[8], - eac.thresholds[9] + eac.thresholds[9], + eac.hours )); } @@ -2424,15 +2427,25 @@ void AmsWebServer::configFileDownload() { } if(ea != NULL) { + EnergyAccountingConfig eac; + config->getEnergyAccountingConfig(eac); EnergyAccountingData ead = ea->getData(); server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("energyaccounting %d %d %.2f %.2f %.2f %.2f"), ead.version, ead.month, - ead.maxHour / 100.0, + 0, ead.costYesterday / 100.0, ead.costThisMonth / 100.0, ead.costLastMonth / 100.0 )); + if(eac.hours > 0) { + uint16_t *maxHours = ea->getMaxHours(); + server.sendContent(buf, snprintf(buf, BufferSize, " %d", eac.hours)); + for(int i = 0; i < eac.hours; i++) { + server.sendContent(buf, snprintf(buf, BufferSize, " %d", maxHours[i])); + } + } + server.sendContent("\n"); } } diff --git a/web/thresholds.html b/web/thresholds.html index 8c66b13c..542d3416 100644 --- a/web/thresholds.html +++ b/web/thresholds.html @@ -103,6 +103,19 @@ +
+
+
+
+ Average of top +
+ +
+ hours +
+
+
+