diff --git a/assets/js/components/ChargingPlan.vue b/assets/js/components/ChargingPlan.vue index 0bf749414..d04601f9e 100644 --- a/assets/js/components/ChargingPlan.vue +++ b/assets/js/components/ChargingPlan.vue @@ -132,6 +132,7 @@ export default { vehicle: Object, vehicleCapacity: Number, vehicleSoc: Number, + vehicleTargetSoc: Number, }, data: function () { return { diff --git a/assets/js/components/ChargingPlanSettings.vue b/assets/js/components/ChargingPlanSettings.vue index d8cdb1ead..624223dc1 100644 --- a/assets/js/components/ChargingPlanSettings.vue +++ b/assets/js/components/ChargingPlanSettings.vue @@ -69,6 +69,7 @@ export default { mode: String, vehicleCapacity: Number, vehicle: Object, + vehicleTargetSoc: Number, }, emits: ["plan-removed", "plan-updated"], data: function () { diff --git a/assets/js/components/ChargingPlanWarnings.vue b/assets/js/components/ChargingPlanWarnings.vue index 76bed673e..5e7062595 100644 --- a/assets/js/components/ChargingPlanWarnings.vue +++ b/assets/js/components/ChargingPlanWarnings.vue @@ -1,28 +1,22 @@ @@ -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); diff --git a/assets/js/components/VehicleSoc.vue b/assets/js/components/VehicleSoc.vue index 24beccbf9..fe47990e4 100644 --- a/assets/js/components/VehicleSoc.vue +++ b/assets/js/components/VehicleSoc.vue @@ -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}%` }" />
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; } diff --git a/core/loadpoint.go b/core/loadpoint.go index 22c86964b..90045e6d3 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -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() { diff --git a/i18n/de.toml b/i18n/de.toml index 41331f1c3..d12c24a57 100644 --- a/i18n/de.toml +++ b/i18n/de.toml @@ -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" diff --git a/i18n/en.toml b/i18n/en.toml index 05d5b6132..0801e0242 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -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"