Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal chargepoint #905

Merged
merged 6 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 12 additions & 59 deletions packages/modules/internal_chargepoint_handler/chargepoint_module.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand All @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
115 changes: 115 additions & 0 deletions packages/modules/internal_chargepoint_handler/clients.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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"""
Expand Down