From 38ae43e10fdb955fd56fca57893ff3f2b6beb495 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 5 Feb 2026 06:50:52 +0100 Subject: [PATCH] improve scheduled charging --- packages/control/ev/charge_template.py | 26 ++++++++++++++------- packages/control/ev/charge_template_test.py | 12 +++++----- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index 11c66be9d5..10c8d0af57 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -136,7 +136,6 @@ class ChargeTemplate: data: ChargeTemplateData = field(default_factory=charge_template_data_factory, metadata={ "topic": ""}) - BUFFER = -1200 # nach mehr als 20 Min Überschreitung wird der Termin als verpasst angesehen CHARGING_PRICE_EXCEEDED = ("Der aktuelle Strompreis liegt über dem maximalen Strompreis. ") CHARGING_PRICE_LOW = "Laden, da der aktuelle Strompreis unter dem maximalen Strompreis liegt." @@ -309,7 +308,8 @@ def eco_charging(self, log.exception("Fehler im ev-Modul "+str(self.data.id)) return 0, "stop", "Keine Ladung, da ein interner Fehler aufgetreten ist: "+traceback.format_exc(), 0 - BUFFER = -1200 # nach mehr als 20 Min Überschreitung wird der Termin als verpasst angesehen + BUFFER_AFTER_END_TIME = -1200 # nach mehr als 20 Min Überschreitung wird der Termin als verpasst angesehen + BUFFER_START_EARLIER = 600 # 10 Min vor dem geplanten Start kann begonnen werden def _find_recent_plan(self, plans: List[ScheduledChargingPlan], @@ -330,7 +330,7 @@ def _find_recent_plan(self, f"oder im Plan {p.name} als Begrenzung Energie einstellen.") try: plans_diff_end_date.append( - {p.id: timecheck.check_end_time(p, self.BUFFER)}) + {p.id: timecheck.check_end_time(p, self.BUFFER_AFTER_END_TIME)}) log.debug(f"Verbleibende Zeit bis zum Zieltermin [s]: {plans_diff_end_date}") except Exception: log.exception("Fehler im ev-Modul "+str(self.data.id)) @@ -340,7 +340,7 @@ def _find_recent_plan(self, if filtered_plans: sorted_plans = sorted(filtered_plans, key=lambda x: list(x.values())[0]) for plan in sorted_plans: - if self.BUFFER < list(plan.values())[0]: + if self.BUFFER_AFTER_END_TIME < list(plan.values())[0]: plan_dict = plan break else: @@ -396,6 +396,7 @@ def scheduled_charging(self, plan_data, soc, used_amount, + max_hw_phases, control_parameter.phases, control_parameter.min_current, soc_request_interval_offset, @@ -435,9 +436,15 @@ def _calc_remaining_time(self, charging_type, ev_template, bidi) phases = control_parameter_phases remaining_time = plan_end_time - duration + elif plan.et_active: + duration, missing_amount = self._calculate_duration( + plan, soc, ev_template.data.battery_capacity, used_amount, max_hw_phases, + charging_type, ev_template, bidi) + phases = max_hw_phases + remaining_time = plan_end_time - duration else: duration_3p, missing_amount = self._calculate_duration( - plan, soc, ev_template.data.battery_capacity, used_amount, 3, + plan, soc, ev_template.data.battery_capacity, used_amount, max_hw_phases, charging_type, ev_template, bidi) remaining_time_3p = plan_end_time - duration_3p duration_1p, missing_amount = self._calculate_duration( @@ -450,7 +457,7 @@ def _calc_remaining_time(self, # Zeit reicht nicht mehr für einphasiges Laden remaining_time = remaining_time_3p duration = duration_3p - phases = 3 + phases = max_hw_phases else: remaining_time = remaining_time_1p duration = duration_1p @@ -459,7 +466,7 @@ def _calc_remaining_time(self, elif plan.phases_to_use == 3 or plan.phases_to_use == 1: duration, missing_amount = self._calculate_duration( plan, soc, ev_template.data.battery_capacity, - used_amount, plan.phases_to_use, charging_type, ev_template, bidi) + used_amount, min(plan.phases_to_use, max_hw_phases), charging_type, ev_template, bidi) remaining_time = plan_end_time - duration phases = plan.phases_to_use @@ -525,6 +532,7 @@ def scheduled_charging_calc_current(self, selected_plan: Optional[SelectedPlan], soc: int, used_amount: float, + max_phases_hw: int, control_parameter_phases: int, min_current: int, soc_request_interval_offset: int, @@ -572,7 +580,8 @@ def scheduled_charging_calc_current(self, phases = plan.phases_to_use_pv elif limit.selected == "amount" and used_amount >= limit.amount: message = self.SCHEDULED_CHARGING_REACHED_AMOUNT - elif 0 - soc_request_interval_offset < selected_plan.remaining_time < 300 + soc_request_interval_offset: + elif (0 - soc_request_interval_offset < selected_plan.remaining_time < + self.BUFFER_START_EARLIER + soc_request_interval_offset): # Wenn der SoC ein paar Minuten alt ist, kann der Termin trotzdem gehalten werden. # Zielladen kann nicht genauer arbeiten, als das Abfrageintervall vom SoC. # 5 Min vor spätestem Ladestart @@ -643,6 +652,7 @@ def convert_loading_hours_to_string(hour_list: List[int]) -> str: if data.data.optional_data.ep_is_charging_allowed_hours_list(hour_list): message = self.SCHEDULED_CHARGING_CHEAP_HOUR.format(get_hours_message()) current = plan_current + phases = max_phases_hw if plan.phases_to_use == 0 else plan.phases_to_use submode = "instant_charging" elif ((limit.selected == "soc" and soc <= limit.soc_limit) or (limit.selected == "amount" and used_amount < limit.amount)): diff --git a/packages/control/ev/charge_template_test.py b/packages/control/ev/charge_template_test.py index 7efe22f3dc..97c45b7639 100644 --- a/packages/control/ev/charge_template_test.py +++ b/packages/control/ev/charge_template_test.py @@ -254,8 +254,8 @@ def test_scheduled_charging_recent_plan(end_time_mock, False, (16, "instant_charging", ChargeTemplate.SCHEDULED_CHARGING_MAX_CURRENT.format(16), 3), id="few minutes too late, but didn't miss for today"), - pytest.param(SelectedPlan(remaining_time=301, duration=3600), 79, 0, "soc", - False, (6, "pv_charging", ChargeTemplate.SCHEDULED_CHARGING_USE_PV.format("um 8:45 Uhr"), 0), + pytest.param(SelectedPlan(remaining_time=601, duration=3600), 79, 0, "soc", + False, (6, "pv_charging", ChargeTemplate.SCHEDULED_CHARGING_USE_PV.format("um 8:50 Uhr"), 0), id="too early, use pv"), ]) def test_scheduled_charging_calc_current(plan_data: SelectedPlan, @@ -275,7 +275,7 @@ def test_scheduled_charging_calc_current(plan_data: SelectedPlan, plan_data.plan = plan # execution - ret = ct.scheduled_charging_calc_current(plan_data, soc, used_amount, 3, 6, + ret = ct.scheduled_charging_calc_current(plan_data, soc, used_amount, 3, 3, 6, 0, ChargingType.AC.value, EvTemplate(), BidiState.BIDI_CAPABLE) # evaluation @@ -288,7 +288,7 @@ def test_scheduled_charging_calc_current_no_plans(): # execution ret = ct.scheduled_charging_calc_current( - None, 63, 5, 3, 6, 0, ChargingType.AC.value, EvTemplate(), BidiState.BIDI_CAPABLE) + None, 63, 5, 3, 3, 6, 0, ChargingType.AC.value, EvTemplate(), BidiState.BIDI_CAPABLE) # evaluation assert ret == (0, "stop", ChargeTemplate.SCHEDULED_CHARGING_NO_PLANS_CONFIGURED, 3) @@ -382,8 +382,8 @@ def test_scheduled_charging_calc_current_electricity_tariff( # execution ret = ct.scheduled_charging_calc_current( - SelectedPlan(plan=plan, remaining_time=301, phases=3, duration=3600), - current_soc, 0, 3, 6, 0, ChargingType.AC.value, EvTemplate(), BidiState.BIDI_CAPABLE) + SelectedPlan(plan=plan, remaining_time=601, phases=3, duration=3600), + current_soc, 0, 3, 3, 6, 0, ChargingType.AC.value, EvTemplate(), BidiState.BIDI_CAPABLE) # evaluation assert ret == expected