diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index 8da14dcfd0..55e7784535 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -1,17 +1,14 @@ import logging import time -from typing import List, NamedTuple, Tuple, Union +from typing import Tuple from modules.common.abstract_chargepoint import AbstractChargepoint from modules.common.component_context import SingleComponentUpdateContext from modules.common.component_state import ChargepointState from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.modbus import ModbusSerialClient_ -from modules.common import sdm -from modules.common import evse -from modules.common import b23 from modules.common.store import get_internal_chargepoint_value_store +from modules.internal_chargepoint_handler.clients import ClientConfig, ClientFactory log = logging.getLogger(__name__) @@ -21,60 +18,6 @@ log.info("failed to import RPi.GPIO! maybe we are not running on a pi") -CONNECTION_MODULES = Union[sdm.Sdm630, b23.B23] - - -class ClientConfig: - def __init__(self, id: int, serial_client: ModbusSerialClient_) -> None: - self.id = id - self.serial_client = serial_client - - -class ClientFactory: - def __init__(self, local_charge_point_num: int, serial_client: ModbusSerialClient_) -> None: - self.local_charge_point_num = local_charge_point_num - self.meter_client, self.evse_client = self.__factory(serial_client) - self.read_error = 0 - - def __factory(self, serial_client: ModbusSerialClient_) -> Tuple[CONNECTION_MODULES, evse.Evse]: - meter_config = NamedTuple("MeterConfig", [('type', CONNECTION_MODULES), ('modbus_id', int)]) - meter_configuration_options = [ - [meter_config(sdm.Sdm630, modbus_id=5), - meter_config(sdm.Sdm630, modbus_id=105), - meter_config(b23.B23, modbus_id=201)], - [meter_config(sdm.Sdm630, modbus_id=6), meter_config(sdm.Sdm630, modbus_id=106)] - ] - - def _check_meter(serial_client: ModbusSerialClient_, meters: List[meter_config]): - for meter_type, modbus_id in meters: - try: - meter_client = meter_type(modbus_id, serial_client) - if meter_client.get_voltages()[0] > 20: - return meter_client - except Exception: - pass - else: - raise Exception("Es konnte keines der Meter in "+str(meters)+" zugeordnet werden.") - - meter_client = _check_meter(serial_client, meter_configuration_options[self.local_charge_point_num]) - evse_client = evse.Evse(self.local_charge_point_num + 1, serial_client) - return meter_client, evse_client - - def get_pins_phase_switch(self, new_phases: int) -> Tuple[int, int]: - # return gpio_cp, gpio_relay - if self.local_charge_point_num == 0: - return 22, 29 if new_phases == 1 else 37 - else: - return 15, 11 if new_phases == 1 else 13 - - def get_pins_cp_interruption(self) -> int: - # return gpio_cp, gpio_relay - if self.local_charge_point_num == 0: - return 22 - else: - return 15 - - class ChargepointModule(AbstractChargepoint): PLUG_STANDBY_POWER_THRESHOLD = 10 @@ -89,6 +32,7 @@ def __init__(self, config: ClientConfig, parent_hostname: str) -> None: self.__client = ClientFactory(self.config.id, self.config.serial_client) time.sleep(0.1) version = self.__client.evse_client.get_firmware_version() + self._check_hardware() if version < 17: self._precise_current = False else: @@ -164,6 +108,15 @@ def get_values(self, phase_switch_cp_active: bool, last_tag: str) -> Tuple[Charg self.store.update() return chargepoint_state, self.set_current_evse + def _check_hardware(self): + if self.__client.meter_client is None and self.__client.evse_client is None: + raise Exception("Auslesen von Zähler UND Evse nicht möglich. Vermutlich ist der USB-Adapter defekt.") + if self.__client.meter_client is None: + raise Exception("Der Zähler antwortet nicht. Vermutlich ist der Zähler falsch konfiguriert oder defekt.") + if self.__client.evse_client is None: + raise Exception( + "Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID.") + def perform_phase_switch(self, phases_to_use: int, duration: int) -> None: gpio_cp, gpio_relay = self.__client.get_pins_phase_switch(phases_to_use) with SingleComponentUpdateContext(self.component_info): diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py new file mode 100644 index 0000000000..453304bc30 --- /dev/null +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -0,0 +1,115 @@ +import logging +from pathlib import Path +from typing import List, NamedTuple, Optional, Tuple, Union + +from modules.common.modbus import ModbusSerialClient_ +from modules.common import mpm3pm, sdm +from modules.common import evse +from modules.common import b23 + +log = logging.getLogger(__name__) + + +BUS_SOURCES = ["/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyACM0", "/dev/serial0"] + +METERS = Union[mpm3pm.Mpm3pm, sdm.Sdm630, b23.B23] +meter_config = NamedTuple("meter_config", [('type', METERS), ('modbus_id', int)]) +CP0_METERS = [meter_config(mpm3pm.Mpm3pm, modbus_id=5), + meter_config(sdm.Sdm630, modbus_id=105), + meter_config(b23.B23, modbus_id=201)] + +CP1_METERS = [meter_config(mpm3pm.Mpm3pm, modbus_id=6), meter_config(sdm.Sdm630, modbus_id=106)] + +EVSE_ID = [1, 2] +EVSE_MIN_FIRMWARE = 7 + + +class ClientConfig: + def __init__(self, id: int, serial_client: ModbusSerialClient_) -> None: + self.id = id + self.serial_client = serial_client + + +class ClientFactory: + def __init__(self, local_charge_point_num: int, serial_client: ModbusSerialClient_) -> None: + self.local_charge_point_num = local_charge_point_num + self.evse_client = self.__evse_factory(serial_client) + self.meter_client = self.find_meter_client(CP0_METERS if self.local_charge_point_num == 0 else CP1_METERS, + serial_client) + self.read_error = 0 + + def __evse_factory(self, serial_client: ModbusSerialClient_) -> evse.Evse: + for modbus_id in EVSE_ID: + try: + evse_client = evse.Evse(modbus_id, serial_client) + with serial_client: + if evse_client.get_firmware_version() > EVSE_MIN_FIRMWARE: + log.debug(serial_client) + log.error("Modbus-ID der EVSE an LP"+str(self.local_charge_point_num)+": "+str(modbus_id)) + return evse_client + except Exception: + pass + else: + return None + + @staticmethod + def find_meter_client(meters: List[meter_config], serial_client: ModbusSerialClient_) -> METERS: + for meter_type, modbus_id in meters: + try: + meter_client = meter_type(modbus_id, serial_client) + with serial_client: + if meter_client.get_voltages()[0] > 200: + log.error("Verbauter Zähler: "+str(meter_type)+" mit Modbus-ID: "+str(modbus_id)) + return meter_client + except Exception: + log.debug(serial_client) + log.debug(f"Zähler {meter_type} mit Modbus-ID:{modbus_id} antwortet nicht.") + else: + return None + + def get_pins_phase_switch(self, new_phases: int) -> Tuple[int, int]: + # return gpio_cp, gpio_relay + if self.local_charge_point_num == 0: + return 22, 29 if new_phases == 1 else 37 + else: + return 15, 11 if new_phases == 1 else 13 + + def get_pins_cp_interruption(self) -> int: + # return gpio_cp, gpio_relay + if self.local_charge_point_num == 0: + return 22 + else: + return 15 + + +def serial_client_factory(local_charge_point_num: int, + created_client: Optional[ModbusSerialClient_] = None) -> ModbusSerialClient_: + 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] + log.debug("resolved_devices"+str(resolved_devices)) + counter = len(resolved_devices) + if counter == 1 and resolved_devices[0] in BUS_SOURCES: + if local_charge_point_num == 0: + log.error("LP0 Device: "+str(resolved_devices[0])) + return ModbusSerialClient_(resolved_devices[0]) + else: + # Don't create two clients for one source! + log.error("LP1 gleiches Device wie LP0") + return created_client + elif counter > 1: + log.error("found "+str(counter)+" possible usb devices: "+str(resolved_devices)) + if local_charge_point_num == 0: + meters = CP0_METERS + else: + meters = CP1_METERS + for device in BUS_SOURCES: + if device in resolved_devices: + 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 = ClientFactory.find_meter_client(meters, serial_client) + if detected_device: + break + log.error("LP"+str(local_charge_point_num)+" Device: "+str(device)) + return serial_client diff --git a/packages/modules/internal_chargepoint_handler/internal_chargepoint_handler.py b/packages/modules/internal_chargepoint_handler/internal_chargepoint_handler.py index f7f06d038f..8097c0672e 100644 --- a/packages/modules/internal_chargepoint_handler/internal_chargepoint_handler.py +++ b/packages/modules/internal_chargepoint_handler/internal_chargepoint_handler.py @@ -15,6 +15,7 @@ from modules.common.component_state import ChargepointState from modules.common.modbus import ModbusSerialClient_ from modules.internal_chargepoint_handler import chargepoint_module +from modules.internal_chargepoint_handler.clients import serial_client_factory from modules.internal_chargepoint_handler.socket import Socket from modules.internal_chargepoint_handler.chargepoint_module import ClientConfig from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import ( @@ -41,7 +42,8 @@ def update_values(self, chargepoint_state: ChargepointState, heartbeat_expired: # iterate over counterstate vars_old_counter_state = vars(self.old_chargepoint_state) for key, value in vars(chargepoint_state).items(): - if value != vars_old_counter_state[key]: + # Zählerstatus immer publishen für Ladelog-Einträge + if value != vars_old_counter_state[key] or key == "imported": self._pub_values_to_2(key, value) else: # Bei Neustart alles publishen @@ -133,13 +135,15 @@ def __init__(self, with SingleComponentUpdateContext(ComponentInfo(parent_cp0, "Interner Ladepunkt 1", "vehicle", parent_hostname=global_data.parent_ip)): # Allgemeine Fehlermeldungen an LP 1: - self.serial_client = ModbusSerialClient_(self.detect_modbus_usb_port()) - self.cp0 = HandlerChargepoint(self.serial_client, 0, mode, global_data, parent_cp0) + self.cp0_serial_client = serial_client_factory(0) + self.cp0 = HandlerChargepoint(self.cp0_serial_client, 0, mode, global_data, parent_cp0) if mode == InternalChargepointMode.DUO.value: log.debug("Zweiter Ladepunkt für Duo konfiguriert.") - self.cp1 = HandlerChargepoint(self.serial_client, 1, mode, global_data, parent_cp1) + self.cp1_serial_client = serial_client_factory(1, self.cp0_serial_client) + self.cp1 = HandlerChargepoint(self.cp1_serial_client, 1, mode, global_data, parent_cp1) else: self.cp1 = None + self.cp1_serial_client = None self.init_gpio() def init_gpio(self) -> None: @@ -157,21 +161,28 @@ def init_gpio(self) -> None: GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP) def loop(self) -> None: + def _loop(): + while True: + self.heartbeat = True + if self.event_stop.is_set(): + break + log.debug("***Start***") + data = copy.deepcopy(SubData.internal_chargepoint_data) + log.debug(data) + log.setLevel(SubData.system_data["system"].data["debug_level"]) + self.cp0.update(data["global_data"], data["cp0"], data["rfid_data"]) + if self.cp1: + self.cp1.update(data["global_data"], data["cp1"], data["rfid_data"]) + time.sleep(1.1) with SingleComponentUpdateContext(self.cp0.module.component_info): # Allgemeine Fehlermeldungen an LP 1: - with self.serial_client: - while True: - self.heartbeat = True - if self.event_stop.is_set(): - break - log.debug("***Start***") - data = copy.deepcopy(SubData.internal_chargepoint_data) - log.debug(data) - log.setLevel(SubData.system_data["system"].data["debug_level"]) - self.cp0.update(data["global_data"], data["cp0"], data["rfid_data"]) - if self.cp1: - self.cp1.update(data["global_data"], data["cp1"], data["rfid_data"]) - time.sleep(1.1) + if self.cp1_serial_client is None: + with self.cp0_serial_client: + _loop() + else: + with self.cp0_serial_client: + with self.cp1_serial_client: + _loop() def detect_modbus_usb_port(self) -> str: """guess USB/modbus device name"""