diff --git a/packages/control/algorithm/additional_current_test.py b/packages/control/algorithm/additional_current_test.py index 35c78396f8..1336f19718 100644 --- a/packages/control/algorithm/additional_current_test.py +++ b/packages/control/algorithm/additional_current_test.py @@ -2,7 +2,8 @@ import pytest from control.algorithm import additional_current -from control.chargepoint.chargepoint import Chargepoint, ChargepointData, Set +from control.chargepoint.chargepoint import Chargepoint, ChargepointData +from control.chargepoint.chargepoint_data import Set from control.chargepoint.control_parameter import ControlParameter from control.ev import ChargeTemplate, Ev from control.loadmanagement import LimitingValue diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index 5c39b291a4..3debfd7340 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -7,7 +7,8 @@ from control import data from control.algorithm import filter_chargepoints from control.chargemode import Chargemode -from control.chargepoint.chargepoint import Chargepoint, ChargepointData, Log, Set +from control.chargepoint.chargepoint import Chargepoint, ChargepointData +from control.chargepoint.chargepoint_data import Log, Set from control.counter_all import CounterAll from control.ev import ControlParameter, Ev, EvData, Get diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 7971d86033..006727fb4e 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -4,7 +4,8 @@ from control.algorithm import surplus_controlled from control.algorithm.surplus_controlled import SurplusControlled -from control.chargepoint.chargepoint import Chargepoint, ChargepointData, Get, Set +from control.chargepoint.chargepoint import Chargepoint, ChargepointData +from control.chargepoint.chargepoint_data import Get, Set from control.chargepoint.control_parameter import ControlParameter from control.ev import ChargeTemplate, Ev diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index b760e28044..c8f164593d 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -14,24 +14,24 @@ Tag-Liste: Tags, mit denen der Ladepunkt freigeschaltet werden kann. Ist diese leer, kann mit jedem Tag der Ladepunkt freigeschaltet werden. """ -from dataclasses import asdict, dataclass, field +from dataclasses import asdict import dataclasses import logging import threading import traceback -from typing import Dict, List, Optional, Tuple +from typing import Dict, Optional, Tuple from control import chargelog from control import cp_interruption from control import data from control.chargemode import Chargemode +from control.chargepoint.chargepoint_data import ChargepointData, ConnectedConfig, ConnectedInfo, ConnectedSoc, Get from control.chargepoint.chargepoint_template import CpTemplate -from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory +from control.chargepoint.control_parameter import ControlParameter +from control.chargepoint.rfid import ChargepointRfidMixin from control.ev import Ev from control import phase_switch from control.chargepoint.chargepoint_state import ChargepointState -from dataclass_utils.factories import empty_dict_factory, currents_list_factory, voltages_list_factory -from helpermodules.constants import NO_ERROR from helpermodules.phase_mapping import convert_single_evu_phase_to_cp_phase from helpermodules.pub import Pub from helpermodules import timecheck @@ -59,202 +59,7 @@ def get_chargepoint_get_default() -> Dict: log = logging.getLogger(__name__) -@dataclass -class ConnectedSoc: - fault_str: str = NO_ERROR - fault_state: int = 0 - range_charged: float = 0 - range_unit: str = "km" - range: float = 0 - soc: int = 0 - timestamp: Optional[str] = None - - -@dataclass -class ConnectedSocConfig: - configured: str = "" - - -@dataclass -class ConnectedInfo: - id: int = 0 - name: str = "Ladepunkt" - - -@dataclass -class ConnectedConfig: - average_consumption: float = 17 - charge_template: int = 0 - chargemode: str = "stop" - current_plan: Optional[int] = 0 - ev_template: int = 0 - priority: bool = False - time_charging_in_use: bool = False - - -def connected_config_factory() -> ConnectedConfig: - return ConnectedConfig() - - -def connected_info_factory() -> ConnectedInfo: - return ConnectedInfo() - - -def connected_soc_factory() -> ConnectedSoc: - return ConnectedSoc() - - -@dataclass -class ConnectedVehicle: - config: ConnectedConfig = field(default_factory=connected_config_factory) - info: ConnectedInfo = field(default_factory=connected_info_factory) - soc: ConnectedSoc = field(default_factory=connected_soc_factory) - - -@dataclass -class Log: - chargemode_log_entry: str = "_" - imported_at_mode_switch: float = 0 - imported_at_plugtime: float = 0 - imported_since_mode_switch: float = 0 - imported_since_plugged: float = 0 - range_charged: float = 0 - time_charged: float = 0 - timestamp_start_charging: Optional[float] = None - - -def connected_vehicle_factory() -> ConnectedVehicle: - return ConnectedVehicle() - - -@dataclass -class Get: - charge_state: bool = False - connected_vehicle: ConnectedVehicle = field(default_factory=connected_vehicle_factory) - currents: List[float] = field(default_factory=currents_list_factory) - daily_imported: float = 0 - daily_exported: float = 0 - evse_current: Optional[float] = None - exported: float = 0 - fault_str: str = NO_ERROR - fault_state: int = 0 - imported: float = 0 - phases_in_use: int = 0 - plug_state: bool = False - power: float = 0 - rfid_timestamp: Optional[float] = None - rfid: Optional[str] = None - soc: Optional[float] = None - soc_timestamp: Optional[int] = None - state_str: Optional[str] = None - voltages: List[float] = field(default_factory=voltages_list_factory) - - -def ev_factory() -> Ev: - return Ev(0) - - -def log_factory() -> Log: - return Log() - - -@dataclass -class Set: - change_ev_permitted: bool = False - charging_ev: int = -1 - charging_ev_prev: int = -1 - current: float = 0 - energy_to_charge: float = 0 - loadmanagement_available: bool = True - log: Log = field(default_factory=log_factory) - manual_lock: bool = False - phases_to_use: int = 0 - plug_state_prev: bool = False - plug_time: Optional[float] = None - required_power: float = 0 - rfid: Optional[str] = None - target_current: float = 0 # Sollstrom aus fest vorgegebener Stromstärke - charging_ev_data: Ev = field(default_factory=ev_factory) - - -@dataclass -class Config: - configuration: Dict = field(default_factory=empty_dict_factory) - ev: int = 0 - name: str = "Standard-Ladepunkt" - type: Optional[str] = None - template: int = 0 - connected_phases: int = 3 - phase_1: int = 0 - auto_phase_switch_hw: bool = False - control_pilot_interruption_hw: bool = False - id: int = 0 - - def __post_init__(self): - self.event_update_state: threading.Event - - @property - def ev(self) -> int: - return self._ev - - @ev.setter - def ev(self, ev: int): - self._ev = ev - try: - self.event_update_state.set() - except AttributeError: - pass - - def __getstate__(self): - state = self.__dict__.copy() - if state.get('event_update_state'): - del state['event_update_state'] - return state - - def __setstate__(self, state): - self.__dict__.update(state) - - -def get_factory() -> Get: - return Get() - - -def set_factory() -> Set: - return Set() - - -def config_factory() -> Config: - return Config() - - -@dataclass -class ChargepointData: - control_parameter: ControlParameter = field(default_factory=control_parameter_factory) - get: Get = field(default_factory=get_factory) - set: Set = field(default_factory=set_factory) - config: Config = field(default_factory=config_factory) - - def set_event(self, event: Optional[threading.Event] = None) -> None: - self.event_update_state = event - if event: - self.config.event_update_state = event - - def __getstate__(self): - # Copy the object's state from self.__dict__ which contains - # all our instance attributes. Always use the dict.copy() - # method to avoid modifying the original state. - state = self.__dict__.copy() - # Remove the unpicklable entries. - if state.get('event_update_state'): - del state['event_update_state'] - return state - - def __setstate__(self, state): - # Restore instance attributes (i.e., filename and lineno). - self.__dict__.update(state) - - -class Chargepoint: +class Chargepoint(ChargepointRfidMixin): """ geht alle Ladepunkte durch, prüft, ob geladen werden darf und ruft die Funktion des angesteckten Autos auf. """ MAX_FAILED_PHASE_SWITCHES = 3 @@ -487,12 +292,14 @@ def prepare_cp(self) -> Tuple[int, Optional[str]]: try: # Für Control-Pilot-Unterbrechung set current merken. self.set_current_prev = self.data.set.current - self.__validate_rfid() + self._validate_rfid() charging_possible, message = self.is_charging_possible() if charging_possible: if self.data.get.rfid is not None: self._link_rfid_to_cp() - num, message_ev = self.template.get_ev(self.data.set.rfid, self.data.config.ev) + num, message_ev = self.template.get_ev(self.data.set.rfid, + self.data.get.vehicle_id, + self.data.config.ev) if message_ev: message = message_ev if num != -1: @@ -760,81 +567,6 @@ def check_min_max_current(self, required_current: float, phases: int, pv: bool = self.set_state_and_log(msg) return required_current - def _link_rfid_to_cp(self) -> None: - """ Wenn der Tag einem EV zugeordnet worden ist, wird der Tag unter set/rfid abgelegt und muss der Timer - zurückgesetzt werden. - """ - rfid = self.data.get.rfid - cp2_num = self.find_duo_partner() - # Tag wird diesem LP der Duo zugewiesen oder es ist keine Duo - if ((cp2_num is not None and - # EV am anderen Ladepunkt, am eigenen wurde zuerst angesteckt - ((data.data.cp_data[f"cp{cp2_num}"].data.get.plug_state and - timecheck.get_difference(self.data.set.plug_time, - data.data.cp_data[f"cp{cp2_num}"].data.set.plug_time) < 0) or - # kein EV am anderen Duo-Ladepunkt - data.data.cp_data[f"cp{cp2_num }"].data.get.plug_state is False)) or - # keine Duo - cp2_num is None): - self.data.set.rfid = rfid - Pub().pub("openWB/chargepoint/"+str(self.num)+"/set/rfid", rfid) - self.chargepoint_module.clear_rfid() - - self.data.get.rfid = None - Pub().pub("openWB/chargepoint/"+str(self.num)+"/get/rfid", None) - self.data.get.rfid_timestamp = None - Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", None) - - def __validate_rfid(self) -> None: - """Prüft, dass der Tag an diesem Ladepunkt gültig ist und dass dieser innerhalb von 5 Minuten einem EV - zugeordnet wird. - """ - msg = "" - if self.data.get.rfid is not None: - if data.data.optional_data.data.rfid.active: - if (self.data.set.log.imported_at_plugtime == 0 or - self.data.set.log.imported_at_plugtime == self.data.get.imported): - rfid = self.data.get.rfid - if self.data.get.rfid_timestamp is None: - self.data.get.rfid_timestamp = timecheck.create_timestamp() - Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", - self.data.get.rfid_timestamp) - return - else: - if (timecheck.check_timestamp(self.data.get.rfid_timestamp, 300) or - self.data.get.plug_state is True): - return - else: - self.data.get.rfid_timestamp = None - if rfid in self.template.data.valid_tags or len(self.template.data.valid_tags) == 0: - Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", None) - msg = "Es ist in den letzten 5 Minuten kein EV angesteckt worden, dem " \ - f"der ID-Tag {rfid} zugeordnet werden kann. Daher wird dieser verworfen." - else: - msg = f"Der ID-Tag {rfid} ist an diesem Ladepunkt nicht gültig." - else: - msg = "Nach Ladestart wird kein anderer ID-Tag akzeptiert." - else: - msg = "Identifikation von Fahrzeugen ist nicht aktiviert." - self.data.get.rfid = None - Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid", None) - self.chargepoint_module.clear_rfid() - self.set_state_and_log(msg) - - def find_duo_partner(self) -> Optional[int]: - try: - # Wurde ein zweiter Ladepunkt an einer Duo konfiguriert? - if self.data.config.type == "external_openwb" or self.data.config.type == "internal_openwb": - for cp2 in data.data.cp_data.values(): - if (cp2.num != self.num and - self.data.config.configuration["ip_address"] == cp2.data.config.configuration[ - "ip_address"]): - return cp2.num - return None - except Exception: - log.exception("Fehler in der allgemeinen Ladepunkt-Klasse") - return None - def set_required_currents(self, required_current: float) -> None: control_parameter = self.data.control_parameter try: @@ -851,10 +583,12 @@ def set_required_currents(self, required_current: float) -> None: def update_ev(self, ev_list: Dict[str, Ev]) -> None: # Für Control-Pilot-Unterbrechung set current merken. self.set_current_prev = self.data.set.current - self.__validate_rfid() + self._validate_rfid() charging_possible = self.is_charging_possible()[0] if charging_possible: - vehicle = self.template.get_ev(self.data.get.rfid or self.data.set.rfid, self.data.config.ev)[0] + vehicle = self.template.get_ev(self.data.get.rfid or self.data.set.rfid, + self.data.get.vehicle_id, + self.data.config.ev)[0] charging_ev = self._get_charging_ev(vehicle, ev_list) self._pub_connected_vehicle(charging_ev) else: diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py new file mode 100644 index 0000000000..e9ab8c6f84 --- /dev/null +++ b/packages/control/chargepoint/chargepoint_data.py @@ -0,0 +1,219 @@ +from dataclasses import dataclass, field +import threading +from typing import Dict, List, Optional, Protocol +from control.chargepoint.chargepoint_template import CpTemplate + +from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory +from control.ev import Ev +from dataclass_utils.factories import currents_list_factory, empty_dict_factory, voltages_list_factory +from helpermodules.constants import NO_ERROR +from modules.common.abstract_chargepoint import AbstractChargepoint + + +@dataclass +class ConnectedSoc: + fault_str: str = NO_ERROR + fault_state: int = 0 + range_charged: float = 0 + range_unit: str = "km" + range: float = 0 + soc: int = 0 + timestamp: Optional[str] = None + + +@dataclass +class ConnectedSocConfig: + configured: str = "" + + +@dataclass +class ConnectedInfo: + id: int = 0 + name: str = "Ladepunkt" + + +@dataclass +class ConnectedConfig: + average_consumption: float = 17 + charge_template: int = 0 + chargemode: str = "stop" + current_plan: Optional[int] = 0 + ev_template: int = 0 + priority: bool = False + time_charging_in_use: bool = False + + +def connected_config_factory() -> ConnectedConfig: + return ConnectedConfig() + + +def connected_info_factory() -> ConnectedInfo: + return ConnectedInfo() + + +def connected_soc_factory() -> ConnectedSoc: + return ConnectedSoc() + + +@dataclass +class ConnectedVehicle: + config: ConnectedConfig = field(default_factory=connected_config_factory) + info: ConnectedInfo = field(default_factory=connected_info_factory) + soc: ConnectedSoc = field(default_factory=connected_soc_factory) + + +@dataclass +class Log: + chargemode_log_entry: str = "_" + imported_at_mode_switch: float = 0 + imported_at_plugtime: float = 0 + imported_since_mode_switch: float = 0 + imported_since_plugged: float = 0 + range_charged: float = 0 + time_charged: float = 0 + timestamp_start_charging: Optional[float] = None + + +def connected_vehicle_factory() -> ConnectedVehicle: + return ConnectedVehicle() + + +@dataclass +class Get: + charge_state: bool = False + connected_vehicle: ConnectedVehicle = field(default_factory=connected_vehicle_factory) + currents: List[float] = field(default_factory=currents_list_factory) + daily_imported: float = 0 + daily_exported: float = 0 + evse_current: Optional[float] = None + exported: float = 0 + fault_str: str = NO_ERROR + fault_state: int = 0 + imported: float = 0 + phases_in_use: int = 0 + plug_state: bool = False + power: float = 0 + rfid_timestamp: Optional[float] = None + rfid: Optional[int] = None + soc: Optional[float] = None + soc_timestamp: Optional[int] = None + state_str: Optional[str] = None + vehicle_id: Optional[str] = None + voltages: List[float] = field(default_factory=voltages_list_factory) + + +def ev_factory() -> Ev: + return Ev(0) + + +def log_factory() -> Log: + return Log() + + +@dataclass +class Set: + change_ev_permitted: bool = False + charging_ev: int = -1 + charging_ev_prev: int = -1 + current: float = 0 + energy_to_charge: float = 0 + loadmanagement_available: bool = True + log: Log = field(default_factory=log_factory) + manual_lock: bool = False + phases_to_use: int = 0 + plug_state_prev: bool = False + plug_time: Optional[float] = None + required_power: float = 0 + rfid: Optional[str] = None + target_current: float = 0 # Sollstrom aus fest vorgegebener Stromstärke + charging_ev_data: Ev = field(default_factory=ev_factory) + + +@dataclass +class Config: + configuration: Dict = field(default_factory=empty_dict_factory) + ev: int = 0 + name: str = "Standard-Ladepunkt" + type: Optional[str] = None + template: int = 0 + connected_phases: int = 3 + phase_1: int = 0 + auto_phase_switch_hw: bool = False + control_pilot_interruption_hw: bool = False + id: int = 0 + + def __post_init__(self): + self.event_update_state: threading.Event + + @property + def ev(self) -> int: + return self._ev + + @ev.setter + def ev(self, ev: int): + self._ev = ev + try: + self.event_update_state.set() + except AttributeError: + pass + + def __getstate__(self): + state = self.__dict__.copy() + if state.get('event_update_state'): + del state['event_update_state'] + return state + + def __setstate__(self, state): + self.__dict__.update(state) + + +def get_factory() -> Get: + return Get() + + +def set_factory() -> Set: + return Set() + + +def config_factory() -> Config: + return Config() + + +@dataclass +class ChargepointData: + control_parameter: ControlParameter = field(default_factory=control_parameter_factory) + get: Get = field(default_factory=get_factory) + set: Set = field(default_factory=set_factory) + config: Config = field(default_factory=config_factory) + + def set_event(self, event: Optional[threading.Event] = None) -> None: + self.event_update_state = event + if event: + self.config.event_update_state = event + + def __getstate__(self): + # Copy the object's state from self.__dict__ which contains + # all our instance attributes. Always use the dict.copy() + # method to avoid modifying the original state. + state = self.__dict__.copy() + # Remove the unpicklable entries. + if state.get('event_update_state'): + del state['event_update_state'] + return state + + def __setstate__(self, state): + # Restore instance attributes (i.e., filename and lineno). + self.__dict__.update(state) + + +class ChargepointProtocol(Protocol): + @property + def template(self) -> CpTemplate: ... + @property + def chargepoint_module(self) -> AbstractChargepoint: ... + @property + def num(self) -> int: ... + @property + def set_current_prev(self) -> float: ... + @property + def data(self) -> ChargepointData: ... diff --git a/packages/control/chargepoint/chargepoint_template.py b/packages/control/chargepoint/chargepoint_template.py index f368ed49c5..736a0e6f48 100644 --- a/packages/control/chargepoint/chargepoint_template.py +++ b/packages/control/chargepoint/chargepoint_template.py @@ -68,7 +68,7 @@ def is_locked_by_autolock(self, charge_state: bool) -> bool: else: return False - def get_ev(self, rfid, assigned_ev): + def get_ev(self, rfid: str, vehicle_id: str, assigned_ev: int) -> int: """ermittelt das dem Ladepunkt zugeordnete EV Parameter @@ -87,8 +87,8 @@ def get_ev(self, rfid, assigned_ev): num = -1 message = None try: - if data.data.optional_data.data.rfid.active and rfid is not None: - vehicle = ev_module.get_ev_to_rfid(rfid) + if data.data.optional_data.data.rfid.active and (rfid is not None or vehicle_id is not None): + vehicle = ev_module.get_ev_to_rfid(rfid, vehicle_id) if vehicle is None: if self.data.rfid_enabling: num = -1 diff --git a/packages/control/chargepoint/rfid.py b/packages/control/chargepoint/rfid.py new file mode 100644 index 0000000000..864a38098b --- /dev/null +++ b/packages/control/chargepoint/rfid.py @@ -0,0 +1,86 @@ +import logging +from typing import Optional + +from control import data +from control.chargepoint.chargepoint_data import ChargepointProtocol +from helpermodules.pub import Pub +from helpermodules import timecheck + +log = logging.getLogger(__name__) + + +class ChargepointRfidMixin: + def _link_rfid_to_cp(self: ChargepointProtocol) -> None: + """ Wenn der Tag einem EV zugeordnet worden ist, wird der Tag unter set/rfid abgelegt und muss der Timer + zurückgesetzt werden. + """ + rfid = self.data.get.rfid + cp2_num = self.find_duo_partner() + # Tag wird diesem LP der Duo zugewiesen oder es ist keine Duo + if ((cp2_num is not None and + # EV am anderen Ladepunkt, am eigenen wurde zuerst angesteckt + ((data.data.cp_data[f"cp{cp2_num}"].data.get.plug_state and + timecheck.get_difference(self.data.set.plug_time, + data.data.cp_data[f"cp{cp2_num}"].data.set.plug_time) < 0) or + # kein EV am anderen Duo-Ladepunkt + data.data.cp_data[f"cp{cp2_num }"].data.get.plug_state is False)) or + # keine Duo + cp2_num is None): + self.data.set.rfid = rfid + Pub().pub("openWB/chargepoint/"+str(self.num)+"/set/rfid", rfid) + self.chargepoint_module.clear_rfid() + + self.data.get.rfid = None + Pub().pub("openWB/chargepoint/"+str(self.num)+"/get/rfid", None) + self.data.get.rfid_timestamp = None + Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", None) + + def _validate_rfid(self) -> None: + """Prüft, dass der Tag an diesem Ladepunkt gültig ist und dass dieser innerhalb von 5 Minuten einem EV + zugeordnet wird. + """ + msg = "" + if self.data.get.rfid is not None: + if data.data.optional_data.data.rfid.active: + if (self.data.set.log.imported_at_plugtime == 0 or + self.data.set.log.imported_at_plugtime == self.data.get.imported): + rfid = self.data.get.rfid + if self.data.get.rfid_timestamp is None: + self.data.get.rfid_timestamp = timecheck.create_timestamp() + Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", + self.data.get.rfid_timestamp) + return + else: + if (timecheck.check_timestamp(self.data.get.rfid_timestamp, 300) or + self.data.get.plug_state is True): + return + else: + self.data.get.rfid_timestamp = None + if rfid in self.template.data.valid_tags or len(self.template.data.valid_tags) == 0: + Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid_timestamp", None) + msg = "Es ist in den letzten 5 Minuten kein EV angesteckt worden, dem " \ + f"der ID-Tag {rfid} zugeordnet werden kann. Daher wird dieser verworfen." + else: + msg = f"Der ID-Tag {rfid} ist an diesem Ladepunkt nicht gültig." + else: + msg = "Nach Ladestart wird kein anderer ID-Tag akzeptiert." + else: + msg = "Identifikation von Fahrzeugen ist nicht aktiviert." + self.data.get.rfid = None + Pub().pub(f"openWB/set/chargepoint/{self.num}/get/rfid", None) + self.chargepoint_module.clear_rfid() + self.set_state_and_log(msg) + + def find_duo_partner(self: ChargepointProtocol) -> Optional[int]: + try: + # Wurde ein zweiter Ladepunkt an einer Duo konfiguriert? + if self.data.config.type == "external_openwb" or self.data.config.type == "internal_openwb": + for cp2 in data.data.cp_data.values(): + if (cp2.num != self.num and + self.data.config.configuration["ip_address"] == cp2.data.config.configuration[ + "ip_address"]): + return cp2.num + return None + except Exception: + log.exception("Fehler in der allgemeinen Ladepunkt-Klasse") + return None diff --git a/packages/control/conftest.py b/packages/control/conftest.py index f6e1050414..f807f98e7c 100644 --- a/packages/control/conftest.py +++ b/packages/control/conftest.py @@ -6,7 +6,8 @@ from control import data from control.bat import Bat, BatData from control.bat import Get as BatGet -from control.chargepoint.chargepoint import Chargepoint, ChargepointData, Config, Get, Set +from control.chargepoint.chargepoint import Chargepoint, ChargepointData +from control.chargepoint.chargepoint_data import Config, Get, Set from control.counter import CounterData from control.counter import Config as CounterConfig from control.counter import Get as CounterGet diff --git a/packages/control/ev.py b/packages/control/ev.py index 62dc486964..c5adeb4ecf 100644 --- a/packages/control/ev.py +++ b/packages/control/ev.py @@ -818,7 +818,7 @@ def stop(self) -> Tuple[int, str, str]: return 0, "stop", "Keine Ladung, da der Lademodus Stop aktiv ist." -def get_ev_to_rfid(rfid: str): +def get_ev_to_rfid(rfid: str, vehicle_id: str): """ ermittelt zum übergebenen ID-Tag das Fahrzeug Parameter @@ -834,7 +834,11 @@ def get_ev_to_rfid(rfid: str): for vehicle in data.data.ev_data: try: if "ev" in vehicle: + if vehicle_id in data.data.ev_data[vehicle].data.tag_id: + log.debug(f"MAC {vehicle_id} wird EV {data.data.ev_data[vehicle].num} zugeordnet.") + return data.data.ev_data[vehicle].num if rfid in data.data.ev_data[vehicle].data.tag_id: + log.debug(f"RFID {rfid} wird EV {data.data.ev_data[vehicle].num} zugeordnet.") return data.data.ev_data[vehicle].num except Exception: log.exception("Fehler im ev-Modul "+vehicle) diff --git a/packages/helpermodules/measurement_logging/write_log.py b/packages/helpermodules/measurement_logging/write_log.py index fd76ebe8c2..662b9d2c84 100644 --- a/packages/helpermodules/measurement_logging/write_log.py +++ b/packages/helpermodules/measurement_logging/write_log.py @@ -92,7 +92,7 @@ def save_log(folder): gibt an, ob ein Tages-oder Monats-Log-Eintrag erstellt werden soll. """ if folder == "daily": - date = timecheck.create_timestamp_time() + date = timecheck.create_timestamp() else: date = timecheck.create_timestamp_YYYYMMDD() current_timestamp = timecheck.create_timestamp() diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 2cbf6100c3..2e88b19961 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -129,7 +129,7 @@ def _validate_value(self, msg: mqtt.MQTTMessage, data_type, ranges=[], collectio if self._validate_collection_value(msg, data_type, ranges, collection): valid = True elif data_type == str: - if isinstance(value, str) or value is None: + if isinstance(value, str) or isinstance(value, type(None)): valid = True else: log.error(f"Payload ungültig: Topic {msg.topic}, Payload {value} sollte ein String sein.") @@ -140,10 +140,10 @@ def _validate_value(self, msg: mqtt.MQTTMessage, data_type, ranges=[], collectio if float in data_type: if self._validate_min_max_value(value, msg, float, ranges): valid = True - if None in data_type and value is None: + if None in data_type and isinstance(value, type(None)): valid = True elif data_type == int or data_type == float: - if self._validate_min_max_value(value, msg, data_type, ranges) or isinstance(value, type(None)): + if isinstance(value, type(None)) or self._validate_min_max_value(value, msg, data_type, ranges): valid = True elif data_type == bool: valid, value = self._validate_bool_value(value, msg) @@ -595,10 +595,13 @@ def process_chargepoint_get_topics(self, msg): self._validate_value(msg, int, [(0, 2)]) elif "/get/evse_current" in msg.topic: self._validate_value(msg, float, [(0, 0), (6, 32), (600, 3200)]) + elif "/get/rfid_timestamp" in msg.topic: + self._validate_value(msg, float) elif ("/get/fault_str" in msg.topic or "/get/state_str" in msg.topic or "/get/heartbeat" in msg.topic or - "/get/rfid" in msg.topic): + "/get/rfid" in msg.topic or + "/get/vehicle_id" in msg.topic): self._validate_value(msg, str) elif "/get/rfid_timestamp" in msg.topic: self._validate_value(msg, float) diff --git a/packages/helpermodules/subdata.py b/packages/helpermodules/subdata.py index 91fae532a6..f4a022ca1c 100644 --- a/packages/helpermodules/subdata.py +++ b/packages/helpermodules/subdata.py @@ -16,6 +16,7 @@ from control import ev from control import general from control.chargepoint.chargepoint_all import AllChargepoints +from control.chargepoint.chargepoint_data import Log from control.chargepoint.chargepoint_state_update import ChargepointStateUpdate from control.chargepoint.chargepoint_template import CpTemplate, CpTemplateData from helpermodules import graph @@ -409,7 +410,7 @@ def process_chargepoint_topic(self, var: Dict[str, chargepoint.Chargepoint], msg if re.search("/chargepoint/[0-9]+/set/", msg.topic) is not None: if re.search("/chargepoint/[0-9]+/set/log$", msg.topic) is not None: var["cp"+index].chargepoint.data.set.log = dataclass_from_dict( - chargepoint.Log, decode_payload(msg.payload)) + Log, decode_payload(msg.payload)) else: self.set_json_payload_class(var["cp"+index].chargepoint.data.set, msg) elif re.search("/chargepoint/[0-9]+/get/", msg.topic) is not None: diff --git a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py index 6d9613c892..f6d904bcc6 100644 --- a/packages/modules/chargepoints/openwb_pro/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_pro/chargepoint_module.py @@ -55,7 +55,7 @@ def get_values(self) -> None: plug_state=json_rsp["plug_state"], charge_state=json_rsp["charge_state"], phases_in_use=json_rsp["phases_in_use"], - rfid=json_rsp["vehicle_id"], + vehicle_id=json_rsp["vehicle_id"], evse_current=json_rsp["offered_current"] ) @@ -67,6 +67,10 @@ def get_values(self) -> None: chargepoint_state.soc_timestamp = json_rsp["soc_timestamp"] if json_rsp.get("frequency"): chargepoint_state.frequency = json_rsp["frequency"] + if json_rsp.get("rfid_tag"): + chargepoint_state.rfid = json_rsp["rfid_tag"] + if json_rsp.get("rfid_timestamp"): + chargepoint_state.rfid_timestamp = json_rsp["rfid_timestamp"] self.store.set(chargepoint_state) self.__client_error_context.reset_error_counter() @@ -83,9 +87,7 @@ def switch_phases(self, phases_to_use: int, duration: int) -> None: time.sleep(duration) def clear_rfid(self) -> None: - with SingleComponentUpdateContext(self.component_info, False): - with self.__client_error_context: - log.debug("Die openWB-Pro unterstützt keine RFID-Tags.") + pass chargepoint_descriptor = DeviceDescriptor(configuration_factory=OpenWBPro) diff --git a/packages/modules/chargepoints/openwb_pro/chargepoint_module_test.py b/packages/modules/chargepoints/openwb_pro/chargepoint_module_test.py index 823a4add90..c2fed0df0f 100644 --- a/packages/modules/chargepoints/openwb_pro/chargepoint_module_test.py +++ b/packages/modules/chargepoints/openwb_pro/chargepoint_module_test.py @@ -19,7 +19,9 @@ plug_state=True, charge_state=True, phases_in_use=3, - rfid="98:ED:5C:B4:EE:8D", + rfid="001180644", + rfid_timestamp=1700839714, + vehicle_id="98:ED:5C:B4:EE:8D", evse_current=6 ) @@ -39,7 +41,9 @@ 'serial': '823950', 'timestamp': 1675104511, 'v2g_ready': 0, - 'vehicle_id': '98:ED:5C:B4:EE:8D'} + 'vehicle_id': '98:ED:5C:B4:EE:8D', + 'rfid_tag': "001180644", + "rfid_timestamp": 1700839714} SAMPLE_CHARGEPOINT_STATE_EXTENDED = ChargepointState( power=0, diff --git a/packages/modules/chargepoints/smartwb/chargepoint_module.py b/packages/modules/chargepoints/smartwb/chargepoint_module.py index f689d59e9a..534beb8e82 100644 --- a/packages/modules/chargepoints/smartwb/chargepoint_module.py +++ b/packages/modules/chargepoints/smartwb/chargepoint_module.py @@ -66,6 +66,14 @@ def get_values(self) -> None: else: voltages = None + if json_rsp.get("RFIDUID"): + if json_rsp["RFIDUID"] == "": + tag = None + else: + tag = json_rsp["RFIDUID"] + else: + tag = None + chargepoint_state = ChargepointState( power=json_rsp["actualPower"] * 1000, currents=currents, @@ -73,16 +81,10 @@ def get_values(self) -> None: plug_state=plug_state, charge_state=charge_state, phases_in_use=self.phases_in_use, - voltages=voltages + voltages=voltages, + rfid=tag ) - if json_rsp.get("RFIDUID"): - if json_rsp["RFIDUID"] == "": - tag = None - else: - tag = json_rsp["RFIDUID"] - chargepoint_state.rfid = tag - self.store.set(chargepoint_state) self.__client_error_context.reset_error_counter() diff --git a/packages/modules/chargepoints/smartwb/smartwb_test.py b/packages/modules/chargepoints/smartwb/smartwb_test.py index ec1de6b179..bfd0b89f02 100644 --- a/packages/modules/chargepoints/smartwb/smartwb_test.py +++ b/packages/modules/chargepoints/smartwb/smartwb_test.py @@ -58,7 +58,8 @@ class TestSmartWb: imported=54350.0, plug_state=True, charge_state=True, - rfid="0a1b2c3d" + rfid="0a1b2c3d", + rfid_timestamp=1652683252 ) SAMPLE_V2 = { "type": "parameters", @@ -95,6 +96,7 @@ class TestSmartWb: plug_state=True, charge_state=True, rfid="0a1b2c3d", + rfid_timestamp=1652683252, phases_in_use=1 ) SAMPLE_NOT_CHARGING_V2 = { diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index cd6ddc5e30..42f647db8f 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -1,5 +1,6 @@ import logging from typing import List, Optional, Tuple +from helpermodules import timecheck from helpermodules.auto_str import auto_str @@ -131,10 +132,12 @@ def __init__(self, charge_state: bool = False, plug_state: bool = False, rfid: Optional[str] = None, + rfid_timestamp: Optional[float] = None, frequency: float = 50, soc: Optional[float] = None, soc_timestamp: Optional[int] = None, - evse_current: Optional[float] = None): + evse_current: Optional[float] = None, + vehicle_id: Optional[str] = None): self.currents, self.powers, self.voltages = _calculate_powers_and_currents(currents, powers, voltages) self.frequency = frequency self.imported = imported @@ -144,9 +147,14 @@ def __init__(self, self.charge_state = charge_state self.plug_state = plug_state self.rfid = rfid + if self.rfid and rfid_timestamp is None: + self.rfid_timestamp = timecheck.create_timestamp() + else: + self.rfid_timestamp = rfid_timestamp if power_factors is None: power_factors = [0.0]*3 self.power_factors = power_factors self.soc = soc self.soc_timestamp = soc_timestamp self.evse_current = evse_current + self.vehicle_id = vehicle_id diff --git a/packages/modules/common/store/_broker.py b/packages/modules/common/store/_broker.py index 03ca986420..e002b61c68 100644 --- a/packages/modules/common/store/_broker.py +++ b/packages/modules/common/store/_broker.py @@ -7,10 +7,11 @@ def pub_to_broker(topic: str, value, digits: Union[int, None] = None) -> None: rounding = get_rounding_function_by_digits(digits) try: - if value is not None: - if isinstance(value, list): - Pub().pub(topic, [rounding(v) for v in value]) - else: - Pub().pub(topic, rounding(value)) + if value is None: + Pub().pub(topic, rounding(value)) + elif isinstance(value, list): + Pub().pub(topic, [rounding(v) for v in value]) + else: + Pub().pub(topic, rounding(value)) except Exception as e: process_error(e) diff --git a/packages/modules/common/store/_chargepoint.py b/packages/modules/common/store/_chargepoint.py index 24e84d0a81..feff212a48 100644 --- a/packages/modules/common/store/_chargepoint.py +++ b/packages/modules/common/store/_chargepoint.py @@ -40,9 +40,11 @@ def update(self): pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/charge_state", self.state.charge_state, 2) pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/plug_state", self.state.plug_state, 2) pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/rfid", self.state.rfid) + pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/rfid_timestamp", self.state.rfid_timestamp) pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/soc", self.state.soc) pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/soc_timestamp", self.state.soc_timestamp) pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/evse_current", self.state.evse_current) + pub_to_broker("openWB/set/chargepoint/" + str(self.num) + "/get/vehicle_id", self.state.vehicle_id) def get_chargepoint_value_store(id: int) -> ValueStore[ChargepointState]: diff --git a/packages/modules/internal_chargepoint_handler/update_values_test.py b/packages/modules/internal_chargepoint_handler/update_values_test.py index c3fdba0285..9632aed402 100644 --- a/packages/modules/internal_chargepoint_handler/update_values_test.py +++ b/packages/modules/internal_chargepoint_handler/update_values_test.py @@ -25,7 +25,7 @@ @pytest.mark.parametrize( "old_chargepoint_state, published_topics", - [(None, 30), + [(None, 34), (OLD_CHARGEPOINT_STATE, 2)] ) diff --git a/packages/modules/update_soc_test.py b/packages/modules/update_soc_test.py index 1ecd4b7dc5..b7cacc8e72 100644 --- a/packages/modules/update_soc_test.py +++ b/packages/modules/update_soc_test.py @@ -4,7 +4,8 @@ import pytest from control import data -from control.chargepoint.chargepoint import Chargepoint, Get, Log, Set +from control.chargepoint.chargepoint import Chargepoint +from control.chargepoint.chargepoint_data import Get, Log, Set from control.chargepoint.chargepoint_state_update import ChargepointStateUpdate from control.ev import Ev, EvTemplate, EvTemplateData from helpermodules.subdata import SubData