diff --git a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py index 9a7d0767d2..5ae7fad614 100644 --- a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py @@ -63,10 +63,18 @@ def set_current(self, current: float) -> None: def get_values(self) -> None: with SingleComponentUpdateContext(self.fault_state): - chargepoint_state = self.request_values() - if chargepoint_state is not None: - # bei Fehler, aber Fehlezähler noch nicht abgelaufen, keine Werte mehr publishen. - self.store.set(chargepoint_state) + try: + chargepoint_state = self.request_values() + if chargepoint_state is not None: + # bei Fehler, aber Fehlezähler noch nicht abgelaufen, keine Werte mehr publishen. + self.store.set(chargepoint_state) + except Exception as e: + if self.client_error_context.error_counter_exceeded(): + chargepoint_state = ChargepointState(plug_state=False, charge_state=False, imported=None, + # bei im-/exported None werden keine Werte gepublished + exported=None, phases_in_use=0, power=0, currents=[0]*3) + self.store.set(chargepoint_state) + raise e def request_values(self) -> ChargepointState: with self.client_error_context: @@ -113,15 +121,6 @@ def request_values(self) -> ChargepointState: self.validate_values(chargepoint_state) self.client_error_context.reset_error_counter() return chargepoint_state - if self.client_error_context.error_counter_exceeded(): - chargepoint_state = ChargepointState() - chargepoint_state.plug_state = False - chargepoint_state.charge_state = False - chargepoint_state.imported = None # bei None werden keine Werte gepublished - chargepoint_state.exported = None - return chargepoint_state - else: - return None def validate_values(self, chargepoint_state: ChargepointState) -> None: if chargepoint_state.charge_state is False and max(chargepoint_state.currents) > 1: diff --git a/packages/modules/internal_chargepoint_handler/pro_plus.py b/packages/modules/internal_chargepoint_handler/pro_plus.py index cb8de42f05..c8af1a97c5 100644 --- a/packages/modules/internal_chargepoint_handler/pro_plus.py +++ b/packages/modules/internal_chargepoint_handler/pro_plus.py @@ -8,6 +8,9 @@ class ProPlus(ChargepointModule): + NO_DATA_SINCE_BOOT = "Es konnten seit dem Start keine Daten abgefangen werden." + NO_CONNECTION_TO_INTERNAL_CP = "Interner Ladepunkt ist nicht erreichbar." + def __init__(self, local_charge_point_num: int, internal_cp: InternalChargepoint, hierarchy_id: int) -> None: @@ -17,7 +20,7 @@ def __init__(self, local_charge_point_num: int, self.old_chargepoint_state = None super().__init__(OpenWBPro(configuration=OpenWBProConfiguration(ip_address="192.168.192.50"))) - super().set_internal_context_handlers(hierarchy_id, internal_cp) + self.set_internal_context_handlers(hierarchy_id, internal_cp) def get_values(self, phase_switch_cp_active: bool, last_tag: str) -> ChargepointState: def store_state(chargepoint_state: ChargepointState) -> None: @@ -25,22 +28,33 @@ def store_state(chargepoint_state: ChargepointState) -> None: self.store.update() self.store_internal.set(chargepoint_state) self.store_internal.update() + self.old_chargepoint_state = chargepoint_state try: - chargepoint_state = super().request_values() + chargepoint_state = self.request_values() if chargepoint_state is not None and last_tag is not None and last_tag != "": chargepoint_state.rfid = last_tag - except (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError): - raise Exception("Interner Ladepunkt ist nicht erreichbar.") - - if chargepoint_state is None: - if self.old_chargepoint_state is None: - raise Exception("Keine erfolgreiche Auslesung der Daten seit dem Start möglich.") - # bei Fehler, aber Fehlerzähler noch nicht abgelaufen - chargepoint_state = self.old_chargepoint_state - store_state(chargepoint_state) - self.old_chargepoint_state = chargepoint_state - return chargepoint_state + if chargepoint_state is not None: + store_state(chargepoint_state) + return chargepoint_state + else: + store_state(self.old_chargepoint_state) + return self.old_chargepoint_state + except Exception as e: + if self.client_error_context.error_counter_exceeded(): + chargepoint_state = ChargepointState(plug_state=False, charge_state=False, imported=None, + # bei im-/exported None werden keine Werte gepublished + exported=None, phases_in_use=0, power=0, currents=[0]*3) + store_state(chargepoint_state) + if isinstance(e, (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError)): + raise Exception(self.NO_CONNECTION_TO_INTERNAL_CP) + else: + raise e + elif self.old_chargepoint_state is not None: + store_state(self.old_chargepoint_state) + return self.old_chargepoint_state + else: + raise Exception(self.NO_DATA_SINCE_BOOT) def perform_phase_switch(self, phases_to_use: int, duration: int) -> None: super().switch_phases(phases_to_use, duration) diff --git a/packages/modules/internal_chargepoint_handler/pro_plus_test.py b/packages/modules/internal_chargepoint_handler/pro_plus_test.py new file mode 100644 index 0000000000..c84415df6f --- /dev/null +++ b/packages/modules/internal_chargepoint_handler/pro_plus_test.py @@ -0,0 +1,75 @@ +import re +from typing import Callable, Tuple +from unittest.mock import Mock + +import pytest +from modules.common.component_state import ChargepointState +from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import InternalChargepoint +from modules.internal_chargepoint_handler.pro_plus import ProPlus + + +@pytest.fixture(autouse=True) +def setup_pro_plus(monkeypatch) -> Tuple[ProPlus, Mock]: + pro_plus = ProPlus(0, InternalChargepoint(), 1) + mock_store_set = Mock() + monkeypatch.setattr(pro_plus.store, "set", mock_store_set) + monkeypatch.setattr(pro_plus.store, "update", lambda: None) + monkeypatch.setattr(pro_plus.store_internal, "set", lambda x: None) + monkeypatch.setattr(pro_plus.store_internal, "update", lambda: None) + return pro_plus, mock_store_set + + +@pytest.fixture() +def chargepoint_state() -> ChargepointState: + return ChargepointState(currents=[0, 0, 0], powers=[0, 0, 0], voltages=[ + 229.4, 229.4, 229.4], imported=0, exported=0, power=0, phases_in_use=2, charge_state=False, plug_state=True) + + +@pytest.mark.parametrize( + "request_values_return, expected_chargepoint_state", + [pytest.param(lambda: chargepoint_state, chargepoint_state, id="Normalfall"), + pytest.param(Mock(side_effect=Exception(ProPlus.NO_CONNECTION_TO_INTERNAL_CP)), + chargepoint_state, id="Fehler, aber Timer noch nicht abgelaufen")]) +def test_get_values(request_values_return: ChargepointState, + expected_chargepoint_state: ChargepointState, + setup_pro_plus: Callable[[], Tuple[ProPlus, Mock]], + monkeypatch): + # setup + pro_plus, mock_store_set = setup_pro_plus + pro_plus.old_chargepoint_state = expected_chargepoint_state + monkeypatch.setattr(pro_plus, "request_values", request_values_return) + monkeypatch.setattr(pro_plus.client_error_context, "error_counter_exceeded", lambda: False) + + # execution + chargepoint_state = pro_plus.get_values(False, None) + + # evalutation + assert chargepoint_state == expected_chargepoint_state + assert mock_store_set.call_args.args[0].__dict__ == expected_chargepoint_state.__dict__ + + +def test_get_values_no_data_since_boot(setup_pro_plus: Callable[[], Tuple[ProPlus, Mock]], monkeypatch): + # setup + pro_plus = setup_pro_plus[0] + monkeypatch.setattr(pro_plus, "request_values", Mock(side_effect=Exception(ProPlus.NO_CONNECTION_TO_INTERNAL_CP))) + monkeypatch.setattr(pro_plus.client_error_context, "error_counter_exceeded", lambda: False) + + # execution + with pytest.raises(Exception, match=re.escape(ProPlus.NO_DATA_SINCE_BOOT)): + pro_plus.get_values(False, None) + + +def test_get_values_error_timer_exceed(setup_pro_plus: Callable[[], Tuple[ProPlus, Mock]], monkeypatch): + # Exception werfen und Ladepunkt-Status zurücksetzen + # setup + pro_plus, mock_store_set = setup_pro_plus + monkeypatch.setattr(pro_plus, "request_values", Mock(side_effect=Exception(ProPlus.NO_CONNECTION_TO_INTERNAL_CP))) + monkeypatch.setattr(pro_plus.client_error_context, "error_counter_exceeded", lambda: True) + + # execution + with pytest.raises(Exception, match=re.escape(ProPlus.NO_CONNECTION_TO_INTERNAL_CP)): + pro_plus.get_values(False, None) + + assert mock_store_set.call_args.args[0].__dict__ == ChargepointState( + plug_state=False, charge_state=False, imported=None, exported=None, + phases_in_use=0, power=0, currents=[0]*3).__dict__