From cbcb9ecefc09f0390844e5e94f55643a67f09bf0 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 18 Sep 2024 15:16:02 +0200 Subject: [PATCH 01/13] extend meter check, request values only once per cycle --- .../openwb_pro/chargepoint_module.py | 7 +- .../chargepoint_module.py | 40 +++++----- packages/modules/common/component_state.py | 8 ++ packages/modules/common/evse.py | 15 +++- packages/modules/common/hardware_check.py | 46 +++++++---- .../modules/common/hardware_check_test.py | 76 ++++++++++--------- packages/modules/common/lovato.py | 12 +++ packages/modules/common/mpm3pm.py | 15 ++++ packages/modules/common/sdm.py | 25 ++++++ .../modules/devices/openwb/openwb_flex/bat.py | 20 ++--- .../devices/openwb/openwb_flex/counter.py | 30 +------- .../devices/openwb/openwb_flex/inverter.py | 27 ++++--- .../chargepoint_module.py | 36 ++++----- .../internal_chargepoint_handler/clients.py | 3 +- 14 files changed, 210 insertions(+), 150 deletions(-) diff --git a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py index a567f21b81..4bf356caa7 100644 --- a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py @@ -10,7 +10,7 @@ from modules.common.fault_state import ComponentInfo, FaultState from modules.common.hardware_check import check_meter_values from modules.common.store import get_chargepoint_value_store -from modules.common.component_state import ChargepointState +from modules.common.component_state import ChargepointState, CounterState from modules.common import req log = logging.getLogger(__name__) @@ -81,7 +81,10 @@ def request_values(self) -> ChargepointState: ) if json_rsp.get("voltages"): - meter_msg = check_meter_values(json_rsp["voltages"]) + meter_msg = check_meter_values(CounterState(voltages=json_rsp["voltages"], + currents=json_rsp["currents"], + powers=json_rsp["powers"], + power=json_rsp["power_all"])) if meter_msg: self.fault_state.warning(meter_msg) chargepoint_state.voltages = json_rsp["voltages"] diff --git a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py index 20734da31f..48d98f2f8c 100644 --- a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py @@ -77,28 +77,26 @@ def get_values(self) -> None: with self.client_error_context: try: self.delay_second_cp(self.CP1_DELAY) - with self._client.client: - self._client.check_hardware(self.fault_state) - if self.version is False: - self._validate_version() - currents = self._client.meter_client.get_currents() - phases_in_use = sum(1 for current in currents if current > 3) - plug_state, charge_state, _ = self._client.evse_client.get_plug_charge_state() + evse_state, counter_state = self._client.request_and_check_hardware() + if self.version is False: + self._validate_version() + + currents = counter_state.currents + phases_in_use = sum(1 for current in currents if current > 3) - chargepoint_state = ChargepointState( - power=self._client.meter_client.get_power()[1], - currents=currents, - imported=self._client.meter_client.get_imported(), - exported=0, - voltages=self._client.meter_client.get_voltages(), - plug_state=plug_state, - charge_state=charge_state, - phases_in_use=phases_in_use, - serial_number=self._client.meter_client.get_serial_number(), - max_evse_current=self.max_evse_current - ) + chargepoint_state = ChargepointState( + power=counter_state.power, + currents=currents, + imported=counter_state.imported, + exported=0, + voltages=counter_state.voltages, + plug_state=evse_state.plug_state, + charge_state=evse_state.charge_state, + phases_in_use=phases_in_use, + serial_number=counter_state.serial_number, + max_evse_current=evse_state.max_evse_current + ) self.store.set(chargepoint_state) - self.client_error_context.reset_error_counter() except AttributeError: self._create_client() self._validate_version() @@ -115,7 +113,6 @@ def set_current(self, current: float) -> None: try: self.delay_second_cp(self.CP1_DELAY) with self._client.client: - self._client.check_hardware(self.fault_state) if self.version: self._client.evse_client.set_current(int(current)) else: @@ -130,7 +127,6 @@ def switch_phases(self, phases_to_use: int, duration: int) -> None: with self.client_error_context: try: with self._client.client: - self._client.check_hardware(self.fault_state) if phases_to_use == 1: self._client.client.delegate.write_register( 0x0001, 256, unit=self.ID_PHASE_SWITCH_UNIT) diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index 437c02a68a..d518a18d03 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -234,3 +234,11 @@ def __init__(self, analog_input: Dict[str, float] = None, self.digital_input = digital_input self.analog_output = analog_output self.digital_output = digital_output + + +class EvseState: + def __init__(self, plug_state: bool, charge_state: bool, set_current: int, version: int) -> None: + self.plug_state = plug_state + self.charge_state = charge_state + self.set_current = set_current + self.version = version diff --git a/packages/modules/common/evse.py b/packages/modules/common/evse.py index 670b779056..af9533d331 100644 --- a/packages/modules/common/evse.py +++ b/packages/modules/common/evse.py @@ -6,12 +6,13 @@ from helpermodules.logger import ModifyLoglevelContext from modules.common import modbus +from modules.common.component_state import EvseState from modules.common.modbus import ModbusDataType log = logging.getLogger(__name__) -class EvseState(IntEnum): +class EvseStatusCode(IntEnum): READY = (1, False, False) EV_PRESENT = (2, True, False) CHARGING = (3, True, True) @@ -41,8 +42,8 @@ def get_plug_charge_state(self) -> Tuple[bool, bool, float]: set_current = int(set_current) log.debug("Gesetzte Stromstärke EVSE: "+str(set_current) + ", Status: "+str(state_number)+", Modbus-ID: "+str(self.id)) - state = EvseState(state_number) - if state == EvseState.FAILURE: + state = EvseStatusCode(state_number) + if state == EvseStatusCode.FAILURE: raise ValueError("Unbekannter Zustand der EVSE: State " + str(state)+", Soll-Stromstärke: "+str(set_current)) plugged = state.plugged @@ -56,6 +57,14 @@ def get_firmware_version(self) -> int: version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id) return version + def get_evse_state(self) -> EvseState: + plugged, charging, set_current = self.get_plug_charge_state() + state = EvseState(plug_state=plugged, + charge_state=charging, + set_current=set_current, + version=self.get_firmware_version()) + return state + def is_precise_current_active(self) -> bool: time.sleep(0.1) value = self.client.read_holding_registers(2005, ModbusDataType.UINT_16, unit=self.id) diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index fcb5cb508c..eb60d53821 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -1,6 +1,7 @@ import pymodbus -from typing import Any, List, Optional, Protocol, Tuple, Union +from typing import Any, Optional, Protocol, Tuple, Union +from modules.common.component_state import CounterState, EvseState from modules.common.evse import Evse from modules.common.fault_state import FaultState from modules.common.modbus import ModbusSerialClient_, ModbusTcpClient_ @@ -16,6 +17,8 @@ "Bitte den openWB series2 satellit stromlos machen.") METER_PROBLEM = "Der Zähler konnte nicht ausgelesen werden. Vermutlich ist der Zähler falsch konfiguriert oder defekt." METER_BROKEN = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt." +METER_BROKEN_VOLTAGES = ("Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden. " + f"Der Zähler ist defekt.") METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die " "Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die " "Funktionalität wird dadurch nicht beeinträchtigt!") @@ -23,17 +26,20 @@ "(Fehlermeldung nur relevant, wenn diese auf der Startseite oder im Status angezeigt wird.)") -def check_meter_values(voltages: List[float]) -> Optional[str]: +def check_meter_values(counter_state: CounterState) -> Optional[str]: def valid_voltage(voltage) -> bool: - return 200 < voltage < 260 - if ((valid_voltage(voltages[0]) and voltages[1] == 0 and voltages[2] == 0) or + return 200 < voltage < 250 + voltages = counter_state.voltages + if not ((valid_voltage(voltages[0]) and voltages[1] == 0 and voltages[2] == 0) or # Zoe lädt einphasig an einphasiger Wallbox und erzeugt Spannung auf L2 (ca 126V) (valid_voltage(voltages[0]) and 115 < voltages[1] < 135 and voltages[2] == 0) or (valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and voltages[2] == 0) or (valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and valid_voltage((voltages[2])))): - return None - else: - return METER_BROKEN.format(voltages) + return METER_BROKEN_VOLTAGES + interdependent_values = [sum(counter_state.currents), counter_state.power] + if not (all(v == 0 for v in interdependent_values) or all(v != 0 for v in interdependent_values)): + return METER_BROKEN + return None class ClientHandlerProtocol(Protocol): @@ -63,16 +69,22 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception): else: return False - def check_hardware(self: ClientHandlerProtocol, fault_state: FaultState): - + def request_and_check_hardware(self: ClientHandlerProtocol, fault_state: FaultState) -> Tuple[EvseState, CounterState]: try: - if self.evse_client.get_firmware_version() > EVSE_MIN_FIRMWARE: + with self.client: + evse_state = self.evse_client.get_evse_state() + if evse_state.version > EVSE_MIN_FIRMWARE: evse_check_passed = True else: evse_check_passed = False except Exception as e: evse_check_passed = self.handle_exception(e) - meter_check_passed, meter_error_msg = self.check_meter() + meter_check_passed, meter_error_msg, counter_state = self.check_meter() + if meter_check_passed is False and evse_check_passed is False: + if isinstance(self.client, ModbusTcpClient_): + raise Exception(LAN_ADAPTER_BROKEN) + else: + raise Exception(USB_ADAPTER_BROKEN) if meter_check_passed is False: if evse_check_passed is False: if isinstance(self.client, ModbusTcpClient_): @@ -90,12 +102,14 @@ def check_hardware(self: ClientHandlerProtocol, fault_state: FaultState): raise Exception(EVSE_BROKEN + " " + meter_error_msg + OPEN_TICKET) else: raise Exception(EVSE_BROKEN + OPEN_TICKET) + return evse_state, counter_state - def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str]]: + def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str], CounterState]: try: - serial_number = self.meter_client.get_serial_number() - if serial_number == "0" or serial_number is None: + with self.client: + counter_state = self.meter_client.get_counter_state() + if counter_state.serial_number == "0" or counter_state.serial_number is None: return True, METER_NO_SERIAL_NUMBER - return True, check_meter_values(self.meter_client.get_voltages()) + return True, check_meter_values(counter_state), counter_state except Exception: - return False, METER_PROBLEM + return False, METER_PROBLEM, None diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index b88dac6464..a106e6efce 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -1,14 +1,14 @@ -import re from typing import List, Optional, Tuple, Union -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import Mock, patch import pytest from modules.common import sdm +from modules.common.component_state import CounterState, EvseState from modules.common.evse import Evse from modules.common.hardware_check import ( - EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN, METER_NO_SERIAL_NUMBER, METER_PROBLEM, OPEN_TICKET, - USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, check_meter_values) -from modules.common.modbus import NO_CONNECTION, ModbusClient, ModbusSerialClient_, ModbusTcpClient_ + EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN, METER_BROKEN_VOLTAGES, METER_NO_SERIAL_NUMBER, METER_PROBLEM, + OPEN_TICKET, USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, check_meter_values) +from modules.common.modbus import NO_CONNECTION, ModbusSerialClient_, ModbusTcpClient_ from modules.conftest import SAMPLE_IP, SAMPLE_PORT from modules.internal_chargepoint_handler.clients import ClientHandler @@ -42,59 +42,67 @@ def test_hardware_check_fails(evse_side_effect, expected_error_msg, monkeypatch): # setup - mock_evse_client = Mock(spec=Evse, get_firmware_version=Mock( - side_effect=evse_side_effect, return_value=evse_return_value)) - mock_evse_factory = Mock(spec=Evse, return_value=mock_evse_client) - monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_factory) - - mock_meter_client = Mock(spec=sdm.Sdm630_72, get_voltages=Mock( - side_effect=meter_side_effect, return_value=meter_return_value)) + evse_state = Mock(spec=EvseState, version=evse_return_value) + mock_evse_client = Mock(spec=Evse, get_evse_state=Mock(side_effect=evse_side_effect, return_value=evse_state)) + mock_evse_facotry = Mock(spec=Evse, return_value=mock_evse_client) + monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) + + counter_state_mock = Mock(spec=CounterState, side_effect=meter_side_effect, + voltages=meter_return_value, currents=[0, 0, 0], powers=[0, 0, 0], power=0) + mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock(return_value=counter_state_mock)) mock_find_meter_client = Mock(spec=sdm.Sdm630_72, return_value=mock_meter_client) monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) handle_exception_mock = Mock(side_effect=handle_exception_side_effect, return_value=handle_exception_return_value) monkeypatch.setattr(SeriesHardwareCheckMixin, "handle_exception", handle_exception_mock) - mock_modbus_client = MagicMock(spec=client_spec, address=SAMPLE_IP, port=SAMPLE_PORT) - mock_modbus_client.__enter__.return_value = mock_modbus_client + enter_mock = Mock(return_value=None) + exit_mock = Mock(return_value=True) + client = Mock(spec=client_spec, __enter__=enter_mock, __exit__=exit_mock) # execution and evaluation - with pytest.raises(Exception, match=re.escape(expected_error_msg)): - ClientHandler(0, mock_modbus_client, [1], Mock()) + with pytest.raises(Exception, match=expected_error_msg): + ClientHandler(0, client, [1], Mock()) def test_hardware_check_succeeds(monkeypatch): # setup - mock_evse_client = Mock(spec=Evse, get_firmware_version=Mock(return_value=18)) - mock_evse_factory = Mock(spec=Evse, return_value=mock_evse_client) - monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_factory) + evse_state = Mock(spec=EvseState, version=17) + mock_evse_client = Mock(spec=Evse, get_evse_state=Mock(return_value=evse_state)) + mock_evse_facotry = Mock(spec=Evse, return_value=mock_evse_client) + monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) - mock_meter_client = Mock(spec=sdm.Sdm630_72, get_voltages=Mock(return_value=[230]*3)) + counter_state_mock = Mock(spec=CounterState, voltages=[230]*3, currents=[0, 0, 0], powers=[0, 0, 0], power=0) + mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock(return_value=counter_state_mock)) mock_find_meter_client = Mock(spec=sdm.Sdm630_72, return_value=mock_meter_client) monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) - mock_modbus_client = MagicMock(spec=ModbusClient) - mock_modbus_client.__enter__.return_value = mock_modbus_client + enter_mock = Mock(return_value=None) + exit_mock = Mock(return_value=True) + client = Mock(spec=ModbusTcpClient_, __enter__=enter_mock, __exit__=exit_mock) # execution and evaluation # keine Exception - ClientHandler(0, mock_modbus_client, [1], Mock()) + ClientHandler(0, client, [1], Mock()) @pytest.mark.parametrize( - "voltages, expected_msg", - [pytest.param([230, 0, 0], None, id="einphasig oder zweiphasig L2 defekt (nicht erkennbar)"), - pytest.param([0, 0, 0], METER_BROKEN, id="einphasig, L1 defekt"), - pytest.param([230, 230, 0], None, id="zweiphasig oder dreiphasig, L3 defekt (nicht erkennbar)"), - pytest.param([0, 230, 0], METER_BROKEN, id="zweiphasig, L1 defekt"), - pytest.param([230, 230, 230], None, id="dreiphasig"), - pytest.param([0, 230, 230], METER_BROKEN, id="dreiphasig, L1 defekt"), - pytest.param([230, 0, 230], METER_BROKEN, id="dreiphasig, L2 defekt"), + "voltages, power, expected_msg", + [pytest.param([230, 0, 0], 0, None, id="einphasig oder zweiphasig L2 defekt (nicht erkennbar)"), + pytest.param([0, 0, 0], 0, METER_BROKEN_VOLTAGES, id="einphasig, L1 defekt"), + pytest.param([230, 230, 0], 0, None, id="zweiphasig oder dreiphasig, L3 defekt (nicht erkennbar)"), + pytest.param([0, 230, 0], 0, METER_BROKEN_VOLTAGES, id="zweiphasig, L1 defekt"), + pytest.param([230, 230, 230], 0, None, id="dreiphasig"), + pytest.param([0, 230, 230], 0, METER_BROKEN_VOLTAGES, id="dreiphasig, L1 defekt"), + pytest.param([230, 0, 230], 0, METER_BROKEN_VOLTAGES, id="dreiphasig, L2 defekt"), + pytest.param([230]*3, 100, METER_BROKEN, id="Phantom-Leistung"), ] ) -def test_check_meter_values(voltages, expected_msg, monkeypatch): - # setup & execution - msg = check_meter_values(voltages) +def test_check_meter(voltages, power, expected_msg, monkeypatch): + # setup + counter_state = Mock(voltages=voltages, currents=[0, 0, 0], powers=[0, 0, 0], power=power) + # execution + msg = check_meter_values(counter_state) # assert assert msg == expected_msg if expected_msg is None else expected_msg.format(voltages) diff --git a/packages/modules/common/lovato.py b/packages/modules/common/lovato.py index 504f37687d..990f670dd8 100644 --- a/packages/modules/common/lovato.py +++ b/packages/modules/common/lovato.py @@ -3,6 +3,7 @@ from modules.common import modbus from typing import List, Tuple from modules.common.abstract_counter import AbstractCounter +from modules.common.component_state import CounterState from modules.common.modbus import ModbusDataType @@ -36,3 +37,14 @@ def get_frequency(self) -> float: def get_currents(self) -> List[float]: return [val / 10000 for val in self.client.read_input_registers( 0x0007, [ModbusDataType.INT_32]*3, unit=self.id)] + + def get_counter_state(self) -> CounterState: + powers, power = self.get_power() + return CounterState( + power=power, + voltages=self.get_voltages(), + currents=self.get_currents(), + powers=powers, + power_factors=self.get_power_factors(), + frequency=self.get_frequency() + ) diff --git a/packages/modules/common/mpm3pm.py b/packages/modules/common/mpm3pm.py index 6cc99fbbe0..71fdbcacfa 100644 --- a/packages/modules/common/mpm3pm.py +++ b/packages/modules/common/mpm3pm.py @@ -3,6 +3,7 @@ from modules.common import modbus from modules.common.abstract_counter import AbstractCounter +from modules.common.component_state import CounterState from modules.common.modbus import ModbusDataType @@ -47,3 +48,17 @@ def get_currents(self) -> List[float]: def get_serial_number(self) -> str: return str(self.client.read_input_registers(0x33, ModbusDataType.UINT_32, unit=self.id)) + + def get_counter_state(self) -> CounterState: + powers, power = self.get_power() + return CounterState( + voltages=self.get_voltages(), + currents=self.get_currents(), + powers=powers, + power=power, + power_factors=self.get_power_factors(), + imported=self.get_imported(), + exported=self.get_exported(), + frequency=self.get_frequency(), + serial_number=self.get_serial_number() + ) diff --git a/packages/modules/common/sdm.py b/packages/modules/common/sdm.py index 68448b8e4b..c5c9ab1a91 100644 --- a/packages/modules/common/sdm.py +++ b/packages/modules/common/sdm.py @@ -4,6 +4,7 @@ from modules.common import modbus from modules.common.abstract_counter import AbstractCounter +from modules.common.component_state import CounterState from modules.common.modbus import ModbusDataType @@ -68,6 +69,19 @@ def get_voltages(self) -> List[float]: self._ensure_min_time_between_queries() return self.client.read_input_registers(0x00, [ModbusDataType.FLOAT_32]*3, unit=self.id) + def get_counter_state(self) -> CounterState: + powers, power = self.get_power() + return CounterState( + imported=self.get_imported(), + exported=self.get_exported(), + power=power, + voltages=self.get_voltages(), + currents=self.get_currents(), + powers=powers, + power_factors=self.get_power_factors(), + frequency=self.get_frequency() + ) + class Sdm120(Sdm): def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: @@ -90,3 +104,14 @@ def get_voltages(self) -> List[float]: def get_power_factors(self) -> List[float]: self._ensure_min_time_between_queries() return [self.client.read_input_registers(0x1E, ModbusDataType.FLOAT_32, unit=self.id), 0.0, 0.0] + + def get_counter_state(self) -> CounterState: + powers, power = self.get_power() + return CounterState( + imported=self.get_imported(), + exported=self.get_exported(), + power=power, + currents=self.get_currents(), + powers=powers, + frequency=self.get_frequency() + ) diff --git a/packages/modules/devices/openwb/openwb_flex/bat.py b/packages/modules/devices/openwb/openwb_flex/bat.py index 494af8bf12..0989a6ef7b 100644 --- a/packages/modules/devices/openwb/openwb_flex/bat.py +++ b/packages/modules/devices/openwb/openwb_flex/bat.py @@ -39,16 +39,16 @@ def update(self): # TCP-Verbindung schließen möglichst bevor etwas anderes gemacht wird, um im Fehlerfall zu verhindern, # dass offene Verbindungen den Modbus-Adapter blockieren. with self.__tcp_client: - if isinstance(self.__client, Sdm630_72): - _, power = self.__client.get_power() - power = power * -1 - else: - _, power = self.__client.get_power() - if isinstance(self.__client, Lovato) or isinstance(self.__client, Sdm120): - imported, exported = self.sim_counter.sim_count(power) - else: - imported = self.__client.get_imported() - exported = self.__client.get_exported() + counter_state = self.__client.get_counter_state() + + power = counter_state.power + if isinstance(self.__client, Sdm630_72): + power = power * -1 + if isinstance(self.__client, Lovato) or isinstance(self.__client, Sdm120): + imported, exported = self.sim_counter.sim_count(power) + else: + imported = counter_state.imported + exported = counter_state.exported voltages = self.__client.get_voltages() powers, power = self.__client.get_power() diff --git a/packages/modules/devices/openwb/openwb_flex/counter.py b/packages/modules/devices/openwb/openwb_flex/counter.py index 7376ff0b25..271a075cec 100644 --- a/packages/modules/devices/openwb/openwb_flex/counter.py +++ b/packages/modules/devices/openwb/openwb_flex/counter.py @@ -3,10 +3,8 @@ from modules.common import modbus from modules.common.abstract_device import AbstractCounter -from modules.common.component_state import CounterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.lovato import Lovato from modules.common.mpm3pm import Mpm3pm from modules.common.b23 import B23 from modules.common.simcount import SimCounter @@ -38,33 +36,13 @@ def update(self): # TCP-Verbindung schließen möglichst bevor etwas anderes gemacht wird, um im Fehlerfall zu verhindern, # dass offene Verbindungen den Modbus-Adapter blockieren. with self.__tcp_client: - voltages = self.__client.get_voltages() - powers, power = self.__client.get_power() - frequency = self.__client.get_frequency() - power_factors = self.__client.get_power_factors() - - if isinstance(self.__client, Mpm3pm or B23): - imported = self.__client.get_imported() - exported = self.__client.get_exported() - else: - currents = self.__client.get_currents() + counter_state = self.__client.get_counter_state() if isinstance(self.__client, Mpm3pm or B23): - currents = [powers[i] / voltages[i] for i in range(3)] + counter_state.currents = [counter_state.powers[i] / counter_state.voltages[i] for i in range(3)] else: - if isinstance(self.__client, Lovato): - power = sum(powers) - imported, exported = self.sim_counter.sim_count(power) - counter_state = CounterState( - voltages=voltages, - currents=currents, - powers=powers, - power_factors=power_factors, - imported=imported, - exported=exported, - power=power, - frequency=frequency - ) + counter_state.imported, counter_state.exported = self.sim_counter.sim_count(counter_state.power) + self.store.set(counter_state) diff --git a/packages/modules/devices/openwb/openwb_flex/inverter.py b/packages/modules/devices/openwb/openwb_flex/inverter.py index 0d72c2a5bc..e87ad41cd6 100644 --- a/packages/modules/devices/openwb/openwb_flex/inverter.py +++ b/packages/modules/devices/openwb/openwb_flex/inverter.py @@ -38,24 +38,23 @@ def update(self) -> None: """ liest die Werte des Moduls aus. """ with self.__tcp_client: - powers, power = self.__client.get_power() - - version = self.component_config.configuration.version - if version == 1: - power = sum(powers) - if power > 10: - power = power*-1 - currents = self.__client.get_currents() - - if isinstance(self.__client, Lovato) or isinstance(self.__client, Sdm120): - _, exported = self.sim_counter.sim_count(power) - else: - exported = self.__client.get_exported() + counter_state = self.__client.get_counter_state() + + power = counter_state.power + version = self.component_config.configuration.version + if version == 1: + power = sum(counter_state.powers) + if power > 10: + power = power*-1 + if isinstance(self.__client, Lovato) or isinstance(self.__client, Sdm120): + _, exported = self.sim_counter.sim_count(power) + else: + exported = counter_state.exported inverter_state = InverterState( power=power, exported=exported, - currents=currents + currents=counter_state.currents ) self.store.set(inverter_state) diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index e1d9d2a752..295aa3146c 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -75,24 +75,17 @@ def store_state(chargepoint_state: ChargepointState) -> None: chargepoint_state = self.old_chargepoint_state self.set_current_evse = chargepoint_state.evse_current - self._client.check_hardware(self.fault_state) - powers, power = self._client.meter_client.get_power() - if power < self.PLUG_STANDBY_POWER_THRESHOLD: + evse_state, counter_state = self._client.request_and_check_hardware() + if counter_state.power < self.PLUG_STANDBY_POWER_THRESHOLD: power = 0 - voltages = self._client.meter_client.get_voltages() - currents = self._client.meter_client.get_currents() - imported = self._client.meter_client.get_imported() - power_factors = self._client.meter_client.get_power_factors() - frequency = self._client.meter_client.get_frequency() - serial_number = self._client.meter_client.get_serial_number() - phases_in_use = sum(1 for current in currents if current > 3) + phases_in_use = sum(1 for current in counter_state.currents if current > 3) if phases_in_use == 0: phases_in_use = self.old_phases_in_use else: self.old_phases_in_use = phases_in_use time.sleep(0.1) - plug_state, charge_state, self.set_current_evse = self._client.evse_client.get_plug_charge_state() + self.set_current_evse = evse_state.set_current self.client_error_context.reset_error_counter() if phase_switch_cp_active: @@ -103,24 +96,25 @@ def store_state(chargepoint_state: ChargepointState) -> None: ) plug_state = self.old_plug_state else: - self.old_plug_state = plug_state + self.old_plug_state = evse_state.plug_state + plug_state = evse_state.plug_state chargepoint_state = ChargepointState( power=power, - currents=currents, - imported=imported, + currents=counter_state.currents, + imported=counter_state.imported, exported=0, - powers=powers, - voltages=voltages, - frequency=frequency, + powers=counter_state.powers, + voltages=counter_state.voltages, + frequency=counter_state.frequency, plug_state=plug_state, - charge_state=charge_state, + charge_state=evse_state.charge_state, phases_in_use=phases_in_use, - power_factors=power_factors, + power_factors=counter_state.power_factors, rfid=last_tag, evse_current=self.set_current_evse, - serial_number=serial_number, - max_evse_current=self.max_evse_current, + serial_number=counter_state.serial_number, + max_evse_current=evse_state.max_evse_current, version=self.version, current_branch=self.current_branch, current_commit=self.current_commit diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index 8b4d3ebc25..1ff47d1db5 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -40,8 +40,7 @@ def __init__(self, self.evse_client = self._evse_factory(client, evse_ids) self.meter_client = self.find_meter_client(CP0_METERS if self.local_charge_point_num == 0 else CP1_METERS, client) - with client: - self.check_hardware(fault_state) + self.request_and_check_hardware() self.read_error = 0 def _evse_factory(self, client: Union[ModbusSerialClient_, ModbusTcpClient_], evse_ids: List[int]) -> evse.Evse: From f3c17fdefb7834afee6436467fc94850c5610e2b Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 19 Sep 2024 09:33:19 +0200 Subject: [PATCH 02/13] fixes --- .../chargepoint_module.py | 2 +- packages/modules/common/component_state.py | 2 + packages/modules/common/hardware_check.py | 14 ++++-- .../modules/common/hardware_check_test.py | 44 ++++++++++++------- packages/modules/common/sdm.py | 6 ++- .../chargepoint_module.py | 3 +- .../internal_chargepoint_handler/clients.py | 2 +- 7 files changed, 48 insertions(+), 25 deletions(-) diff --git a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py index 48d98f2f8c..a94158f72d 100644 --- a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py @@ -77,7 +77,7 @@ def get_values(self) -> None: with self.client_error_context: try: self.delay_second_cp(self.CP1_DELAY) - evse_state, counter_state = self._client.request_and_check_hardware() + evse_state, counter_state = self._client.request_and_check_hardware(self.fault_state) if self.version is False: self._validate_version() diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index d518a18d03..f1a6923b21 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -88,6 +88,7 @@ def __init__( powers: Optional[List[Optional[float]]] = None, power_factors: Optional[List[Optional[float]]] = None, frequency: float = 50, + serial_number: str = "", ): """Args: imported: total imported energy in Wh @@ -107,6 +108,7 @@ def __init__( self.exported = exported self.power = power self.frequency = frequency + self.serial_number = serial_number @auto_str diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index eb60d53821..48a73526f7 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -17,8 +17,7 @@ "Bitte den openWB series2 satellit stromlos machen.") METER_PROBLEM = "Der Zähler konnte nicht ausgelesen werden. Vermutlich ist der Zähler falsch konfiguriert oder defekt." METER_BROKEN = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt." -METER_BROKEN_VOLTAGES = ("Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden. " - f"Der Zähler ist defekt.") +METER_BROKEN_VOLTAGES = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden. Der Zähler ist defekt." METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die " "Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die " "Funktionalität wird dadurch nicht beeinträchtigt!") @@ -55,6 +54,12 @@ def evse_client(self) -> Evse: ... def meter_client(self) -> Any: ... @property def read_error(self) -> int: ... + @property + def handle_exception(self, exception: Exception) -> bool: ... + @property + def request_and_check_hardware(self, fault_state: FaultState) -> Tuple[EvseState, CounterState]: ... + @property + def check_meter(self) -> Tuple[bool, Optional[str], CounterState]: ... class SeriesHardwareCheckMixin: @@ -69,7 +74,8 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception): else: return False - def request_and_check_hardware(self: ClientHandlerProtocol, fault_state: FaultState) -> Tuple[EvseState, CounterState]: + def request_and_check_hardware(self: ClientHandlerProtocol, + fault_state: FaultState) -> Tuple[EvseState, CounterState]: try: with self.client: evse_state = self.evse_client.get_evse_state() @@ -109,7 +115,7 @@ def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str], Count with self.client: counter_state = self.meter_client.get_counter_state() if counter_state.serial_number == "0" or counter_state.serial_number is None: - return True, METER_NO_SERIAL_NUMBER + return True, METER_NO_SERIAL_NUMBER, counter_state return True, check_meter_values(counter_state), counter_state except Exception: return False, METER_PROBLEM, None diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index a106e6efce..7ff4637d09 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -3,6 +3,7 @@ import pytest from modules.common import sdm +from modules.common import hardware_check from modules.common.component_state import CounterState, EvseState from modules.common.evse import Evse from modules.common.hardware_check import ( @@ -19,7 +20,7 @@ [pytest.param(Exception("Modbus"), None, None, [230]*3, None, False, ModbusSerialClient_, EVSE_BROKEN, id="EVSE defekt"), pytest.param(Exception("Modbus"), None, None, [230, 0, 230], None, False, ModbusSerialClient_, - EVSE_BROKEN + " " + METER_BROKEN.format([230, 0, 230]) + OPEN_TICKET, + EVSE_BROKEN + " " + METER_BROKEN_VOLTAGES.format([230, 0, 230]) + OPEN_TICKET, id="EVSE defekt und Zähler eine Phase defekt"), pytest.param(None, 18, Exception("Modbus"), None, None, None, ModbusSerialClient_, METER_PROBLEM, id="Zähler falsch konfiguriert"), @@ -48,7 +49,11 @@ def test_hardware_check_fails(evse_side_effect, monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) counter_state_mock = Mock(spec=CounterState, side_effect=meter_side_effect, - voltages=meter_return_value, currents=[0, 0, 0], powers=[0, 0, 0], power=0) + voltages=meter_return_value, + currents=[0, 0, 0], + powers=[0, 0, 0], + power=0, + serial_number="1234") mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock(return_value=counter_state_mock)) mock_find_meter_client = Mock(spec=sdm.Sdm630_72, return_value=mock_meter_client) monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) @@ -72,7 +77,8 @@ def test_hardware_check_succeeds(monkeypatch): mock_evse_facotry = Mock(spec=Evse, return_value=mock_evse_client) monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) - counter_state_mock = Mock(spec=CounterState, voltages=[230]*3, currents=[0, 0, 0], powers=[0, 0, 0], power=0) + counter_state_mock = Mock(spec=CounterState, voltages=[ + 230]*3, currents=[0, 0, 0], powers=[0, 0, 0], power=0, serial_number="1234") mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock(return_value=counter_state_mock)) mock_find_meter_client = Mock(spec=sdm.Sdm630_72, return_value=mock_meter_client) monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) @@ -98,7 +104,7 @@ def test_hardware_check_succeeds(monkeypatch): pytest.param([230]*3, 100, METER_BROKEN, id="Phantom-Leistung"), ] ) -def test_check_meter(voltages, power, expected_msg, monkeypatch): +def test_check_meter_values(voltages, power, expected_msg, monkeypatch): # setup counter_state = Mock(voltages=voltages, currents=[0, 0, 0], powers=[0, 0, 0], power=power) # execution @@ -109,28 +115,34 @@ def test_check_meter(voltages, power, expected_msg, monkeypatch): @patch('modules.common.hardware_check.ClientHandlerProtocol') -@pytest.mark.parametrize("serial_number_return, voltages_return, expected", - [("0", [230]*3, (True, METER_NO_SERIAL_NUMBER)), - (12345, [230]*3, (True, None)), - (Exception(), [230]*3, (False, METER_PROBLEM))]) +@pytest.mark.parametrize("serial_number, voltages, expected", + [("0", [230]*3, (True, METER_NO_SERIAL_NUMBER, CounterState)), + (12345, [230]*3, (True, None, CounterState)), + (Exception(), [230]*3, (False, METER_PROBLEM, None))]) def test_check_meter( MockClientHandlerProtocol: Mock, - serial_number_return: Union[int, Exception], - voltages_return: List[int], + serial_number: Union[int, Exception], + voltages: List[int], expected: Tuple[bool, Optional[str]], + monkeypatch, ): # Arrange - mock_meter_client = Mock() - if isinstance(serial_number_return, Exception): - mock_meter_client.get_serial_number.side_effect = serial_number_return + + if isinstance(serial_number, Exception): + counter_state_mock = Mock(spec=CounterState, side_effect=serial_number) else: - mock_meter_client.get_serial_number.return_value = serial_number_return - mock_meter_client.get_voltages.return_value = voltages_return + counter_state_mock = Mock(spec=CounterState, serial_number=serial_number, voltages=voltages) + mock_meter_client = Mock() + mock_meter_client.get_counter_state.return_value = counter_state_mock MockClientHandlerProtocol.meter_client = mock_meter_client mixin = SeriesHardwareCheckMixin + mock_check_meter_values = Mock(return_value=expected[1]) + monkeypatch.setattr(hardware_check, "check_meter_values", mock_check_meter_values) # Act result = mixin.check_meter(MockClientHandlerProtocol) # Assert - assert result == expected + assert result[0] == expected[0] + assert result[1] == expected[1] + assert isinstance(result[2], expected[2] if expected[2] is not None else type(result[2])) diff --git a/packages/modules/common/sdm.py b/packages/modules/common/sdm.py index c5c9ab1a91..45db5028d1 100644 --- a/packages/modules/common/sdm.py +++ b/packages/modules/common/sdm.py @@ -79,7 +79,8 @@ def get_counter_state(self) -> CounterState: currents=self.get_currents(), powers=powers, power_factors=self.get_power_factors(), - frequency=self.get_frequency() + frequency=self.get_frequency(), + serial_number=self.get_serial_number() ) @@ -113,5 +114,6 @@ def get_counter_state(self) -> CounterState: power=power, currents=self.get_currents(), powers=powers, - frequency=self.get_frequency() + frequency=self.get_frequency(), + serial_number=self.get_serial_number() ) diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index 295aa3146c..e606a396c8 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -75,7 +75,8 @@ def store_state(chargepoint_state: ChargepointState) -> None: chargepoint_state = self.old_chargepoint_state self.set_current_evse = chargepoint_state.evse_current - evse_state, counter_state = self._client.request_and_check_hardware() + evse_state, counter_state = self._client.request_and_check_hardware(self.fault_state) + power = counter_state.power if counter_state.power < self.PLUG_STANDBY_POWER_THRESHOLD: power = 0 phases_in_use = sum(1 for current in counter_state.currents if current > 3) diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index 1ff47d1db5..2b55a3a586 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -40,7 +40,7 @@ def __init__(self, self.evse_client = self._evse_factory(client, evse_ids) self.meter_client = self.find_meter_client(CP0_METERS if self.local_charge_point_num == 0 else CP1_METERS, client) - self.request_and_check_hardware() + self.request_and_check_hardware(fault_state) self.read_error = 0 def _evse_factory(self, client: Union[ModbusSerialClient_, ModbusTcpClient_], evse_ids: List[int]) -> evse.Evse: From c2b9ff35b2aa679ed6a48f793838b4240ebc32da Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 19 Sep 2024 09:55:16 +0200 Subject: [PATCH 03/13] fixes --- .../openwb_series2_satellit/chargepoint_module.py | 1 + packages/modules/common/hardware_check.py | 7 +++---- packages/modules/common/hardware_check_test.py | 15 ++++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py index a94158f72d..44e996f08c 100644 --- a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py @@ -97,6 +97,7 @@ def get_values(self) -> None: max_evse_current=evse_state.max_evse_current ) self.store.set(chargepoint_state) + self.client_error_context.reset_error_counter() except AttributeError: self._create_client() self._validate_version() diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index 48a73526f7..97087ebb45 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -16,8 +16,7 @@ LAN_ADAPTER_BROKEN = (f"{RS485_ADAPTER_BROKEN.format('der LAN-Konverter abgestürzt,')} " "Bitte den openWB series2 satellit stromlos machen.") METER_PROBLEM = "Der Zähler konnte nicht ausgelesen werden. Vermutlich ist der Zähler falsch konfiguriert oder defekt." -METER_BROKEN = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt." -METER_BROKEN_VOLTAGES = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden. Der Zähler ist defekt." +METER_BROKEN_VOLTAGES = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt." METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die " "Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die " "Funktionalität wird dadurch nicht beeinträchtigt!") @@ -34,10 +33,10 @@ def valid_voltage(voltage) -> bool: (valid_voltage(voltages[0]) and 115 < voltages[1] < 135 and voltages[2] == 0) or (valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and voltages[2] == 0) or (valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and valid_voltage((voltages[2])))): - return METER_BROKEN_VOLTAGES + return METER_BROKEN_VOLTAGES.format(voltages) interdependent_values = [sum(counter_state.currents), counter_state.power] if not (all(v == 0 for v in interdependent_values) or all(v != 0 for v in interdependent_values)): - return METER_BROKEN + return METER_PROBLEM return None diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index 7ff4637d09..708a7b25fa 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -1,3 +1,4 @@ +import re from typing import List, Optional, Tuple, Union from unittest.mock import Mock, patch @@ -7,7 +8,7 @@ from modules.common.component_state import CounterState, EvseState from modules.common.evse import Evse from modules.common.hardware_check import ( - EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN, METER_BROKEN_VOLTAGES, METER_NO_SERIAL_NUMBER, METER_PROBLEM, + EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN_VOLTAGES, METER_NO_SERIAL_NUMBER, METER_PROBLEM, OPEN_TICKET, USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, check_meter_values) from modules.common.modbus import NO_CONNECTION, ModbusSerialClient_, ModbusTcpClient_ from modules.conftest import SAMPLE_IP, SAMPLE_PORT @@ -66,7 +67,7 @@ def test_hardware_check_fails(evse_side_effect, client = Mock(spec=client_spec, __enter__=enter_mock, __exit__=exit_mock) # execution and evaluation - with pytest.raises(Exception, match=expected_error_msg): + with pytest.raises(Exception, match=re.escape(expected_error_msg)): ClientHandler(0, client, [1], Mock()) @@ -95,13 +96,13 @@ def test_hardware_check_succeeds(monkeypatch): @pytest.mark.parametrize( "voltages, power, expected_msg", [pytest.param([230, 0, 0], 0, None, id="einphasig oder zweiphasig L2 defekt (nicht erkennbar)"), - pytest.param([0, 0, 0], 0, METER_BROKEN_VOLTAGES, id="einphasig, L1 defekt"), + pytest.param([0, 0, 0], 0, METER_BROKEN_VOLTAGES.format([0]*3), id="einphasig, L1 defekt"), pytest.param([230, 230, 0], 0, None, id="zweiphasig oder dreiphasig, L3 defekt (nicht erkennbar)"), - pytest.param([0, 230, 0], 0, METER_BROKEN_VOLTAGES, id="zweiphasig, L1 defekt"), + pytest.param([0, 230, 0], 0, METER_BROKEN_VOLTAGES.format([0, 230, 0]), id="zweiphasig, L1 defekt"), pytest.param([230, 230, 230], 0, None, id="dreiphasig"), - pytest.param([0, 230, 230], 0, METER_BROKEN_VOLTAGES, id="dreiphasig, L1 defekt"), - pytest.param([230, 0, 230], 0, METER_BROKEN_VOLTAGES, id="dreiphasig, L2 defekt"), - pytest.param([230]*3, 100, METER_BROKEN, id="Phantom-Leistung"), + pytest.param([0, 230, 230], 0, METER_BROKEN_VOLTAGES.format([0, 230, 230]), id="dreiphasig, L1 defekt"), + pytest.param([230, 0, 230], 0, METER_BROKEN_VOLTAGES.format([230, 0, 230]), id="dreiphasig, L2 defekt"), + pytest.param([230]*3, 100, METER_PROBLEM, id="Phantom-Leistung"), ] ) def test_check_meter_values(voltages, power, expected_msg, monkeypatch): From f7e9c38d549885099a190e979bc3934ed9e63d57 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Jan 2025 09:36:52 +0100 Subject: [PATCH 04/13] improve --- packages/modules/common/evse.py | 7 ++++--- packages/modules/common/sdm.py | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/modules/common/evse.py b/packages/modules/common/evse.py index af9533d331..41d5d5a501 100644 --- a/packages/modules/common/evse.py +++ b/packages/modules/common/evse.py @@ -33,6 +33,9 @@ class Evse: def __init__(self, modbus_id: int, client: modbus.ModbusSerialClient_) -> None: self.client = client self.id = modbus_id + with client: + time.sleep(0.1) + self.version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id) def get_plug_charge_state(self) -> Tuple[bool, bool, float]: time.sleep(0.1) @@ -53,9 +56,7 @@ def get_plug_charge_state(self) -> Tuple[bool, bool, float]: return plugged, charging, set_current def get_firmware_version(self) -> int: - time.sleep(0.1) - version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id) - return version + return self.version def get_evse_state(self) -> EvseState: plugged, charging, set_current = self.get_plug_charge_state() diff --git a/packages/modules/common/sdm.py b/packages/modules/common/sdm.py index 45db5028d1..2062d242f1 100644 --- a/packages/modules/common/sdm.py +++ b/packages/modules/common/sdm.py @@ -14,6 +14,8 @@ def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: self.id = modbus_id self.last_query = self._get_time_ms() self.WAIT_MS_BETWEEN_QUERIES = 100 + with client: + self.serial_number = str(self.client.read_holding_registers(0xFC00, ModbusDataType.UINT_32, unit=self.id)) def get_imported(self) -> float: self._ensure_min_time_between_queries() @@ -30,10 +32,6 @@ def get_frequency(self) -> float: frequency = frequency / 10 return frequency - def get_serial_number(self) -> Optional[str]: - self._ensure_min_time_between_queries() - return str(self.client.read_holding_registers(0xFC00, ModbusDataType.INT_32, unit=self.id)) - # These meters require some minimum time between subsequent Modbus reads. Some Eastron papers recommend 100 ms. # Sometimes the time between calls to the get_* methods are much shorter so we forcibly wait for the remaining time. def _ensure_min_time_between_queries(self) -> None: @@ -46,6 +44,9 @@ def _ensure_min_time_between_queries(self) -> None: def _get_time_ms(self) -> float: return time.time_ns() / 1e6 + def get_serial_number(self) -> str: + return self.serial_number + class Sdm630_72(Sdm): def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: From 367bca47b56ab62571591dab9174d96492d52615 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Jan 2025 09:49:54 +0100 Subject: [PATCH 05/13] evse max current --- .../chargepoint_module.py | 3 +-- packages/modules/common/component_state.py | 3 ++- packages/modules/common/evse.py | 10 ++++------ .../chargepoint_module.py | 17 +++++++++++------ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py index 44e996f08c..d3d302c27a 100644 --- a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py @@ -40,7 +40,6 @@ def __init__(self, config: OpenWBseries2Satellit) -> None: f"openWB/set/chargepoint/{self.config.id}/get/error_timestamp", CP_ERROR, hide_exception=True) self._create_client() self._validate_version() - self.max_evse_current = self._client.evse_client.get_max_current() def delay_second_cp(self, delay: float): if self.config.configuration.duo_num == 0: @@ -94,7 +93,7 @@ def get_values(self) -> None: charge_state=evse_state.charge_state, phases_in_use=phases_in_use, serial_number=counter_state.serial_number, - max_evse_current=evse_state.max_evse_current + max_evse_current=evse_state.max_current ) self.store.set(chargepoint_state) self.client_error_context.reset_error_counter() diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index f1a6923b21..5740799452 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -239,8 +239,9 @@ def __init__(self, analog_input: Dict[str, float] = None, class EvseState: - def __init__(self, plug_state: bool, charge_state: bool, set_current: int, version: int) -> None: + def __init__(self, plug_state: bool, charge_state: bool, set_current: int, version: int, max_current: int) -> None: self.plug_state = plug_state self.charge_state = charge_state self.set_current = set_current self.version = version + self.max_current = max_current diff --git a/packages/modules/common/evse.py b/packages/modules/common/evse.py index 41d5d5a501..187bd7aff8 100644 --- a/packages/modules/common/evse.py +++ b/packages/modules/common/evse.py @@ -36,6 +36,8 @@ def __init__(self, modbus_id: int, client: modbus.ModbusSerialClient_) -> None: with client: time.sleep(0.1) self.version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id) + time.sleep(0.1) + self.max_current = self.client.read_holding_registers(2007, ModbusDataType.UINT_16, unit=self.id) def get_plug_charge_state(self) -> Tuple[bool, bool, float]: time.sleep(0.1) @@ -63,7 +65,8 @@ def get_evse_state(self) -> EvseState: state = EvseState(plug_state=plugged, charge_state=charging, set_current=set_current, - version=self.get_firmware_version()) + version=self.get_firmware_version() + max_current=self.max_current) return state def is_precise_current_active(self) -> bool: @@ -102,8 +105,3 @@ def deactivate_precise_current(self) -> None: def set_current(self, current: int) -> None: time.sleep(0.1) self.client.write_registers(1000, current, unit=self.id) - - def get_max_current(self) -> int: - time.sleep(0.1) - current = self.client.read_holding_registers(2007, ModbusDataType.UINT_16, unit=self.id) - return current diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index e606a396c8..56c77f7c5a 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -54,12 +54,17 @@ def __init__(self, local_charge_point_num: int, if self._client.evse_client.is_precise_current_active() is False: self._client.evse_client.activate_precise_current() self._precise_current = self._client.evse_client.is_precise_current_active() - self.max_evse_current = self._client.evse_client.get_max_current() - self.version = SubData.system_data["system"].data["version"] - self.current_branch = SubData.system_data["system"].data["current_branch"] - self.current_commit = SubData.system_data["system"].data["current_commit"] - def set_current(self, current: float) -> None: + +<< << << < HEAD + self.max_evse_current = self._client.evse_client.get_max_current() + self.version = SubData.system_data["system"].data["version"] + self.current_branch = SubData.system_data["system"].data["current_branch"] + self.current_commit = SubData.system_data["system"].data["current_commit"] +== == == = +>>>>>> > 1ece26b0c(evse max current) + + def set_current(self, current: float) -> None: with SingleComponentUpdateContext(self.fault_state, update_always=False): formatted_current = round(current*100) if self._precise_current else round(current) if self.set_current_evse != formatted_current: @@ -115,7 +120,7 @@ def store_state(chargepoint_state: ChargepointState) -> None: rfid=last_tag, evse_current=self.set_current_evse, serial_number=counter_state.serial_number, - max_evse_current=evse_state.max_evse_current, + max_evse_current=evse_state.max_current version=self.version, current_branch=self.current_branch, current_commit=self.current_commit From 7590c232f1c9e7f6c6d5bcdfcbc937c73e9141b1 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Jan 2025 09:55:17 +0100 Subject: [PATCH 06/13] improve --- packages/modules/common/component_state.py | 3 +-- packages/modules/common/evse.py | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index 5740799452..c3e308e681 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -239,9 +239,8 @@ def __init__(self, analog_input: Dict[str, float] = None, class EvseState: - def __init__(self, plug_state: bool, charge_state: bool, set_current: int, version: int, max_current: int) -> None: + def __init__(self, plug_state: bool, charge_state: bool, set_current: int, max_current: int) -> None: self.plug_state = plug_state self.charge_state = charge_state self.set_current = set_current - self.version = version self.max_current = max_current diff --git a/packages/modules/common/evse.py b/packages/modules/common/evse.py index 187bd7aff8..b50779cebb 100644 --- a/packages/modules/common/evse.py +++ b/packages/modules/common/evse.py @@ -38,6 +38,14 @@ def __init__(self, modbus_id: int, client: modbus.ModbusSerialClient_) -> None: self.version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id) time.sleep(0.1) self.max_current = self.client.read_holding_registers(2007, ModbusDataType.UINT_16, unit=self.id) + with ModifyLoglevelContext(log, logging.DEBUG): + log.debug(f"Firmware-Version der EVSE: {self.version}") + if self.version < 17: + self._precise_current = False + else: + if self.is_precise_current_active() is False: + self.activate_precise_current() + self._precise_current = self.is_precise_current_active() def get_plug_charge_state(self) -> Tuple[bool, bool, float]: time.sleep(0.1) @@ -65,7 +73,6 @@ def get_evse_state(self) -> EvseState: state = EvseState(plug_state=plugged, charge_state=charging, set_current=set_current, - version=self.get_firmware_version() max_current=self.max_current) return state From 02bf7d666cc8addf38a1e1742a050740b5344970 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Jan 2025 10:07:27 +0100 Subject: [PATCH 07/13] fixes --- packages/helpermodules/utils/error_handling.py | 2 +- packages/modules/common/hardware_check.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/helpermodules/utils/error_handling.py b/packages/helpermodules/utils/error_handling.py index 0545c503f2..7f6e5c5684 100644 --- a/packages/helpermodules/utils/error_handling.py +++ b/packages/helpermodules/utils/error_handling.py @@ -28,7 +28,7 @@ def __exit__(self, exception_type, exception, exception_traceback) -> bool: if self.error_timestamp is None: self.error_timestamp = timecheck.create_timestamp() Pub().pub(self.topic, self.error_timestamp) - log.error(exception) + log.exception(exception) if self.hide_exception is False or timecheck.check_timestamp(self.error_timestamp, self.timeout) is False: return False return True diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index 97087ebb45..7d89882d95 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -78,10 +78,7 @@ def request_and_check_hardware(self: ClientHandlerProtocol, try: with self.client: evse_state = self.evse_client.get_evse_state() - if evse_state.version > EVSE_MIN_FIRMWARE: evse_check_passed = True - else: - evse_check_passed = False except Exception as e: evse_check_passed = self.handle_exception(e) meter_check_passed, meter_error_msg, counter_state = self.check_meter() From 974f002b791f94c3cd280a6f66e555aa60c5cc2e Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 31 Jan 2025 10:56:44 +0100 Subject: [PATCH 08/13] meter check for kits draft --- .../openwb_pro/chargepoint_module.py | 11 +++++------ packages/modules/common/hardware_check.py | 10 ++++++++-- packages/modules/common/hardware_check_test.py | 4 ++-- packages/modules/common/lovato.py | 9 +++++++-- packages/modules/common/mpm3pm.py | 9 +++++++-- packages/modules/common/sdm.py | 16 ++++++++++++---- .../modules/devices/openwb/openwb_flex/bat.py | 3 +-- .../devices/openwb/openwb_flex/counter.py | 3 +-- .../devices/openwb/openwb_flex/inverter.py | 3 +-- .../internal_chargepoint_handler/clients.py | 11 ++++++----- 10 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py index 4bf356caa7..d7af682d01 100644 --- a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py @@ -81,12 +81,11 @@ def request_values(self) -> ChargepointState: ) if json_rsp.get("voltages"): - meter_msg = check_meter_values(CounterState(voltages=json_rsp["voltages"], - currents=json_rsp["currents"], - powers=json_rsp["powers"], - power=json_rsp["power_all"])) - if meter_msg: - self.fault_state.warning(meter_msg) + check_meter_values(CounterState(voltages=json_rsp["voltages"], + currents=json_rsp["currents"], + powers=json_rsp["powers"], + power=json_rsp["power_all"]), + self.fault_state) chargepoint_state.voltages = json_rsp["voltages"] if json_rsp.get("soc_value"): chargepoint_state.soc = json_rsp["soc_value"] diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index 7d89882d95..e2702cc2ed 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -24,7 +24,13 @@ "(Fehlermeldung nur relevant, wenn diese auf der Startseite oder im Status angezeigt wird.)") -def check_meter_values(counter_state: CounterState) -> Optional[str]: +def check_meter_values(counter_state: CounterState, fault_state: Optional[FaultState] = None) -> None: + meter_msg = _check_meter_values(counter_state) + if fault_state and meter_msg: + fault_state.warning(meter_msg) + + +def _check_meter_values(counter_state: CounterState) -> Optional[str]: def valid_voltage(voltage) -> bool: return 200 < voltage < 250 voltages = counter_state.voltages @@ -112,6 +118,6 @@ def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str], Count counter_state = self.meter_client.get_counter_state() if counter_state.serial_number == "0" or counter_state.serial_number is None: return True, METER_NO_SERIAL_NUMBER, counter_state - return True, check_meter_values(counter_state), counter_state + return True, _check_meter_values(counter_state), counter_state except Exception: return False, METER_PROBLEM, None diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index 708a7b25fa..d3c1b658ec 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -9,7 +9,7 @@ from modules.common.evse import Evse from modules.common.hardware_check import ( EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN_VOLTAGES, METER_NO_SERIAL_NUMBER, METER_PROBLEM, - OPEN_TICKET, USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, check_meter_values) + OPEN_TICKET, USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, _check_meter_values) from modules.common.modbus import NO_CONNECTION, ModbusSerialClient_, ModbusTcpClient_ from modules.conftest import SAMPLE_IP, SAMPLE_PORT from modules.internal_chargepoint_handler.clients import ClientHandler @@ -109,7 +109,7 @@ def test_check_meter_values(voltages, power, expected_msg, monkeypatch): # setup counter_state = Mock(voltages=voltages, currents=[0, 0, 0], powers=[0, 0, 0], power=power) # execution - msg = check_meter_values(counter_state) + msg = _check_meter_values(counter_state) # assert assert msg == expected_msg if expected_msg is None else expected_msg.format(voltages) diff --git a/packages/modules/common/lovato.py b/packages/modules/common/lovato.py index 990f670dd8..cb8a0b596e 100644 --- a/packages/modules/common/lovato.py +++ b/packages/modules/common/lovato.py @@ -4,13 +4,16 @@ from typing import List, Tuple from modules.common.abstract_counter import AbstractCounter from modules.common.component_state import CounterState +from modules.common.fault_state import FaultState +from modules.common.hardware_check import check_meter_values from modules.common.modbus import ModbusDataType class Lovato(AbstractCounter): - def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: + def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_, fault_state: FaultState) -> None: self.client = client self.id = modbus_id + self.fault_state = fault_state def get_voltages(self) -> List[float]: return [val / 100 for val in self.client.read_input_registers( @@ -40,7 +43,7 @@ def get_currents(self) -> List[float]: def get_counter_state(self) -> CounterState: powers, power = self.get_power() - return CounterState( + counter_state = CounterState( power=power, voltages=self.get_voltages(), currents=self.get_currents(), @@ -48,3 +51,5 @@ def get_counter_state(self) -> CounterState: power_factors=self.get_power_factors(), frequency=self.get_frequency() ) + check_meter_values(counter_state, self.fault_state) + return counter_state diff --git a/packages/modules/common/mpm3pm.py b/packages/modules/common/mpm3pm.py index 71fdbcacfa..4791398dc7 100644 --- a/packages/modules/common/mpm3pm.py +++ b/packages/modules/common/mpm3pm.py @@ -4,13 +4,16 @@ from modules.common import modbus from modules.common.abstract_counter import AbstractCounter from modules.common.component_state import CounterState +from modules.common.fault_state import FaultState +from modules.common.hardware_check import check_meter_values from modules.common.modbus import ModbusDataType class Mpm3pm(AbstractCounter): - def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: + def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_, fault_state: FaultState) -> None: self.client = client self.id = modbus_id + self.fault_state = fault_state def get_voltages(self) -> List[float]: return [val / 10 for val in self.client.read_input_registers( @@ -51,7 +54,7 @@ def get_serial_number(self) -> str: def get_counter_state(self) -> CounterState: powers, power = self.get_power() - return CounterState( + counter_state = CounterState( voltages=self.get_voltages(), currents=self.get_currents(), powers=powers, @@ -62,3 +65,5 @@ def get_counter_state(self) -> CounterState: frequency=self.get_frequency(), serial_number=self.get_serial_number() ) + check_meter_values(counter_state, self.fault_state) + return counter_state diff --git a/packages/modules/common/sdm.py b/packages/modules/common/sdm.py index 2062d242f1..1b57a4dc6a 100644 --- a/packages/modules/common/sdm.py +++ b/packages/modules/common/sdm.py @@ -5,6 +5,8 @@ from modules.common import modbus from modules.common.abstract_counter import AbstractCounter from modules.common.component_state import CounterState +from modules.common.fault_state import FaultState +from modules.common.hardware_check import check_meter_values from modules.common.modbus import ModbusDataType @@ -49,8 +51,9 @@ def get_serial_number(self) -> str: class Sdm630_72(Sdm): - def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: + def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_, fault_state: FaultState) -> None: super().__init__(modbus_id, client) + self.fault_state = fault_state def get_currents(self) -> List[float]: self._ensure_min_time_between_queries() @@ -72,7 +75,7 @@ def get_voltages(self) -> List[float]: def get_counter_state(self) -> CounterState: powers, power = self.get_power() - return CounterState( + counter_state = CounterState( imported=self.get_imported(), exported=self.get_exported(), power=power, @@ -83,11 +86,14 @@ def get_counter_state(self) -> CounterState: frequency=self.get_frequency(), serial_number=self.get_serial_number() ) + check_meter_values(counter_state, self.fault_state) + return counter_state class Sdm120(Sdm): - def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: + def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_, fault_state: FaultState) -> None: super().__init__(modbus_id, client) + self.fault_state = fault_state def get_power(self) -> Tuple[List[float], float]: self._ensure_min_time_between_queries() @@ -109,7 +115,7 @@ def get_power_factors(self) -> List[float]: def get_counter_state(self) -> CounterState: powers, power = self.get_power() - return CounterState( + counter_state = CounterState( imported=self.get_imported(), exported=self.get_exported(), power=power, @@ -118,3 +124,5 @@ def get_counter_state(self) -> CounterState: frequency=self.get_frequency(), serial_number=self.get_serial_number() ) + check_meter_values(counter_state, self.fault_state) + return counter_state diff --git a/packages/modules/devices/openwb/openwb_flex/bat.py b/packages/modules/devices/openwb/openwb_flex/bat.py index 0989a6ef7b..3de35b94af 100644 --- a/packages/modules/devices/openwb/openwb_flex/bat.py +++ b/packages/modules/devices/openwb/openwb_flex/bat.py @@ -30,10 +30,9 @@ def initialize(self) -> None: self.__device_id: int = self.kwargs['device_id'] self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] factory = kit_bat_version_factory(self.component_config.configuration.version) - self.__client = factory(self.component_config.configuration.id, self.__tcp_client) + self.__client = factory(self.component_config.configuration.id, self.__tcp_client, self.fault_state) self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self): # TCP-Verbindung schließen möglichst bevor etwas anderes gemacht wird, um im Fehlerfall zu verhindern, diff --git a/packages/modules/devices/openwb/openwb_flex/counter.py b/packages/modules/devices/openwb/openwb_flex/counter.py index 271a075cec..1d37f2f72c 100644 --- a/packages/modules/devices/openwb/openwb_flex/counter.py +++ b/packages/modules/devices/openwb/openwb_flex/counter.py @@ -27,10 +27,9 @@ def initialize(self) -> None: self.__device_id: int = self.kwargs['device_id'] self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] factory = kit_counter_version_factory(self.component_config.configuration.version) - self.__client = factory(self.component_config.configuration.id, self.__tcp_client) + self.__client = factory(self.component_config.configuration.id, self.__tcp_client, self.fault_state) self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") self.store = get_counter_value_store(self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self): # TCP-Verbindung schließen möglichst bevor etwas anderes gemacht wird, um im Fehlerfall zu verhindern, diff --git a/packages/modules/devices/openwb/openwb_flex/inverter.py b/packages/modules/devices/openwb/openwb_flex/inverter.py index e87ad41cd6..668b734568 100644 --- a/packages/modules/devices/openwb/openwb_flex/inverter.py +++ b/packages/modules/devices/openwb/openwb_flex/inverter.py @@ -28,11 +28,10 @@ def initialize(self) -> None: self.__device_id: int = self.kwargs['device_id'] self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] factory = kit_inverter_version_factory(self.component_config.configuration.version) - self.__client = factory(self.component_config.configuration.id, self.__tcp_client) + self.__client = factory(self.component_config.configuration.id, self.__tcp_client, self.fault_state) self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") self.simulation = {} self.store = get_inverter_value_store(self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self) -> None: """ liest die Werte des Moduls aus. diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index 2b55a3a586..f8e9324ebc 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -59,9 +59,9 @@ def _evse_factory(self, client: Union[ModbusSerialClient_, ModbusTcpClient_], ev return None @staticmethod - def find_meter_client(meters: List[meter_config], client: Union[ModbusSerialClient_, ModbusTcpClient_]) -> METERS: + def find_meter_client(meters: List[meter_config], client: Union[ModbusSerialClient_, ModbusTcpClient_], fault_state: FaultState) -> METERS: for meter_type, modbus_id in meters: - meter_client = meter_type(modbus_id, client) + meter_client = meter_type(modbus_id, client, fault_state) with client: try: if meter_client.get_voltages()[0] > 200: @@ -93,12 +93,13 @@ def client_factory(local_charge_point_num: int, fault_state: FaultState, created_client_handler: Optional[ClientHandler] = None) -> ClientHandler: serial_client, evse_ids = get_modbus_client( - local_charge_point_num, created_client_handler) + local_charge_point_num, created_client_handler, fault_state) return ClientHandler(local_charge_point_num, serial_client, evse_ids, fault_state) def get_modbus_client(local_charge_point_num: int, - created_client_handler: Optional[ClientHandler] = None): + created_client_handler: Optional[ClientHandler] = None, + fault_state: Optional[FaultState] = None) -> Tuple[Union[ModbusSerialClient_, ModbusTcpClient_], List[int]]: tty_devices = list(Path("/dev/serial/by-path").glob("*")) log.debug("tty_devices"+str(tty_devices)) resolved_devices = [str(file.resolve()) for file in tty_devices] @@ -140,7 +141,7 @@ def get_modbus_client(local_charge_point_num: int, serial_client = ModbusSerialClient_(device) # Source immer an der Modbus-ID des Zählers fest machen, da diese immer fest ist. # Die USB-Anschlüsse können vertauscht sein. - detected_device = ClientHandler.find_meter_client(meters, serial_client) + detected_device = ClientHandler.find_meter_client(meters, serial_client, fault_state) if detected_device: break with ModifyLoglevelContext(log, logging.DEBUG): From e2cff289c90643d85e16fa0d6562a0cc10898adb Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 5 Feb 2025 14:28:23 +0100 Subject: [PATCH 09/13] fix --- packages/modules/common/hardware_check.py | 7 ++++- .../modules/common/hardware_check_test.py | 28 +++++++++---------- .../internal_chargepoint_handler/clients.py | 7 +++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index e2702cc2ed..f52428b93d 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -81,10 +81,15 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception): def request_and_check_hardware(self: ClientHandlerProtocol, fault_state: FaultState) -> Tuple[EvseState, CounterState]: + # evse_check_passed = False try: with self.client: evse_state = self.evse_client.get_evse_state() - evse_check_passed = True + evse_check_passed = True + # if self.evse_client.version > EVSE_MIN_FIRMWARE: + # evse_check_passed = True + # else: + # evse_check_passed = False except Exception as e: evse_check_passed = self.handle_exception(e) meter_check_passed, meter_error_msg, counter_state = self.check_meter() diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index d3c1b658ec..c5da13afe6 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -16,26 +16,25 @@ @pytest.mark.parametrize( - ("evse_side_effect, evse_return_value, meter_side_effect, meter_return_value, handle_exception_side_effect," + ("evse_side_effect, meter_side_effect, meter_return_value, handle_exception_side_effect," "handle_exception_return_value, client_spec, expected_error_msg"), - [pytest.param(Exception("Modbus"), None, None, [230]*3, None, False, ModbusSerialClient_, EVSE_BROKEN, + [pytest.param(Exception("Modbus"), None, [230]*3, None, False, ModbusSerialClient_, EVSE_BROKEN, id="EVSE defekt"), - pytest.param(Exception("Modbus"), None, None, [230, 0, 230], None, False, ModbusSerialClient_, + pytest.param(Exception("Modbus"), None, [230, 0, 230], None, False, ModbusSerialClient_, EVSE_BROKEN + " " + METER_BROKEN_VOLTAGES.format([230, 0, 230]) + OPEN_TICKET, id="EVSE defekt und Zähler eine Phase defekt"), - pytest.param(None, 18, Exception("Modbus"), None, None, None, + pytest.param(None, Exception("Modbus"), None, None, None, ModbusSerialClient_, METER_PROBLEM, id="Zähler falsch konfiguriert"), - pytest.param(Exception("Modbus"), None, Exception("Modbus"), None, None, False, ModbusSerialClient_, + pytest.param(Exception("Modbus"), Exception("Modbus"), None, None, False, ModbusSerialClient_, USB_ADAPTER_BROKEN, id="USB-Adapter defekt"), - pytest.param(Exception("Modbus"), None, Exception("Modbus"), None, None, False, ModbusTcpClient_, + pytest.param(Exception("Modbus"), Exception("Modbus"), None, None, False, ModbusTcpClient_, LAN_ADAPTER_BROKEN, id="LAN-Adapter defekt"), - pytest.param(Exception("Modbus"), None, Exception("Modbus"), None, + pytest.param(Exception("Modbus"), Exception("Modbus"), None, Exception(NO_CONNECTION.format(SAMPLE_IP, SAMPLE_PORT)), None, ModbusTcpClient_, NO_CONNECTION.format(SAMPLE_IP, SAMPLE_PORT), id="LAN-Adapter nicht erreichbar"), ] ) def test_hardware_check_fails(evse_side_effect, - evse_return_value, meter_side_effect, meter_return_value, handle_exception_side_effect, @@ -44,9 +43,8 @@ def test_hardware_check_fails(evse_side_effect, expected_error_msg, monkeypatch): # setup - evse_state = Mock(spec=EvseState, version=evse_return_value) - mock_evse_client = Mock(spec=Evse, get_evse_state=Mock(side_effect=evse_side_effect, return_value=evse_state)) - mock_evse_facotry = Mock(spec=Evse, return_value=mock_evse_client) + mock_evse_client = Mock(spec=Evse, version=18, get_evse_state=Mock(side_effect=[evse_side_effect])) + mock_evse_facotry = Mock(return_value=mock_evse_client) monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) counter_state_mock = Mock(spec=CounterState, side_effect=meter_side_effect, @@ -73,9 +71,8 @@ def test_hardware_check_fails(evse_side_effect, def test_hardware_check_succeeds(monkeypatch): # setup - evse_state = Mock(spec=EvseState, version=17) - mock_evse_client = Mock(spec=Evse, get_evse_state=Mock(return_value=evse_state)) - mock_evse_facotry = Mock(spec=Evse, return_value=mock_evse_client) + mock_evse_client = Mock(spec=Evse, get_evse_state=Mock(return_value=Mock(spec=EvseState)), version=17) + mock_evse_facotry = Mock(return_value=mock_evse_client) monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) counter_state_mock = Mock(spec=CounterState, voltages=[ @@ -132,7 +129,8 @@ def test_check_meter( if isinstance(serial_number, Exception): counter_state_mock = Mock(spec=CounterState, side_effect=serial_number) else: - counter_state_mock = Mock(spec=CounterState, serial_number=serial_number, voltages=voltages) + counter_state_mock = Mock(spec=CounterState, serial_number=serial_number, + voltages=voltages, currents=[0, 0, 0], powers=[0, 0, 0], power=0) mock_meter_client = Mock() mock_meter_client.get_counter_state.return_value = counter_state_mock MockClientHandlerProtocol.meter_client = mock_meter_client diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index f8e9324ebc..8e3cf04c9c 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -59,7 +59,9 @@ def _evse_factory(self, client: Union[ModbusSerialClient_, ModbusTcpClient_], ev return None @staticmethod - def find_meter_client(meters: List[meter_config], client: Union[ModbusSerialClient_, ModbusTcpClient_], fault_state: FaultState) -> METERS: + def find_meter_client(meters: List[meter_config], + client: Union[ModbusSerialClient_, ModbusTcpClient_], + fault_state: FaultState) -> METERS: for meter_type, modbus_id in meters: meter_client = meter_type(modbus_id, client, fault_state) with client: @@ -99,7 +101,8 @@ def client_factory(local_charge_point_num: int, def get_modbus_client(local_charge_point_num: int, created_client_handler: Optional[ClientHandler] = None, - fault_state: Optional[FaultState] = None) -> Tuple[Union[ModbusSerialClient_, ModbusTcpClient_], List[int]]: + fault_state: Optional[FaultState] = None) -> Tuple[Union[ModbusSerialClient_, ModbusTcpClient_], + List[int]]: tty_devices = list(Path("/dev/serial/by-path").glob("*")) log.debug("tty_devices"+str(tty_devices)) resolved_devices = [str(file.resolve()) for file in tty_devices] From fe31073e1bf5fc2c9259f22d7bc34c1b4daeecd4 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 6 Feb 2025 08:49:12 +0100 Subject: [PATCH 10/13] fix pytest --- .../modules/common/hardware_check_test.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index c5da13afe6..2748001853 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -44,25 +44,22 @@ def test_hardware_check_fails(evse_side_effect, monkeypatch): # setup mock_evse_client = Mock(spec=Evse, version=18, get_evse_state=Mock(side_effect=[evse_side_effect])) - mock_evse_facotry = Mock(return_value=mock_evse_client) - monkeypatch.setattr(ClientHandler, "_evse_factory", mock_evse_facotry) + monkeypatch.setattr(ClientHandler, "_evse_factory", Mock(return_value=mock_evse_client)) - counter_state_mock = Mock(spec=CounterState, side_effect=meter_side_effect, + counter_state_mock = Mock(spec=CounterState, voltages=meter_return_value, currents=[0, 0, 0], powers=[0, 0, 0], power=0, serial_number="1234") - mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock(return_value=counter_state_mock)) - mock_find_meter_client = Mock(spec=sdm.Sdm630_72, return_value=mock_meter_client) - monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) + mock_meter_client = Mock(spec=sdm.Sdm630_72, get_counter_state=Mock( + side_effect=meter_side_effect, return_value=counter_state_mock)) + monkeypatch.setattr(ClientHandler, "find_meter_client", Mock(return_value=mock_meter_client)) - handle_exception_mock = Mock(side_effect=handle_exception_side_effect, return_value=handle_exception_return_value) - monkeypatch.setattr(SeriesHardwareCheckMixin, "handle_exception", handle_exception_mock) + monkeypatch.setattr(SeriesHardwareCheckMixin, "handle_exception", Mock( + side_effect=handle_exception_side_effect, return_value=handle_exception_return_value)) - enter_mock = Mock(return_value=None) - exit_mock = Mock(return_value=True) - client = Mock(spec=client_spec, __enter__=enter_mock, __exit__=exit_mock) + client = Mock(spec=client_spec, __enter__=Mock(return_value=None), __exit__=Mock(return_value=None)) # execution and evaluation with pytest.raises(Exception, match=re.escape(expected_error_msg)): From e9727d5f8a07a5569d16c79f9450c65ca29977c0 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 6 Feb 2025 15:42:56 +0100 Subject: [PATCH 11/13] fixes --- packages/modules/common/hardware_check.py | 4 +++- packages/modules/internal_chargepoint_handler/clients.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index f52428b93d..538801e30c 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -22,6 +22,8 @@ "Funktionalität wird dadurch nicht beeinträchtigt!") EVSE_BROKEN = ("Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID. " "(Fehlermeldung nur relevant, wenn diese auf der Startseite oder im Status angezeigt wird.)") +METER_IMPLAUSIBLE_VALUE = ("Der Zähler hat einen unplausiblen Wert zurückgegeben: Leistungen {}W, Ströme {}A, " + "Spannungen {}V.") def check_meter_values(counter_state: CounterState, fault_state: Optional[FaultState] = None) -> None: @@ -42,7 +44,7 @@ def valid_voltage(voltage) -> bool: return METER_BROKEN_VOLTAGES.format(voltages) interdependent_values = [sum(counter_state.currents), counter_state.power] if not (all(v == 0 for v in interdependent_values) or all(v != 0 for v in interdependent_values)): - return METER_PROBLEM + return METER_IMPLAUSIBLE_VALUE.format(counter_state.powers, counter_state.currents, counter_state.voltages) return None diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index 8e3cf04c9c..d90ecb10a8 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -39,7 +39,7 @@ def __init__(self, self.local_charge_point_num = local_charge_point_num self.evse_client = self._evse_factory(client, evse_ids) self.meter_client = self.find_meter_client(CP0_METERS if self.local_charge_point_num == 0 else CP1_METERS, - client) + client, fault_state) self.request_and_check_hardware(fault_state) self.read_error = 0 From 00f1d9f929d8e948a0f30bd3d058bfae24e56274 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 25 Jun 2025 15:51:45 +0200 Subject: [PATCH 12/13] pytest --- packages/modules/common/sdm.py | 2 +- .../modules/devices/openwb/openwb_flex/bat.py | 1 - .../modules/devices/openwb/openwb_flex/counter.py | 1 - .../devices/openwb/openwb_flex/inverter.py | 1 - .../chargepoint_module.py | 15 +++++---------- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/modules/common/sdm.py b/packages/modules/common/sdm.py index 1b57a4dc6a..b77eec9eac 100644 --- a/packages/modules/common/sdm.py +++ b/packages/modules/common/sdm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import time -from typing import List, Tuple, Optional +from typing import List, Tuple from modules.common import modbus from modules.common.abstract_counter import AbstractCounter diff --git a/packages/modules/devices/openwb/openwb_flex/bat.py b/packages/modules/devices/openwb/openwb_flex/bat.py index 3de35b94af..b8a873ebc0 100644 --- a/packages/modules/devices/openwb/openwb_flex/bat.py +++ b/packages/modules/devices/openwb/openwb_flex/bat.py @@ -5,7 +5,6 @@ from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor -from modules.common.fault_state import ComponentInfo, FaultState from modules.common.lovato import Lovato from modules.common.mpm3pm import Mpm3pm from modules.common.sdm import Sdm120 diff --git a/packages/modules/devices/openwb/openwb_flex/counter.py b/packages/modules/devices/openwb/openwb_flex/counter.py index 1d37f2f72c..172012a13c 100644 --- a/packages/modules/devices/openwb/openwb_flex/counter.py +++ b/packages/modules/devices/openwb/openwb_flex/counter.py @@ -4,7 +4,6 @@ from modules.common import modbus from modules.common.abstract_device import AbstractCounter from modules.common.component_type import ComponentDescriptor -from modules.common.fault_state import ComponentInfo, FaultState from modules.common.mpm3pm import Mpm3pm from modules.common.b23 import B23 from modules.common.simcount import SimCounter diff --git a/packages/modules/devices/openwb/openwb_flex/inverter.py b/packages/modules/devices/openwb/openwb_flex/inverter.py index 668b734568..7d79eee8c7 100644 --- a/packages/modules/devices/openwb/openwb_flex/inverter.py +++ b/packages/modules/devices/openwb/openwb_flex/inverter.py @@ -5,7 +5,6 @@ from modules.common.abstract_device import AbstractInverter from modules.common.component_state import InverterState from modules.common.component_type import ComponentDescriptor -from modules.common.fault_state import ComponentInfo, FaultState from modules.common.lovato import Lovato from modules.common.sdm import Sdm120 from modules.common.simcount import SimCounter diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index 9e37025a47..b6b6096265 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -57,16 +57,11 @@ def __init__(self, local_charge_point_num: int, self._client.evse_client.activate_precise_current() self._precise_current = self._client.evse_client.is_precise_current_active() + self.version = SubData.system_data["system"].data["version"] + self.current_branch = SubData.system_data["system"].data["current_branch"] + self.current_commit = SubData.system_data["system"].data["current_commit"] -<< << << < HEAD - self.max_evse_current = self._client.evse_client.get_max_current() - self.version = SubData.system_data["system"].data["version"] - self.current_branch = SubData.system_data["system"].data["current_branch"] - self.current_commit = SubData.system_data["system"].data["current_commit"] -== == == = ->>>>>> > 1ece26b0c(evse max current) - - def set_current(self, current: float) -> None: + def set_current(self, current: float) -> None: with SingleComponentUpdateContext(self.fault_state, update_always=False): formatted_current = round(current*100) if self._precise_current else round(current) if self.set_current_evse != formatted_current: @@ -122,7 +117,7 @@ def store_state(chargepoint_state: ChargepointState) -> None: rfid=last_tag, evse_current=self.set_current_evse, serial_number=counter_state.serial_number, - max_evse_current=evse_state.max_current + max_evse_current=evse_state.max_current, version=self.version, current_branch=self.current_branch, current_commit=self.current_commit From f92a650afc2aab00ab53fb2399121c44b1b5b1c5 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 7 Jul 2025 12:09:49 +0200 Subject: [PATCH 13/13] remove dead code --- packages/modules/common/hardware_check.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index 538801e30c..5515f41547 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -83,15 +83,10 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception): def request_and_check_hardware(self: ClientHandlerProtocol, fault_state: FaultState) -> Tuple[EvseState, CounterState]: - # evse_check_passed = False try: with self.client: evse_state = self.evse_client.get_evse_state() evse_check_passed = True - # if self.evse_client.version > EVSE_MIN_FIRMWARE: - # evse_check_passed = True - # else: - # evse_check_passed = False except Exception as e: evse_check_passed = self.handle_exception(e) meter_check_passed, meter_error_msg, counter_state = self.check_meter()