OpenDTU-old/webapp/src/views/PowerLimiterAdminView.vue
Bernhard Kirchen f3297930b5
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.
2023-07-12 13:20:37 +02:00

381 lines
21 KiB
Vue

<template>
<BasePage :title="'Dynamic Power limiter Settings'" :isLoading="dataLoading">
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }}
</BootstrapAlert>
<form @submit="savePowerLimiterConfig">
<CardElement :text="$t('powerlimiteradmin.General')" textVariant="text-bg-primary">
<InputElement :label="$t('powerlimiteradmin.Enable')"
v-model="powerLimiterConfigList.enabled"
type="checkbox" wide/>
<InputElement v-show="powerLimiterConfigList.enabled"
:label="$t('powerlimiteradmin.EnableSolarPassthrough')"
v-model="powerLimiterConfigList.solar_passthrough_enabled"
type="checkbox" wide/>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled && powerLimiterConfigList.solar_passthrough_enabled">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.BatteryDrainStrategy') }}:
</label>
<div class="col-sm-10">
<select class="form-select" v-model="powerLimiterConfigList.battery_drain_strategy">
<option v-for="batteryDrainStrategy in batteryDrainStrategyList" :key="batteryDrainStrategy.key" :value="batteryDrainStrategy.key">
{{ $t(batteryDrainStrategy.value) }}
</option>
</select>
</div>
</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">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterId') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.InverterIdHint')" />
</label>
<div class="col-sm-10">
<select class="form-select" v-model="powerLimiterConfigList.inverter_id">
<option v-for="inverter in inverterList" :key="inverter.key" :value="inverter.key">
{{ inverter.value }}
</option>
</select>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterChannelId') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.InverterChannelIdHint')" />
</label>
<div class="col-sm-10">
<select class="form-select" v-model="powerLimiterConfigList.inverter_channel_id">
<option v-for="inverterChannel in inverterChannelList" :key="inverterChannel.key" :value="inverterChannel.key">
{{ inverterChannel.value }}
</option>
</select>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="targetPowerConsumption" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.TargetPowerConsumption') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.TargetPowerConsumptionHint')" />
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="targetPowerConsumption"
placeholder="75" v-model="powerLimiterConfigList.target_power_consumption"
aria-describedby="targetPowerConsumptionDescription" required/>
<span class="input-group-text" id="targetPowerConsumptionDescription">W</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="targetPowerConsumptionHyteresis" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.TargetPowerConsumptionHysteresis') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.TargetPowerConsumptionHysteresisHint')" required/>
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="targetPowerConsumptionHysteresis"
placeholder="30" min="0" v-model="powerLimiterConfigList.target_power_consumption_hysteresis"
aria-describedby="targetPowerConsumptionHysteresisDescription" />
<span class="input-group-text" id="targetPowerConsumptionHysteresisDescription">W</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputLowerPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.LowerPowerLimit') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="inputLowerPowerLimit"
placeholder="50" min="10" v-model="powerLimiterConfigList.lower_power_limit"
aria-describedby="lowerPowerLimitDescription" required/>
<span class="input-group-text" id="lowerPowerLimitDescription">W</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputUpperPowerLimit" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.UpperPowerLimit') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="inputUpperPowerLimit"
placeholder="800" v-model="powerLimiterConfigList.upper_power_limit"
aria-describedby="upperPowerLimitDescription" required/>
<span class="input-group-text" id="upperPowerLimitDescription">W</span>
</div>
</div>
</div>
</CardElement>
<CardElement :text="$t('powerlimiteradmin.PowerMeters')" textVariant="text-bg-primary" add-space
v-show="powerLimiterConfigList.enabled"
>
<InputElement
:label="$t('powerlimiteradmin.InverterIsBehindPowerMeter')"
v-model="powerLimiterConfigList.is_inverter_behind_powermeter"
type="checkbox" wide/>
</CardElement>
<CardElement :text="$t('powerlimiteradmin.Battery')" textVariant="text-bg-primary" add-space
v-show="powerLimiterConfigList.enabled"
>
<div class="row mb-3">
<label for="batterySocStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStartThreshold') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="batterySocStartThreshold"
placeholder="80" v-model="powerLimiterConfigList.battery_soc_start_threshold"
aria-describedby="batterySocStartThresholdDescription" min="0" max="100" required/>
<span class="input-group-text" id="batterySocStartThresholdDescription">%</span>
</div>
</div>
</div>
<div class="row mb-3">
<label for="batterySocStopThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocStopThreshold') }}</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="batterySocStopThreshold"
placeholder="20" v-model="powerLimiterConfigList.battery_soc_stop_threshold"
aria-describedby="batterySocStopThresholdDescription" min="0" max="100" required/>
<span class="input-group-text" id="batterySocStopThresholdDescription">%</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.solar_passthrough_enabled">
<label for="batterySocSolarPassthroughStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.BatterySocSolarPassthroughStartThreshold') }}
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.BatterySocSolarPassthroughStartThresholdHint')" />
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="batterySocSolarPassthroughStartThreshold"
placeholder="20" v-model="powerLimiterConfigList.full_solar_passthrough_soc"
aria-describedby="batterySocSolarPassthroughStartThresholdDescription" min="0" max="100" required/>
<span class="input-group-text" id="batterySocSolarPassthroughStartThresholdDescription">%</span>
</div>
</div>
</div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.BatterySocInfo')"></div>
<div class="row mb-3">
<label for="inputVoltageStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageStartThreshold') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="inputVoltageStartThreshold"
placeholder="50" v-model="powerLimiterConfigList.voltage_start_threshold"
aria-describedby="voltageStartThresholdDescription" required/>
<span class="input-group-text" id="voltageStartThresholdDescription">V</span>
</div>
</div>
</div>
<div class="row mb-3">
<label for="inputVoltageStopThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageStopThreshold') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="inputVoltageStopThreshold"
placeholder="49" v-model="powerLimiterConfigList.voltage_stop_threshold"
aria-describedby="voltageStopThresholdDescription" required/>
<span class="input-group-text" id="voltageStopThresholdDescription">V</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.solar_passthrough_enabled">
<label for="inputVoltageSolarPassthroughStartThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageSolarPassthroughStartThreshold') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.VoltageSolarPassthroughStartThresholdHint')" />
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="inputVoltageSolarPassthroughStartThreshold"
placeholder="49" v-model="powerLimiterConfigList.full_solar_passthrough_start_voltage"
aria-describedby="voltageSolarPassthroughStartThresholdDescription" required/>
<span class="input-group-text" id="voltageSolarPassthroughStartThresholdDescription">V</span>
</div>
</div>
</div>
<div class="row mb-3" v-show="powerLimiterConfigList.solar_passthrough_enabled">
<label for="inputVoltageSolarPassthroughStopThreshold" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageSolarPassthroughStopThreshold') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="inputVoltageSolarPassthroughStopThreshold"
placeholder="49" v-model="powerLimiterConfigList.full_solar_passthrough_stop_voltage"
aria-describedby="voltageSolarPassthroughStopThresholdDescription" required/>
<span class="input-group-text" id="voltageSolarPassthroughStopThresholdDescription">V</span>
</div>
</div>
</div>
<div class="row mb-3">
<label for="inputVoltageLoadCorrectionFactor" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.VoltageLoadCorrectionFactor') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.0001" class="form-control" id="inputVoltageLoadCorrectionFactor"
placeholder="49" v-model="powerLimiterConfigList.voltage_load_correction_factor"
aria-describedby="voltageLoadCorrectionFactorDescription" required/>
<span class="input-group-text" id="voltageLoadCorrectionFactorDescription">V</span>
</div>
</div>
</div>
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.VoltageLoadCorrectionInfo')"></div>
</CardElement>
<CardElement :text="$t('powerlimiteradmin.InverterRestart')" textVariant="text-bg-primary" add-space
v-show="powerLimiterConfigList.enabled"
>
<div class="row mb-3" v-show="powerLimiterConfigList.enabled">
<label for="inputTimezone" class="col-sm-2 col-form-label">
{{ $t('powerlimiteradmin.InverterRestartHour') }}:
<BIconInfoCircle v-tooltip :title="$t('powerlimiteradmin.InverterRestartHint')" />
</label>
<div class="col-sm-10">
<select class="form-select" v-model="powerLimiterConfigList.inverter_restart_hour">
<option v-for="hour in restartHourList" :key="hour.key" :value="hour.key">
{{ hour.value }}
</option>
</select>
</div>
</div>
</CardElement>
<button type="submit" class="btn btn-primary mb-3">{{ $t('powerlimiteradmin.Save') }}</button>
</form>
</BasePage>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue";
import { handleResponse, authHeader } from '@/utils/authentication';
import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue';
import { BIconInfoCircle } from 'bootstrap-icons-vue';
import type { PowerLimiterConfig } from "@/types/PowerLimiterConfig";
export default defineComponent({
components: {
BasePage,
BootstrapAlert,
CardElement,
InputElement,
BIconInfoCircle,
},
data() {
return {
dataLoading: true,
powerLimiterConfigList: {} as PowerLimiterConfig,
inverterList: [
{ key: 0, value: "ID 00" },
{ key: 1, value: "ID 01" },
{ key: 2, value: "ID 02" },
{ key: 3, value: "ID 03" },
{ key: 4, value: "ID 04" },
{ key: 5, value: "ID 05" },
{ key: 6, value: "ID 06" },
{ key: 7, value: "ID 07" },
{ key: 8, value: "ID 08" },
{ key: 9, value: "ID 09" },
{ key: 10, value: "ID 10" },
],
inverterChannelList: [
{ key: 0, value: "CH 0" },
{ key: 1, value: "CH 1" },
{ key: 2, value: "CH 2" },
{ key: 3, value: "CH 3" },
],
batteryDrainStrategyList: [
{ key: 0, value: "powerlimiteradmin.BatteryDrainWhenFull"},
{ key: 1, value: "powerlimiteradmin.BatteryDrainAtNight" },
],
restartHourList: [
{ key: -1, value: "- - - -" },
{ key: 0, value: "0:00" },
{ key: 1, value: "1:00" },
{ key: 2, value: "2:00" },
{ key: 3, value: "3:00" },
{ key: 4, value: "4:00" },
{ key: 5, value: "5:00" },
{ key: 6, value: "6:00" },
{ key: 7, value: "7:00" },
{ key: 8, value: "8:00" },
{ key: 9, value: "9:00" },
{ key: 10, value: "10:00" },
{ key: 11, value: "11:00" },
{ key: 12, value: "12:00" },
{ key: 13, value: "13:00" },
{ key: 14, value: "14:00" },
{ key: 15, value: "15:00" },
{ key: 16, value: "16:00" },
{ key: 17, value: "17:00" },
{ key: 18, value: "18:00" },
{ key: 19, value: "19:00" },
{ key: 20, value: "20:00" },
{ key: 21, value: "21:00" },
{ key: 22, value: "22:00" },
{ key: 23, value: "23:00" },
],
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getPowerLimiterConfig();
},
methods: {
getPowerLimiterConfig() {
this.dataLoading = true;
fetch("/api/powerlimiter/config", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.powerLimiterConfigList = data;
this.dataLoading = false;
});
},
savePowerLimiterConfig(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.powerLimiterConfigList));
fetch("/api/powerlimiter/config", {
method: "POST",
headers: authHeader(),
body: formData,
})
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then(
(response) => {
this.alertMessage = response.message;
this.alertType = response.type;
this.showAlert = true;
}
);
},
},
});
</script>