From 079338dce0877b7a7d16ac1382f22efb70a33fe4 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 22 Oct 2026 11:42:46 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20=C3=9Cberschuss-Verrechnung=20=C3=BCber?= =?UTF-8?q?=20mehrere=20Z=C3=A4hlerebenen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../control/algorithm/additional_current.py | 2 +- packages/control/algorithm/common.py | 14 ++-- .../multi_level_loadmanagement_test.py | 69 +++++++++++++++++++ .../control/algorithm/surplus_controlled.py | 2 +- 4 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 packages/control/algorithm/integration_test/multi_level_loadmanagement_test.py diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index ae3723fd3a..ce9857b20d 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -40,7 +40,7 @@ def set_additional_current(self, grouped_loads: List[Load]) -> None: load.data.set.current, available_for_cp, load.data.set.target_current) self._set_loadmangement_message(current, limit, load) common.set_current_counterdiff( - load.data.control_parameter.min_current, + load.data.set.target_current, current, load) preferenced_loads.pop(0) diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 02eb1aa0a7..df950aecc9 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -151,8 +151,10 @@ def available_current_for_load(load: Load, def update_raw_data(preferenced_loads: List[Load], diff_to_zero: bool = False, surplus: bool = False) -> None: - """alle CP, die schon einen Soll-Strom haben, wieder herausrechnen, da dieser neu gesetzt wird - und die neue Differenz bei den Zählern eingetragen wird.""" + """Zuteilungen dieser Stufe bis auf target_current herausrechnen, bevor sie neu gesetzt werden. + + In der Mindeststromstufe (diff_to_zero) stattdessen den Mindeststrom herausrechnen. + """ for load in preferenced_loads: required_currents = load.data.control_parameter.required_currents max_target_set_current = max(load.data.set.target_current, load.data.set.current or 0) @@ -161,12 +163,8 @@ def update_raw_data(preferenced_loads: List[Load], if diff_to_zero is False: if load.data.control_parameter.min_current < max_target_set_current: - if surplus: - diffs = [load.data.set.target_current - - max_target_set_current if required_currents[i] != 0 else 0 for i in range(3)] - else: - diffs = [load.data.control_parameter.min_current - - max_target_set_current if required_currents[i] != 0 else 0 for i in range(3)] + diffs = [load.data.set.target_current - + max_target_set_current if required_currents[i] != 0 else 0 for i in range(3)] else: continue else: diff --git a/packages/control/algorithm/integration_test/multi_level_loadmanagement_test.py b/packages/control/algorithm/integration_test/multi_level_loadmanagement_test.py new file mode 100644 index 0000000000..2ba6cdc775 --- /dev/null +++ b/packages/control/algorithm/integration_test/multi_level_loadmanagement_test.py @@ -0,0 +1,69 @@ +import pytest + +from control import data, loadmanagement +from control.algorithm.additional_current import AdditionalCurrent +from control.algorithm.surplus_controlled import SurplusControlled +from control.chargemode import Chargemode +from control.chargepoint.chargepoint_state import ChargepointState + + +@pytest.mark.parametrize("phases", [1, 2, 3]) +@pytest.mark.parametrize("consider_less_charging", [False, True]) +def test_surplus_preserves_previous_allocation_across_counter_levels(phases, consider_less_charging, monkeypatch): + """Previously allocated current must not consume surplus again at each counter.""" + monkeypatch.setattr(loadmanagement, "get_component_name_by_id", lambda num: f"counter{num}") + data.data.counter_all_data.data.config.consider_less_charging = consider_less_charging + for counter in data.data.counter_data.values(): + counter.data.set.raw_currents_left = [100]*3 + counter.data.set.surplus_power_left = 4000 + + allocated = data.data.cp_data["cp4"] + pv = data.data.cp_data["cp5"] + for cp, target, required, used_phases in [(allocated, 16, 16, 3), (pv, 6, 32, phases)]: + cp.data.control_parameter.chargemode = Chargemode.PV_CHARGING + cp.data.control_parameter.submode = Chargemode.PV_CHARGING + cp.data.control_parameter.state = ChargepointState.CHARGING_ALLOWED + cp.data.control_parameter.min_current = 6 + cp.data.control_parameter.required_current = required + cp.data.control_parameter.required_currents = [required]*used_phases + [0]*(3-used_phases) + cp.data.control_parameter.timestamp_charge_start = 0 + cp.data.get.charge_state = True + cp.data.get.currents = [target]*used_phases + [0]*(3-used_phases) + cp.data.get.voltages = [230]*3 + cp.data.get.evse_current = None + cp.data.set.target_current = target + cp.data.set.current_prev = target + cp.data.set.charging_ev_data.ev_template.data.nominal_difference = 0 + cp.chargemode_changed = False + + SurplusControlled().set_surplus_current([allocated, pv]) + + assert allocated.data.set.current == 16 + assert pv.data.set.current == 11 + expected_surplus = 4000 - 5*phases*230 if consider_less_charging else 4000 + for counter in data.data.counter_data.values(): + assert counter.data.set.surplus_power_left == pytest.approx(expected_surplus) + + +@pytest.mark.parametrize("phases", [1, 2, 3]) +def test_additional_current_preserves_target_across_counter_levels(phases): + """The previous stage's allocation may differ from the configured minimum.""" + cp = data.data.cp_data["cp4"] + cp.data.control_parameter.chargemode = Chargemode.INSTANT_CHARGING + cp.data.control_parameter.submode = Chargemode.INSTANT_CHARGING + cp.data.control_parameter.min_current = 6 + cp.data.control_parameter.required_current = 16 + cp.data.control_parameter.required_currents = [16]*phases + [0]*(3-phases) + cp.data.get.charge_state = True + cp.data.get.voltages = [230]*3 + cp.data.set.target_current = 10 + for counter in data.data.counter_data.values(): + counter.data.set.raw_currents_left = [100]*3 + counter.data.set.raw_power_left = 40000 + + AdditionalCurrent().set_additional_current([cp]) + + assert cp.data.set.current == 16 + for counter in data.data.counter_data.values(): + assert counter.data.set.raw_power_left == pytest.approx(40000 - 6*phases*230) + assert counter.data.set.raw_currents_left == [94]*phases + [100]*(3-phases) diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index ec07d7dd8d..182c4e36cd 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -84,7 +84,7 @@ def _set(self, self._set_loadmangement_message(current, limit, load) limited_current = limit_adjust_current(load, current) common.set_current_counterdiff( - load.data.control_parameter.min_current, + load.data.set.target_current, limited_current, load, surplus=True)