From 1de3b4816619b7675fbb9917c287590086eafe10 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 22 Nov 2023 20:21:25 +0100 Subject: [PATCH] Feature: Implement offset cache for "YieldDay" Thanks to @broth-itk for the idea! Fix: #1258 #1397 --- include/Configuration.h | 1 + lib/Hoymiles/src/Hoymiles.cpp | 1 + lib/Hoymiles/src/parser/StatisticsParser.cpp | 46 ++++++++++++++++++++ lib/Hoymiles/src/parser/StatisticsParser.h | 7 +++ src/Configuration.cpp | 2 + src/InverterSettings.cpp | 1 + src/WebApi_inverter.cpp | 3 ++ webapp/src/locales/de.json | 4 +- webapp/src/locales/en.json | 4 +- webapp/src/locales/fr.json | 4 +- webapp/src/views/InverterAdminView.vue | 6 +++ 11 files changed, 76 insertions(+), 3 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index e2ed0b3..5e74c3a 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -48,6 +48,7 @@ struct INVERTER_CONFIG_T { uint8_t ReachableThreshold; bool ZeroRuntimeDataIfUnrechable; bool ZeroYieldDayOnMidnight; + bool YieldDayCorrection; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index d138e4f..a4558ac 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -134,6 +134,7 @@ void HoymilesClass::loop() if (inv->getZeroYieldDayOnMidnight()) { inv->Statistics()->zeroDailyData(); } + inv->Statistics()->resetYieldDayCorrection(); } lastWeekDay = currentWeekDay; diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 71c1ebb..84f1929 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -82,6 +82,8 @@ void StatisticsParser::clearBuffer() { memset(_payloadStatistic, 0, STATISTIC_PACKET_SIZE); _statisticLength = 0; + + memset(_lastYieldDay, 0, sizeof(_lastYieldDay)); } void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) @@ -94,6 +96,31 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _statisticLength += len; } +void StatisticsParser::endAppendFragment() +{ + Parser::endAppendFragment(); + + if (!_enableYieldDayCorrection) { + resetYieldDayCorrection(); + return; + } + + for (auto& c : getChannelsByType(TYPE_DC)) { + // check if current yield day is smaller then last cached yield day + if (getChannelFieldValue(TYPE_DC, c, FLD_YD) < _lastYieldDay[static_cast(c)]) { + // currently all values are zero --> Add last known values to offset + Hoymiles.getMessageOutput()->printf("Yield Day reset detected!\r\n"); + + setChannelFieldOffset(TYPE_DC, c, FLD_YD, + getChannelFieldOffset(TYPE_DC, c, FLD_YD) + _lastYieldDay[static_cast(c)]); + + _lastYieldDay[static_cast(c)] = 0; + } else { + _lastYieldDay[static_cast(c)] = getChannelFieldValue(TYPE_DC, c, FLD_YD); + } + } +} + const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) { @@ -329,6 +356,16 @@ void StatisticsParser::setLastUpdateFromInternal(uint32_t lastUpdate) _lastUpdateFromInternal = lastUpdate; } +bool StatisticsParser::getYieldDayCorrection() +{ + return _enableYieldDayCorrection; +} + +void StatisticsParser::setYieldDayCorrection(bool enabled) +{ + _enableYieldDayCorrection = enabled; +} + void StatisticsParser::zeroFields(const FieldId_t* fields) { // Loop all channels @@ -344,6 +381,15 @@ void StatisticsParser::zeroFields(const FieldId_t* fields) setLastUpdateFromInternal(millis()); } +void StatisticsParser::resetYieldDayCorrection() +{ + // new day detected, reset counters + for (auto& c : getChannelsByType(TYPE_DC)) { + setChannelFieldOffset(TYPE_DC, c, FLD_YD, 0); + _lastYieldDay[static_cast(c)] = 0; + } +} + static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0) { float yield = 0; diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index da29100..367858a 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -106,6 +106,7 @@ public: StatisticsParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void endAppendFragment(); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); @@ -140,6 +141,7 @@ public: void zeroRuntimeData(); void zeroDailyData(); + void resetYieldDayCorrection(); // Update time when new data from the inverter is received void setLastUpdate(uint32_t lastUpdate); @@ -148,6 +150,8 @@ public: uint32_t getLastUpdateFromInternal(); void setLastUpdateFromInternal(uint32_t lastUpdate); + bool getYieldDayCorrection(); + void setYieldDayCorrection(bool enabled); private: void zeroFields(const FieldId_t* fields); @@ -162,4 +166,7 @@ private: uint32_t _rxFailureCount = 0; uint32_t _lastUpdateFromInternal = 0; + + bool _enableYieldDayCorrection = false; + float _lastYieldDay[CH_CNT]; }; \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ffb70f2..8874a57 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -117,6 +117,7 @@ bool ConfigurationClass::write() inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold; inv["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable; inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; + inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; JsonArray channel = inv.createNestedArray("channel"); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { @@ -272,6 +273,7 @@ bool ConfigurationClass::read() config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD; config.Inverter[i].ZeroRuntimeDataIfUnrechable = inv["zero_runtime"] | false; config.Inverter[i].ZeroYieldDayOnMidnight = inv["zero_day"] | false; + config.Inverter[i].YieldDayCorrection = inv["yieldday_correction"] | false; JsonArray channel = inv["channel"]; for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index ce692bf..425ecd6 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -74,6 +74,7 @@ void InverterSettingsClass::init() inv->setReachableThreshold(config.Inverter[i].ReachableThreshold); inv->setZeroValuesIfUnreachable(config.Inverter[i].ZeroRuntimeDataIfUnrechable); inv->setZeroYieldDayOnMidnight(config.Inverter[i].ZeroYieldDayOnMidnight); + inv->Statistics()->setYieldDayCorrection(config.Inverter[i].YieldDayCorrection); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset); diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index d5ea9b4..3ec01b9 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -61,6 +61,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold; obj["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable; obj["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; + obj["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial); uint8_t max_channels; @@ -288,6 +289,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD; inverter.ZeroRuntimeDataIfUnrechable = root["zero_runtime"] | false; inverter.ZeroYieldDayOnMidnight = root["zero_day"] | false; + inverter.YieldDayCorrection = root["yieldday_correction"] | false; arrayCount++; } @@ -321,6 +323,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inv->setReachableThreshold(inverter.ReachableThreshold); inv->setZeroValuesIfUnreachable(inverter.ZeroRuntimeDataIfUnrechable); inv->setZeroYieldDayOnMidnight(inverter.ZeroYieldDayOnMidnight); + inv->Statistics()->setYieldDayCorrection(inverter.YieldDayCorrection); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast(c), FLD_YT, inverter.channel[c].YieldTotalOffset); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index e14e281..81ac5b4 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?", - "Delete": "Löschen" + "Delete": "Löschen", + "YieldDayCorrection": "Tagesertragskorrektur", + "YieldDayCorrectionHint": "Summiert den Tagesertrag, auch wenn der Wechselrichter neu gestartet wird. Der Wert wird um Mitternacht zurückgesetzt" }, "configadmin": { "ConfigManagement": "Konfigurationsverwaltung", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 4a76935..67faba0 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?", - "Delete": "Delete" + "Delete": "Delete", + "YieldDayCorrection": "Yield Day Correction", + "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, "configadmin": { "ConfigManagement": "Config Management", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index e3afc29..006ab01 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?", - "Delete": "Supprimer" + "Delete": "Supprimer", + "YieldDayCorrection": "Yield Day Correction", + "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, "configadmin": { "ConfigManagement": "Gestion de la configuration", diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index d1f199a..300abdb 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -192,6 +192,11 @@ v-model="selectedInverterData.zero_day" type="checkbox" :tooltip="$t('inverteradmin.ZeroDayHint')" wide/> + + @@ -269,6 +274,7 @@ declare interface Inverter { reachable_threshold: number; zero_runtime: boolean; zero_day: boolean; + yieldday_correction: boolean; channel: Array; }