Planner: allow plan to take precedence over vehicle or session limits (#11013)

This commit is contained in:
andig 2023-12-06 18:23:12 +01:00 • committed by GitHub
parent 11c6fed4bc
commit 098a0fc2b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 49 deletions

View file

@ -132,6 +132,7 @@ export default {
vehicle: Object,
vehicleCapacity: Number,
vehicleSoc: Number,
vehicleTargetSoc: Number,
},
data: function () {
return {

View file

@ -69,6 +69,7 @@ export default {
mode: String,
vehicleCapacity: Number,
vehicle: Object,
vehicleTargetSoc: Number,
},
emits: ["plan-removed", "plan-updated"],
data: function () {

View file

@ -1,28 +1,22 @@
<template>
<p class="mb-0">
<span v-if="timeInThePast" class="d-block text-danger mb-1">
{{ $t("main.targetCharge.targetIsInThePast") }}
</span>
<span v-if="targetIsAboveVehicleLimit" class="d-block text-danger mb-1">
{{ $t("main.targetCharge.targetIsAboveVehicleLimit", { limit: vehicleLimitFmt }) }}
</span>
<span v-if="['off', 'now'].includes(mode)" class="d-block text-secondary mb-1">
{{ $t("main.targetCharge.onlyInPvMode") }}
</span>
<span v-if="targetIsAboveLimit" class="d-block text-danger mb-1">
{{
$t("main.targetCharge.targetIsAboveLimit", {
limit: limitFmt,
goal: goalFmt,
})
}}
</span>
<span v-if="timeInThePast" class="d-block text-danger mb-1">
{{ $t("main.targetCharge.targetIsInThePast") }}
<span v-if="targetIsAboveLimit" class="d-block text-secondary mb-1">
{{ $t("main.targetCharge.targetIsAboveLimit", { limit: limitFmt }) }}
</span>
<span v-if="timeTooFarInTheFuture" class="d-block text-secondary mb-1">
{{ $t("main.targetCharge.targetIsTooFarInTheFuture") }}
</span>
<span v-if="costLimitExists" class="d-block text-secondary mb-1">
{{
$t("main.targetCharge.costLimitIgnore", {
limit: costLimitText,
})
}}
{{ $t("main.targetCharge.costLimitIgnore", { limit: costLimitText }) }}
</span>
</p>
</template>
@ -50,6 +44,7 @@ export default {
mode: String,
tariff: Object,
selectedTargetTime: Date,
vehicleTargetSoc: Number,
},
computed: {
timeInThePast: function () {
@ -72,12 +67,21 @@ export default {
}
return this.limitEnergy && this.planEnergy > this.limitEnergy;
},
targetIsAboveVehicleLimit: function () {
if (this.socBasedPlanning) {
return this.effectivePlanSoc > (this.vehicleTargetSoc || 100);
}
return false;
},
limitFmt: function () {
if (this.socBasedPlanning) {
return this.fmtSoc(this.effectiveLimitSoc);
}
return this.fmtKWh(this.limitEnergy * 1e3);
},
vehicleLimitFmt: function () {
return this.fmtSoc(this.vehicleTargetSoc);
},
goalFmt: function () {
if (this.socBasedPlanning) {
return this.fmtSoc(this.effectivePlanSoc);

View file

@ -32,14 +32,17 @@
v-show="energyLimitMarkerPosition"
class="energy-limit-marker"
data-bs-toggle="tooltip"
:class="{ 'energy-limit-marker--active': energyLimitMarkerPosition < 100 }"
:class="{
'energy-limit-marker--active': energyLimitMarkerActive,
'energy-limit-marker--visible': energyLimitMarkerPosition < 100,
}"
:style="{ left: `${energyLimitMarkerPosition}%` }"
/>
<div
v-show="planMarkerAvailable"
class="plan-marker"
data-bs-toggle="tooltip"
:class="{ 'plan-marker--warn': !planMarkerActive }"
:class="{ 'plan-marker--warn': planMarkerUnreachable }"
:style="{ left: `${planMarkerPosition}%` }"
data-testid="plan-marker"
@click="$emit('plan-clicked')"
@ -141,14 +144,12 @@ export default {
}
return this.planMarkerPosition > 0;
},
planMarkerActive: function () {
planMarkerUnreachable: function () {
if (this.socBasedPlanning) {
const sessionLimit = this.selectedLimitSoc || this.effectiveLimitSoc;
const vehicleLimit = this.vehicleTargetSoc || 100;
const maxLimit = Math.min(sessionLimit, vehicleLimit);
return this.effectivePlanSoc <= maxLimit;
return this.effectivePlanSoc > vehicleLimit;
}
return this.planEnergy <= this.limitEnergy || !this.limitEnergy;
return false;
},
energyLimitMarkerPosition: function () {
if (this.socBasedCharging) {
@ -159,8 +160,19 @@ export default {
}
return 100;
},
energyLimitMarkerActive: function () {
if (this.socBasedCharging) {
return false;
}
if (this.planEnergy) {
return this.limitEnergy >= this.planEnergy;
}
return true;
},
sliderActive: function () {
return !this.vehicleTargetSoc || this.visibleLimitSoc <= this.vehicleTargetSoc;
const isBelowVehicleLimit = this.visibleLimitSoc <= (this.vehicleTargetSoc || 100);
const isAbovePlanLimit = this.visibleLimitSoc >= (this.effectivePlanSoc || 0);
return isBelowVehicleLimit && isAbovePlanLimit;
},
progressColor: function () {
if (this.minSocActive) {
@ -179,14 +191,14 @@ export default {
if (this.minSocActive) {
return this.minSoc - this.vehicleSoc;
}
let soc = this.sliderActive ? this.visibleLimitSoc : this.vehicleTargetSoc;
if (soc > this.vehicleSoc) {
return soc - this.vehicleSoc;
const limit = Math.min(
this.vehicleTargetSoc || 100,
Math.max(this.visibleLimitSoc, this.effectivePlanSoc || 0)
);
if (limit > this.vehicleSoc) {
return limit - this.vehicleSoc;
}
} else {
if (this.limitEnergy && this.planEnergy > this.limitEnergy) {
return (100 / this.planEnergy) * this.limitEnergy - this.vehicleSocDisplayWidth;
}
return 100 - this.vehicleSocDisplayWidth;
}
@ -383,12 +395,15 @@ export default {
transform: translateX(-50%);
width: 4px;
opacity: 0;
background-color: var(--evcc-dark-green);
transition-property: opacity, left;
background-color: var(--evcc-gray);
transition-property: opacity, left, background-color;
transition-timing-function: linear;
transition-duration: var(--evcc-transition-fast);
}
.energy-limit-marker--active {
background-color: var(--evcc-dark-green);
}
.energy-limit-marker--visible {
opacity: 1;
}
</style>

View file

@ -1560,6 +1560,19 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
case lp.scalePhasesRequired():
err = lp.scalePhases(lp.ConfiguredPhases)
case lp.remoteControlled(loadpoint.RemoteHardDisable):
remoteDisabled = loadpoint.RemoteHardDisable
fallthrough
case mode == api.ModeOff:
err = lp.setLimit(0, true)
// minimum or target charging
case lp.minSocNotReached() || plannerActive:
err = lp.fastCharging()
lp.resetPhaseTimer()
lp.elapsePVTimer() // let PV mode disable immediately afterwards
case lp.limitEnergyReached():
lp.log.DEBUG.Printf("limitEnergy reached: %.0fkWh > %0.1fkWh", lp.getChargedEnergy()/1e3, lp.limitEnergy)
err = lp.disableUnlessClimater()
@ -1568,23 +1581,10 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
lp.log.DEBUG.Printf("limitSoc reached: %.1f%% > %d%%", lp.vehicleSoc, lp.effectiveLimitSoc())
err = lp.disableUnlessClimater()
case lp.remoteControlled(loadpoint.RemoteHardDisable):
remoteDisabled = loadpoint.RemoteHardDisable
fallthrough
case mode == api.ModeOff:
err = lp.setLimit(0, true)
// immediate charging
// immediate charging- must be placed after limits are evaluated
case mode == api.ModeNow:
err = lp.fastCharging()
// minimum or target charging
case lp.minSocNotReached() || plannerActive:
err = lp.fastCharging()
lp.resetPhaseTimer()
lp.elapsePVTimer() // let PV mode disable immediately afterwards
case mode == api.ModeMinPV || mode == api.ModePV:
// cheap tariff
if autoCharge && lp.EffectivePlanTime().IsZero() {

View file

@ -247,7 +247,7 @@ descriptionEnergy = "Bis wann sollen {targetEnergy} ins Fahrzeug geladen sein?"
descriptionSoc = "Wann soll das Fahrzeug auf {targetSoc}% geladen sein?"
inactiveLabel = "Zielzeit"
onlyInPvMode = "Ladeplanung ist nur im PV-Modus aktiv."
planDescription = "Gib eine Abfahrtszeit an, und evcc lädt dein Fahrzeug so kostengünstig und umweltfreundlich wie möglich."
planDescription = "Gib eine Abfahrtszeit an, und evcc lädt das Fahrzeug so kostengünstig und umweltfreundlich wie möglich."
planDuration = "Ladedauer"
planPeriodLabel = "Zeitraum"
planPeriodValue = "{start} bis {end}"
@ -256,7 +256,8 @@ priceLimit = "Preisgrenze von {price}"
remove = "Entfernen"
setPlan = "Ladeplan festlegen"
setTargetTime = "keine"
targetIsAboveLimit = "Das Ladeziel {goal} liegt über dem konfigurierten Limit {limit}. Erhöhe das Limit, um das Ziel zu erreichen."
targetIsAboveLimit = "Das eingestellte Ladelimit von {limit} wird in diesem Zeitraum ignoriert."
targetIsAboveVehicleLimit = "Erhöhe das Fahrzeuglimit ({limit}), um das Ladeziel zu erreichen."
targetIsInThePast = "Wähle einen Zeitpunkt in der Zukunft, Marty."
targetIsTooFarInTheFuture = "Wir passen den Plan an, sobald wir mehr über die Zukunft wissen."
title = "Zielzeit"

View file

@ -248,7 +248,7 @@ descriptionEnergy = "Until when should {targetEnergy} be loaded into the vehicle
descriptionSoc = "When should the vehicle be charged to {targetSoc}%?"
inactiveLabel = "Target time"
onlyInPvMode = "Charging plan only works in solar mode."
planDescription = "Enter your departure time, and evcc will charge your vehicle as cost-effectively or environmentally friendly as possible."
planDescription = "Enter a departure time, and evcc will charge the vehicle as cost-effectively or environmentally friendly as possible."
planDuration = "Charging time"
planPeriodLabel = "Period"
planPeriodValue = "{start} to {end}"
@ -257,7 +257,8 @@ priceLimit = "price limit of {price}"
remove = "Remove"
setPlan = "Set a charging plan"
setTargetTime = "none"
targetIsAboveLimit = "Charging goal {goal} is above the configured limit {limit}. You must increase the limit to reach the goal."
targetIsAboveLimit = "The configured charging limit of {limit} will be ignored during this period."
targetIsAboveVehicleLimit = "Increase vehicle limit ({limit}) to reach the charging goal."
targetIsInThePast = "Pick a time in the future, Marty."
targetIsTooFarInTheFuture = "We will adjust the plan as soon as we know more about the future."
title = "Target Time"