DPL: account for solar passthrough losses (#307)

* fix another fixable "passtrough" typo

the typo in the config's identifier is not changed to preserve
compatibility while not spending the effort to migrate the setting.

* webapp language: prefer SoC over SOC

* DPL: implement solar passthrough loss factor

in (full) solar passthrough mode, the inverter output power is coupled
to the charge controler output power. the inverter efficiency is already
accounted for. however, the battery might still be slowly discharged for
two reasons: (1) line losses are not accounted for and (2) the inverter
outputs a little bit more than permitted by the power limit.

this is undesirable since the battery is significantly drained if solar
passthrough is active for a longer period of time. also, when using full
solar passthrough and a battery communication interface, the SoC will
slowly degrade to a value below the threshold value for full solar
passthrough. this makes the system switch from charging the battery
(potentially rapidly) to discharging the battery slowly. this switch
might happen in rather fast succession. that's effectively
trickle-charging the battery.

instead, this new factor helps to account for line losses between the
solar charge controller and the inverter, such that the battery is
actually not involved in solar passthrough. the value can be increased
until it is observed that the battery is not discharging when solar
passthrough is active.
This commit is contained in:
Bernhard Kirchen 2023-07-12 13:20:37 +02:00 committed by GitHub
parent 95d7ac7adf
commit f3297930b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 36 additions and 6 deletions

View File

@ -141,6 +141,7 @@ struct CONFIG_T {
bool PowerLimiter_Enabled; bool PowerLimiter_Enabled;
bool PowerLimiter_SolarPassThroughEnabled; bool PowerLimiter_SolarPassThroughEnabled;
uint8_t PowerLimiter_SolarPassThroughLosses;
uint8_t PowerLimiter_BatteryDrainStategy; uint8_t PowerLimiter_BatteryDrainStategy;
uint32_t PowerLimiter_Interval; uint32_t PowerLimiter_Interval;
bool PowerLimiter_IsInverterBehindPowerMeter; bool PowerLimiter_IsInverterBehindPowerMeter;

View File

@ -107,7 +107,8 @@
#define POWERLIMITER_ENABLED false #define POWERLIMITER_ENABLED false
#define POWERLIMITER_SOLAR_PASSTROUGH_ENABLED true #define POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED true
#define POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES 3
#define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0 #define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0
#define POWERLIMITER_INTERVAL 10 #define POWERLIMITER_INTERVAL 10
#define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true #define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true

View File

@ -151,6 +151,7 @@ bool ConfigurationClass::write()
JsonObject powerlimiter = doc.createNestedObject("powerlimiter"); JsonObject powerlimiter = doc.createNestedObject("powerlimiter");
powerlimiter["enabled"] = config.PowerLimiter_Enabled; powerlimiter["enabled"] = config.PowerLimiter_Enabled;
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassThroughEnabled; powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassThroughEnabled;
powerlimiter["solar_passtrough_losses"] = config.PowerLimiter_SolarPassThroughLosses;
powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy; powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy;
powerlimiter["interval"] = config.PowerLimiter_Interval; powerlimiter["interval"] = config.PowerLimiter_Interval;
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter; powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter;
@ -358,7 +359,8 @@ bool ConfigurationClass::read()
JsonObject powerlimiter = doc["powerlimiter"]; JsonObject powerlimiter = doc["powerlimiter"];
config.PowerLimiter_Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED; config.PowerLimiter_Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED;
config.PowerLimiter_SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTROUGH_ENABLED; config.PowerLimiter_SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED;
config.PowerLimiter_SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES;
config.PowerLimiter_BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY; config.PowerLimiter_BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY;
config.PowerLimiter_Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL; config.PowerLimiter_Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL;
config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;

View File

@ -314,7 +314,10 @@ int32_t PowerLimiterClass::inverterPowerDcToAc(std::shared_ptr<InverterAbstract>
// is currently not producing (efficiency is zero in that case) // is currently not producing (efficiency is zero in that case)
float inverterEfficiencyFactor = (inverterEfficiencyPercent > 0) ? inverterEfficiencyPercent/100 : 0.967; float inverterEfficiencyFactor = (inverterEfficiencyPercent > 0) ? inverterEfficiencyPercent/100 : 0.967;
return dcPower * inverterEfficiencyFactor; // account for losses between solar charger and inverter (cables, junctions...)
float lossesFactor = 1.00 - static_cast<float>(config.PowerLimiter_SolarPassThroughLosses)/100;
return dcPower * inverterEfficiencyFactor * lossesFactor;
} }
/** /**

View File

@ -39,6 +39,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
root[F("enabled")] = config.PowerLimiter_Enabled; root[F("enabled")] = config.PowerLimiter_Enabled;
root[F("solar_passthrough_enabled")] = config.PowerLimiter_SolarPassThroughEnabled; root[F("solar_passthrough_enabled")] = config.PowerLimiter_SolarPassThroughEnabled;
root[F("solar_passthrough_losses")] = config.PowerLimiter_SolarPassThroughLosses;
root[F("battery_drain_strategy")] = config.PowerLimiter_BatteryDrainStategy; root[F("battery_drain_strategy")] = config.PowerLimiter_BatteryDrainStategy;
root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter; root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter;
root[F("inverter_id")] = config.PowerLimiter_InverterId; root[F("inverter_id")] = config.PowerLimiter_InverterId;
@ -125,6 +126,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
config.PowerLimiter_Enabled = root[F("enabled")].as<bool>(); config.PowerLimiter_Enabled = root[F("enabled")].as<bool>();
PowerLimiter.setMode(PL_MODE_ENABLE_NORMAL_OP); // User input sets PL to normal operation PowerLimiter.setMode(PL_MODE_ENABLE_NORMAL_OP); // User input sets PL to normal operation
config.PowerLimiter_SolarPassThroughEnabled = root[F("solar_passthrough_enabled")].as<bool>(); config.PowerLimiter_SolarPassThroughEnabled = root[F("solar_passthrough_enabled")].as<bool>();
config.PowerLimiter_SolarPassThroughLosses = root[F("solar_passthrough_losses")].as<uint8_t>();
config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>(); config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>();
config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>(); config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>(); config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>();

View File

@ -532,6 +532,8 @@
"General": "Allgemein", "General": "Allgemein",
"Enable": "Aktiviert", "Enable": "Aktiviert",
"EnableSolarPassthrough": "Aktiviere Solar-Passthrough", "EnableSolarPassthrough": "Aktiviere Solar-Passthrough",
"SolarPassthroughLosses": "(Full) Solar-Passthrough Verluste:",
"SolarPassthroughLossesInfo": "<b>Hinweis:</b> Bei der Übertragung von Energie vom Solarladeregler zum Inverter sind Leitungsverluste zu erwarten. Um eine schleichende Entladung der Batterie im (Full) Solar-Passthrough Modus zu unterbinden, können diese Verluste berücksichtigt werden. Das am Inverter einzustellende Power Limit wird nach Berücksichtigung von dessen Effizienz zusätzlich um diesen Faktor verringert.",
"BatteryDrainStrategy": "Strategie zur Batterieentleerung", "BatteryDrainStrategy": "Strategie zur Batterieentleerung",
"BatteryDrainWhenFull": "Leeren, wenn voll", "BatteryDrainWhenFull": "Leeren, wenn voll",
"BatteryDrainAtNight": "Leeren zur Nacht", "BatteryDrainAtNight": "Leeren zur Nacht",
@ -750,8 +752,8 @@
"Property": "Eigenschaft", "Property": "Eigenschaft",
"Value": "Wert", "Value": "Wert",
"Unit": "Einheit", "Unit": "Einheit",
"stateOfCharge": "Ladezustand (SOC)", "stateOfCharge": "Ladezustand (SoC)",
"stateOfHealth": "Batteriezustand (SOH)", "stateOfHealth": "Batteriezustand (SoH)",
"voltage": "Spannung", "voltage": "Spannung",
"current": "Strom", "current": "Strom",
"temperature": "Temperatur", "temperature": "Temperatur",

View File

@ -536,6 +536,8 @@
"General": "General", "General": "General",
"Enable": "Enable", "Enable": "Enable",
"EnableSolarPassthrough": "Enable Solar-Passthrough", "EnableSolarPassthrough": "Enable Solar-Passthrough",
"SolarPassthroughLosses": "(Full) Solar Passthrough Losses:",
"SolarPassthroughLossesInfo": "<b>Hint:</b> Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.",
"BatteryDrainStrategy": "Battery drain strategy", "BatteryDrainStrategy": "Battery drain strategy",
"BatteryDrainWhenFull": "Empty when full", "BatteryDrainWhenFull": "Empty when full",
"BatteryDrainAtNight": "Empty at night", "BatteryDrainAtNight": "Empty at night",
@ -557,7 +559,7 @@
"BatterySocStartThreshold": "Battery SOC - Start threshold", "BatterySocStartThreshold": "Battery SOC - Start threshold",
"BatterySocStopThreshold": "Battery SOC - Stop threshold", "BatterySocStopThreshold": "Battery SOC - Stop threshold",
"BatterySocSolarPassthroughStartThreshold": "Battery SOC - Start threshold for full solar passthrough", "BatterySocSolarPassthroughStartThreshold": "Battery SOC - Start threshold for full solar passthrough",
"BatterySocSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) if battery SOC is over this limit. Use this if you like to supply excess power to the grid when battery is full", "BatterySocSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) if battery SoC is over this limit. Use this if you like to supply excess power to the grid when battery is full",
"VoltageStartThreshold": "DC Voltage - Start threshold", "VoltageStartThreshold": "DC Voltage - Start threshold",
"VoltageStopThreshold": "DC Voltage - Stop threshold", "VoltageStopThreshold": "DC Voltage - Stop threshold",
"VoltageSolarPassthroughStartThreshold": "DC Voltage - Start threshold for full solar passthrough", "VoltageSolarPassthroughStartThreshold": "DC Voltage - Start threshold for full solar passthrough",

View File

@ -555,6 +555,8 @@
"General": "General", "General": "General",
"Enable": "Enable", "Enable": "Enable",
"EnableSolarPassthrough": "Enable Solar-Passthrough", "EnableSolarPassthrough": "Enable Solar-Passthrough",
"SolarPassthroughLosses": "(Full) Solar Passthrough Losses:",
"SolarPassthroughLossesInfo": "<b>Hint:</b> Line losses are to be expected when transferring energy from the solar charge controller to the inverter. These losses can be taken into account to prevent the battery from gradually discharging in (full) solar passthrough mode. The power limit to be set on the inverter is additionally reduced by this factor after taking its efficiency into account.",
"BatteryDrainStrategy": "Battery drain strategy", "BatteryDrainStrategy": "Battery drain strategy",
"BatteryDrainWhenFull": "Empty when full", "BatteryDrainWhenFull": "Empty when full",
"BatteryDrainAtNight": "Empty at night", "BatteryDrainAtNight": "Empty at night",

View File

@ -1,6 +1,7 @@
export interface PowerLimiterConfig { export interface PowerLimiterConfig {
enabled: boolean; enabled: boolean;
solar_passthrough_enabled: boolean; solar_passthrough_enabled: boolean;
solar_passthrough_losses: number;
battery_drain_strategy: number; battery_drain_strategy: number;
is_inverter_behind_powermeter: boolean; is_inverter_behind_powermeter: boolean;
inverter_id: number; inverter_id: number;

View File

@ -30,6 +30,20 @@
<div class="alert alert-secondary" v-show="powerLimiterConfigList.enabled" role="alert" v-html="$t('powerlimiteradmin.SolarpassthroughInfo')"></div> <div class="alert alert-secondary" v-show="powerLimiterConfigList.enabled" role="alert" v-html="$t('powerlimiteradmin.SolarpassthroughInfo')"></div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled && powerLimiterConfigList.solar_passthrough_enabled">
<label for="solarPassthroughLosses" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.SolarPassthroughLosses') }}</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="solarPassthroughLosses"
placeholder="3" v-model="powerLimiterConfigList.solar_passthrough_losses"
aria-describedby="solarPassthroughLossesDescription" min="0" max="10" required/>
<span class="input-group-text" id="solarPassthroughLossesDescription">%</span>
</div>
</div>
</div>
<div class="alert alert-secondary" role="alert" v-show="powerLimiterConfigList.enabled && powerLimiterConfigList.solar_passthrough_enabled" v-html="$t('powerlimiteradmin.SolarPassthroughLossesInfo')"></div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled"> <div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputTimezone" class="col-sm-2 col-form-label"> <label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterId') }}: {{ $t('powerlimiteradmin.InverterId') }}: