Skip to content

Commit

Permalink
add new DPL feature 'overscaling' to allow to compensate for shading
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasBoehm committed May 6, 2024
1 parent 2f7e1f3 commit ddec3cf
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,35 @@ static int32_t scalePowerLimit(std::shared_ptr<InverterAbstract> 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<int32_t>(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) {
Expand Down
2 changes: 2 additions & 0 deletions src/WebApi_powerlimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -159,6 +160,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)

config.PowerLimiter.IsInverterBehindPowerMeter = root["is_inverter_behind_powermeter"].as<bool>();
config.PowerLimiter.IsInverterSolarPowered = root["is_inverter_solar_powered"].as<bool>();
config.PowerLimiter.UseOverscalingToCompensateShading = root["use_overscaling_to_compensate_shading"].as<bool>();
config.PowerLimiter.InverterId = root["inverter_serial"].as<uint64_t>();
config.PowerLimiter.InverterChannelId = root["inverter_channel_id"].as<uint8_t>();
config.PowerLimiter.TargetPowerConsumption = root["target_power_consumption"].as<int32_t>();
Expand Down
1 change: 1 addition & 0 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@
"BatterySocInfo": "<b>Hinweis:</b> 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 Wechselrichterleistung",
"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": "<b>Hinweis:</b> 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",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@
"BatterySocInfo": "<b>Hint:</b> 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",
"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": "<b>Hint:</b> 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",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/types/PowerLimiterConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions webapp/src/views/PowerLimiterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
v-model="powerLimiterConfigList.is_inverter_solar_powered"
type="checkbox" wide/>

<InputElement v-show="canUseOverscaling()"
:label="$t('powerlimiteradmin.UseOverscalingToCompensateShading')"
v-model="powerLimiterConfigList.use_overscaling_to_compensate_shading"
type="checkbox" wide/>

<div class="row mb-3" v-if="needsChannelSelection()">
<label for="inverter_channel" class="col-sm-4 col-form-label">
{{ $t('powerlimiteradmin.InverterChannelId') }}
Expand Down Expand Up @@ -308,6 +313,10 @@ export default defineComponent({
hasPowerMeter() {
return this.powerLimiterMetaData.power_meter_enabled;
},
canUseOverscaling() {
const cfg = this.powerLimiterConfigList;
return cfg.is_inverter_solar_powered;
},
canUseSolarPassthrough() {
const cfg = this.powerLimiterConfigList;
const meta = this.powerLimiterMetaData;
Expand Down

0 comments on commit ddec3cf

Please sign in to comment.