From 5d39c9b14621cdf38cf9b2c7b91cf250a47256fa Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 10 Oct 2024 19:55:04 +0200 Subject: [PATCH] Update overscaling for better mixed setup management --- include/Configuration.h | 1 + include/defaults.h | 1 + src/Battery.cpp | 2 +- src/Configuration.cpp | 2 + src/PowerLimiter.cpp | 48 ++++++++++------------ src/WebApi_powerlimiter.cpp | 4 +- webapp/src/locales/de.json | 1 + webapp/src/locales/en.json | 1 + webapp/src/locales/fr.json | 1 + webapp/src/types/PowerLimiterConfig.ts | 1 + webapp/src/views/PowerLimiterAdminView.vue | 14 ++++++- 11 files changed, 46 insertions(+), 30 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 3b99c38bb..f10f6c7c5 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -294,6 +294,7 @@ struct CONFIG_T { int32_t TargetPowerConsumptionHysteresis; int32_t LowerPowerLimit; int32_t BaseLoadLimit; + int32_t ShadedFactor; int32_t UpperPowerLimit; bool IgnoreSoc; uint32_t BatterySocStartThreshold; diff --git a/include/defaults.h b/include/defaults.h index 2ab8ec9e8..5bd9d46b9 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -139,6 +139,7 @@ #define POWERLIMITER_LOWER_POWER_LIMIT 10 #define POWERLIMITER_BASE_LOAD_LIMIT 100 #define POWERLIMITER_UPPER_POWER_LIMIT 800 +#define POWERLIMITER_SHADED_FACTOR 98 #define POWERLIMITER_IGNORE_SOC false #define POWERLIMITER_BATTERY_SOC_START_THRESHOLD 80 #define POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD 20 diff --git a/src/Battery.cpp b/src/Battery.cpp index 7483c1de4..e10abb1fa 100644 --- a/src/Battery.cpp +++ b/src/Battery.cpp @@ -108,7 +108,7 @@ float BatteryClass::getDischargeCurrentLimit() } if (dischargeCurrentValid) { - return dischargeCurrentLimit; + return dischargeCurrentValid; } return FLT_MAX; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 61ee7758d..d8c68db65 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -264,6 +264,7 @@ bool ConfigurationClass::write() powerlimiter["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit; powerlimiter["base_load_limit"] = config.PowerLimiter.BaseLoadLimit; powerlimiter["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit; + powerlimiter["shaded_factor"] = config.PowerLimiter.ShadedFactor; powerlimiter["ignore_soc"] = config.PowerLimiter.IgnoreSoc; powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold; powerlimiter["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold; @@ -630,6 +631,7 @@ bool ConfigurationClass::read() config.PowerLimiter.LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT; config.PowerLimiter.BaseLoadLimit = powerlimiter["base_load_limit"] | POWERLIMITER_BASE_LOAD_LIMIT; config.PowerLimiter.UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT; + config.PowerLimiter.ShadedFactor = powerlimiter["shaded_factor"] | POWERLIMITER_SHADED_FACTOR; config.PowerLimiter.IgnoreSoc = powerlimiter["ignore_soc"] | POWERLIMITER_IGNORE_SOC; config.PowerLimiter.BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD; config.PowerLimiter.BatterySocStopThreshold = powerlimiter["battery_soc_stop_threshold"] | POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index c96a5a26f..e2bfe5b55 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -225,6 +225,7 @@ void PowerLimiterClass::loop() // Check if NTP time is set and next inverter restart not calculated yet if ((config.PowerLimiter.RestartHour >= 0) && (_nextInverterRestart == 0) ) { + MessageOutput.printf("[DPL::loop] Setting next restart \r\n"); // check every 5 seconds if (_nextCalculateCheck < millis()) { struct tm timeinfo; @@ -739,13 +740,14 @@ static int32_t scalePowerLimit(std::shared_ptr inverter, int32 // overscalling allows us to compensate for shaded panels by increasing the // total power limit, if the inverter is solar powered. if (allowOverscaling && isInverterSolarPowered) { - auto inverterOutputAC = inverter->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC); float inverterEfficiencyFactor = getInverterEfficiency(inverter); - // 98% of the expected power is good enough - auto expectedAcPowerPerChannel = (currentLimitWatts / dcTotalChnls) * 0.98; + // defined % of the expected power is good enough + auto factor_shaded=static_cast(config.PowerLimiter.ShadedFactor)/static_cast(100); + auto expectedAcPowerPerChannel = (currentLimitWatts / dcTotalChnls) * static_cast(factor_shaded); if (log) { + MessageOutput.printf("[DPL::scalePowerLimit] shaded factor raw %d, adapted %f \r\n",config.PowerLimiter.ShadedFactor,factor_shaded); MessageOutput.printf("[DPL::scalePowerLimit] expected AC power per channel %f W\r\n", expectedAcPowerPerChannel); } @@ -771,32 +773,30 @@ static int32_t scalePowerLimit(std::shared_ptr inverter, int32 // we currently need. if (dcShadedChnls == 0 || shadedChannelACPowerSum >= newLimit) { return newLimit; } - if (dcShadedChnls == dcTotalChnls) { - // keep the currentLimit when: - // - all channels are shaded - // - currentLimit >= newLimit - // - we get the expected AC power or less and - if (currentLimitWatts >= newLimit && inverterOutputAC <= newLimit) { - if (log) { - MessageOutput.printf("[DPL::scalePowerLimit] all channels are shaded, " - "keeping the current limit of %d W\r\n", currentLimitWatts); - } - - return currentLimitWatts; + size_t dcNonShadedChnls = dcTotalChnls - dcShadedChnls; - } else { - return newLimit; - } + if (log) { + MessageOutput.printf("[DPL::scalePowerLimit] newLimit %d W\r\n", newLimit); + MessageOutput.printf("[DPL::scalePowerLimit] shadedChannelACPowerSum %d W\r\n", static_cast(shadedChannelACPowerSum)); + MessageOutput.printf("[DPL::scalePowerLimit] dcNonShadedChnls %d ch\r\n", dcNonShadedChnls); + MessageOutput.printf("[DPL::scalePowerLimit] dcTotalChnls %d ch\r\n", dcTotalChnls); } - size_t dcNonShadedChnls = dcTotalChnls - dcShadedChnls; - auto overScaledLimit = static_cast((newLimit - shadedChannelACPowerSum) / dcNonShadedChnls * dcTotalChnls); + // if all channels are shaded, hopefully 1 can patch-up + if (dcNonShadedChnls == 0) { + dcNonShadedChnls=1; + if (log) { + MessageOutput.printf("[DPL::scalePowerLimit] all channels are shaded, hopefully 1 can patch-up\r\n"); + } + } + + auto overScaledLimit = static_cast((newLimit - static_cast(shadedChannelACPowerSum)) / (static_cast(dcNonShadedChnls)/static_cast(dcTotalChnls))); if (overScaledLimit <= newLimit) { return newLimit; } if (log) { MessageOutput.printf("[DPL::scalePowerLimit] %d/%d channels are shaded, " - "scaling %d W\r\n", dcShadedChnls, dcTotalChnls, overScaledLimit); + "scaling %f W\r\n", dcShadedChnls, dcTotalChnls, overScaledLimit); } return overScaledLimit; @@ -1005,12 +1005,6 @@ void PowerLimiterClass::calcNextInverterRestart() return; } - if (config.PowerLimiter.IsInverterSolarPowered) { - _nextInverterRestart = 1; - MessageOutput.println("[DPL::calcNextInverterRestart] not restarting solar-powered inverters"); - return; - } - // read time from timeserver, if time is not synced then return struct tm timeinfo; if (getLocalTime(&timeinfo, 5)) { diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 89e671d8c..67aca3131 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -46,6 +46,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) root["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit; root["base_load_limit"] = config.PowerLimiter.BaseLoadLimit; root["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit; + root["shaded_factor"] = config.PowerLimiter.ShadedFactor; root["ignore_soc"] = config.PowerLimiter.IgnoreSoc; root["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold; root["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold; @@ -167,7 +168,8 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) config.PowerLimiter.TargetPowerConsumptionHysteresis = root["target_power_consumption_hysteresis"].as(); config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as(); config.PowerLimiter.BaseLoadLimit = root["base_load_limit"].as(); - config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as(); + config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as(); + config.PowerLimiter.ShadedFactor = root["shaded_factor"].as(); if (config.Battery.Enabled) { config.PowerLimiter.IgnoreSoc = root["ignore_soc"].as(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index b424c74a1..3667ba144 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -685,6 +685,7 @@ "VoltageLoadCorrectionFactor": "Lastkorrekturfaktor", "BatterySocInfo": "Hinweis: Die Akku SoC (State of Charge) Werte werden nur benutzt, wenn die Batterie-Kommunikationsschnittstelle innerhalb der letzten Minute gültige Werte geschickt hat. Andernfalls werden als Fallback-Option die Spannungseinstellungen verwendet.", "InverterIsBehindPowerMeter": "Stromzählermessung beinhaltet Wechselrichter", + "ShadedFactor": "Factor to consider if panels are shaded 0-100, in % vs expected channel power", "InverterIsBehindPowerMeterHint": "Aktivieren falls sich der Stromzähler-Messwert um die Ausgangsleistung des Wechselrichters verringert, wenn dieser Strom produziert. Normalerweise ist das zutreffend.", "InverterIsSolarPowered": "Wechselrichter wird von Solarmodulen gespeist", "UseOverscalingToCompensateShading": "Verschattung durch Überskalierung ausgleichen", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 6b641edb7..e295c1d35 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -687,6 +687,7 @@ "VoltageLoadCorrectionFactor": "Load correction factor", "BatterySocInfo": "Hint: The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.", "InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output", + "ShadedFactor": "Factor to consider if panels are shaded 0-100, in % vs expected channel power", "InverterIsBehindPowerMeterHint": "Enable this option if the power meter reading is reduced by the inverter's output when it produces power. This is typically true.", "InverterIsSolarPowered": "Inverter is powered by solar modules", "UseOverscalingToCompensateShading": "Compensate for shading", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 426750a1e..bb9b2e4ac 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -745,6 +745,7 @@ "VoltageLoadCorrectionFactor": "Load correction factor", "BatterySocInfo": "Hint: The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.", "InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output", + "ShadedFactor": "Factor to consider if panels are shaded 0-100, in % vs expected channel power", "InverterIsBehindPowerMeterHint": "Enable this option if the power meter reading is reduced by the inverter's output when it produces power. This is typically true.", "InverterIsSolarPowered": "Inverter is powered by solar modules", "VoltageThresholds": "Battery Voltage Thresholds", diff --git a/webapp/src/types/PowerLimiterConfig.ts b/webapp/src/types/PowerLimiterConfig.ts index f8220a7d5..d3fd79376 100644 --- a/webapp/src/types/PowerLimiterConfig.ts +++ b/webapp/src/types/PowerLimiterConfig.ts @@ -34,6 +34,7 @@ export interface PowerLimiterConfig { lower_power_limit: number; base_load_limit: number; upper_power_limit: number; + shaded_factor: number; ignore_soc: boolean; battery_soc_start_threshold: number; battery_soc_stop_threshold: number; diff --git a/webapp/src/views/PowerLimiterAdminView.vue b/webapp/src/views/PowerLimiterAdminView.vue index 05b7bdd9c..f7a26946f 100644 --- a/webapp/src/views/PowerLimiterAdminView.vue +++ b/webapp/src/views/PowerLimiterAdminView.vue @@ -187,7 +187,19 @@ wide /> -
+ + +