diff --git a/include/Configuration.h b/include/Configuration.h index a78b80626..c93991529 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -210,6 +210,7 @@ struct CONFIG_T { uint32_t Interval; bool IsInverterBehindPowerMeter; bool IsInverterSolarPowered; + bool UseOverscalingToCompensateShading; uint64_t InverterId; uint8_t InverterChannelId; int32_t TargetPowerConsumption; diff --git a/include/defaults.h b/include/defaults.h index 237a94b06..32d1e54cd 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -127,6 +127,7 @@ #define POWERLIMITER_INTERVAL 10 #define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true #define POWERLIMITER_IS_INVERTER_SOLAR_POWERED false +#define POWERLIMITER_USE_OVERSCALING_TO_COMPENSATE_SHADING false #define POWERLIMITER_INVERTER_ID 0ULL #define POWERLIMITER_INVERTER_CHANNEL_ID 0 #define POWERLIMITER_TARGET_POWER_CONSUMPTION 0 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index d23ac9ba4..1495753d8 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -184,6 +184,7 @@ bool ConfigurationClass::write() powerlimiter["interval"] = config.PowerLimiter.Interval; powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; powerlimiter["is_inverter_solar_powered"] = config.PowerLimiter.IsInverterSolarPowered; + powerlimiter["use_overscaling_to_compensate_shading"] = config.PowerLimiter.UseOverscalingToCompensateShading; powerlimiter["inverter_id"] = config.PowerLimiter.InverterId; powerlimiter["inverter_channel_id"] = config.PowerLimiter.InverterChannelId; powerlimiter["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption; @@ -444,6 +445,7 @@ bool ConfigurationClass::read() config.PowerLimiter.Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL; config.PowerLimiter.IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; config.PowerLimiter.IsInverterSolarPowered = powerlimiter["is_inverter_solar_powered"] | POWERLIMITER_IS_INVERTER_SOLAR_POWERED; + config.PowerLimiter.UseOverscalingToCompensateShading = powerlimiter["use_overscaling_to_compensate_shading"] | POWERLIMITER_USE_OVERSCALING_TO_COMPENSATE_SHADING; config.PowerLimiter.InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID; config.PowerLimiter.InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID; config.PowerLimiter.TargetPowerConsumption = powerlimiter["target_power_consumption"] | POWERLIMITER_TARGET_POWER_CONSUMPTION; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 7ce0d8bd9..7b18dfb5d 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -713,6 +713,35 @@ static int32_t scalePowerLimit(std::shared_ptr inverter, int32 // producing very little due to the very low limit. if (currentLimitWatts < dcTotalChnls * 10) { return newLimit; } + auto const& config = Configuration.get(); + auto allowOverscaling = config.PowerLimiter.UseOverscalingToCompensateShading; + + // overscalling allows us to compensate for shaded panels by increasing the + // total power limit. + if(allowOverscaling) { + auto expectedPowerPerChannel = currentLimitWatts / dcTotalChnls; + + size_t dcLimitedChnls = 0; + auto limitedChannelPowerSum = 0.0; + + for (auto& c : dcChnls) { + auto channelPower = inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC); + + if (channelPower < expectedPowerPerChannel) { + dcLimitedChnls++; + limitedChannelPowerSum += channelPower; + } + } + + if(dcLimitedChnls == 0 || dcLimitedChnls == dcTotalChnls) { return newLimit; } + + auto overScaled = static_cast((newLimit - limitedChannelPowerSum) * dcTotalChnls); + + MessageOutput.printf("[DPL::scalePowerLimit] %d/%d channels are producing less than expected, " + "scaling from %d to %d W\r\n", dcLimitedChnls, dcTotalChnls, newLimit, overScaled); + return overScaled; + } + size_t dcProdChnls = 0; for (auto& c : dcChnls) { if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 2.0) { diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 114ced773..b28380d3c 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -38,6 +38,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) root["battery_always_use_at_night"] = config.PowerLimiter.BatteryAlwaysUseAtNight; root["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; root["is_inverter_solar_powered"] = config.PowerLimiter.IsInverterSolarPowered; + root["use_overscaling_to_compensate_shading"] = config.PowerLimiter.UseOverscalingToCompensateShading; root["inverter_serial"] = String(config.PowerLimiter.InverterId); root["inverter_channel_id"] = config.PowerLimiter.InverterChannelId; root["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption; @@ -159,6 +160,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) config.PowerLimiter.IsInverterBehindPowerMeter = root["is_inverter_behind_powermeter"].as(); config.PowerLimiter.IsInverterSolarPowered = root["is_inverter_solar_powered"].as(); + config.PowerLimiter.UseOverscalingToCompensateShading = root["use_overscaling_to_compensate_shading"].as(); config.PowerLimiter.InverterId = root["inverter_serial"].as(); config.PowerLimiter.InverterChannelId = root["inverter_channel_id"].as(); config.PowerLimiter.TargetPowerConsumption = root["target_power_consumption"].as(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 591259e8c..a4625273b 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -634,6 +634,7 @@ "InverterIsBehindPowerMeter": "Stromzählermessung beinhaltet Wechselrichter", "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": "Wechselrichter-Limit im Zweifel Überskalieren, um Verschattung eines oder mehrerer Eingänge auszugleichen", "VoltageThresholds": "Batterie Spannungs-Schwellwerte ", "VoltageLoadCorrectionInfo": "Hinweis: Wenn Leistung von der Batterie abgegeben wird, bricht ihre Spannung etwas ein. Der Spannungseinbruch skaliert mit dem Entladestrom. Damit nicht vorzeitig der Wechselrichter ausgeschaltet wird sobald der Stop-Schwellenwert unterschritten wurde, wird der hier angegebene Korrekturfaktor mit einberechnet um die Spannung zu errechnen die der Akku in Ruhe hätte. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) * Korrekturfaktor).", "InverterRestartHour": "Uhrzeit für geplanten Neustart", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index b81c6a6b8..2bd4d938b 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -640,6 +640,7 @@ "InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output", "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": "Allow overscaling of inverter limit to compensate for shading of one or multiple inputs", "VoltageThresholds": "Battery Voltage Thresholds", "VoltageLoadCorrectionInfo": "Hint: When the battery is discharged, its voltage drops. The voltage drop scales with the discharge current. In order to not stop the inverter too early (stop threshold), this load correction factor can be specified to calculate the battery voltage if it was idle. Corrected voltage = DC Voltage + (Current power * correction factor).", "InverterRestartHour": "Automatic Restart Time", diff --git a/webapp/src/types/PowerLimiterConfig.ts b/webapp/src/types/PowerLimiterConfig.ts index 0d7ad388d..f8220a7d5 100644 --- a/webapp/src/types/PowerLimiterConfig.ts +++ b/webapp/src/types/PowerLimiterConfig.ts @@ -26,6 +26,7 @@ export interface PowerLimiterConfig { battery_always_use_at_night: boolean; is_inverter_behind_powermeter: boolean; is_inverter_solar_powered: boolean; + use_overscaling_to_compensate_shading: boolean; inverter_serial: string; inverter_channel_id: number; target_power_consumption: number; diff --git a/webapp/src/views/PowerLimiterAdminView.vue b/webapp/src/views/PowerLimiterAdminView.vue index e6cc0f22e..fae639e7e 100644 --- a/webapp/src/views/PowerLimiterAdminView.vue +++ b/webapp/src/views/PowerLimiterAdminView.vue @@ -68,6 +68,11 @@ v-model="powerLimiterConfigList.is_inverter_solar_powered" type="checkbox" wide/> + +