diff --git a/include/Configuration.h b/include/Configuration.h index ae61ba1c..280256ea 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -150,6 +150,7 @@ struct CONFIG_T { float PowerLimiter_VoltageStartThreshold; float PowerLimiter_VoltageStopThreshold; float PowerLimiter_VoltageLoadCorrectionFactor; + int8_t PowerLimiter_RestartHour; bool Battery_Enabled; bool Huawei_Enabled; diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index e8448811..e0fa72a0 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -31,6 +31,7 @@ public: int32_t getLastRequestedPowewrLimit(); void setDisable(bool disable); bool getDisable(); + void calcNextInverterRestart(); private: uint32_t _lastLoop = 0; @@ -39,6 +40,8 @@ private: plStates _plState; bool _disabled = false; bool _batteryDischargeEnabled = false; + uint32_t _nextInverterRestart = 0; // Values: 0->not calculated / 1->no restart configured / >1->time of next inverter restart in millis() + uint32_t _nextCalculateCheck = 5000; // time in millis for next NTP check to calulate restart float _powerMeter1Power; float _powerMeter2Power; diff --git a/include/defaults.h b/include/defaults.h index 7fe0d511..06bba4f1 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -122,6 +122,7 @@ #define POWERLIMITER_VOLTAGE_START_THRESHOLD 50.0 #define POWERLIMITER_VOLTAGE_STOP_THRESHOLD 49.0 #define POWERLIMITER_VOLTAGE_LOAD_CORRECTION_FACTOR 0.001 +#define POWERLIMITER_RESTART_HOUR -1 #define BATTERY_ENABLED false diff --git a/src/Configuration.cpp b/src/Configuration.cpp index b6d9c605..ea93e387 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -163,6 +163,7 @@ bool ConfigurationClass::write() powerlimiter["voltage_start_threshold"] = config.PowerLimiter_VoltageStartThreshold; powerlimiter["voltage_stop_threshold"] = config.PowerLimiter_VoltageStopThreshold; powerlimiter["voltage_load_correction_factor"] = config.PowerLimiter_VoltageLoadCorrectionFactor; + powerlimiter["inverter_restart_hour"] = config.PowerLimiter_RestartHour; JsonObject battery = doc.createNestedObject("battery"); battery["enabled"] = config.Battery_Enabled; @@ -360,6 +361,7 @@ bool ConfigurationClass::read() config.PowerLimiter_VoltageStartThreshold = powerlimiter["voltage_start_threshold"] | POWERLIMITER_VOLTAGE_START_THRESHOLD; config.PowerLimiter_VoltageStopThreshold = powerlimiter["voltage_stop_threshold"] | POWERLIMITER_VOLTAGE_STOP_THRESHOLD; config.PowerLimiter_VoltageLoadCorrectionFactor = powerlimiter["voltage_load_correction_factor"] | POWERLIMITER_VOLTAGE_LOAD_CORRECTION_FACTOR; + config.PowerLimiter_RestartHour = powerlimiter["inverter_restart_hour"] | POWERLIMITER_RESTART_HOUR; JsonObject battery = doc["battery"]; config.Battery_Enabled = battery["enabled"] | BATTERY_ENABLED; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index efe0b44b..b96809db 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -53,6 +53,27 @@ void PowerLimiterClass::loop() return; } + // Check if next inverter restart time is reached + if ((_nextInverterRestart > 1) && (_nextInverterRestart <= millis())) { + MessageOutput.println("[PowerLimiterClass::loop] send inverter restart"); + inverter->sendRestartControlRequest(); + calcNextInverterRestart(); + } + + // Check if NTP time is set and next inverter restart not calculated yet + if ((config.PowerLimiter_RestartHour >= 0) && (_nextInverterRestart == 0) ) { + // check every 5 seconds + if (_nextCalculateCheck < millis()) { + struct tm timeinfo; + if (getLocalTime(&timeinfo, 5)) { + calcNextInverterRestart(); + } else { + MessageOutput.println("[PowerLimiterClass::loop] inverter restart calculation: NTP not ready"); + _nextCalculateCheck += 5000; + } + } + } + // Make sure inverter is turned off if PL is disabled by user/MQTT if (((!config.PowerLimiter_Enabled || _disabled) && _plState != SHUTDOWN)) { if (inverter->isProducing()) { @@ -155,7 +176,7 @@ void PowerLimiterClass::loop() MessageOutput.printf("[PowerLimiterClass::loop] Status Batt: Ena: %i, SOC: %i, StartTH: %i, StopTH: %i, LastUpdate: %li\r\n", config.Battery_Enabled, Battery.stateOfCharge, config.PowerLimiter_BatterySocStartThreshold, config.PowerLimiter_BatterySocStopThreshold, millis() - Battery.stateOfChargeLastUpdate); MessageOutput.printf("[PowerLimiterClass::loop] ******************* Leaving PL, PL set to: %i, SP: %i, Batt: %i, PM: %f\r\n", newPowerLimit, canUseDirectSolarPower(), _batteryDischargeEnabled, round(PowerMeter.getPowerTotal())); -#endif +#endif } uint8_t PowerLimiterClass::getPowerLimiterState() { @@ -384,4 +405,44 @@ bool PowerLimiterClass::isStopThresholdReached(std::shared_ptr float correctedDcVoltage = getLoadCorrectedVoltage(inverter); return correctedDcVoltage <= config.PowerLimiter_VoltageStopThreshold; +} + +/// @brief calculate next inverter restart in millis +void PowerLimiterClass::calcNextInverterRestart() +{ + CONFIG_T& config = Configuration.get(); + + // first check if restart is configured at all + if (config.PowerLimiter_RestartHour < 0) { + _nextInverterRestart = 1; + MessageOutput.println("[PowerLimiterClass::calcNextInverterRestart] _nextInverterRestart disabled"); + return; + } + + // read time from timeserver, if time is not synced then return + struct tm timeinfo; + if (getLocalTime(&timeinfo, 5)) { + // calculation first step is offset to next restart in minutes + uint16_t dayMinutes = timeinfo.tm_hour * 60 + timeinfo.tm_min; + uint16_t targetMinutes = config.PowerLimiter_RestartHour * 60; + if (config.PowerLimiter_RestartHour > timeinfo.tm_hour) { + // next restart is on the same day + _nextInverterRestart = targetMinutes - dayMinutes; + } else { + // next restart is on next day + _nextInverterRestart = 1440 - dayMinutes + targetMinutes; + } + #ifdef POWER_LIMITER_DEBUG + MessageOutput.printf("[PowerLimiterClass::calcNextInverterRestart] Localtime read %d %d / configured RestartHour %d\r\n", timeinfo.tm_hour, timeinfo.tm_min, config.PowerLimiter_RestartHour); + MessageOutput.printf("[PowerLimiterClass::calcNextInverterRestart] dayMinutes %d / targetMinutes %d\r\n", dayMinutes, targetMinutes); + MessageOutput.printf("[PowerLimiterClass::calcNextInverterRestart] next inverter restart in %d minutes\r\n", _nextInverterRestart); + #endif + // then convert unit for next restart to milliseconds and add current uptime millis() + _nextInverterRestart *= 60000; + _nextInverterRestart += millis(); + } else { + MessageOutput.println("[PowerLimiterClass::calcNextInverterRestart] getLocalTime not successful, no calculation"); + _nextInverterRestart = 0; + } + MessageOutput.printf("[PowerLimiterClass::calcNextInverterRestart] _nextInverterRestart @ %d millis\r\n", _nextInverterRestart); } \ No newline at end of file diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index a031ed74..2fc69c22 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -52,6 +52,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) root[F("voltage_start_threshold")] = static_cast(config.PowerLimiter_VoltageStartThreshold * 100 +0.5) / 100.0; root[F("voltage_stop_threshold")] = static_cast(config.PowerLimiter_VoltageStopThreshold * 100 +0.5) / 100.0;; root[F("voltage_load_correction_factor")] = config.PowerLimiter_VoltageLoadCorrectionFactor; + root[F("inverter_restart_hour")] = config.PowerLimiter_RestartHour; response->setLength(); request->send(response); @@ -136,8 +137,11 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) config.PowerLimiter_VoltageStopThreshold = root[F("voltage_stop_threshold")].as(); config.PowerLimiter_VoltageStopThreshold = static_cast(config.PowerLimiter_VoltageStopThreshold * 100) / 100.0; config.PowerLimiter_VoltageLoadCorrectionFactor = root[F("voltage_load_correction_factor")].as(); + config.PowerLimiter_RestartHour = root[F("inverter_restart_hour")].as(); Configuration.write(); + PowerLimiter.calcNextInverterRestart(); + retMsg[F("type")] = F("success"); retMsg[F("message")] = F("Settings saved!"); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 1e4cccc8..bf1276e5 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -549,6 +549,9 @@ "InverterIsBehindPowerMeter": "Welchselrichter ist hinter Leistungsmesser", "Battery": "DC / Akku", "VoltageLoadCorrectionInfo": "Hinweis: Wenn Leistung von der Batterie abgegeben wird, bricht normalerweise die Spannung etwas ein. Damit nicht vorzeitig der Wechelrichter ausgeschaltet wird sobald der \"Stop\"-Schwellenwert erreicht wird, wird der hier angegebene Korrekturfaktor mit einberechnet. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) + Korrekturfaktor).", + "InverterRestart": "Wechselrichter Neustart", + "InverterRestartHour": "Stunde für Neustart", + "InverterRestartHint": "Neustart des Wechselrichter einmal täglich um die \"Tagesertrag\" Werte wieder auf Null zu setzen.", "Save": "@:dtuadmin.Save" }, "batteryadmin": { diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 28aaadcc..6ad73897 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -553,6 +553,9 @@ "InverterIsBehindPowerMeter": "Inverter is behind Power meter", "Battery": "DC / Battery", "VoltageLoadCorrectionInfo": "Hint: When the power output is higher, the voltage is usually decreasing. In order to not stop the inverter too early (Stop treshold), a power factor can be specified here to correct this. Corrected voltage = DC Voltage + (Current power * correction factor).", + "InverterRestart": "Inverter Restart", + "InverterRestartHour": "Restart Hour", + "InverterRestartHint": "Restart the Inverter once a day to reset the \"YieldDay\" values.", "Save": "@:dtuadmin.Save" }, "batteryadmin": { diff --git a/webapp/src/types/PowerLimiterConfig.ts b/webapp/src/types/PowerLimiterConfig.ts index bbc01d3b..81e84ae3 100644 --- a/webapp/src/types/PowerLimiterConfig.ts +++ b/webapp/src/types/PowerLimiterConfig.ts @@ -14,4 +14,5 @@ export interface PowerLimiterConfig { voltage_start_threshold: number; voltage_stop_threshold: number; voltage_load_correction_factor: number; + inverter_restart_hour: number; } diff --git a/webapp/src/views/PowerLimiterAdminView.vue b/webapp/src/views/PowerLimiterAdminView.vue index 834f341b..b940102d 100644 --- a/webapp/src/views/PowerLimiterAdminView.vue +++ b/webapp/src/views/PowerLimiterAdminView.vue @@ -193,6 +193,24 @@ + +
+ +
+ +
+
+
+ @@ -243,6 +261,33 @@ export default defineComponent({ { key: 0, value: "powerlimiteradmin.BatteryDrainWhenFull"}, { key: 1, value: "powerlimiteradmin.BatteryDrainAtNight" }, ], + restartHourList: [ + { key: -1, value: "- - - -" }, + { key: 0, value: "0:00" }, + { key: 1, value: "1:00" }, + { key: 2, value: "2:00" }, + { key: 3, value: "3:00" }, + { key: 4, value: "4:00" }, + { key: 5, value: "5:00" }, + { key: 6, value: "6:00" }, + { key: 7, value: "7:00" }, + { key: 8, value: "8:00" }, + { key: 9, value: "9:00" }, + { key: 10, value: "10:00" }, + { key: 11, value: "11:00" }, + { key: 12, value: "12:00" }, + { key: 13, value: "13:00" }, + { key: 14, value: "14:00" }, + { key: 15, value: "15:00" }, + { key: 16, value: "16:00" }, + { key: 17, value: "17:00" }, + { key: 18, value: "18:00" }, + { key: 19, value: "19:00" }, + { key: 20, value: "20:00" }, + { key: 21, value: "21:00" }, + { key: 22, value: "22:00" }, + { key: 23, value: "23:00" }, + ], alertMessage: "", alertType: "info", showAlert: false,