From 18a0fa215e001e774a787c8f2aa89af5af597ec3 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Thu, 8 Oct 2026 11:36:51 +0200 Subject: [PATCH 1/4] =?UTF-8?q?Verbraucher:=20voltages/currents=20bei=20?= =?UTF-8?q?=C3=84nderung=20von=20connected=5Fphases/phase=5F1=20neu=20bere?= =?UTF-8?q?chnen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wurden bisher nur einmalig (bei None) berechnet und immer ab Index 0 befüllt. Änderte sich connected_phases oder phase_1 nachträglich, blieb das alte Array mit falscher Länge bzw. falschem Index stehen - das zeigte in der Modulübersicht Werte für nicht mehr angeschlossene Phasen an und passte nicht mehr zum phasenkorrekten required_currents aus set_control_parameter(). --- packages/control/consumer/consumer.py | 26 ++++++++---- packages/control/consumer/consumer_test.py | 46 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index 0134bed4c3..f485666d1c 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -48,14 +48,24 @@ def update(self): if self.data.usage.type in NOT_CONTROLLED: return else: - if self.data.get.voltages is None: - self.data.get.voltages = [230 for i in range(0, self.data.config.connected_phases)] - if self.data.get.currents is None: - self.data.get.currents = [0]*3 - for i in range(0, self.data.config.connected_phases): - self.data.get.currents[i] = (self.data.get.power / - self.data.config.connected_phases / - self.data.get.voltages[i]) + # Index wie required_currents in set_control_parameter() am EVU-Phasen-Index ausrichten, nicht an 0. + try: + connected_evu_phases = { + convert_single_evu_phase_to_cp_phase(self.data.config.phase_1, i) + for i in range(0, self.data.config.connected_phases)} + except KeyError: + connected_evu_phases = set(range(0, self.data.config.connected_phases)) + # neu berechnen, wenn sich connected_phases oder phase_1 geändert haben, nicht nur bei None + phase_config_changed = (self.data.get.voltages is None or + len(self.data.get.voltages) != 3 or + {i for i, v in enumerate(self.data.get.voltages) if v} != connected_evu_phases) + if phase_config_changed: + self.data.get.voltages = [230 if i in connected_evu_phases else 0 for i in range(3)] + if self.data.get.currents is None or phase_config_changed: + self.data.get.currents = [ + (self.data.get.power / self.data.config.connected_phases / self.data.get.voltages[i]) + if i in connected_evu_phases else 0 + for i in range(3)] self.data.get.phases_in_use = self.data.config.connected_phases self.data.set.phases_to_use = self.data.config.connected_phases self.data.get.charge_state = max(self.data.get.currents) > self.STANDBY_THRESHOLD diff --git a/packages/control/consumer/consumer_test.py b/packages/control/consumer/consumer_test.py index a871b55f38..1c2230829e 100644 --- a/packages/control/consumer/consumer_test.py +++ b/packages/control/consumer/consumer_test.py @@ -590,3 +590,49 @@ def test_reset_chargemode_at_time( assert check_end_time_mock.call_count == expected_check_end_time_calls if expected_check_end_time_calls: check_end_time_mock.assert_called_once_with(consumer.data.usage.reset_chargemode) + + +@pytest.mark.parametrize( + "connected_phases, phase_1, previous_voltages, previous_currents, expected_voltages, expected_currents", + [ + pytest.param(1, 1, [230, 230, 230], [0, 0, 0], [230, 0, 0], [0.0, 0, 0], + id="Phasenzahl 3->1: alte Werte für nicht mehr vorhandene Phasen verschwinden"), + pytest.param(3, 1, [230, 0, 0], [0, 0, 0], [230, 230, 230], [0.0, 0.0, 0.0], + id="Phasenzahl 1->3: Werte für neue Phasen werden ergänzt"), + pytest.param(1, 1, [230, 0, 0], [0, 0, 0], [230, 0, 0], [0.0, 0, 0], + id="Phasenzahl unverändert: keine Neuberechnung nötig, bleibt stabil"), + pytest.param(1, 2, [230, 0, 0], [0, 0, 0], [0, 230, 0], [0, 0.0, 0], + id="einphasig an L2 angeschlossen: Wert landet auf EVU-Index 1, nicht 0"), + pytest.param(1, 2, [230, 0, 0], [0, 0, 0], [0, 230, 0], [0, 0.0, 0], + id="phase_1 nachträglich 1->2 geändert, connected_phases bleibt 1: Index wandert mit"), + ], +) +def test_update_recalculates_voltages_and_currents_when_phase_config_changed( + consumer: Consumer, + connected_phases: int, + phase_1: int, + previous_voltages: List[float], + previous_currents: List[float], + expected_voltages: List[float], + expected_currents: List[float], + monkeypatch): + """ Neuberechnung bei Änderung von connected_phases oder phase_1, Werte am richtigen EVU-Index statt bei 0. """ + # setup + consumer.data.config.connected_phases = connected_phases + consumer.data.config.phase_1 = phase_1 + consumer.data.get.voltages = previous_voltages + consumer.data.get.currents = previous_currents + consumer.data.get.power = 0 + monkeypatch.setattr(consumer, "reset_chargemode_at_time", Mock()) + monkeypatch.setattr(consumer, "is_switch_interval_elapsed", Mock()) + monkeypatch.setattr(consumer, "get_parameter", Mock(return_value=(0, None, None, None))) + monkeypatch.setattr(consumer, "set_mode_changed", Mock()) + monkeypatch.setattr(consumer, "set_control_parameter", Mock()) + data.data.counter_all_data = Mock() + + # execution + consumer.update() + + # evaluation + assert consumer.data.get.voltages == expected_voltages + assert consumer.data.get.currents == expected_currents From 59d464441851bbe57528464ee21c5b431c43d295 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 9 Oct 2026 14:28:48 +0200 Subject: [PATCH 2/4] =?UTF-8?q?Review:=20Phasenverdrehung=20geh=C3=B6rt=20?= =?UTF-8?q?ins=20Lastmanagement,=20nicht=20in=20consumer.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/control/consumer/consumer.py | 27 +++++++-------------- packages/control/consumer/consumer_data.py | 4 ++-- packages/control/consumer/consumer_test.py | 28 ++++++++++------------ 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index f485666d1c..dfb4035276 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -48,24 +48,15 @@ def update(self): if self.data.usage.type in NOT_CONTROLLED: return else: - # Index wie required_currents in set_control_parameter() am EVU-Phasen-Index ausrichten, nicht an 0. - try: - connected_evu_phases = { - convert_single_evu_phase_to_cp_phase(self.data.config.phase_1, i) - for i in range(0, self.data.config.connected_phases)} - except KeyError: - connected_evu_phases = set(range(0, self.data.config.connected_phases)) - # neu berechnen, wenn sich connected_phases oder phase_1 geändert haben, nicht nur bei None - phase_config_changed = (self.data.get.voltages is None or - len(self.data.get.voltages) != 3 or - {i for i, v in enumerate(self.data.get.voltages) if v} != connected_evu_phases) - if phase_config_changed: - self.data.get.voltages = [230 if i in connected_evu_phases else 0 for i in range(3)] - if self.data.get.currents is None or phase_config_changed: - self.data.get.currents = [ - (self.data.get.power / self.data.config.connected_phases / self.data.get.voltages[i]) - if i in connected_evu_phases else 0 - for i in range(3)] + # Gegen den Default statt auf None prüfen, sonst bleibt ein Array mit der alten Phasenzahl stehen, + # wenn connected_phases nachträglich geändert wird (zB 3 auf 1 Phase). + if self.data.get.voltages == [230.0]*3: + self.data.get.voltages = [230 for i in range(0, self.data.config.connected_phases)] + if self.data.get.currents == [0.0]*3: + for i in range(0, self.data.config.connected_phases): + self.data.get.currents[i] = (self.data.get.power / + self.data.config.connected_phases / + self.data.get.voltages[i]) self.data.get.phases_in_use = self.data.config.connected_phases self.data.set.phases_to_use = self.data.config.connected_phases self.data.get.charge_state = max(self.data.get.currents) > self.STANDBY_THRESHOLD diff --git a/packages/control/consumer/consumer_data.py b/packages/control/consumer/consumer_data.py index 99aaefa9f4..482a1a7ce7 100644 --- a/packages/control/consumer/consumer_data.py +++ b/packages/control/consumer/consumer_data.py @@ -4,7 +4,7 @@ from control.chargemode import Chargemode from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory from control.consumer.usage import ConsumerUsage -from dataclass_utils.factories import empty_list_factory, voltages_list_factory +from dataclass_utils.factories import currents_list_factory, empty_list_factory, voltages_list_factory from helpermodules.abstract_plans import ScheduledPlanConsumer, TimeChargingPlanConsumer, TimepointPlan from helpermodules.constants import NO_ERROR from modules.common.consumer_setup import ConsumerSetup @@ -63,7 +63,7 @@ class ConsumerConfig: @dataclass class Get: charge_state: bool = False - currents: Optional[List[Optional[float]]] = None + currents: List[float] = field(default_factory=currents_list_factory) daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) error_timestamp: int = 0 exported: float = 0 diff --git a/packages/control/consumer/consumer_test.py b/packages/control/consumer/consumer_test.py index 1c2230829e..376fc1f7de 100644 --- a/packages/control/consumer/consumer_test.py +++ b/packages/control/consumer/consumer_test.py @@ -593,36 +593,32 @@ def test_reset_chargemode_at_time( @pytest.mark.parametrize( - "connected_phases, phase_1, previous_voltages, previous_currents, expected_voltages, expected_currents", + "connected_phases, previous_voltages, previous_currents, power, expected_voltages, expected_currents", [ - pytest.param(1, 1, [230, 230, 230], [0, 0, 0], [230, 0, 0], [0.0, 0, 0], - id="Phasenzahl 3->1: alte Werte für nicht mehr vorhandene Phasen verschwinden"), - pytest.param(3, 1, [230, 0, 0], [0, 0, 0], [230, 230, 230], [0.0, 0.0, 0.0], - id="Phasenzahl 1->3: Werte für neue Phasen werden ergänzt"), - pytest.param(1, 1, [230, 0, 0], [0, 0, 0], [230, 0, 0], [0.0, 0, 0], - id="Phasenzahl unverändert: keine Neuberechnung nötig, bleibt stabil"), - pytest.param(1, 2, [230, 0, 0], [0, 0, 0], [0, 230, 0], [0, 0.0, 0], - id="einphasig an L2 angeschlossen: Wert landet auf EVU-Index 1, nicht 0"), - pytest.param(1, 2, [230, 0, 0], [0, 0, 0], [0, 230, 0], [0, 0.0, 0], - id="phase_1 nachträglich 1->2 geändert, connected_phases bleibt 1: Index wandert mit"), + pytest.param(1, [230.0]*3, [0.0]*3, 0, [230], [0.0, 0.0, 0.0], + id="noch beim Default (3 Phasen): Spannung wird auf 1 Phase neu berechnet"), + pytest.param(1, [230.0]*3, [0.0]*3, 230, [230], [1.0, 0.0, 0.0], + id="Neuberechnung respektiert die tatsächliche Leistung"), + pytest.param(1, [230], [0.0], 0, [230], [0.0], + id="bereits korrekt (nicht beim Default): keine Neuberechnung nötig, bleibt stabil"), ], ) -def test_update_recalculates_voltages_and_currents_when_phase_config_changed( +def test_update_recalculates_voltages_and_currents_when_still_at_default( consumer: Consumer, connected_phases: int, - phase_1: int, previous_voltages: List[float], previous_currents: List[float], + power: float, expected_voltages: List[float], expected_currents: List[float], monkeypatch): - """ Neuberechnung bei Änderung von connected_phases oder phase_1, Werte am richtigen EVU-Index statt bei 0. """ + """ Neuberechnung muss gegen den Default-Wert prüfen, nicht gegen None - sonst bleibt ein Array mit der alten + Phasenzahl stehen, wenn connected_phases nachträglich geändert wird (zB 3 auf 1 Phase). """ # setup consumer.data.config.connected_phases = connected_phases - consumer.data.config.phase_1 = phase_1 consumer.data.get.voltages = previous_voltages consumer.data.get.currents = previous_currents - consumer.data.get.power = 0 + consumer.data.get.power = power monkeypatch.setattr(consumer, "reset_chargemode_at_time", Mock()) monkeypatch.setattr(consumer, "is_switch_interval_elapsed", Mock()) monkeypatch.setattr(consumer, "get_parameter", Mock(return_value=(0, None, None, None))) From cee68adab81ee2a75d5cf85f8b8f70af3eab104d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Fri, 9 Oct 2026 14:37:45 +0200 Subject: [PATCH 3/4] Update packages/control/consumer/consumer.py Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> --- packages/control/consumer/consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index dfb4035276..9e471cf802 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -51,7 +51,7 @@ def update(self): # Gegen den Default statt auf None prüfen, sonst bleibt ein Array mit der alten Phasenzahl stehen, # wenn connected_phases nachträglich geändert wird (zB 3 auf 1 Phase). if self.data.get.voltages == [230.0]*3: - self.data.get.voltages = [230 for i in range(0, self.data.config.connected_phases)] + self.data.get.voltages = [230 if i < self.data.config.connected_phases else 0 for i in range(0, self.data.config.connected_phases)] if self.data.get.currents == [0.0]*3: for i in range(0, self.data.config.connected_phases): self.data.get.currents[i] = (self.data.get.power / From cbb03409aabeee3acf9126234eaf531dccefe4ab Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 9 Oct 2026 14:40:53 +0200 Subject: [PATCH 4/4] =?UTF-8?q?Review:=20voltages=20immer=20auf=203=20Eint?= =?UTF-8?q?r=C3=A4ge=20auff=C3=BCllen=20statt=20auf=20connected=5Fphases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/control/consumer/consumer.py | 3 ++- packages/control/consumer/consumer_test.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index dfb4035276..fa5f5e0343 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -51,7 +51,8 @@ def update(self): # Gegen den Default statt auf None prüfen, sonst bleibt ein Array mit der alten Phasenzahl stehen, # wenn connected_phases nachträglich geändert wird (zB 3 auf 1 Phase). if self.data.get.voltages == [230.0]*3: - self.data.get.voltages = [230 for i in range(0, self.data.config.connected_phases)] + self.data.get.voltages = [230 if i < self.data.config.connected_phases else 0 + for i in range(0, 3)] if self.data.get.currents == [0.0]*3: for i in range(0, self.data.config.connected_phases): self.data.get.currents[i] = (self.data.get.power / diff --git a/packages/control/consumer/consumer_test.py b/packages/control/consumer/consumer_test.py index 376fc1f7de..e0f8b47c1b 100644 --- a/packages/control/consumer/consumer_test.py +++ b/packages/control/consumer/consumer_test.py @@ -595,11 +595,11 @@ def test_reset_chargemode_at_time( @pytest.mark.parametrize( "connected_phases, previous_voltages, previous_currents, power, expected_voltages, expected_currents", [ - pytest.param(1, [230.0]*3, [0.0]*3, 0, [230], [0.0, 0.0, 0.0], + pytest.param(1, [230.0]*3, [0.0]*3, 0, [230, 0, 0], [0.0, 0.0, 0.0], id="noch beim Default (3 Phasen): Spannung wird auf 1 Phase neu berechnet"), - pytest.param(1, [230.0]*3, [0.0]*3, 230, [230], [1.0, 0.0, 0.0], + pytest.param(1, [230.0]*3, [0.0]*3, 230, [230, 0, 0], [1.0, 0.0, 0.0], id="Neuberechnung respektiert die tatsächliche Leistung"), - pytest.param(1, [230], [0.0], 0, [230], [0.0], + pytest.param(1, [230, 0, 0], [0.0, 1.0, 0.0], 0, [230, 0, 0], [0.0, 1.0, 0.0], id="bereits korrekt (nicht beim Default): keine Neuberechnung nötig, bleibt stabil"), ], )