From f4b9c8e5dcfe44e34305e6c278bdef31c6d03cb8 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Thu, 17 Sep 2026 17:16:57 +0200 Subject: [PATCH] PV-Reset: NO_CHARGING_ALLOWED blockiert den robustness-Reset nicht mehr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach PR #3966 blockierte ein eingesteckter, aber unterhalb der Einschaltschwelle wartender Ladepunkt (Zustand NO_CHARGING_ALLOWED) weiterhin dauerhaft den Reset von reserved_surplus/released_surplus - das ist aber der mit Abstand häufigste, langlebigste Zustand eines PV-ladenden Fahrzeugs. Jede Reservierung wird jedoch bereits beim Übergang in diesen Zustand freigegeben (siehe zB Counter.reset_switch_on_off()), sodass er nie mit einer noch offenen Reservierung erreicht wird - er gehörte also nie in die Blockierliste. Betroffen war zB ein Fall, in dem eine verwaiste reserved_surplus-Reservierung aus einem unterbrochenen Vorgang nie mehr zurückgesetzt wurde, wodurch die Einschaltschwelle dauerhaft nicht mehr erreicht werden konnte (PV-Laden startet gar nicht mehr) - siehe Diskussion. Ersetzt die 5 Einzelvergleiche + Chargepoint/Consumer-Fallunterscheidung + plug_state-Sonderfall durch eine einzige Zustandsliste der tatsächlich reservierungshaltenden Zustände (PERFORMING_PHASE_SWITCH, PHASE_SWITCH_DELAY, SWITCH_OFF_DELAY, SWITCH_ON_DELAY), verifiziert gegen jede +=/-=-Stelle in counter.py/ev.py/chargepoint.py. references https://github.com/openWB/core/discussions/3908#discussioncomment-18483601 --- packages/control/load.py | 29 ++++++++++++----------------- packages/control/load_test.py | 26 ++++++++++++++------------ 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/control/load.py b/packages/control/load.py index 48dfa99fb3..ec24a305f6 100644 --- a/packages/control/load.py +++ b/packages/control/load.py @@ -9,31 +9,26 @@ log = logging.getLogger(__name__) +# Zustände, in denen reserved_surplus/released_surplus gerade eine laufende Reservierung halten (siehe die +# jeweiligen += beim Eintritt und -= beim Verlassen dieser Zustände in counter.py/ev.py/chargepoint.py). +# NO_CHARGING_ALLOWED gehört bewusst nicht dazu: jede Reservierung wird spätestens beim Übergang in diesen +# Zustand bereits wieder freigegeben (siehe zB Counter.reset_switch_on_off()), er wird hier also nie mit einer +# noch offenen Reservierung erreicht. +RESERVATION_HOLDING_STATES = (ChargepointState.PERFORMING_PHASE_SWITCH, + ChargepointState.PHASE_SWITCH_DELAY, + ChargepointState.SWITCH_OFF_DELAY, + ChargepointState.SWITCH_ON_DELAY) + def reset_pv_data_if_no_active_delays() -> None: - """ Wenn keine EV angesteckt sind oder keine Verzögerungen aktiv sind, werden die Algorithmus-Werte + """ Wenn keine Komponente gerade eine Reservierung offen hält, werden die Algorithmus-Werte zurückgesetzt. (dient der Robustheit) """ try: for load in list(data.data.cp_data.values()) + list(data.data.consumer_data.values()): try: - control_parameter = load.data.control_parameter - if isinstance(load, Chargepoint): - if (not load.data.get.plug_state or - (control_parameter.state != ChargepointState.PERFORMING_PHASE_SWITCH and - control_parameter.state != ChargepointState.PHASE_SWITCH_DELAY and - control_parameter.state != ChargepointState.SWITCH_OFF_DELAY and - control_parameter.state != ChargepointState.SWITCH_ON_DELAY and - control_parameter.state != ChargepointState.NO_CHARGING_ALLOWED)): - continue - break - else: - # Verbraucher haben keinen Plug-Status. - # Nur laufende Ein-/Ausschaltverzögerungen müssen den Reset verhindern. - if (control_parameter.state != ChargepointState.SWITCH_OFF_DELAY and - control_parameter.state != ChargepointState.SWITCH_ON_DELAY): - continue + if load.data.control_parameter.state in RESERVATION_HOLDING_STATES: break except Exception: log.exception(f"Fehler beim Bereinigen der Werte für Ladepunkt/Verbraucher {load.num}") diff --git a/packages/control/load_test.py b/packages/control/load_test.py index c9aa4d7e18..0c13dd8e4e 100644 --- a/packages/control/load_test.py +++ b/packages/control/load_test.py @@ -1,6 +1,8 @@ from typing import Dict from unittest.mock import Mock +import pytest + from control import data, load from control.chargepoint.chargepoint import Chargepoint from control.chargepoint.chargepoint_state import ChargepointState @@ -38,27 +40,27 @@ def test_do_not_reset_pv_data_when_consumer_switch_off_delay_active(): evu_counter.reset_pv_data.assert_not_called() -def test_do_not_reset_pv_data_with_plugged_chargepoint_no_charging_allowed(): +def test_reset_pv_data_with_chargepoint_no_charging_allowed(): + # eine wartende, aber nicht (mehr) reservierte Komponente darf den robustness-Reset nicht dauerhaft + # blockieren - jede Reservierung wird bereits beim Übergang in NO_CHARGING_ALLOWED freigegeben. cp = Mock(spec=Chargepoint, num="1") - cp.data = Mock( - control_parameter=Mock(state=ChargepointState.NO_CHARGING_ALLOWED), - get=Mock(plug_state=True), - ) + cp.data = Mock(control_parameter=Mock(state=ChargepointState.NO_CHARGING_ALLOWED)) evu_counter = _setup_control_data({"cp1": cp}, {}) load.reset_pv_data_if_no_active_delays() - evu_counter.reset_pv_data.assert_not_called() + evu_counter.reset_pv_data.assert_called_once() -def test_reset_pv_data_with_unplugged_chargepoint_no_charging_allowed(): +@pytest.mark.parametrize("state", [ChargepointState.PERFORMING_PHASE_SWITCH, + ChargepointState.PHASE_SWITCH_DELAY, + ChargepointState.SWITCH_OFF_DELAY, + ChargepointState.SWITCH_ON_DELAY]) +def test_do_not_reset_pv_data_with_chargepoint_holding_a_reservation(state): cp = Mock(spec=Chargepoint, num="1") - cp.data = Mock( - control_parameter=Mock(state=ChargepointState.NO_CHARGING_ALLOWED), - get=Mock(plug_state=False), - ) + cp.data = Mock(control_parameter=Mock(state=state)) evu_counter = _setup_control_data({"cp1": cp}, {}) load.reset_pv_data_if_no_active_delays() - evu_counter.reset_pv_data.assert_called_once() + evu_counter.reset_pv_data.assert_not_called()