Feature: DPL: add switch allowing to ignore SoC

unfortunately, the battery SoC values reported by battery BMSs are
unreliable, at least for some users, or at least without regular
(manual) full charge cycles to calibrate the BMS. it offers great
advantages to connect OpenDTU-OnBattery to a BMS (MQTT publishing of
values, Home Assistent integration, etc.), but previously the users
were then forced to configure the DPL by SoC values.

this change allows to configure the DPL such that SoC values are
ignored. instead, the voltage limits are used to make DPL decisions, as
if no SoC was available in the first place.

the SoC related setting are hidden from the DPL settings view if SoC
values are configured to be ignored.

closes #654.
This commit is contained in:
Bernhard Kirchen 2024-02-12 21:34:18 +01:00
parent b794f46ef0
commit 921302bf73
10 changed files with 30 additions and 18 deletions

View File

@ -213,6 +213,7 @@ struct CONFIG_T {
int32_t TargetPowerConsumptionHysteresis;
int32_t LowerPowerLimit;
int32_t UpperPowerLimit;
bool IgnoreSoc;
uint32_t BatterySocStartThreshold;
uint32_t BatterySocStopThreshold;
float VoltageStartThreshold;

View File

@ -128,6 +128,7 @@
#define POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS 0
#define POWERLIMITER_LOWER_POWER_LIMIT 10
#define POWERLIMITER_UPPER_POWER_LIMIT 800
#define POWERLIMITER_IGNORE_SOC false
#define POWERLIMITER_BATTERY_SOC_START_THRESHOLD 80
#define POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD 20
#define POWERLIMITER_VOLTAGE_START_THRESHOLD 50.0

View File

@ -191,6 +191,7 @@ bool ConfigurationClass::write()
powerlimiter["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis;
powerlimiter["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit;
powerlimiter["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit;
powerlimiter["ignore_soc"] = config.PowerLimiter.IgnoreSoc;
powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold;
powerlimiter["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold;
powerlimiter["voltage_start_threshold"] = config.PowerLimiter.VoltageStartThreshold;
@ -435,6 +436,7 @@ bool ConfigurationClass::read()
config.PowerLimiter.TargetPowerConsumptionHysteresis = powerlimiter["target_power_consumption_hysteresis"] | POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS;
config.PowerLimiter.LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT;
config.PowerLimiter.UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT;
config.PowerLimiter.IgnoreSoc = powerlimiter["ignore_soc"] | POWERLIMITER_IGNORE_SOC;
config.PowerLimiter.BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD;
config.PowerLimiter.BatterySocStopThreshold = powerlimiter["battery_soc_stop_threshold"] | POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD;
config.PowerLimiter.VoltageStartThreshold = powerlimiter["voltage_start_threshold"] | POWERLIMITER_VOLTAGE_START_THRESHOLD;

View File

@ -290,12 +290,13 @@ void PowerLimiterClass::loop()
}
if (_verboseLogging) {
MessageOutput.printf("[DPL::loop] battery interface %s, SoC: %d %%, StartTH: %d %%, StopTH: %d %%, SoC age: %d s\r\n",
MessageOutput.printf("[DPL::loop] battery interface %s, SoC: %d %%, StartTH: %d %%, StopTH: %d %%, SoC age: %d s, ignore: %s\r\n",
(config.Battery.Enabled?"enabled":"disabled"),
Battery.getStats()->getSoC(),
config.PowerLimiter.BatterySocStartThreshold,
config.PowerLimiter.BatterySocStopThreshold,
Battery.getStats()->getSoCAgeSeconds());
Battery.getStats()->getSoCAgeSeconds(),
(config.PowerLimiter.IgnoreSoc?"yes":"no"));
float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t)config.PowerLimiter.InverterChannelId, FLD_UDC);
MessageOutput.printf("[DPL::loop] dcVoltage: %.2f V, loadCorrectedVoltage: %.2f V, StartTH: %.2f V, StopTH: %.2f V\r\n",
@ -608,8 +609,10 @@ bool PowerLimiterClass::testThreshold(float socThreshold, float voltThreshold,
{
CONFIG_T& config = Configuration.get();
// prefer SoC provided through battery interface
if (config.Battery.Enabled && socThreshold > 0.0
// prefer SoC provided through battery interface, unless disabled by user
if (!config.PowerLimiter.IgnoreSoc
&& config.Battery.Enabled
&& socThreshold > 0.0
&& Battery.getStats()->isValid()
&& Battery.getStats()->getSoCAgeSeconds() < 60) {
return compare(Battery.getStats()->getSoC(), socThreshold);

View File

@ -45,6 +45,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
root["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis;
root["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit;
root["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit;
root["ignore_soc"] = config.PowerLimiter.IgnoreSoc;
root["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold;
root["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold;
root["voltage_start_threshold"] = static_cast<int>(config.PowerLimiter.VoltageStartThreshold * 100 +0.5) / 100.0;
@ -133,6 +134,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
config.PowerLimiter.TargetPowerConsumptionHysteresis = root["target_power_consumption_hysteresis"].as<int32_t>();
config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as<int32_t>();
config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as<int32_t>();
config.PowerLimiter.IgnoreSoc = root["ignore_soc"].as<bool>();
config.PowerLimiter.BatterySocStartThreshold = root["battery_soc_start_threshold"].as<uint32_t>();
config.PowerLimiter.BatterySocStopThreshold = root["battery_soc_stop_threshold"].as<uint32_t>();
config.PowerLimiter.VoltageStartThreshold = root["voltage_start_threshold"].as<float>();

View File

@ -595,17 +595,18 @@
"LowerPowerLimit": "Unteres Leistungslimit",
"UpperPowerLimit": "Oberes Leistungslimit",
"PowerMeters": "Leistungsmesser",
"IgnoreSoc": "Batterie SoC ignorieren",
"BatterySocStartThreshold": "Akku SoC - Start",
"BatterySocStopThreshold": "Akku SoC - Stop",
"BatterySocSolarPassthroughStartThreshold": "Akku SoC - Start solar passthrough",
"BatterySocSolarPassthroughStartThresholdHint": "Wenn der Batterie SOC über diesem Limit ist wird die Inverter Leistung entsprechend der Victron MPPT Leistung gesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist.",
"BatterySocSolarPassthroughStartThresholdHint": "Wenn der Batterie SoC über diesem Limit ist wird die Inverter Leistung entsprechend der Victron MPPT Leistung gesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist.",
"VoltageStartThreshold": "DC Spannung - Start",
"VoltageStopThreshold": "DC Spannung - Stop",
"VoltageSolarPassthroughStartThreshold": "DC Spannung - Start Solar-Passthrough",
"VoltageSolarPassthroughStopThreshold": "DC Spannung - Stop Solar-Passthrough",
"VoltageSolarPassthroughStartThresholdHint": "Wenn der Batteriespannung über diesem Limit ist wird die Inverter Leistung entsprechend der Victron MPPT Leistung gesetzt (abzüglich Effizienzkorrekturfaktor). Kann verwendet werden um überschüssige Solarleistung an das Netz zu liefern wenn die Batterie voll ist. Dieser Mode wird aktiv wenn das Start Spannungslimit überschritten wird und inaktiv wenn das Stop Spannungslimit unterschritten wird.",
"VoltageLoadCorrectionFactor": "DC Spannung - Lastkorrekturfaktor",
"BatterySocInfo": "<b>Hinweis:</b> Die Akku SoC (State of Charge) Werte können nur benutzt werden, wenn die Batterie-Kommunikationsschnittstelle aktiviert ist. Wenn die Batterie innerhalb der letzten Minute keine Werte geschickt hat, werden als Fallback-Option die Spannungseinstellungen verwendet.",
"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": "Welchselrichter ist hinter Leistungsmesser",
"Battery": "DC / Akku",
"VoltageLoadCorrectionInfo": "<b>Hinweis:</b> Wenn Leistung von der Batterie abgegeben wird, bricht normalerweise die Spannung etwas ein. Damit nicht vorzeitig der Wechelrichter ausgeschaltet wird sobald der \"Stop\"-Schwellenwert erreicht wird, wird der hier angegebene Korrekturfaktor mit einberechnet. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) * Korrekturfaktor).",

View File

@ -601,9 +601,7 @@
"LowerPowerLimit": "Lower power limit",
"UpperPowerLimit": "Upper power limit",
"PowerMeters": "Power meter",
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (optional)",
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (optional)",
"IgnoreSoc": "Ignore Battery SoC",
"BatterySocStartThreshold": "Battery SoC - Start threshold",
"BatterySocStopThreshold": "Battery SoC - Stop threshold",
"BatterySocSolarPassthroughStartThreshold": "Battery SoC - Start threshold for full solar passthrough",
@ -614,7 +612,7 @@
"VoltageSolarPassthroughStopThreshold": "DC Voltage - Stop threshold for full solar passthrough",
"VoltageSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) when full solar passthrough is active. Use this if you like to supply excess power to the grid when battery is full. This is started when battery voltage goes over this limit and stopped if voltage drops below stop limit.",
"VoltageLoadCorrectionFactor": "DC Voltage - Load correction factor",
"BatterySocInfo": "<b>Hint:</b> The battery SoC (State of Charge) values can only be used if the battery communication interface is enabled. If the battery has not reported any SoC updates in the last minute, the voltage thresholds will be used as fallback.",
"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": "Inverter is behind Power meter",
"Battery": "DC / Battery",
"VoltageLoadCorrectionInfo": "<b>Hint:</b> When the power output is higher, the voltage is usually decreasing. In order to not stop the inverter too early (Stop treshold), a power factor can be specified here to correct this. Corrected voltage = DC Voltage + (Current power * correction factor).",

View File

@ -648,9 +648,7 @@
"LowerPowerLimit": "Lower power limit",
"UpperPowerLimit": "Upper power limit",
"PowerMeters": "Power meter",
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (optional)",
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (optional)",
"IgnoreSoc": "Ignore Battery SoC",
"BatterySocStartThreshold": "Battery SoC - Start threshold",
"BatterySocStopThreshold": "Battery SoC - Stop threshold",
"BatterySocSolarPassthroughStartThreshold": "Battery SoC - Start threshold for full solar passthrough",
@ -661,7 +659,7 @@
"VoltageSolarPassthroughStopThreshold": "DC Voltage - Stop threshold for full solar passthrough",
"VoltageSolarPassthroughStartThresholdHint": "Inverter power is set according to Victron MPPT power (minus efficiency factors) when full solar passthrough is active. Use this if you like to supply excess power to the grid when battery is full. This is started when battery voltage goes over this limit and stopped if voltage drops below stop limit.",
"VoltageLoadCorrectionFactor": "DC Voltage - Load correction factor",
"BatterySocInfo": "<b>Hint:</b> The battery SoC (State of Charge) values can only be used if the battery communication interface is enabled. If the battery has not reported any SoC updates in the last minute, the voltage thresholds will be used as fallback.",
"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": "Inverter is behind Power meter",
"Battery": "DC / Battery",
"VoltageLoadCorrectionInfo": "<b>Hint:</b> When the power output is higher, the voltage is usually decreasing. In order to not stop the inverter too early (Stop treshold), a power factor can be specified here to correct this. Corrected voltage = DC Voltage + (Current power * correction factor)."

View File

@ -11,6 +11,7 @@ export interface PowerLimiterConfig {
target_power_consumption_hysteresis: number;
lower_power_limit: number;
upper_power_limit: number;
ignore_soc: boolean;
battery_soc_start_threshold: number;
battery_soc_stop_threshold: number;
voltage_start_threshold: number;

View File

@ -142,7 +142,12 @@
<CardElement :text="$t('powerlimiteradmin.Battery')" textVariant="text-bg-primary" add-space
v-show="powerLimiterConfigList.enabled"
>
<div class="row mb-3">
<InputElement
:label="$t('powerlimiteradmin.IgnoreSoc')"
v-model="powerLimiterConfigList.ignore_soc"
type="checkbox"/>
<div class="row mb-3" v-show="!powerLimiterConfigList.ignore_soc">
<label for="batterySocStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStartThreshold') }}:</label>
<div class="col-sm-10">
<div class="input-group">
@ -154,7 +159,7 @@
</div>
</div>
<div class="row mb-3">
<div class="row mb-3" v-show="!powerLimiterConfigList.ignore_soc">
<label for="batterySocStopThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStopThreshold') }}</label>
<div class="col-sm-10">
<div class="input-group">
@ -166,7 +171,7 @@
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.solar_passthrough_enabled">
<div class="row mb-3" v-show="powerLimiterConfigList.solar_passthrough_enabled && !powerLimiterConfigList.ignore_soc">
<label for="batterySocSolarPassthroughStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocSolarPassthroughStartThreshold') }}
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.BatterySocSolarPassthroughStartThresholdHint')" />
</label>
@ -180,7 +185,7 @@
</div>
</div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.BatterySocInfo')"></div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.BatterySocInfo')" v-show="!powerLimiterConfigList.ignore_soc"></div>
<div class="row mb-3">
<label for="inputVoltageStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageStartThreshold') }}:</label>