From 64ad4bded14b4d2aef020e8d8d9eea70f675a513 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Thu, 7 Mar 2024 20:39:40 +0100 Subject: [PATCH] fix: DPL: limit scaling sanity checks do not scale limit if inverter is not producing, as DC channel power is expected to be close to zero anyways. do not scale limit if current inverter limit is small, such that channels might produce very little power exactly because the limit is so low. move the calculation out of setNewPowerLimit and into a new function, so that we can make use of return statements there. --- src/PowerLimiter.cpp | 65 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 58b35db93..32fa9762d 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -612,6 +612,47 @@ bool PowerLimiterClass::updateInverter() return reset(); } +/** + * scale the desired inverter limit such that the actual inverter AC output is + * close to the desired power limit, even if some input channels are producing + * less than the limit allows. this happens because the inverter seems to split + * the total power limit equally among all MPPTs (not inputs; some inputs share + * the same MPPT on some models). + * + * TODO(schlimmchen): the current implementation is broken and is in need of + * refactoring. currently it only works for inverters that provide one MPPT for + * each input. it also does not work as expected if any input produces *some* + * energy, but is limited by its respective solar input. + */ +static int32_t scalePowerLimit(std::shared_ptr inverter, int32_t newLimit, int32_t currentLimitWatts) +{ + // prevent scaling if inverter is not producing, as input channels are not + // producing energy and hence are detected as not-producing, causing + // unreasonable scaling. + if (!inverter->isProducing()) { return newLimit; } + + std::list dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC); + size_t dcTotalChnls = dcChnls.size(); + + // test for a reasonable power limit that allows us to assume that an input + // channel with little energy is actually not producing, rather than + // producing very little due to the very low limit. + if (currentLimitWatts < dcTotalChnls * 10) { return newLimit; } + + size_t dcProdChnls = 0; + for (auto& c : dcChnls) { + if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 2.0) { + dcProdChnls++; + } + } + + if (dcProdChnls == dcTotalChnls) { return newLimit; } + + MessageOutput.printf("[DPL::scalePowerLimit] %d channels total, %d producing " + "channels, scaling power limit\r\n", dcTotalChnls, dcProdChnls); + return round(newLimit * static_cast(dcTotalChnls) / dcProdChnls); +} + /** * enforces limits on the requested power limit, after scaling the power limit * to the ratio of total and producing inverter channels. commits the sanitized @@ -632,31 +673,17 @@ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr inver // enforce configured upper power limit int32_t effPowerLimit = std::min(newPowerLimit, config.PowerLimiter.UpperPowerLimit); - // scale the power limit by the amount of all inverter channels devided by - // the amount of producing inverter channels. the inverters limit each of - // the n channels to 1/n of the total power limit. scaling the power limit - // ensures the total inverter output is what we are asking for. - std::list dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC); - int dcProdChnls = 0, dcTotalChnls = dcChnls.size(); - for (auto& c : dcChnls) { - if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 2.0) { - dcProdChnls++; - } - } - if ((dcProdChnls > 0) && (dcProdChnls != dcTotalChnls)) { - MessageOutput.printf("[DPL::setNewPowerLimit] %d channels total, %d producing channels, scaling power limit\r\n", - dcTotalChnls, dcProdChnls); - effPowerLimit = round(effPowerLimit * static_cast(dcTotalChnls) / dcProdChnls); - } - // 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(); - effPowerLimit = std::min(effPowerLimit, maxPower); - float currentLimitPercent = inverter->SystemConfigPara()->getLimitPercent(); auto currentLimitAbs = static_cast(currentLimitPercent * maxPower / 100); + + effPowerLimit = scalePowerLimit(inverter, effPowerLimit, currentLimitAbs); + + effPowerLimit = std::min(effPowerLimit, maxPower); + auto diff = std::abs(currentLimitAbs - effPowerLimit); auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis;