From e36c143ed0eb0333abcc87089ca20646571ace3a Mon Sep 17 00:00:00 2001 From: seaspotter Date: Sat, 26 Sep 2026 15:44:49 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Control-Pilot-Unterbrechung:=20state=5Fprev?= =?UTF-8?q?=20statt=20state=20pr=C3=BCfen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initiate_control_pilot_interruption() schloss eine CP-Unterbrechung beim Ladestart aus, sobald control_parameter.state == WAIT_FOR_USING_PHASES war (#3328) - gedacht, um nach einer echten Phasenumschaltung keine doppelte CP-Umschaltung auszulösen, da die Umschaltung selbst bereits CP umschaltet. WAIT_FOR_USING_PHASES wird aber unconditional auch direkt nach Ablauf der Einschaltverzögerung erreicht (counter.py, switch_on_timer_expired(), unabhängig von einer Phasenumschaltung) - bei Ladepunkten ohne Phasenumschalt-Hardware (auto_phase_switch_hw=False) damit bei jedem PV-Ladestart, siehe Discussion #3908. Neues state_prev-Feld (analog zu current_prev/charge_state_prev/ plug_state_prev) erlaubt die Unterscheidung: nur wenn der vorherige Zyklus tatsächlich noch PERFORMING_PHASE_SWITCH war, hat die Umschaltung selbst gerade erst CP umgeschaltet. --- packages/control/chargepoint/chargepoint.py | 9 ++++- .../control/chargepoint/chargepoint_data.py | 6 +++ .../control/chargepoint/chargepoint_test.py | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index db95b7666c..7e0183ec11 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -248,6 +248,7 @@ def remember_previous_values(self): self.data.set.plug_state_prev = self.data.get.plug_state self.data.set.current_prev = self.data.set.current self.data.set.ev_prev = self.data.config.ev + self.data.set.state_prev = self.data.control_parameter.state def reset_log_data_chargemode_switch(self) -> None: reset_log = Log() @@ -284,9 +285,13 @@ def initiate_control_pilot_interruption(self): if self.data.config.control_pilot_interruption_hw: control_parameter = self.data.control_parameter retry_interval = charging_ev.ev_template.data.control_pilot_interruption_retry_interval - # Wird die Ladung gestartet? (nicht nach Phasenumschaltung, da diese bereits CP umschaltet) + # Wird die Ladung gestartet? (nicht direkt nach einer Phasenumschaltung, da diese + # bereits CP umschaltet - state_prev statt state, da WAIT_FOR_USING_PHASES auch + # unabhängig von einer Phasenumschaltung erreicht wird, zB direkt nach Ablauf der + # Einschaltverzögerung. Nur wenn der vorherige Zyklus tatsächlich noch + # PERFORMING_PHASE_SWITCH war, hat die Umschaltung selbst gerade erst CP umgeschaltet.) started_now = (self.data.set.current_prev == 0 and self.data.set.current != 0 and - self.data.control_parameter.state != ChargepointState.WAIT_FOR_USING_PHASES) + self.data.set.state_prev != ChargepointState.PERFORMING_PHASE_SWITCH) # Ladung angefordert, Auto lädt aber trotz gültigem Signal seit geraumer Zeit nicht. stuck = ( retry_interval > 0 and diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py index 429d20bdc3..65f687409e 100644 --- a/packages/control/chargepoint/chargepoint_data.py +++ b/packages/control/chargepoint/chargepoint_data.py @@ -2,6 +2,7 @@ from threading import Event from typing import Dict, List, Optional, Protocol from control.chargemode import Chargemode +from control.chargepoint.chargepoint_state import ChargepointState from control.chargepoint.chargepoint_template import CpTemplate from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory @@ -174,6 +175,11 @@ class Set: charging_ev_data: Ev = field(default_factory=ev_factory) ocpp_transaction_id: Optional[int] = field(default=None, metadata={"topic": "set/ocpp_transaction_id"}) charge_state_prev: bool = field(default=False, metadata={"topic": "set/charge_state_prev"}) + # control_parameter.state aus dem vorherigen Zyklus, um eine Control-Pilot-Unterbrechung beim + # Ladestart von einer, die direkt nach einer echten Phasenumschaltung erfolgt, zu unterscheiden + # (siehe initiate_control_pilot_interruption()). + state_prev: ChargepointState = field(default=ChargepointState.NO_CHARGING_ALLOWED, + metadata={"topic": "set/state_prev"}) @dataclass diff --git a/packages/control/chargepoint/chargepoint_test.py b/packages/control/chargepoint/chargepoint_test.py index 57a746649b..d531f46997 100644 --- a/packages/control/chargepoint/chargepoint_test.py +++ b/packages/control/chargepoint/chargepoint_test.py @@ -66,6 +66,43 @@ def test_check_min_max_current(required_current, phases, expected_required_curre assert ret == expected_required_current +@pytest.mark.parametrize("state_prev, expected_message_present", + [ + pytest.param(ChargepointState.SWITCH_ON_DELAY, True, + id="Ladestart nach Einschaltverzoegerung: CP-Unterbrechung noetig"), + pytest.param(ChargepointState.NO_CHARGING_ALLOWED, True, + id="regulaerer Ladestart: CP-Unterbrechung noetig"), + pytest.param(ChargepointState.PERFORMING_PHASE_SWITCH, False, + id="direkt nach Phasenumschaltung: keine zusaetzliche CP-Unterbrechung"), + ]) +def test_initiate_control_pilot_interruption_uses_state_prev_not_state(state_prev, expected_message_present): + """ WAIT_FOR_USING_PHASES wird nicht nur direkt nach einer echten Phasenumschaltung erreicht, sondern + zB auch direkt nach Ablauf der Einschaltverzoegerung (siehe Discussion #3908, lackylacky) - dort war + dann also nach jedem PV-Ladestart mit reiner Ein-Phasen-Ladung (kein hw_supports_phase_switch()) + dauerhaft keine Control-Pilot-Unterbrechung mehr moeglich. state_prev unterscheidet die beiden + Faelle korrekt, state (aktuell WAIT_FOR_USING_PHASES in beiden Faellen) nicht. """ + # setup + cp = Chargepoint(0, None) + cp.chargepoint_module = Mock(interrupt_cp=Mock(), config=Mock(id=0)) + cp.data.set.charging_ev_data = Ev(0) + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption = True + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_duration = 4 + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_retry_interval = 0 + cp.data.config.control_pilot_interruption_hw = True + cp.data.control_parameter.state = ChargepointState.WAIT_FOR_USING_PHASES + cp.data.set.state_prev = state_prev + cp.data.set.current_prev = 0 + cp.data.set.current = 6 + + # evaluation + cp.initiate_control_pilot_interruption() + + # assertion + message_present = (cp.data.get.state_str is not None and + "Control-Pilot-Unterbrechung" in cp.data.get.state_str) + assert message_present == expected_message_present + + @dataclass class Params: name: str From 6315053ae40ac494d58227f363f55b93317592e1 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 16 Oct 2026 15:28:52 +0200 Subject: [PATCH 2/2] =?UTF-8?q?CP-Unterbrechung:=20pending=20Phasenumschal?= =?UTF-8?q?tung=20ausschlie=C3=9Fen,=20state=5Fprev=20in=20setdata.py=20be?= =?UTF-8?q?handeln?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/control/chargepoint/chargepoint.py | 11 +++--- .../control/chargepoint/chargepoint_test.py | 36 +++++++++++++++++-- packages/helpermodules/setdata.py | 2 ++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index b78823f61b..2f18782d94 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -285,13 +285,12 @@ def initiate_control_pilot_interruption(self): if self.data.config.control_pilot_interruption_hw: control_parameter = self.data.control_parameter retry_interval = charging_ev.ev_template.data.control_pilot_interruption_retry_interval - # Wird die Ladung gestartet? (nicht direkt nach einer Phasenumschaltung, da diese - # bereits CP umschaltet - state_prev statt state, da WAIT_FOR_USING_PHASES auch - # unabhängig von einer Phasenumschaltung erreicht wird, zB direkt nach Ablauf der - # Einschaltverzögerung. Nur wenn der vorherige Zyklus tatsächlich noch - # PERFORMING_PHASE_SWITCH war, hat die Umschaltung selbst gerade erst CP umgeschaltet.) + # Wird die Ladung gestartet? Nicht, wenn eine Phasenumschaltung gerade lief oder jetzt + # ansteht - die schaltet CP bereits selbst um (state_prev statt state, da + # WAIT_FOR_USING_PHASES auch unabhängig von einer Umschaltung erreicht wird). started_now = (self.data.set.current_prev == 0 and self.data.set.current != 0 and - self.data.set.state_prev != ChargepointState.PERFORMING_PHASE_SWITCH) + self.data.set.state_prev != ChargepointState.PERFORMING_PHASE_SWITCH and + self._is_phase_switch_required() is False) # Ladung angefordert, Auto lädt aber trotz gültigem Signal seit geraumer Zeit nicht. stuck = ( retry_interval > 0 and diff --git a/packages/control/chargepoint/chargepoint_test.py b/packages/control/chargepoint/chargepoint_test.py index afd1d89bce..e93e04dc5f 100644 --- a/packages/control/chargepoint/chargepoint_test.py +++ b/packages/control/chargepoint/chargepoint_test.py @@ -91,9 +91,12 @@ def test_initiate_control_pilot_interruption_uses_state_prev_not_state(state_pre cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_duration = 4 cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_retry_interval = 0 cp.data.config.control_pilot_interruption_hw = True + # state_prev ueber remember_previous_values() setzen statt direkt zuzuweisen + cp.data.set.current = 0 + cp.data.control_parameter.state = state_prev + cp.remember_previous_values() + # aktueller Zyklus: Ladung startet cp.data.control_parameter.state = ChargepointState.WAIT_FOR_USING_PHASES - cp.data.set.state_prev = state_prev - cp.data.set.current_prev = 0 cp.data.set.current = 6 # evaluation @@ -105,6 +108,35 @@ def test_initiate_control_pilot_interruption_uses_state_prev_not_state(state_pre assert message_present == expected_message_present +def test_initiate_control_pilot_interruption_skipped_when_phase_switch_pending(): + """ Kontaktoren stehen noch auf falscher Phasenzahl -> initiate_phase_switch() schaltet im selben + Zyklus bereits CP um, keine zusaetzliche Unterbrechung noetig. """ + # setup + cp = Chargepoint(0, None) + cp.chargepoint_module = Mock(interrupt_cp=Mock(), config=Mock(id=0)) + cp.data.set.charging_ev_data = Ev(0) + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption = True + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_duration = 4 + cp.data.set.charging_ev_data.ev_template.data.control_pilot_interruption_retry_interval = 0 + cp.data.config.control_pilot_interruption_hw = True + cp.data.set.current = 0 + cp.data.control_parameter.state = ChargepointState.SWITCH_ON_DELAY + cp.remember_previous_values() + cp.data.control_parameter.state = ChargepointState.WAIT_FOR_USING_PHASES + cp.data.set.current = 6 + # Kontaktoren (1p) weichen von der benoetigten Phasenzahl (3p) ab + cp.data.set.phases_to_use = 3 + cp.data.control_parameter.phases = 1 + + # evaluation + cp.initiate_control_pilot_interruption() + + # assertion + message_present = (cp.data.get.state_str is not None and + "Control-Pilot-Unterbrechung" in cp.data.get.state_str) + assert message_present is False + + @pytest.mark.parametrize("required_current, max_discharge_power, dc_max_current, expected_required_current", [ pytest.param(-50, -30000, 25, -25, diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 665ff72fb9..877b4e8fbf 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -497,6 +497,8 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): "/set/charge_state_prev" in msg.topic or "/set/plug_state_prev" in msg.topic): self._validate_value(msg, bool) + elif "/set/state_prev" in msg.topic: + self._validate_value(msg, int, [(0, 7)]) elif "/set/autolock_state" in msg.topic: self._validate_value(msg, int, [(0, 4)]) elif "/set/rfid" in msg.topic: