From b41252a42c1f6a36a57f39e3ce13c525499ebe20 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 26 May 2026 14:14:17 +0200 Subject: [PATCH 1/2] scheduled cahrging: consider efficiency --- packages/control/ev/charge_template.py | 50 +++++++++++---------- packages/control/ev/charge_template_test.py | 8 ++-- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index 4f196b6fd2..bfab627404 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -419,39 +419,42 @@ def _calc_remaining_time(self, soc_request_interval_offset: int, bidi_state: BidiState) -> SelectedPlan: bidi = bidi_state == BidiState.BIDI_CAPABLE and plan.bidi_charging_enabled + battery_capacity = ev_template.data.battery_capacity + efficiency = ev_template.data.efficiency + + def calc_for_phases(phases_to_use: int) -> Tuple[float, float]: + return self._calculate_duration( + plan, + soc, + battery_capacity, + efficiency, + used_amount, + phases_to_use, + charging_type, + ev_template, + bidi) + if bidi: - duration, missing_amount = self._calculate_duration( - plan, soc, ev_template.data.battery_capacity, - used_amount, control_parameter_phases, charging_type, ev_template, bidi) + duration, missing_amount = calc_for_phases(control_parameter_phases) remaining_time = plan_end_time - duration phases = control_parameter_phases elif plan.phases_to_use == 0: if max_hw_phases == 1: - duration, missing_amount = self._calculate_duration( - plan, soc, ev_template.data.battery_capacity, - used_amount, 1, charging_type, ev_template, bidi) + duration, missing_amount = calc_for_phases(1) remaining_time = plan_end_time - duration phases = 1 elif phase_switch_supported is False: - duration, missing_amount = self._calculate_duration( - plan, soc, ev_template.data.battery_capacity, used_amount, control_parameter_phases, - charging_type, ev_template, bidi) + duration, missing_amount = calc_for_phases(control_parameter_phases) 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) + duration, missing_amount = calc_for_phases(max_hw_phases) 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, max_hw_phases, - charging_type, ev_template, bidi) + duration_3p, missing_amount = calc_for_phases(max_hw_phases) remaining_time_3p = plan_end_time - duration_3p - duration_1p, missing_amount = self._calculate_duration( - plan, soc, ev_template.data.battery_capacity, used_amount, 1, - charging_type, ev_template, bidi) + duration_1p, missing_amount = calc_for_phases(1) remaining_time_1p = plan_end_time - duration_1p # Kurz vor dem nächsten Abfragen des SoC, wenn noch der alte SoC da ist, kann es sein, dass die Zeit # für 1p nicht mehr reicht, weil die Regelung den neuen SoC noch nicht kennt. @@ -466,11 +469,9 @@ def _calc_remaining_time(self, phases = 1 log.debug(f"Dauer 1p: {duration_1p}, Dauer 3p: {duration_3p}") 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, min(plan.phases_to_use, max_hw_phases), charging_type, ev_template, bidi) - remaining_time = plan_end_time - duration phases = min(plan.phases_to_use, max_hw_phases) + duration, missing_amount = calc_for_phases(phases) + remaining_time = plan_end_time - duration log.debug(f"Verbleibende Zeit bis zum Ladestart [s]:{remaining_time}, Dauer [h]: {duration/3600}") return remaining_time, missing_amount, phases, duration @@ -479,6 +480,7 @@ def _calculate_duration(self, plan: ScheduledChargingPlan, soc: Optional[float], battery_capacity: float, + efficiency: float, used_amount: float, phases: int, charging_type: str, @@ -487,11 +489,11 @@ def _calculate_duration(self, if plan.limit.selected == "soc": if soc is not None: - missing_amount = ((plan.limit.soc_scheduled - soc) / 100) * battery_capacity + missing_amount = ((plan.limit.soc_scheduled - soc) / 100) * battery_capacity / (efficiency / 100) else: raise ValueError("Um Zielladen mit SoC-Ziel nutzen zu können, bitte ein SoC-Modul konfigurieren.") else: - missing_amount = plan.limit.amount - used_amount + missing_amount = (plan.limit.amount - used_amount) / (efficiency / 100) if bidi: duration = missing_amount/plan.bidi_power * 3600 else: diff --git a/packages/control/ev/charge_template_test.py b/packages/control/ev/charge_template_test.py index 68fe7c4d83..3514521d1d 100644 --- a/packages/control/ev/charge_template_test.py +++ b/packages/control/ev/charge_template_test.py @@ -169,9 +169,9 @@ def test_calc_remaining_time(phases_to_use, @pytest.mark.parametrize( "selected, phases, bidi_charging_enabled, expected_duration, expected_missing_amount", [ - pytest.param("soc", 1, False, 10062.111801242236, 9000, id="soc, one phase"), - pytest.param("amount", 2, False, 447.2049689440994, 800, id="amount, two phases"), - pytest.param("soc", 2, True, 3240.0, 9000, id="bidi"), + pytest.param("soc", 1, False, 11180.124223602485, 10000, id="soc, one phase"), + pytest.param("amount", 2, False, 496.8944099378882, 888.8888888888889, id="amount, two phases"), + pytest.param("soc", 2, True, 3600.0, 10000, id="bidi"), ]) def test_calculate_duration(selected: str, phases: int, @@ -184,7 +184,7 @@ def test_calculate_duration(selected: str, plan.limit.selected = selected # execution duration, missing_amount = ct._calculate_duration( - plan, 60, 45000, 200, phases, ChargingType.AC.value, EvTemplate(), bidi_charging_enabled) + plan, 60, 45000, 90, 200, phases, ChargingType.AC.value, EvTemplate(), bidi_charging_enabled) # evaluation assert duration == expected_duration From 5582dd47c2ba6be6abb6a935b7517fbb96cf2b35 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 26 May 2026 15:11:26 +0200 Subject: [PATCH 2/2] review --- packages/control/ev/charge_template.py | 4 ++-- packages/control/ev/charge_template_test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index bfab627404..a77aa4d64e 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -417,7 +417,7 @@ def _calc_remaining_time(self, charging_type: str, control_parameter_phases: int, soc_request_interval_offset: int, - bidi_state: BidiState) -> SelectedPlan: + bidi_state: BidiState) -> Tuple[float, float, int, float]: bidi = bidi_state == BidiState.BIDI_CAPABLE and plan.bidi_charging_enabled battery_capacity = ev_template.data.battery_capacity efficiency = ev_template.data.efficiency @@ -493,7 +493,7 @@ def _calculate_duration(self, else: raise ValueError("Um Zielladen mit SoC-Ziel nutzen zu können, bitte ein SoC-Modul konfigurieren.") else: - missing_amount = (plan.limit.amount - used_amount) / (efficiency / 100) + missing_amount = plan.limit.amount - used_amount if bidi: duration = missing_amount/plan.bidi_power * 3600 else: diff --git a/packages/control/ev/charge_template_test.py b/packages/control/ev/charge_template_test.py index 3514521d1d..228deb4e5a 100644 --- a/packages/control/ev/charge_template_test.py +++ b/packages/control/ev/charge_template_test.py @@ -170,7 +170,7 @@ def test_calc_remaining_time(phases_to_use, "selected, phases, bidi_charging_enabled, expected_duration, expected_missing_amount", [ pytest.param("soc", 1, False, 11180.124223602485, 10000, id="soc, one phase"), - pytest.param("amount", 2, False, 496.8944099378882, 888.8888888888889, id="amount, two phases"), + pytest.param("amount", 2, False, 447.2049689440994, 800, id="amount, two phases"), pytest.param("soc", 2, True, 3600.0, 10000, id="bidi"), ]) def test_calculate_duration(selected: str,