diff --git a/data/config/mosquitto/openwb_local.conf b/data/config/mosquitto/openwb_local.conf index 13fbd9ec8c..0a3bc92c65 100644 --- a/data/config/mosquitto/openwb_local.conf +++ b/data/config/mosquitto/openwb_local.conf @@ -1,4 +1,4 @@ -# openwb-version:16 +# openwb-version:17 listener 1886 localhost allow_anonymous true @@ -22,6 +22,8 @@ topic openWB/chargepoint/+/set/phases_to_use out 2 topic openWB/chargepoint/+/set/manual_lock out 2 topic openWB/chargepoint/+/set/autolock_state out 2 topic openWB/chargepoint/+/set/rfid out 2 +topic openWB/chargepoint/+/set/charge_template out 2 +topic openWB/chargepoint/+/set/charge_template/# out 2 topic openWB/chargepoint/+/get/# out 2 topic openWB/chargepoint/+/config/# out 2 topic openWB/chargepoint/template/# out 2 diff --git a/docs/Ladeprofile.md b/docs/Ladeprofile.md index 1238da39ad..f0231162f1 100644 --- a/docs/Ladeprofile.md +++ b/docs/Ladeprofile.md @@ -3,3 +3,10 @@ _Einstellungen -> Konfiguration -> Fahrzeuge -> Lade-Profile_ Unter den Lade-Profilen werden die Einstellungen für das Ladeprofil verwaltet. Die Einstellungen auf der Hauptseite werden aus diesem Profil geladen und dorthin geschrieben. Ist nur ein Fahrzeug vorhanden, so wird in den meisten Fällen nur das Standard-Ladeprofil benötigt. Ausgenommen hiervon ist, wenn per RFID-Tag Ladevorgaben ausgewählt werden. In den fahrzeugspezifischen Einstellungen wird ein Ladeprofil einem Fahrzeug zugeordnet. Werden zwei Fahrzeuge geladen, empfiehlt es sich dazu ein zweites Ladeprofil anzulegen. + +### Temporäre Ladeprofile (ab Version 2.1.7) +Anpassungen am Ladeprofil, die über die Hauptseite (Web-Themes) oder ein Display (Display-Themes) vorgenommen werden, sind temporär. Die Lade-Profile müssen direkt in den Einstellungen bearbeitet werden. +Die temporären Einstellungen werden mit dem Ladeprofil aus den Einstellungen überschrieben, wenn +... abgesteckt wird. +... das Fahrzeug gewechselt wird. Das Lade-Profil des neuen Fahrzeugs wird geladen. +... das Ladeprofil geändert wird und kein Fahrzeug angesteckt ist. Ist ein Fahrzeug angesteckt, gelten die temporären Einstellungen bis zum Abstecken und werden dann durch das Ladeprofil überschrieben. \ No newline at end of file diff --git a/packages/control/algorithm/additional_current_test.py b/packages/control/algorithm/additional_current_test.py index 89c552aeff..4a8904a082 100644 --- a/packages/control/algorithm/additional_current_test.py +++ b/packages/control/algorithm/additional_current_test.py @@ -30,7 +30,7 @@ def test_set_loadmangement_message(set_current, limit, expected_msg, monkeypatch): # setup ev = Ev(0) - ev.charge_template = ChargeTemplate(0) + ev.charge_template = ChargeTemplate() cp1 = Chargepoint(1, None) cp1.data = ChargepointData(set=Set(current=set_current), control_parameter=ControlParameter(required_currents=[8]*3)) diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index 51e0aaa1c7..725b2315cb 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -96,9 +96,9 @@ def _set_loadmangement_message(self, # tested def filter_by_feed_in_limit(self, chargepoints: List[Chargepoint]) -> Tuple[List[Chargepoint], List[Chargepoint]]: - cp_with_feed_in = list(filter(lambda cp: cp.data.set.charging_ev_data.charge_template.data.chargemode. + cp_with_feed_in = list(filter(lambda cp: cp.data.set.charge_template.data.chargemode. pv_charging.feed_in_limit is True, chargepoints)) - cp_without_feed_in = list(filter(lambda cp: cp.data.set.charging_ev_data.charge_template.data.chargemode. + cp_without_feed_in = list(filter(lambda cp: cp.data.set.charge_template.data.chargemode. pv_charging.feed_in_limit is False, chargepoints)) return cp_with_feed_in, cp_without_feed_in @@ -110,7 +110,7 @@ def _limit_adjust_current(self, chargepoint: Chargepoint, new_current: float) -> MAX_CURRENT = 30 msg = None nominal_difference = chargepoint.data.set.charging_ev_data.ev_template.data.nominal_difference - if chargepoint.data.set.charging_ev_data.chargemode_changed or chargepoint.data.get.charge_state is False: + if chargepoint.chargemode_changed or chargepoint.data.get.charge_state is False: return new_current else: # Um max. +/- 5A pro Zyklus regeln @@ -153,7 +153,7 @@ def check_submode_pv_charging(self) -> None: def phase_switch_necessary() -> bool: return cp.cp_ev_chargemode_support_phase_switch() and cp.data.get.phases_in_use != 1 control_parameter = cp.data.control_parameter - if cp.data.set.charging_ev_data.chargemode_changed or cp.data.set.charging_ev_data.submode_changed: + if cp.chargemode_changed or cp.submode_changed: if control_parameter.state == ChargepointState.CHARGING_ALLOWED: if (cp.data.set.charging_ev_data.ev_template.data.prevent_charge_stop is False and phase_switch_necessary() is False): diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 6e50210534..d9eb6411f2 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -11,7 +11,6 @@ from control.chargepoint.chargepoint_data import Get, Set from control.chargepoint.chargepoint_template import CpTemplate from control.chargepoint.control_parameter import ControlParameter -from control.ev.charge_template import ChargeTemplate from control.ev.ev import Ev @@ -40,10 +39,8 @@ def test_filter_by_feed_in_limit(feed_in_limit_1: bool, expected_sorted: int): # setup def setup_cp(cp: Chargepoint, feed_in_limit: bool) -> Chargepoint: - ev = Ev(0) - ev.charge_template = ChargeTemplate(0) - ev.charge_template.data.chargemode.pv_charging.feed_in_limit = feed_in_limit - cp.data = ChargepointData(set=Set(charging_ev_data=ev)) + cp.data = ChargepointData() + cp.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit = feed_in_limit return cp cp1 = setup_cp(mock_cp1, feed_in_limit_1) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index f3f909d332..a7f9c09fb4 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -14,6 +14,7 @@ Tag-Liste: Tags, mit denen der Ladepunkt freigeschaltet werden kann. Ist diese leer, kann mit jedem Tag der Ladepunkt freigeschaltet werden. """ +import copy from dataclasses import asdict import dataclasses import logging @@ -29,9 +30,11 @@ from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory from control.chargepoint.charging_type import ChargingType from control.chargepoint.rfid import ChargepointRfidMixin +from control.ev.charge_template import ChargeTemplate from control.ev.ev import Ev from control import phase_switch from control.chargepoint.chargepoint_state import CHARGING_STATES, ChargepointState +from helpermodules.broker import InternalBrokerClient from helpermodules.phase_mapping import convert_single_evu_phase_to_cp_phase from helpermodules.pub import Pub from helpermodules import timecheck @@ -226,7 +229,7 @@ def _process_charge_stop(self) -> None: if not self.data.get.plug_state: self.data.control_parameter = control_parameter_factory() # Standardprofil nach Abstecken laden - if data.data.ev_data["ev"+str(self.data.set.charging_ev_prev)].charge_template.data.load_default: + if self.data.set.charge_template.data.load_default: self.data.config.ev = 0 Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/config/ev", 0) # Ladepunkt nach Abstecken sperren @@ -235,6 +238,8 @@ def _process_charge_stop(self) -> None: Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/manual_lock", True) log.debug("/set/manual_lock True") # Ev wurde noch nicht aktualisiert. + # Ladeprofil aus den Einstellungen laden. + self.update_charge_template(self.data.set.charging_ev_data.charge_template) chargelog.save_and_reset_data(self, data.data.ev_data["ev"+str(self.data.set.charging_ev_prev)]) self.data.set.charging_ev_prev = -1 Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/charging_ev_prev", @@ -280,8 +285,8 @@ def set_control_parameter(self, submode: str, required_current: float): self.data.control_parameter.chargemode = Chargemode.TIME_CHARGING else: self.data.control_parameter.chargemode = Chargemode( - self.data.set.charging_ev_data.charge_template.data.chargemode.selected) - self.data.control_parameter.prio = self.data.set.charging_ev_data.charge_template.data.prio + self.data.set.charge_template.data.chargemode.selected) + self.data.control_parameter.prio = self.data.set.charge_template.data.prio self.data.control_parameter.required_current = required_current if self.template.data.charging_type == ChargingType.AC.value: self.data.control_parameter.min_current = self.data.set.charging_ev_data.ev_template.data.min_current @@ -302,8 +307,10 @@ def _set_values_at_start(self): def remember_previous_values(self): self.data.set.plug_state_prev = self.data.get.plug_state self.data.set.current_prev = self.data.set.current + self.data.set.ev_prev = self.data.config.ev Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/plug_state_prev", self.data.set.plug_state_prev) Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/current_prev", self.data.set.current_prev) + Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/ev_prev", self.data.set.ev_prev) def reset_log_data_chargemode_switch(self) -> None: reset_log = Log() @@ -509,7 +516,7 @@ def get_phases_by_selected_chargemode(self) -> int: if self.data.control_parameter.submode == "time_charging": mode = "time_charging" else: - mode = charging_ev.charge_template.data.chargemode.selected + mode = self.data.set.charge_template.data.chargemode.selected chargemode = data.data.general_data.get_phases_chargemode(mode, self.data.control_parameter.submode) if (chargemode is None or @@ -622,6 +629,18 @@ def set_timestamp_charge_start(self): elif self.data.set.current == 0: self.data.control_parameter.timestamp_charge_start = None + def set_chargemode_changed(self, submode: str) -> None: + if ((submode == "time_charging" and self.data.control_parameter.chargemode != "time_charging") or + (submode != "time_charging" and + self.data.control_parameter.chargemode != self.data.set.charge_template.data.chargemode.selected)): + self.chargemode_changed = True + log.debug("Änderung des Lademodus") + else: + self.chargemode_changed = False + + def set_submode_changed(self, submode: str) -> None: + self.submode_changed = (submode != self.data.control_parameter.submode) + def update_ev(self, ev_list: Dict[str, Ev]) -> None: self._validate_rfid() charging_possible = self.is_charging_possible()[0] @@ -634,6 +653,8 @@ def update_ev(self, ev_list: Dict[str, Ev]) -> None: else: vehicle = -1 self._pub_configured_ev(ev_list) + if self.data.config.ev != self.data.set.ev_prev: + self.update_charge_template(ev_list[f"ev{self.data.config.ev}"].charge_template) def update(self, ev_list: Dict[str, Ev]) -> None: try: @@ -654,6 +675,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None: self.data.control_parameter.phases = min( self.get_phases_by_selected_chargemode(), max_phase_hw) state, message_ev, submode, required_current, phases = charging_ev.get_required_current( + self.data.set.charge_template, self.data.control_parameter, self.data.get.imported, max_phase_hw, @@ -666,20 +688,20 @@ def update(self, ev_list: Dict[str, Ev]) -> None: required_current = self.check_min_max_current( required_current, self.data.control_parameter.phases) required_current = self.chargepoint_module.add_conversion_loss_to_current(required_current) - charging_ev.set_chargemode_changed(self.data.control_parameter, submode) - charging_ev.set_submode_changed(self.data.control_parameter, submode) + self.set_chargemode_changed(submode) + self.set_submode_changed(submode) self.set_control_parameter(submode, required_current) self.set_required_currents(required_current) self.check_phase_switch_completed() - if charging_ev.chargemode_changed or charging_ev.submode_changed: + if self.chargemode_changed or self.submode_changed: data.data.counter_all_data.get_evu_counter().reset_switch_on_off( self, charging_ev) charging_ev.reset_phase_switch(self.data.control_parameter) message = message_ev if message_ev else message # Ein Eintrag muss nur erstellt werden, wenn vorher schon geladen wurde und auch danach noch # geladen werden soll. - if charging_ev.chargemode_changed and self.data.set.log.imported_since_mode_switch != 0 and state: + if self.chargemode_changed and self.data.set.log.imported_since_mode_switch != 0 and state: chargelog.save_interim_data(self, charging_ev) # Wenn die Nachrichten gesendet wurden, EV wieder löschen, wenn das EV im Algorithmus nicht @@ -695,7 +717,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None: str(self.num)+"/set/charging_ev", -1) log.debug(f'LP {self.num}, EV: {self.data.set.charging_ev_data.data.name}' f' (EV-Nr.{vehicle}): Lademodus ' - f'{charging_ev.charge_template.data.chargemode.selected}, Submodus: ' + f'{self.data.set.charge_template.data.chargemode.selected}, Submodus: ' f'{self.data.control_parameter.submode}') else: if (self.data.control_parameter.state == ChargepointState.SWITCH_ON_DELAY and @@ -705,10 +727,10 @@ def update(self, ev_list: Dict[str, Ev]) -> None: log.info( f"LP {self.num}, EV: {self.data.set.charging_ev_data.data.name} (EV-Nr.{vehicle}): " f"Theoretisch benötigter Strom {required_current}A, Lademodus " - f"{charging_ev.charge_template.data.chargemode.selected}, Submodus: " + f"{self.data.set.charge_template.data.chargemode.selected}, Submodus: " f"{self.data.control_parameter.submode}, Phasen: " f"{self.data.control_parameter.phases}" - f", Priorität: {charging_ev.charge_template.data.prio}" + f", Priorität: {self.data.control_parameter.prio}" f", max. Ist-Strom: {max(self.data.get.currents)}") except Exception: log.exception("Fehler im Prepare-Modul für Ladepunkt "+str(self.num)) @@ -717,8 +739,12 @@ def update(self, ev_list: Dict[str, Ev]) -> None: self._process_charge_stop() if vehicle != -1: self._pub_connected_vehicle(ev_list[f"ev{vehicle}"]) + if self.data.set.charge_template.data.id != ev_list[f"ev{vehicle}"].charge_template.data.id: + self.update_charge_template(ev_list[f"ev{vehicle}"].charge_template) else: self._pub_configured_ev(ev_list) + if self.data.set.charge_template.data.id != ev_list[f"ev{self.data.config.ev}"].charge_template.data.id: + self.update_charge_template(ev_list[f"ev{self.data.config.ev}"].charge_template) # OCPP Start Transaction nach Anstecken if ((self.data.get.plug_state and self.data.set.plug_state_prev is False) or (self.data.set.ocpp_transaction_id is None and self.data.get.charge_state)): @@ -761,6 +787,9 @@ def _get_charging_ev(self, vehicle: int, ev_list: Dict[str, Ev]) -> Ev: if self.data.set.charging_ev != vehicle and self.data.set.charging_ev_prev != vehicle: Pub().pub(f"openWB/set/vehicle/{charging_ev.num}/get/force_soc_update", True) log.debug("SoC nach EV-Wechsel") + self.update_charge_template(charging_ev.charge_template) + if self.data.set.charge_template.data.id != charging_ev.charge_template.data.id: + self.update_charge_template(charging_ev) self.data.set.charging_ev_data = charging_ev self.data.set.charging_ev = vehicle Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/charging_ev", vehicle) @@ -768,6 +797,32 @@ def _get_charging_ev(self, vehicle: int, ev_list: Dict[str, Ev]) -> Ev: Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/charging_ev_prev", vehicle) return charging_ev + def update_charge_template(self, charge_template: ChargeTemplate) -> None: + # evtl noch vorhandene, aber in den Einstellungen gelöschte Pläne entfernen + def on_connect(client, userdata, flags, rc): + client.subscribe(f'openWB/chargepoint/{self.num}/set/charge_template/#', 2) + + def __get_payload(client, userdata, msg): + received_topics.append(msg.topic) + if self.data.get.plug_state: + return + received_topics = [] + InternalBrokerClient("processBrokerBranch", on_connect, __get_payload).start_finite_loop() + for topic in received_topics: + Pub().pub(topic, "") + self.data.set.charge_template = copy.deepcopy(charge_template) + pub_template = copy.deepcopy(self.data.set.charge_template.data) + pub_template = dataclasses.asdict(pub_template) + pub_template["chargemode"]["scheduled_charging"]["plans"].clear() + pub_template["time_charging"]["plans"].clear() + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/charge_template", pub_template) + for id, plan in self.data.set.charge_template.data.time_charging.plans.items(): + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/charge_template/time_charging/plans/{id}", + dataclasses.asdict(plan)) + for id, plan in self.data.set.charge_template.data.chargemode.scheduled_charging.plans.items(): + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/charge_template/chargemode/scheduled_charging/plans/{id}", + dataclasses.asdict(plan)) + def _pub_connected_vehicle(self, vehicle: Ev): """ published die Daten, die zur Anzeige auf der Hauptseite benötigt werden. @@ -791,22 +846,22 @@ def _pub_connected_vehicle(self, vehicle: Ev): soc_obj.range = vehicle.data.get.range info_obj = ConnectedInfo(id=vehicle.num, name=vehicle.data.name) - if (vehicle.charge_template.data.chargemode.selected == "time_charging" or - vehicle.charge_template.data.chargemode.selected == "scheduled_charging"): + if (self.data.set.charge_template.data.chargemode.selected == "time_charging" or + self.data.set.charge_template.data.chargemode.selected == "scheduled_charging"): current_plan = self.data.control_parameter.current_plan else: current_plan = None config_obj = ConnectedConfig( - charge_template=vehicle.charge_template.ct_num, + charge_template=self.data.set.charge_template.data.id, ev_template=vehicle.ev_template.et_num, - chargemode=vehicle.charge_template.data.chargemode.selected, - priority=vehicle.charge_template.data.prio, + chargemode=self.data.set.charge_template.data.chargemode.selected, + priority=self.data.set.charge_template.data.prio, current_plan=current_plan, average_consumption=vehicle.ev_template.data.average_consump, time_charging_in_use=True if (self.data.control_parameter.submode == "time_charging") else False) if soc_obj != self.data.get.connected_vehicle.soc: - Pub().pub("openWB/chargepoint/"+str(self.num) + + Pub().pub("openWB/chargepoint/"+str(self.data.id) + "/get/connected_vehicle/soc", dataclasses.asdict(soc_obj)) if info_obj != self.data.get.connected_vehicle.info: Pub().pub("openWB/chargepoint/"+str(self.num) + diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py index 9d2f9bb02c..c4fa538a52 100644 --- a/packages/control/chargepoint/chargepoint_data.py +++ b/packages/control/chargepoint/chargepoint_data.py @@ -4,6 +4,7 @@ from control.chargepoint.chargepoint_template import CpTemplate from control.chargepoint.control_parameter import ControlParameter, control_parameter_factory +from control.ev.charge_template import ChargeTemplate from control.ev.ev import Ev from dataclass_utils.factories import currents_list_factory, empty_dict_factory, voltages_list_factory from helpermodules.constants import NO_ERROR @@ -117,6 +118,10 @@ class Get: voltages: List[float] = field(default_factory=voltages_list_factory) +def charge_template_factory() -> ChargeTemplate: + return ChargeTemplate() + + def ev_factory() -> Ev: return Ev(0) @@ -129,8 +134,10 @@ def log_factory() -> Log: class Set: charging_ev: int = -1 charging_ev_prev: int = -1 + charge_template: ChargeTemplate = field(default_factory=charge_template_factory) current: float = 0 energy_to_charge: float = 0 + ev_prev: int = 0 loadmanagement_available: bool = True log: Log = field(default_factory=log_factory) manual_lock: bool = False diff --git a/packages/control/counter.py b/packages/control/counter.py index fb98a789ee..47dde30a72 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -265,7 +265,7 @@ def calc_switch_on_power(self, chargepoint: Chargepoint) -> Tuple[float, float]: control_parameter = chargepoint.data.control_parameter pv_config = data.data.general_data.data.chargemode_config.pv_charging - if chargepoint.data.set.charging_ev_data.charge_template.data.chargemode.pv_charging.feed_in_limit: + if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit: threshold = pv_config.feed_in_yield else: threshold = pv_config.switch_on_threshold*control_parameter.phases @@ -275,7 +275,7 @@ def switch_on_threshold_reached(self, chargepoint: Chargepoint) -> None: try: message = None control_parameter = chargepoint.data.control_parameter - feed_in_limit = chargepoint.data.set.charging_ev_data.charge_template.data.chargemode.pv_charging.\ + feed_in_limit = chargepoint.data.set.charge_template.data.chargemode.pv_charging.\ feed_in_limit pv_config = data.data.general_data.data.chargemode_config.pv_charging timestamp_switch_on_off = control_parameter.timestamp_switch_on_off @@ -337,7 +337,7 @@ def switch_on_timer_expired(self, chargepoint: Chargepoint) -> None: msg = self.SWITCH_ON_EXPIRED.format(pv_config.switch_on_threshold) control_parameter.state = ChargepointState.CHARGING_ALLOWED - if chargepoint.data.set.charging_ev_data.charge_template.data.chargemode.pv_charging.feed_in_limit: + if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit: feed_in_yield = pv_config.feed_in_yield else: feed_in_yield = 0 @@ -385,7 +385,7 @@ def switch_off_check_timer(self, chargepoint: Chargepoint) -> None: def calc_switch_off_threshold(self, chargepoint: Chargepoint) -> Tuple[float, float]: pv_config = data.data.general_data.data.chargemode_config.pv_charging control_parameter = chargepoint.data.control_parameter - if chargepoint.data.set.charging_ev_data.charge_template.data.chargemode.pv_charging.feed_in_limit: + if chargepoint.data.set.charge_template.data.chargemode.pv_charging.feed_in_limit: # Der EVU-Überschuss muss ggf um die Einspeisegrenze bereinigt werden. # Wnn die Leistung nicht Einspeisegrenze + Einschaltschwelle erreicht, darf die Ladung nicht pulsieren. # Abschaltschwelle um Einschaltschwelle reduzieren. diff --git a/packages/control/counter_test.py b/packages/control/counter_test.py index 4b10d1335f..4b3e59466a 100644 --- a/packages/control/counter_test.py +++ b/packages/control/counter_test.py @@ -140,7 +140,7 @@ def test_switch_on_threshold_reached(params: Params, caplog, general_data_fixtur cp.data.control_parameter.phases = 1 cp.data.control_parameter.state = params.state cp.data.control_parameter.timestamp_switch_on_off = params.timestamp_switch_on_off - ev.data.charge_template = ChargeTemplate(0) + ev.data.charge_template = ChargeTemplate() ev.data.charge_template.data.chargemode.pv_charging.feed_in_limit = params.feed_in_limit cp.data.set.charging_ev_data = ev mock_calc_switch_on_power = Mock(return_value=[params.surplus, params.threshold]) diff --git a/packages/control/ev/charge_template.py b/packages/control/ev/charge_template.py index ed0493350c..97214b5a1e 100644 --- a/packages/control/ev/charge_template.py +++ b/packages/control/ev/charge_template.py @@ -97,6 +97,7 @@ def et_factory() -> Et: @dataclass class ChargeTemplateData: + id: int = 0 name: str = "Lade-Profil" prio: bool = False load_default: bool = False @@ -124,7 +125,6 @@ class SelectedPlan: class ChargeTemplate: """ Klasse der Lade-Profile """ - ct_num: int data: ChargeTemplateData = field(default_factory=charge_template_data_factory, metadata={ "topic": ""}) @@ -175,7 +175,7 @@ def time_charging(self, log.debug(message) return 0, "stop", message, None except Exception: - log.exception("Fehler im ev-Modul "+str(self.ct_num)) + log.exception("Fehler im ev-Modul "+str(self.data.id)) return 0, "stop", "Keine Ladung, da da ein interner Fehler aufgetreten ist: "+traceback.format_exc(), None INSTANT_CHARGING_SOC_REACHED = "Kein Sofortladen, da der Soc bereits erreicht wurde." @@ -215,7 +215,7 @@ def instant_charging(self, else: raise TypeError(f'{instant_charging.limit.selected} unbekanntes Sofortladen-Limit.') except Exception: - log.exception("Fehler im ev-Modul "+str(self.ct_num)) + log.exception("Fehler im ev-Modul "+str(self.data.id)) return 0, "stop", "Keine Ladung, da da ein interner Fehler aufgetreten ist: "+traceback.format_exc() PV_CHARGING_SOC_REACHED = "Keine Ladung, da der maximale Soc bereits erreicht wurde." @@ -250,7 +250,7 @@ def pv_charging(self, soc: Optional[float], min_current: int, charging_type: str else: return 0, "stop", self.PV_CHARGING_SOC_REACHED except Exception: - log.exception("Fehler im ev-Modul "+str(self.ct_num)) + log.exception("Fehler im ev-Modul "+str(self.data.id)) return 0, "stop", "Keine Ladung, da ein interner Fehler aufgetreten ist: "+traceback.format_exc() def scheduled_charging_recent_plan(self, @@ -347,7 +347,7 @@ def _search_plan(self, log.debug(f"Plan-Nr. {plan.id}: Differenz zum Start {remaining_time}s, Dauer {duration/3600}h, " f"Termin heute verpasst: {missed_date_today}") except Exception: - log.exception("Fehler im ev-Modul "+str(self.ct_num)) + log.exception("Fehler im ev-Modul "+str(self.data.id)) return plan_data def _calculate_duration(self, diff --git a/packages/control/ev/charge_template_test.py b/packages/control/ev/charge_template_test.py index a036ffed0a..c1572c6f68 100644 --- a/packages/control/ev/charge_template_test.py +++ b/packages/control/ev/charge_template_test.py @@ -7,7 +7,7 @@ from control import optional from control.ev.charge_template import SelectedPlan from control.chargepoint.charging_type import ChargingType -from control.ev.ev import ChargeTemplate +from control.ev.charge_template import ChargeTemplate from control.ev.ev_template import EvTemplate, EvTemplateData from control.general import General from helpermodules import timecheck @@ -57,7 +57,7 @@ def test_time_charging(plans: Dict[int, TimeChargingPlan], soc: float, used_amou expected: Tuple[int, str, Optional[str], Optional[str]], monkeypatch): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() ct.data.time_charging.plans = plans check_plans_timeframe_mock = Mock(return_value=plan_found) monkeypatch.setattr(timecheck, "check_plans_timeframe", check_plans_timeframe_mock) @@ -85,7 +85,7 @@ def test_instant_charging(selected: str, current_soc: float, used_amount: float, expected: Tuple[int, str, Optional[str]]): # setup data.data.optional_data.data.et.active = False - ct = ChargeTemplate(0) + ct = ChargeTemplate() ct.data.chargemode.instant_charging.limit.selected = selected # execution @@ -109,7 +109,7 @@ def test_instant_charging(selected: str, current_soc: float, used_amount: float, def test_pv_charging(min_soc: int, min_current: int, current_soc: float, expected: Tuple[int, str, Optional[str]]): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() ct.data.chargemode.pv_charging.min_soc = min_soc ct.data.chargemode.pv_charging.min_current = min_current data.data.bat_all_data.data.config.configured = True @@ -147,7 +147,7 @@ def test_pv_charging(min_soc: int, min_current: int, current_soc: float, @pytest.mark.parametrize("params", cases, ids=[c.name for c in cases]) def test_scheduled_charging_recent_plan(params: Params, monkeypatch): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() get_phases_chargemode_mock = Mock(return_value=params.chargemode_phases) monkeypatch.setattr(data.data.general_data, "get_phases_chargemode", get_phases_chargemode_mock) search_plan_mock = Mock(return_value=params.search_plan) @@ -172,7 +172,7 @@ def test_scheduled_charging_recent_plan(params: Params, monkeypatch): ]) def test_calculate_duration(selected: str, phases: int, expected_duration: float, expected_missing_amount: float): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() plan = ScheduledChargingPlan() plan.limit.selected = selected # execution @@ -201,7 +201,7 @@ def test_search_plan(check_duration_return1: Tuple[Optional[float], bool], monkeypatch.setattr(ChargeTemplate, "_calculate_duration", calculate_duration_mock) check_duration_mock = Mock(side_effect=[check_duration_return1, check_duration_return2]) monkeypatch.setattr(timecheck, "check_duration", check_duration_mock) - ct = ChargeTemplate(0) + ct = ChargeTemplate() plan_mock_0 = Mock(spec=ScheduledChargingPlan, active=True, current=14, id=0, limit=Limit(selected="amount")) plan_mock_1 = Mock(spec=ScheduledChargingPlan, active=True, current=14, id=1, limit=Limit(selected="amount")) ct.data.chargemode.scheduled_charging.plans = {"0": plan_mock_0, "1": plan_mock_1} @@ -252,7 +252,7 @@ def test_scheduled_charging_calc_current(plan_data: SelectedPlan, selected: str, expected: Tuple[float, str, str, int]): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() plan = ScheduledChargingPlan(active=True, id=0) plan.limit.selected = selected # json verwandelt Keys in strings @@ -267,7 +267,7 @@ def test_scheduled_charging_calc_current(plan_data: SelectedPlan, def test_scheduled_charging_calc_current_no_plans(): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() # execution ret = ct.scheduled_charging_calc_current(None, 63, 5, 3, 6, 0) @@ -284,7 +284,7 @@ def test_scheduled_charging_calc_current_no_plans(): ]) def test_scheduled_charging_calc_current_electricity_tariff(loading_hour, expected, monkeypatch): # setup - ct = ChargeTemplate(0) + ct = ChargeTemplate() plan = ScheduledChargingPlan(active=True) plan.limit.selected = "soc" ct.data.chargemode.scheduled_charging.plans = {"0": plan} diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index 2f31fd1425..8baf194900 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -11,10 +11,10 @@ from typing import List, Optional, Tuple from control import data +from control.ev.charge_template import ChargeTemplate from control.chargepoint.chargepoint_state import ChargepointState, PHASE_SWITCH_STATES from control.chargepoint.charging_type import ChargingType from control.chargepoint.control_parameter import ControlParameter -from control.ev.charge_template import ChargeTemplate from control.ev.ev_template import EvTemplate from control.limiting_value import LimitingValue from dataclass_utils.factories import empty_list_factory @@ -86,10 +86,8 @@ class Ev: def __init__(self, index: int): try: self.ev_template: EvTemplate = EvTemplate() - self.charge_template: ChargeTemplate = ChargeTemplate(0) + self.charge_template: ChargeTemplate = ChargeTemplate() self.soc_module: ConfigurableVehicle = None - self.chargemode_changed = False - self.submode_changed = False self.num = index self.data = EvData() except Exception: @@ -114,6 +112,7 @@ def soc_interval_expired(self, vehicle_update_data: VehicleUpdateData) -> bool: return request_soc def get_required_current(self, + charge_template: ChargeTemplate, control_parameter: ControlParameter, imported: float, max_phases_hw: int, @@ -142,11 +141,11 @@ def get_required_current(self, message = None state = True try: - if self.charge_template.data.chargemode.selected == "scheduled_charging": + if charge_template.data.chargemode.selected == "scheduled_charging": if control_parameter.imported_at_plan_start is None: control_parameter.imported_at_plan_start = imported used_amount = imported - control_parameter.imported_at_plan_start - plan_data = self.charge_template.scheduled_charging_recent_plan( + plan_data = charge_template.scheduled_charging_recent_plan( self.data.get.soc, self.ev_template, control_parameter.phases, @@ -170,7 +169,7 @@ def get_required_current(self, control_parameter.current_plan = plan_data.id else: control_parameter.current_plan = None - required_current, submode, message, phases = self.charge_template.scheduled_charging_calc_current( + required_current, submode, message, phases = charge_template.scheduled_charging_calc_current( plan_data, self.data.get.soc, used_amount, @@ -180,7 +179,7 @@ def get_required_current(self, # Wenn Zielladen auf Überschuss wartet, prüfen, ob Zeitladen aktiv ist. if (submode != "instant_charging" and - self.charge_template.data.time_charging.active): + charge_template.data.time_charging.active): if control_parameter.imported_at_plan_start is None: control_parameter.imported_at_plan_start = imported used_amount = imported - control_parameter.imported_at_plan_start @@ -199,28 +198,28 @@ def get_required_current(self, required_current = tmp_current submode = tmp_submode if (required_current == 0) or (required_current is None): - if self.charge_template.data.chargemode.selected == "instant_charging": + if charge_template.data.chargemode.selected == "instant_charging": # Wenn der Submode auf stop gestellt wird, wird auch die Energiemenge seit Wechsel des Modus # zurückgesetzt, dann darf nicht die Energiemenge erneute geladen werden. if control_parameter.imported_instant_charging is None: control_parameter.imported_instant_charging = imported used_amount = imported - control_parameter.imported_instant_charging - required_current, submode, message = self.charge_template.instant_charging( + required_current, submode, message = charge_template.instant_charging( self.data.get.soc, used_amount, charging_type) - elif self.charge_template.data.chargemode.selected == "pv_charging": - required_current, submode, message = self.charge_template.pv_charging( + elif charge_template.data.chargemode.selected == "pv_charging": + required_current, submode, message = charge_template.pv_charging( self.data.get.soc, control_parameter.min_current, charging_type) - elif self.charge_template.data.chargemode.selected == "standby": + elif charge_template.data.chargemode.selected == "standby": # Text von Zeit-und Zielladen nicht überschreiben. if message is None: - required_current, submode, message = self.charge_template.standby() + required_current, submode, message = charge_template.standby() else: - required_current, submode, _ = self.charge_template.standby() - elif self.charge_template.data.chargemode.selected == "stop": - required_current, submode, message = self.charge_template.stop() - if submode == "stop" or submode == "standby" or (self.charge_template.data.chargemode.selected == "stop"): + required_current, submode, _ = charge_template.standby() + elif charge_template.data.chargemode.selected == "stop": + required_current, submode, message = charge_template.stop() + if submode == "stop" or submode == "standby" or (charge_template.data.chargemode.selected == "stop"): state = False if phases is None: phases = control_parameter.phases @@ -230,18 +229,6 @@ def get_required_current(self, return (False, f"Kein Ladevorgang, da ein Fehler aufgetreten ist: {' '.join(e.args)}", "stop", 0, control_parameter.phases) - def set_chargemode_changed(self, control_parameter: ControlParameter, submode: str) -> None: - if ((submode == "time_charging" and control_parameter.chargemode != "time_charging") or - (submode != "time_charging" and - control_parameter.chargemode != self.charge_template.data.chargemode.selected)): - self.chargemode_changed = True - log.debug("Änderung des Lademodus") - else: - self.chargemode_changed = False - - def set_submode_changed(self, control_parameter: ControlParameter, submode: str) -> None: - self.submode_changed = (submode != control_parameter.submode) - def check_min_max_current(self, control_parameter: ControlParameter, required_current: float, diff --git a/packages/control/ev/ev_template.py b/packages/control/ev/ev_template.py index 8da234a1ff..a8e8fbe02d 100644 --- a/packages/control/ev/ev_template.py +++ b/packages/control/ev/ev_template.py @@ -5,6 +5,7 @@ class EvTemplateData: dc_min_current: int = 0 dc_max_current: int = 0 + id: int = 0 name: str = "Fahrzeug-Profil" max_current_multi_phases: int = 16 max_phases: int = 3 @@ -33,4 +34,3 @@ class EvTemplate: data: EvTemplateData = field(default_factory=ev_template_data_factory, metadata={ "topic": "config"}) - et_num: int = 0 diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index c2aa3f9f70..f766ad35c7 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -177,6 +177,17 @@ def setup_added_chargepoint(): Pub().pub(f'openWB/chargepoint/{new_id}/config', chargepoint_config) Pub().pub(f'openWB/chargepoint/{new_id}/set/manual_lock', False) {Pub().pub(f"openWB/chargepoint/{new_id}/get/"+k, v) for (k, v) in asdict(chargepoint.Get()).items()} + charge_template = SubData.ev_charge_template_data[f"ct{SubData.ev_data['ev0'].data.charge_template}"] + for time_plan in charge_template.data.time_charging.plans: + Pub().pub(f'openWB/chargepoint/{new_id}/set/charge_template/time_charging/plans', + dataclass_utils.asdict(time_plan)) + for scheduled_plan in charge_template.data.chargemode.scheduled_charging.plans: + Pub().pub(f'openWB/chargepoint/{new_id}/set/charge_template/chargemode/scheduled_charging/plans', + scheduled_plan) + charge_template = dataclass_utils.asdict(charge_template.data) + charge_template["chargemode"]["scheduled_charging"]["plans"].clear() + charge_template["time_charging"]["plans"].clear() + Pub().pub(f'openWB/chargepoint/{new_id}/set/charge_template', charge_template) self.max_id_hierarchy = self.max_id_hierarchy + 1 Pub().pub("openWB/set/command/max_id/hierarchy", self.max_id_hierarchy) if self.max_id_chargepoint_template == -1: diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index dec04bf379..98ffc77749 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -73,11 +73,17 @@ def on_message(self, client: mqtt.Client, userdata, msg: mqtt.MQTTMessage): if "openWB/set/vehicle/" in msg.topic: if "openWB/set/vehicle/template/ev_template/" in msg.topic: self.event_ev_template.wait(5) + self.process_vehicle_ev_template_topic(msg) elif "openWB/set/vehicle/template/charge_template/" in msg.topic: self.event_charge_template.wait(5) - self.process_vehicle_topic(msg) + self.process_vehicle_charge_template_topic(msg) + else: + self.process_vehicle_topic(msg) elif "openWB/set/chargepoint/" in msg.topic: - self.process_chargepoint_topic(msg) + if "openWB/set/chargepoint/" in msg.topic and "/set/charge_template" in msg.topic: + self.process_vehicle_charge_template_topic(msg) + else: + self.process_chargepoint_topic(msg) elif "openWB/set/pv/" in msg.topic: self.process_pv_topic(msg) elif "openWB/set/bat/" in msg.topic: @@ -402,8 +408,6 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, str) elif "/info" in msg.topic: self._validate_value(msg, "json") - elif "openWB/set/vehicle/template" in msg.topic: - self._subprocess_vehicle_chargemode_topic(msg) elif "openWB/set/vehicle/set/vehicle_update_completed" in msg.topic: self._validate_value(msg, bool) elif "/set/soc_error_counter" in msg.topic: @@ -437,7 +441,7 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): except Exception: log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}") - def _subprocess_vehicle_chargemode_topic(self, msg: mqtt.MQTTMessage): + def process_vehicle_charge_template_topic(self, msg: mqtt.MQTTMessage): """ Handler für die Lade-Profil-Topics Parameters ---------- @@ -503,6 +507,15 @@ def _subprocess_vehicle_chargemode_topic(self, msg: mqtt.MQTTMessage): except Exception: log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}") + def process_vehicle_ev_template_topic(self, msg: mqtt.MQTTMessage): + try: + if "ev_template" in msg.topic: + self._validate_value(msg, "json") + else: + self.__unknown_topic(msg) + except Exception: + log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}") + def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): """ Handler für die Ladepunkt-Topics @@ -527,7 +540,8 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, "json") elif subdata.SubData.cp_data.get(f"cp{get_index(msg.topic)}"): if ("/set/charging_ev" in msg.topic or - "/set/charging_ev_prev" in msg.topic): + "/set/charging_ev_prev" in msg.topic or + "/set/ev_prev" in msg.topic): self._validate_value(msg, int, [(-1, float("inf"))]) elif ("/set/current" in msg.topic or "/set/current_prev" in msg.topic): diff --git a/packages/helpermodules/subdata.py b/packages/helpermodules/subdata.py index f834771322..fe710065f7 100644 --- a/packages/helpermodules/subdata.py +++ b/packages/helpermodules/subdata.py @@ -300,11 +300,23 @@ def process_vehicle_topic(self, client: mqtt.Client, var: Dict[str, ev.Ev], msg: client.subscribe(f"openWB/vehicle/{index}/soc_module/general_config", 2) self.event_soc.set() else: + # temporäres ChargeTemplate aktualisieren, wenn dem Fahrzeug ein anderes Ladeprofil zugeordnet + # wird self.set_json_payload_class(var["ev"+index].data, msg) + if re.search("/vehicle/[0-9]+/charge_template$", msg.topic) is not None: + charge_template_id = int(decode_payload(msg.payload)) + if var["ev"+index].data.charge_template != charge_template_id: + ev_id = get_index(msg.topic) + for cp in self.cp_data.values(): + if ((cp.chargepoint.data.set.charging_ev != -1 and + cp.chargepoint.data.set.charging_ev == ev_id) or + cp.chargepoint.data.config.ev == ev_id): + cp.chargepoint.update_charge_template( + self.ev_charge_template_data[f"ct{charge_template_id}"]) except Exception: log.exception("Fehler im subdata-Modul") - def process_vehicle_charge_template_topic(self, var: Dict[str, ev.ChargeTemplate], msg: mqtt.MQTTMessage): + def process_vehicle_charge_template_topic(self, var: Dict[str, ChargeTemplate], msg: mqtt.MQTTMessage): """ Handler für die EV-Topics Parameter @@ -322,41 +334,57 @@ def process_vehicle_charge_template_topic(self, var: Dict[str, ev.ChargeTemplate var.pop("ct"+index) else: if "ct"+index not in var: - var["ct"+index] = ev.ChargeTemplate(int(index)) - if re.search("/vehicle/template/charge_template/[0-9]+/chargemode/scheduled_charging/plans/[0-9]+$", - msg.topic) is not None: - index_second = get_second_index(msg.topic) - if decode_payload(msg.payload) == "": - try: - var["ct"+index].data.chargemode.scheduled_charging.plans.pop(index_second) - except KeyError: - log.error("Es konnte kein Zielladen-Plan mit der ID " + - str(index_second)+" in dem Lade-Profil "+str(index)+" gefunden werden.") - else: - var["ct"+index].data.chargemode.scheduled_charging.plans[ - index_second] = dataclass_from_dict(ScheduledChargingPlan, decode_payload(msg.payload)) + var["ct"+index] = ChargeTemplate() + self.process_charge_template_topic(var["ct"+index], msg) + if re.search("/chargemode/scheduled_charging/plans/[0-9]+$", msg.topic) is not None: self.event_scheduled_charging_plan.set() - elif re.search("/vehicle/template/charge_template/[0-9]+/time_charging/plans/[0-9]+$", - msg.topic) is not None: - index_second = get_second_index(msg.topic) - if decode_payload(msg.payload) == "": - try: - var["ct"+index].data.time_charging.plans.pop(index_second) - except KeyError: - log.error("Es konnte kein Zeitladen-Plan mit der ID " + - str(index_second)+" in dem Lade-Profil "+str(index)+" gefunden werden.") - else: - var["ct"+index].data.time_charging.plans[ - index_second] = dataclass_from_dict(TimeChargingPlan, decode_payload(msg.payload)) + elif re.search("/time_charging/plans/[0-9]+$", msg.topic) is not None: self.event_time_charging_plan.set() else: - # Pläne unverändert übernehmen - scheduled_charging_plans = var["ct" + index].data.chargemode.scheduled_charging.plans - time_charging_plans = var["ct" + index].data.time_charging.plans - var["ct" + index].data = dataclass_from_dict(ChargeTemplateData, decode_payload(msg.payload)) - var["ct"+index].data.time_charging.plans = time_charging_plans - var["ct"+index].data.chargemode.scheduled_charging.plans = scheduled_charging_plans self.event_charge_template.set() + # Temporäres ChargeTemplate aktualisieren, wenn persistentes geändert wird + for vehicle in self.ev_data.values(): + if vehicle.data.charge_template == int(index): + vehicle.data.charge_template = var["ct"+index] + for cp in self.cp_data.values(): + if ((cp.chargepoint.data.set.charging_ev != -1 and + cp.chargepoint.data.set.charging_ev == vehicle.num) or + cp.chargepoint.data.config.ev == vehicle.num): + cp.chargepoint.update_charge_template(var["ct"+index]) + except Exception: + log.exception("Fehler im subdata-Modul") + + def process_charge_template_topic(self, var: ChargeTemplate, msg: mqtt.MQTTMessage): + try: + if re.search("/chargemode/scheduled_charging/plans/[0-9]+$", msg.topic) is not None: + index_second = get_second_index(msg.topic) + if decode_payload(msg.payload) == "": + try: + var.data.chargemode.scheduled_charging.plans.pop(index_second) + except KeyError: + log.error(f"Es konnte kein Zielladen-Plan mit der ID {index_second} " + "in dem Lade-Profil gefunden werden.") + else: + var.data.chargemode.scheduled_charging.plans[ + index_second] = dataclass_from_dict(ScheduledChargingPlan, decode_payload(msg.payload)) + elif re.search("/time_charging/plans/[0-9]+$", msg.topic) is not None: + index_second = get_second_index(msg.topic) + if decode_payload(msg.payload) == "": + try: + var.data.time_charging.plans.pop(index_second) + except KeyError: + log.error("Es konnte kein Zeitladen-Plan mit der ID " + + str(index_second)+" in dem Lade-Profil gefunden werden.") + else: + var.data.time_charging.plans[ + index_second] = dataclass_from_dict(TimeChargingPlan, decode_payload(msg.payload)) + else: + # Pläne unverändert übernehmen + scheduled_charging_plans = var.data.chargemode.scheduled_charging.plans + time_charging_plans = var.data.time_charging.plans + var.data = dataclass_from_dict(ChargeTemplateData, decode_payload(msg.payload)) + var.data.time_charging.plans = time_charging_plans + var.data.chargemode.scheduled_charging.plans = scheduled_charging_plans except Exception: log.exception("Fehler im subdata-Modul") @@ -378,7 +406,7 @@ def process_vehicle_ev_template_topic(self, var: Dict[str, EvTemplate], msg: mqt var.pop("et"+index) else: if "et"+index not in var: - var["et"+index] = EvTemplate(et_num=int(index)) + var["et"+index] = EvTemplate() var["et" + index].data = dataclass_from_dict(EvTemplateData, decode_payload(msg.payload)) self.event_ev_template.set() except Exception: @@ -398,11 +426,12 @@ def process_chargepoint_topic(self, var: Dict[str, chargepoint.Chargepoint], msg if re.search("/chargepoint/[0-9]+/", msg.topic) is not None: index = get_index(msg.topic) if decode_payload(msg.payload) == "": - log.debug("Stop des Handlers für den internen Ladepunkt.") - self.event_stop_internal_chargepoint.set() - if "cp"+index in var: - var.pop("cp"+index) - self.set_internal_chargepoint_configured() + if re.search("/chargepoint/[0-9]+/config", msg.topic) is not None: + log.debug("Stop des Handlers für den internen Ladepunkt.") + self.event_stop_internal_chargepoint.set() + if "cp"+index in var: + var.pop("cp"+index) + self.set_internal_chargepoint_configured() else: if "cp"+index not in var: var["cp"+index] = ChargepointStateUpdate( @@ -419,6 +448,13 @@ def process_chargepoint_topic(self, var: Dict[str, chargepoint.Chargepoint], msg Log, decode_payload(msg.payload)) else: self.set_json_payload_class(var["cp"+index].chargepoint.data.set, msg) + if "charge_template" in msg.topic: + if re.search("/chargepoint/[0-9]+/set/charge_template$", msg.topic) is not None: + self.set_json_payload_class( + var["cp"+index].chargepoint.data.set.charge_template.data, msg) + else: + self.process_charge_template_topic( + var["cp"+index].chargepoint.data.set.charge_template, msg) elif re.search("/chargepoint/[0-9]+/get/", msg.topic) is not None: if re.search("/chargepoint/[0-9]+/get/connected_vehicle/", msg.topic) is not None: self.set_json_payload_class(var["cp"+index].chargepoint.data.get.connected_vehicle, msg) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 59f5387e4e..ab57f1ace0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -8,7 +8,6 @@ import time from typing import List, Optional from paho.mqtt.client import Client as MqttClient, MQTTMessage - import dataclass_utils from control.chargepoint.chargepoint_template import get_chargepoint_template_default @@ -51,7 +50,7 @@ class UpdateConfig: - DATASTORE_VERSION = 75 + DATASTORE_VERSION = 77 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/config/power_limit_mode$", @@ -127,6 +126,9 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/get/rfid$", "^openWB/chargepoint/[0-9]+/get/rfid_timestamp$", "^openWB/chargepoint/[0-9]+/set/charging_ev$", + "^openWB/chargepoint/[0-9]+/set/charge_template/time_charging/plans/[0-9]+$", + "^openWB/chargepoint/[0-9]+/set/charge_template/chargemode/scheduled_charging/plans/[0-9]+$", + "^openWB/chargepoint/[0-9]+/set/charge_template$", "^openWB/chargepoint/[0-9]+/set/current$", "^openWB/chargepoint/[0-9]+/set/energy_to_charge$", "^openWB/chargepoint/[0-9]+/set/manual_lock$", @@ -452,10 +454,10 @@ class UpdateConfig: ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), ("openWB/vehicle/0/name", "Standard-Fahrzeug"), ("openWB/vehicle/0/info", {"manufacturer": None, "model": None}), - ("openWB/vehicle/0/charge_template", ev.Ev(0).charge_template.ct_num), + ("openWB/vehicle/0/charge_template", ev.Ev(0).charge_template.data.id), ("openWB/vehicle/0/soc_module/config", NO_MODULE), ("openWB/vehicle/0/soc_module/general_config", dataclass_utils.asdict(GeneralVehicleConfig())), - ("openWB/vehicle/0/ev_template", ev.Ev(0).ev_template.et_num), + ("openWB/vehicle/0/ev_template", ev.Ev(0).ev_template.data.id), ("openWB/vehicle/0/tag_id", ev.Ev(0).data.tag_id), ("openWB/vehicle/0/get/soc", ev.Ev(0).data.get.soc), ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", @@ -853,7 +855,7 @@ def upgrade(topic: str, payload) -> Optional[dict]: if re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None: payload = decode_payload(payload) if "keep_charge_active_duration" not in payload: - payload["keep_charge_active_duration"] = ev.EvTemplateData().keep_charge_active_duration + payload["keep_charge_active_duration"] = EvTemplateData().keep_charge_active_duration return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 8) @@ -1957,3 +1959,32 @@ def upgrade(topic: str, payload) -> None: Pub().pub(topic, payload) self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 75) + + def upgrade_datastore_75(self) -> None: + def upgrade(topic: str, payload) -> None: + if (re.search("openWB/vehicle/template/charge_template/[0-9]+", topic) is not None or + re.search("openWB/vehicle/template/ev_template/[0-9]+", topic) is not None): + payload = decode_payload(payload) + index = get_index(topic) + payload.update({"id": index}) + Pub().pub(topic, payload) + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 76) + + def upgrade_datastore_76(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/chargepoint/[0-9]+/config", topic) is not None: + topics = {} + payload = decode_payload(payload) + index = get_index(topic) + charge_template_id = decode_payload( + self.all_received_topics[f'openWB/vehicle/{payload["ev"]}/charge_template']) + for template_topic, template_payload in self.all_received_topics.items(): + if f'openWB/vehicle/template/charge_template/{charge_template_id}' in template_topic: + topics.update( + {template_topic.replace(f'openWB/vehicle/template/charge_template/{charge_template_id}', + f"openWB/chargepoint/{index}/set/charge_template"): + decode_payload(template_payload)}) + return topics + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 77) diff --git a/packages/modules/web_themes/standard_legacy/web/index.html b/packages/modules/web_themes/standard_legacy/web/index.html index 2b2fe2e0d1..686f319b5b 100644 --- a/packages/modules/web_themes/standard_legacy/web/index.html +++ b/packages/modules/web_themes/standard_legacy/web/index.html @@ -496,7 +496,7 @@