From 110526bd3656e0e1e96fa425da9a9d90fdcc05cc Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Tue, 27 Feb 2024 22:07:30 +0100 Subject: [PATCH] DPL: update inverter state continuously we previously only called commitPowerLimit() if the desired limit changed such that the change was bigger than the hysteresis. we found that if the limit update was not received and the desired limit would not change much, the limit of the inverter was wrong for a long time. to mitigate this, we introduced re-sending the limit update every 60 seconds, regardless of what the limit reported by the inverter was at that time. if the powe-up command was not received, we also would repeat it only once every 60 seconds. this new approach calls commitPowerLimit unconditionally, and commitPowerLimit itself compares the desired inverter state to the reported state and sends an update (limit update or power on state) every time the inverter state is not as expected. this should make sure that the inverter is in the desired state even if conditions change slowly and commands were not received as expected. --- include/PowerLimiter.h | 3 +- src/PowerLimiter.cpp | 88 +++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index 32260150a..65702e25f 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -69,7 +69,6 @@ class PowerLimiterClass { Task _loopTask; int32_t _lastRequestedPowerLimit = 0; - uint32_t _lastPowerLimitMillis = 0; uint32_t _shutdownTimeout = 0; Status _lastStatus = Status::Initializing; uint32_t _lastStatusPrinted = 0; @@ -93,7 +92,7 @@ class PowerLimiterClass { void unconditionalSolarPassthrough(std::shared_ptr inverter); bool canUseDirectSolarPower(); int32_t calcPowerLimit(std::shared_ptr inverter, bool solarPowerEnabled, bool batteryDischargeEnabled); - void commitPowerLimit(std::shared_ptr inverter, int32_t limit, bool enablePowerProduction); + bool commitPowerLimit(std::shared_ptr inverter, int32_t newLimitAbs, bool enablePowerProduction); bool setNewPowerLimit(std::shared_ptr inverter, int32_t newPowerLimit); int32_t getSolarChargePower(); float getLoadCorrectedVoltage(); diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index b4c2229a9..96c44ba72 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -105,10 +105,8 @@ bool PowerLimiterClass::shutdown(PowerLimiterClass::Status status) auto lastPowerCommandState = _inverter->PowerCommand()->getLastPowerCommandSuccess(); if (CMD_PENDING == lastPowerCommandState) { return true; } - CONFIG_T& config = Configuration.get(); - commitPowerLimit(_inverter, config.PowerLimiter.LowerPowerLimit, false); - - return true; + auto const& config = Configuration.get(); + return commitPowerLimit(_inverter, config.PowerLimiter.LowerPowerLimit, false); } void PowerLimiterClass::loop() @@ -527,34 +525,74 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr inve return newPowerLimit; } -void PowerLimiterClass::commitPowerLimit(std::shared_ptr inverter, int32_t limit, bool enablePowerProduction) +/** + * updates the inverter state (power production and limit). returns true if a + * change to its state was requested. + * + * TODO(schlimmchen): there is no guarantee that the limit update will complete + * successfully before the power control request enabled the inverter. this + * should be a two-step process, where the limit is updated und repeated until + * set as desired, and then the inverter is started. + */ +bool PowerLimiterClass::commitPowerLimit(std::shared_ptr inverter, int32_t newLimitAbs, bool enablePowerProduction) { + bool change = false; + // disable power production as soon as possible. // setting the power limit is less important. if (!enablePowerProduction && inverter->isProducing()) { MessageOutput.println("[DPL::commitPowerLimit] Stopping inverter..."); inverter->sendPowerControlRequest(false); + change = true; } - inverter->sendActivePowerControlRequest(static_cast(limit), - PowerLimitControlType::AbsolutNonPersistent); + // early in the loop we make it a pre-requisite that this + // value is non-zero, so we can assume it to be valid. + auto maxPower = inverter->DevInfo()->getMaxPower(); + + float currentLimitPercent = inverter->SystemConfigPara()->getLimitPercent(); + auto currentLimitAbs = static_cast(currentLimitPercent * maxPower / 100); + auto diff = std::abs(currentLimitAbs - newLimitAbs); + + auto const& config = Configuration.get(); + auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis; + + if (hysteresis < (maxPower / 100)) { + MessageOutput.printf("WARNING: Your DPL hysteresis setting of %d W is unreasonably small" + " (less than 1%% of the inverter's max power of %d W)\r\n", + hysteresis, maxPower); + } + + if (diff > hysteresis) { + if (_verboseLogging) { + MessageOutput.printf("[DPL::commitPowerLimit] requested: %d W, reported: %d W," + " last: %d W, diff: %d W, hysteresis: %d W\r\n", + newLimitAbs, currentLimitAbs, _lastRequestedPowerLimit, diff, hysteresis); + } - _lastRequestedPowerLimit = limit; - _lastPowerLimitMillis = millis(); + inverter->sendActivePowerControlRequest(static_cast(newLimitAbs), + PowerLimitControlType::AbsolutNonPersistent); + + _lastRequestedPowerLimit = newLimitAbs; + change = true; + } // enable power production only after setting the desired limit, // such that an older, greater limit will not cause power spikes. if (enablePowerProduction && !inverter->isProducing()) { MessageOutput.println("[DPL::commitPowerLimit] Starting up inverter..."); inverter->sendPowerControlRequest(true); + change = true; } + + return change; } /** - * enforces limits and a hystersis on the requested power limit, after scaling - * the power limit to the ratio of total and producing inverter channels. - * commits the sanitized power limit. returns true if a limit update was - * committed, false otherwise. + * enforces limits on the requested power limit, after scaling the power limit + * to the ratio of total and producing inverter channels. commits the sanitized + * power limit. returns true if an inverter update was committed, false + * otherwise. */ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr inverter, int32_t newPowerLimit) { @@ -589,29 +627,7 @@ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr inver effPowerLimit = std::min(effPowerLimit, inverter->DevInfo()->getMaxPower()); - // Check if the new value is within the limits of the hysteresis - auto diff = std::abs(effPowerLimit - _lastRequestedPowerLimit); - auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis; - - // (re-)send power limit in case the last was sent a long time ago. avoids - // staleness in case a power limit update was not received by the inverter. - auto ageMillis = millis() - _lastPowerLimitMillis; - - if (diff < hysteresis && ageMillis < 60 * 1000) { - if (_verboseLogging) { - MessageOutput.printf("[DPL::setNewPowerLimit] requested: %d W, last limit: %d W, diff: %d W, hysteresis: %d W, age: %ld ms\r\n", - newPowerLimit, _lastRequestedPowerLimit, diff, hysteresis, ageMillis); - } - return false; - } - - if (_verboseLogging) { - MessageOutput.printf("[DPL::setNewPowerLimit] requested: %d W, (re-)sending limit: %d W\r\n", - newPowerLimit, effPowerLimit); - } - - commitPowerLimit(inverter, effPowerLimit, true); - return true; + return commitPowerLimit(inverter, effPowerLimit, true); } int32_t PowerLimiterClass::getSolarChargePower()