Skip to content

Commit

Permalink
DPL: account for solar passthrough losses (hoylabs#307)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
schlimmchen authored Jul 12, 2023
1 parent 95d7ac7 commit f329793
Show file tree
Hide file tree
Showing 10 changed files with 36 additions and 6 deletions.
1 change: 1 addition & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ struct CONFIG_T {

bool PowerLimiter_Enabled;
bool PowerLimiter_SolarPassThroughEnabled;
uint8_t PowerLimiter_SolarPassThroughLosses;
uint8_t PowerLimiter_BatteryDrainStategy;
uint32_t PowerLimiter_Interval;
bool PowerLimiter_IsInverterBehindPowerMeter;
Expand Down
3 changes: 2 additions & 1 deletion include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@


#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_INTERVAL 10
#define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true
Expand Down
4 changes: 3 additions & 1 deletion src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ bool ConfigurationClass::write()
JsonObject powerlimiter = doc.createNestedObject("powerlimiter");
powerlimiter["enabled"] = config.PowerLimiter_Enabled;
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassThroughEnabled;
powerlimiter["solar_passtrough_losses"] = config.PowerLimiter_SolarPassThroughLosses;
powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy;
powerlimiter["interval"] = config.PowerLimiter_Interval;
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter;
Expand Down Expand Up @@ -358,7 +359,8 @@ bool ConfigurationClass::read()

JsonObject powerlimiter = doc["powerlimiter"];
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_Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL;
config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
Expand Down
5 changes: 4 additions & 1 deletion src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,10 @@ int32_t PowerLimiterClass::inverterPowerDcToAc(std::shared_ptr<InverterAbstract>
// is currently not producing (efficiency is zero in that case)
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;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/WebApi_powerlimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)

root[F("enabled")] = config.PowerLimiter_Enabled;
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("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter;
root[F("inverter_id")] = config.PowerLimiter_InverterId;
Expand Down Expand Up @@ -125,6 +126,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
config.PowerLimiter_Enabled = root[F("enabled")].as<bool>();
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_SolarPassThroughLosses = root[F("solar_passthrough_losses")].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_InverterId = root[F("inverter_id")].as<uint8_t>();
Expand Down
6 changes: 4 additions & 2 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@
"General": "Allgemein",
"Enable": "Aktiviert",
"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",
"BatteryDrainWhenFull": "Leeren, wenn voll",
"BatteryDrainAtNight": "Leeren zur Nacht",
Expand Down Expand Up @@ -750,8 +752,8 @@
"Property": "Eigenschaft",
"Value": "Wert",
"Unit": "Einheit",
"stateOfCharge": "Ladezustand (SOC)",
"stateOfHealth": "Batteriezustand (SOH)",
"stateOfCharge": "Ladezustand (SoC)",
"stateOfHealth": "Batteriezustand (SoH)",
"voltage": "Spannung",
"current": "Strom",
"temperature": "Temperatur",
Expand Down
4 changes: 3 additions & 1 deletion webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,8 @@
"General": "General",
"Enable": "Enable",
"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",
"BatteryDrainWhenFull": "Empty when full",
"BatteryDrainAtNight": "Empty at night",
Expand All @@ -557,7 +559,7 @@
"BatterySocStartThreshold": "Battery SOC - Start threshold",
"BatterySocStopThreshold": "Battery SOC - Stop threshold",
"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",
"VoltageStopThreshold": "DC Voltage - Stop threshold",
"VoltageSolarPassthroughStartThreshold": "DC Voltage - Start threshold for full solar passthrough",
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,8 @@
"General": "General",
"Enable": "Enable",
"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",
"BatteryDrainWhenFull": "Empty when full",
"BatteryDrainAtNight": "Empty at night",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/types/PowerLimiterConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface PowerLimiterConfig {
enabled: boolean;
solar_passthrough_enabled: boolean;
solar_passthrough_losses: number;
battery_drain_strategy: number;
is_inverter_behind_powermeter: boolean;
inverter_id: number;
Expand Down
14 changes: 14 additions & 0 deletions webapp/src/views/PowerLimiterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@

<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">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterId') }}:
Expand Down

0 comments on commit f329793

Please sign in to comment.