Skip to content

Commit

Permalink
BREAKING CHANGE: remove default scaling/compensation for unused inputs
Browse files Browse the repository at this point in the history
Remove default scaling/compensation for unused inputs and add hints why and when overscaling should be turned on or off.
  • Loading branch information
AndreasBoehm authored and schlimmchen committed Dec 23, 2024
1 parent e30f83e commit 2fa550e
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 92 deletions.
2 changes: 1 addition & 1 deletion include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ struct POWERLIMITER_INVERTER_CONFIG_T {
bool IsGoverned;
bool IsBehindPowerMeter;
bool IsSolarPowered;
bool UseOverscalingToCompensateShading;
bool UseOverscaling;
uint16_t LowerPowerLimit;
uint16_t UpperPowerLimit;
};
Expand Down
2 changes: 1 addition & 1 deletion include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
#define POWERLIMITER_BATTERY_ALWAYS_USE_AT_NIGHT false
#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_USE_OVERSCALING false
#define POWERLIMITER_INVERTER_CHANNEL_ID 0
#define POWERLIMITER_TARGET_POWER_CONSUMPTION 0
#define POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS 0
Expand Down
6 changes: 3 additions & 3 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void ConfigurationClass::serializePowerLimiterConfig(PowerLimiterConfig const& s
t["is_governed"] = s.IsGoverned;
t["is_behind_power_meter"] = s.IsBehindPowerMeter;
t["is_solar_powered"] = s.IsSolarPowered;
t["use_overscaling_to_compensate_shading"] = s.UseOverscalingToCompensateShading;
t["use_overscaling_to_compensate_shading"] = s.UseOverscaling;
t["lower_power_limit"] = s.LowerPowerLimit;
t["upper_power_limit"] = s.UpperPowerLimit;
}
Expand Down Expand Up @@ -469,7 +469,7 @@ void ConfigurationClass::deserializePowerLimiterConfig(JsonObject const& source,
inv.IsGoverned = s["is_governed"] | false;
inv.IsBehindPowerMeter = s["is_behind_power_meter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
inv.IsSolarPowered = s["is_solar_powered"] | POWERLIMITER_IS_INVERTER_SOLAR_POWERED;
inv.UseOverscalingToCompensateShading = s["use_overscaling_to_compensate_shading"] | POWERLIMITER_USE_OVERSCALING_TO_COMPENSATE_SHADING;
inv.UseOverscaling = s["use_overscaling_to_compensate_shading"] | POWERLIMITER_USE_OVERSCALING;
inv.LowerPowerLimit = s["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT;
inv.UpperPowerLimit = s["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT;
}
Expand Down Expand Up @@ -885,7 +885,7 @@ void ConfigurationClass::migrateOnBattery()
inv.IsGoverned = true;
inv.IsBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
inv.IsSolarPowered = powerlimiter["is_inverter_solar_powered"] | POWERLIMITER_IS_INVERTER_SOLAR_POWERED;
inv.UseOverscalingToCompensateShading = powerlimiter["use_overscaling_to_compensate_shading"] | POWERLIMITER_USE_OVERSCALING_TO_COMPENSATE_SHADING;
inv.UseOverscaling = powerlimiter["use_overscaling_to_compensate_shading"] | POWERLIMITER_USE_OVERSCALING;
inv.LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT;
inv.UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT;

Expand Down
139 changes: 60 additions & 79 deletions src/PowerLimiterSolarInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ uint16_t PowerLimiterSolarInverter::standby()

uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
{
// overscalling allows us to compensate for shaded panels by increasing the
// total power limit, if the inverter is solar powered.
// this feature should not be used when homyiles 'Power Distribution Logic' is available
// as the inverter will take care of the power distribution across the MPPTs itself.
// (added in inverter firmware 01.01.12 on supported models (HMS-1600/1800/2000))
// When disabled we return the expected output.
if (!_config.UseOverscaling) { return expectedOutputWatts; }

// prevent scaling if inverter is not producing, as input channels are not
// producing energy and hence are detected as not-producing, causing
// unreasonable scaling.
Expand All @@ -136,108 +144,81 @@ uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
// producing very little due to the very low limit.
if (getCurrentLimitWatts() < dcTotalChnls * 10) { return expectedOutputWatts; }

// overscalling allows us to compensate for shaded panels by increasing the
// total power limit, if the inverter is solar powered.
if (_config.UseOverscalingToCompensateShading) {
auto inverterOutputAC = pStats->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC);

float inverterEfficiencyFactor = pStats->getChannelFieldValue(TYPE_INV, CH0, FLD_EFF);

// fall back to hoymiles peak efficiency as per datasheet if inverter
// is currently not producing (efficiency is zero in that case)
inverterEfficiencyFactor = (inverterEfficiencyFactor > 0) ? inverterEfficiencyFactor/100 : 0.967;

// 98% of the expected power is good enough
auto expectedAcPowerPerMppt = (getCurrentLimitWatts() / dcTotalMppts) * 0.98;
auto inverterOutputAC = pStats->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC);

if (_verboseLogging) {
MessageOutput.printf("%s expected AC power per MPPT %.0f W\r\n",
_logPrefix, expectedAcPowerPerMppt);
}
float inverterEfficiencyFactor = pStats->getChannelFieldValue(TYPE_INV, CH0, FLD_EFF);

size_t dcShadedMppts = 0;
auto shadedChannelACPowerSum = 0.0;
// fall back to hoymiles peak efficiency as per datasheet if inverter
// is currently not producing (efficiency is zero in that case)
inverterEfficiencyFactor = (inverterEfficiencyFactor > 0) ? inverterEfficiencyFactor/100 : 0.967;

for (auto& m : dcMppts) {
float mpptPowerAC = 0.0;
std::vector<ChannelNum_t> mpptChnls = _spInverter->getChannelsDCByMppt(m);
// 98% of the expected power is good enough
auto expectedAcPowerPerMppt = (getCurrentLimitWatts() / dcTotalMppts) * 0.98;

for (auto& c : mpptChnls) {
mpptPowerAC += pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC) * inverterEfficiencyFactor;
}
if (_verboseLogging) {
MessageOutput.printf("%s expected AC power per MPPT %.0f W\r\n",
_logPrefix, expectedAcPowerPerMppt);
}

if (mpptPowerAC < expectedAcPowerPerMppt) {
dcShadedMppts++;
shadedChannelACPowerSum += mpptPowerAC;
}
size_t dcShadedMppts = 0;
auto shadedChannelACPowerSum = 0.0;

if (_verboseLogging) {
MessageOutput.printf("%s MPPT-%c AC power %.0f W\r\n",
_logPrefix, m + 'a', mpptPowerAC);
}
}
for (auto& m : dcMppts) {
float mpptPowerAC = 0.0;
std::vector<ChannelNum_t> mpptChnls = _spInverter->getChannelsDCByMppt(m);

// no shading or the shaded channels provide more power than what
// we currently need.
if (dcShadedMppts == 0 || shadedChannelACPowerSum >= expectedOutputWatts) {
return expectedOutputWatts;
for (auto& c : mpptChnls) {
mpptPowerAC += pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC) * inverterEfficiencyFactor;
}

if (dcShadedMppts == dcTotalMppts) {
// keep the currentLimit when:
// - all channels are shaded
// - currentLimit >= expectedOutputWatts
// - we get the expected AC power or less and
if (getCurrentLimitWatts() >= expectedOutputWatts &&
inverterOutputAC <= expectedOutputWatts) {
if (_verboseLogging) {
MessageOutput.printf("%s all mppts are shaded, "
"keeping the current limit of %d W\r\n",
_logPrefix, getCurrentLimitWatts());
}

return getCurrentLimitWatts();

} else {
return expectedOutputWatts;
}
if (mpptPowerAC < expectedAcPowerPerMppt) {
dcShadedMppts++;
shadedChannelACPowerSum += mpptPowerAC;
}

size_t dcNonShadedMppts = dcTotalMppts - dcShadedMppts;
uint16_t overScaledLimit = (expectedOutputWatts - shadedChannelACPowerSum) / dcNonShadedMppts * dcTotalMppts;

if (overScaledLimit <= expectedOutputWatts) { return expectedOutputWatts; }

if (_verboseLogging) {
MessageOutput.printf("%s %d/%d mppts are shaded, scaling %d W\r\n",
_logPrefix, dcShadedMppts, dcTotalMppts, overScaledLimit);
MessageOutput.printf("%s MPPT-%c AC power %.0f W\r\n",
_logPrefix, m + 'a', mpptPowerAC);
}
}

return overScaledLimit;
// no shading or the shaded channels provide more power than what
// we currently need.
if (dcShadedMppts == 0 || shadedChannelACPowerSum >= expectedOutputWatts) {
return expectedOutputWatts;
}

size_t dcProdMppts = 0;
for (auto& m : dcMppts) {
float dcPowerMppt = 0.0;
std::vector<ChannelNum_t> mpptChnls = _spInverter->getChannelsDCByMppt(m);
if (dcShadedMppts == dcTotalMppts) {
// keep the currentLimit when:
// - all channels are shaded
// - currentLimit >= expectedOutputWatts
// - we get the expected AC power or less and
if (getCurrentLimitWatts() >= expectedOutputWatts &&
inverterOutputAC <= expectedOutputWatts) {
if (_verboseLogging) {
MessageOutput.printf("%s all mppts are shaded, "
"keeping the current limit of %d W\r\n",
_logPrefix, getCurrentLimitWatts());
}

for (auto& c : mpptChnls) {
dcPowerMppt += pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
}
return getCurrentLimitWatts();

if (dcPowerMppt > 2.0 * mpptChnls.size()) {
dcProdMppts++;
} else {
return expectedOutputWatts;
}
}

if (dcProdMppts == 0 || dcProdMppts == dcTotalMppts) { return expectedOutputWatts; }
size_t dcNonShadedMppts = dcTotalMppts - dcShadedMppts;
uint16_t overScaledLimit = (expectedOutputWatts - shadedChannelACPowerSum) / dcNonShadedMppts * dcTotalMppts;

uint16_t scaled = expectedOutputWatts / dcProdMppts * dcTotalMppts;
MessageOutput.printf("%s %d/%d mppts are producing, scaling from %d to "
"%d W\r\n", _logPrefix, dcProdMppts, dcTotalMppts,
expectedOutputWatts, scaled);
if (overScaledLimit <= expectedOutputWatts) { return expectedOutputWatts; }

if (_verboseLogging) {
MessageOutput.printf("%s %d/%d mppts are not-producing/shaded, scaling %d W\r\n",
_logPrefix, dcShadedMppts, dcTotalMppts, overScaledLimit);
}

return scaled;
return overScaledLimit;
}

void PowerLimiterSolarInverter::setAcOutput(uint16_t expectedOutputWatts)
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,9 @@
"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": "Verschattung durch Überskalierung ausgleichen",
"UseOverscalingToCompensateShadingHint": "Erlaubt das Überskalieren des Wechselrichter-Limits, um Verschattung eines oder mehrerer Eingänge auszugleichen.",
"UseOverscaling": "Verschattetet/Ungenutzte Eingänge ausgleichen",
"UseOverscalingHint": "Erlaubt das Überskalieren des Wechselrichter-Limits, um ungenutzte Eingänge oder Verschattung eines oder mehrerer Eingänge auszugleichen.",
"UseOverscalingInfo": "<b>Hint:</b> Aktiviere das Ausgleichen von Verschatteten oder ungenutzten Eingängen NICHT, wenn dein Wechselrichter Hoymiles 'Power Distribution Logic' unterstützt, da hiermit der AC-Ausgang limitiert wird und nicht die DC-Eingänge (Verfügbar seit Wechselrichter Firmware Version 01.01.12 auf unterstützten Modellen).",
"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 Wechselrichter nicht vorzeitig ausgeschaltet werden 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 automatischen Wechselrichterneustart",
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -711,8 +711,9 @@
"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": "Compensate for shading",
"UseOverscalingToCompensateShadingHint": "Allow to overscale the inverter limit to compensate for shading of one or multiple inputs.",
"UseOverscaling": "Compensate shaded or unused inputs",
"UseOverscalingHint": "Allow to overscale the inverter limit to compensate for unused inputs or shading of one or multiple inputs.",
"UseOverscalingInfo": "<b>Hint:</b> Only enable compensation of shaded or unused inputs if your inverter does NOT support hoymiles 'Power Distribution Logic' which will limit the AC output instead of limiting the DC inputs (added in inverter firmware version 01.01.12 on supported models).",
"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 inverters 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 Inverter Restart Time",
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -777,8 +777,9 @@
"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": "Compensate for shading",
"UseOverscalingToCompensateShadingHint": "Allow to overscale the inverter limit to compensate for shading of one or multiple inputs.",
"UseOverscaling": "Compensate shaded or unused inputs",
"UseOverscalingHint": "Allow to overscale the inverter limit to compensate for unused inputs or shading of one or multiple inputs.",
"UseOverscalingInfo": "<b>Hint:</b> Only enable compensation of shaded or unused inputs if your inverter does NOT support hoymiles 'Power Distribution Logic' which will limit the AC output instead of limiting the DC inputs (added in inverter firmware version 01.01.12 on supported models).",
"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 inverters 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 Inverter Restart Time",
Expand Down
11 changes: 9 additions & 2 deletions webapp/src/views/PowerLimiterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,20 @@

<InputElement
v-if="powerLimiterConfigList.inverters[idx].is_solar_powered"
:label="$t('powerlimiteradmin.UseOverscalingToCompensateShading')"
:tooltip="$t('powerlimiteradmin.UseOverscalingToCompensateShadingHint')"
:label="$t('powerlimiteradmin.UseOverscaling')"
:tooltip="$t('powerlimiteradmin.UseOverscalingHint')"
v-model="powerLimiterConfigList.inverters[idx].use_overscaling_to_compensate_shading"
type="checkbox"
wide
/>

<div
v-if="powerLimiterConfigList.inverters[idx].use_overscaling_to_compensate_shading"
class="alert alert-secondary"
role="alert"
v-html="$t('powerlimiteradmin.UseOverscalingInfo')"
></div>

<InputElement
:label="$t('powerlimiteradmin.LowerPowerLimit')"
:tooltip="$t('powerlimiteradmin.LowerPowerLimitHint')"
Expand Down

0 comments on commit 2fa550e

Please sign in to comment.