From a3984c2547a0e3a1283cf12b9e12ca2842f4815b Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 20 Aug 2026 15:24:32 +0200 Subject: [PATCH 1/3] draft --- .../control/counter_all/counter_all_data.py | 6 ++--- .../counter_all/loadmanagement_prio.py | 23 +++++++++++-------- .../counter_all/loadmanagement_prio_test.py | 9 ++++---- packages/helpermodules/command.py | 4 ++-- packages/modules/common/component_type.py | 1 + 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/control/counter_all/counter_all_data.py b/packages/control/counter_all/counter_all_data.py index c137c5add5..23c90f20cf 100644 --- a/packages/control/counter_all/counter_all_data.py +++ b/packages/control/counter_all/counter_all_data.py @@ -99,9 +99,9 @@ def hierarchy_remove_item(self, id_to_find: int, keep_children: bool = True) -> class LoadmanagementPrioProtocol(Protocol): @property def data(self) -> CounterAllData: ... - def add_loadmanagement_prio_item(self, type: str, id: int) -> None: ... - def remove_loadmanagement_prio_item(self, id: int) -> None: ... - def _remove_loadmanagement_prio_item(self, id: int, entry: Dict) -> None: ... + def add_loadmanagement_prio_item(self, type: ComponentType, id: int) -> None: ... + def remove_loadmanagement_prio_item(self, id: int, type: ComponentType) -> None: ... + def _remove_loadmanagement_prio_item(self, id: int, type: ComponentType, entry: Dict) -> None: ... def sort_cps_by_loadmanagement_prios_nested(self, filtered_cps: List[Chargepoint]) -> List[List[Chargepoint]]: ... def generator_cps_by_loadmanagement_prios( diff --git a/packages/control/counter_all/loadmanagement_prio.py b/packages/control/counter_all/loadmanagement_prio.py index 2ac9cbc403..9e6ecc406f 100644 --- a/packages/control/counter_all/loadmanagement_prio.py +++ b/packages/control/counter_all/loadmanagement_prio.py @@ -4,27 +4,30 @@ from control.algorithm.filter_chargepoints import get_preferenced_chargepoint from control.chargepoint.chargepoint import Chargepoint from control.counter_all.counter_all_data import LoadmanagementPrioProtocol +from modules.common.component_type import ComponentType log = logging.getLogger(__name__) class LoadmanagementPrioMixin: - def add_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, type: str, id: int) -> None: - self.data.get.loadmanagement_prios.append({"type": type, "id": id}) + def add_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, type: ComponentType, id: int) -> None: + self.data.get.loadmanagement_prios.append({"type": type.value, "id": id}) - def remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, id: int) -> None: - if self._remove_loadmanagement_prio_item(id, self.data.get.loadmanagement_prios) is False: + def remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, id: int, type: ComponentType) -> None: + if self._remove_loadmanagement_prio_item(id, type, self.data.get.loadmanagement_prios) is False: raise IndexError(f"Element {id} konnte nicht in der Prioritätensteuerung gefunden werden.") - def _remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, id: int, entry: List[Dict]) -> bool: + def _remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, + id: int, + type: ComponentType, + entry: List[Dict]) -> bool: for item in entry: - if item["type"] == "vehicle": - if item["id"] == id: - entry.remove(item) - return True + if item["type"] == type.value and item["id"] == id: + entry.remove(item) + return True elif item["type"] == "group": - removed_item = self._remove_loadmanagement_prio_item(id, item["children"]) + removed_item = self._remove_loadmanagement_prio_item(id, type, item["children"]) if removed_item and len(item["children"]) == 0: entry.remove(item) if removed_item: diff --git a/packages/control/counter_all/loadmanagement_prio_test.py b/packages/control/counter_all/loadmanagement_prio_test.py index f6a7c01433..4924cfbe3b 100644 --- a/packages/control/counter_all/loadmanagement_prio_test.py +++ b/packages/control/counter_all/loadmanagement_prio_test.py @@ -6,6 +6,7 @@ from control.chargepoint.chargepoint import Chargepoint from control.counter_all.counter_all import CounterAll +from modules.common.component_type import ComponentType @pytest.fixture @@ -32,8 +33,8 @@ def cp3(): @pytest.mark.parametrize( "loadmanagement_prios, id, type, expected_loadmanagement_prios", [ - pytest.param([], 2, "vehicle", [{"type": "vehicle", "id": 2}], id="emtpy list"), - pytest.param([{"type": "vehicle", "id": 3}], 2, "vehicle", [{"type": "vehicle", "id": 3}, + pytest.param([], 2, ComponentType.VEHICLE, [{"type": "vehicle", "id": 2}], id="emtpy list"), + pytest.param([{"type": "vehicle", "id": 3}], 2, ComponentType.VEHICLE, [{"type": "vehicle", "id": 3}, {"type": "vehicle", "id": 2}], id="flat list"), pytest.param([ { @@ -45,7 +46,7 @@ def cp3(): ] }, {"type": "vehicle", "id": 2}, - ], 4, "vehicle", [ + ], 4, ComponentType.VEHICLE, [ { "type": "group", "label": "Wichtige Fahrzeuge", @@ -137,7 +138,7 @@ def test_remove_loadmanagement_prio_item(loadmanagement_prios: List[Dict], c.data.get.loadmanagement_prios = loadmanagement_prios # execution - c.remove_loadmanagement_prio_item(id) + c.remove_loadmanagement_prio_item(id, ComponentType.VEHICLE) # assert assert c.data.get.loadmanagement_prios == expected_loadmanagement_prios diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index 1b89f0158b..d9f5c14ca0 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -776,7 +776,7 @@ def addVehicle(self, connection_id: str, payload: dict) -> None: # add ACL roles for vehicle access, if user management is active if SubData.system_data["system"].data["security"]["user_management_active"]: add_acl_role("vehicle--access", new_id) - data.data.counter_all_data.add_loadmanagement_prio_item("vehicle", new_id) + data.data.counter_all_data.add_loadmanagement_prio_item(ComponentType.VEHICLE, new_id) Pub().pub("openWB/set/counter/get/loadmanagement_prios", data.data.counter_all_data.data.get.loadmanagement_prios) pub_user_message(payload, connection_id, f'Neues EV mit ID \'{new_id}\' hinzugefügt.', MessageType.SUCCESS) @@ -794,7 +794,7 @@ def removeVehicle(self, connection_id: str, payload: dict) -> None: if SubData.system_data["system"].data["security"]["user_management_active"]: remove_acl_role("vehicle--access", payload["data"]["id"]) remove_acl_role("vehicle--write-access", payload["data"]["id"]) - data.data.counter_all_data.remove_loadmanagement_prio_item(payload["data"]["id"]) + data.data.counter_all_data.remove_loadmanagement_prio_item(payload["data"]["id"], ComponentType.VEHICLE) Pub().pub("openWB/set/counter/get/loadmanagement_prios", data.data.counter_all_data.data.get.loadmanagement_prios) pub_user_message( diff --git a/packages/modules/common/component_type.py b/packages/modules/common/component_type.py index a759d3c533..69fd3694a5 100644 --- a/packages/modules/common/component_type.py +++ b/packages/modules/common/component_type.py @@ -11,6 +11,7 @@ class ComponentType(Enum): GRID_FEE = "grid_tariff" INVERTER = "inverter" IO = "io" + VEHICLE = "vehicle" def special_to_general_type_mapping(component_type: str) -> ComponentType: From a0cb3be3d0bd8c310e50df38b493dd4586b3af76 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 20 Aug 2026 15:32:59 +0200 Subject: [PATCH 2/3] improve style --- packages/control/counter_all/counter_all_data.py | 4 ++-- packages/control/counter_all/loadmanagement_prio.py | 8 ++++---- packages/control/counter_all/loadmanagement_prio_test.py | 7 ++++--- packages/helpermodules/command.py | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/control/counter_all/counter_all_data.py b/packages/control/counter_all/counter_all_data.py index 23c90f20cf..432522e59b 100644 --- a/packages/control/counter_all/counter_all_data.py +++ b/packages/control/counter_all/counter_all_data.py @@ -100,8 +100,8 @@ class LoadmanagementPrioProtocol(Protocol): @property def data(self) -> CounterAllData: ... def add_loadmanagement_prio_item(self, type: ComponentType, id: int) -> None: ... - def remove_loadmanagement_prio_item(self, id: int, type: ComponentType) -> None: ... - def _remove_loadmanagement_prio_item(self, id: int, type: ComponentType, entry: Dict) -> None: ... + def remove_loadmanagement_prio_item(self, type: ComponentType, id: int) -> None: ... + def _remove_loadmanagement_prio_item(self, type: ComponentType, id: int, entry: Dict) -> None: ... def sort_cps_by_loadmanagement_prios_nested(self, filtered_cps: List[Chargepoint]) -> List[List[Chargepoint]]: ... def generator_cps_by_loadmanagement_prios( diff --git a/packages/control/counter_all/loadmanagement_prio.py b/packages/control/counter_all/loadmanagement_prio.py index 9e6ecc406f..9c68557c75 100644 --- a/packages/control/counter_all/loadmanagement_prio.py +++ b/packages/control/counter_all/loadmanagement_prio.py @@ -14,20 +14,20 @@ class LoadmanagementPrioMixin: def add_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, type: ComponentType, id: int) -> None: self.data.get.loadmanagement_prios.append({"type": type.value, "id": id}) - def remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, id: int, type: ComponentType) -> None: - if self._remove_loadmanagement_prio_item(id, type, self.data.get.loadmanagement_prios) is False: + def remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, type: ComponentType, id: int) -> None: + if self._remove_loadmanagement_prio_item(type, id, self.data.get.loadmanagement_prios) is False: raise IndexError(f"Element {id} konnte nicht in der Prioritätensteuerung gefunden werden.") def _remove_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, - id: int, type: ComponentType, + id: int, entry: List[Dict]) -> bool: for item in entry: if item["type"] == type.value and item["id"] == id: entry.remove(item) return True elif item["type"] == "group": - removed_item = self._remove_loadmanagement_prio_item(id, type, item["children"]) + removed_item = self._remove_loadmanagement_prio_item(type, id, item["children"]) if removed_item and len(item["children"]) == 0: entry.remove(item) if removed_item: diff --git a/packages/control/counter_all/loadmanagement_prio_test.py b/packages/control/counter_all/loadmanagement_prio_test.py index 4924cfbe3b..110e0bab2d 100644 --- a/packages/control/counter_all/loadmanagement_prio_test.py +++ b/packages/control/counter_all/loadmanagement_prio_test.py @@ -34,8 +34,9 @@ def cp3(): "loadmanagement_prios, id, type, expected_loadmanagement_prios", [ pytest.param([], 2, ComponentType.VEHICLE, [{"type": "vehicle", "id": 2}], id="emtpy list"), - pytest.param([{"type": "vehicle", "id": 3}], 2, ComponentType.VEHICLE, [{"type": "vehicle", "id": 3}, - {"type": "vehicle", "id": 2}], id="flat list"), + pytest.param([{"type": "vehicle", "id": 3}], 2, ComponentType.VEHICLE, + [{"type": "vehicle", "id": 3}, + {"type": "vehicle", "id": 2}], id="flat list"), pytest.param([ { "type": "group", @@ -138,7 +139,7 @@ def test_remove_loadmanagement_prio_item(loadmanagement_prios: List[Dict], c.data.get.loadmanagement_prios = loadmanagement_prios # execution - c.remove_loadmanagement_prio_item(id, ComponentType.VEHICLE) + c.remove_loadmanagement_prio_item(ComponentType.VEHICLE, id) # assert assert c.data.get.loadmanagement_prios == expected_loadmanagement_prios diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index d9f5c14ca0..b8ffb350d2 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -794,7 +794,7 @@ def removeVehicle(self, connection_id: str, payload: dict) -> None: if SubData.system_data["system"].data["security"]["user_management_active"]: remove_acl_role("vehicle--access", payload["data"]["id"]) remove_acl_role("vehicle--write-access", payload["data"]["id"]) - data.data.counter_all_data.remove_loadmanagement_prio_item(payload["data"]["id"], ComponentType.VEHICLE) + data.data.counter_all_data.remove_loadmanagement_prio_item(ComponentType.VEHICLE, payload["data"]["id"]) Pub().pub("openWB/set/counter/get/loadmanagement_prios", data.data.counter_all_data.data.get.loadmanagement_prios) pub_user_message( From 5496a813b852eda7e85301f2d8af8a3ae8c8f241 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 21 Aug 2026 13:57:14 +0200 Subject: [PATCH 3/3] improve update config --- packages/helpermodules/update_config.py | 7 +++++- packages/helpermodules/update_config_test.py | 25 +++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 346852c4d5..75ccc46c2a 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3591,10 +3591,15 @@ def upgrade(topic: str, payload) -> None: charge_template = decode_payload( self.all_received_topics[f"openWB/vehicle/template/charge_template/{charge_template_id}"]) if charge_template["chargemode"]["selected"] == chargemode and charge_template["prio"] == prio: - loadmanagement_prios.append({"type": "vehicle", "id": int(get_index(topic))}) + grouped_vehicles.append({"type": "vehicle", "id": int(get_index(topic))}) loadmanagement_prios = [] for chargemode, prio in CHARGEMODES: + grouped_vehicles = [] self._loop_all_received_topics(upgrade) + if len(grouped_vehicles) == 1: + loadmanagement_prios.append(grouped_vehicles[0]) + elif len(grouped_vehicles) > 1: + loadmanagement_prios.append({"type": "group", "children": grouped_vehicles}) self.__update_topic("openWB/counter/get/loadmanagement_prios", loadmanagement_prios) self._append_datastore_version(141) diff --git a/packages/helpermodules/update_config_test.py b/packages/helpermodules/update_config_test.py index 5ff0fabdb1..30359a1cfd 100644 --- a/packages/helpermodules/update_config_test.py +++ b/packages/helpermodules/update_config_test.py @@ -239,13 +239,23 @@ def test_upgrade_datastore_125_is_idempotent_for_already_converted_values(mock_p assert mock_pub.pub.call_count == 1 # einmal publishen für Upgrade der Datastore-Version -@pytest.mark.parametrize("ev0_prio, ev0_chargemode, ev1_prio, ev1_chargemode, ev2_prio, ev2_chargemode, id_ordered", [ +@pytest.mark.parametrize("ev0_prio, ev0_chargemode, ev1_prio, ev1_chargemode, ev2_prio, ev2_chargemode, expected", [ pytest.param(False, Chargemode.INSTANT_CHARGING.value, False, Chargemode.INSTANT_CHARGING.value, - False, Chargemode.INSTANT_CHARGING.value, [0, 1, 2], id="alle gleich"), + False, Chargemode.INSTANT_CHARGING.value, + [{"type": "group", "children": [{"type": "vehicle", "id": 0}, + {"type": "vehicle", "id": 1}, + {"type": "vehicle", "id": 2}]}], + id="alle gleich"), pytest.param(False, Chargemode.INSTANT_CHARGING.value, False, Chargemode.INSTANT_CHARGING.value, - False, Chargemode.SCHEDULED_CHARGING.value, [2, 0, 1], id="Lademodi unterschiedlich"), + False, Chargemode.SCHEDULED_CHARGING.value, + [{"type": "vehicle", "id": 2}, + {"type": "group", "children": [{"type": "vehicle", "id": 0}, {"type": "vehicle", "id": 1}]}], + id="Lademodi unterschiedlich"), pytest.param(False, Chargemode.INSTANT_CHARGING.value, True, Chargemode.INSTANT_CHARGING.value, - False, Chargemode.INSTANT_CHARGING.value, [1, 0, 2], id="Prioritäten unterschiedlich") + False, Chargemode.INSTANT_CHARGING.value, + [{"type": "vehicle", "id": 1}, + {"type": "group", "children": [{"type": "vehicle", "id": 0}, {"type": "vehicle", "id": 2}]}], + id="Prioritäten unterschiedlich") ] ) def test_upgrade_datastore_141_ev_chargemode_conversion(ev0_prio: bool, @@ -254,7 +264,7 @@ def test_upgrade_datastore_141_ev_chargemode_conversion(ev0_prio: bool, ev1_chargemode: str, ev2_prio: bool, ev2_chargemode: str, - id_ordered: List[int], + expected: List[dict], mock_pub: Mock): # setup ev0_charge_template = ChargeTemplate() @@ -285,9 +295,6 @@ def test_upgrade_datastore_141_ev_chargemode_conversion(ev0_prio: bool, uc.upgrade_datastore_141() # evaluation - assert uc.all_received_topics["openWB/counter/get/loadmanagement_prios"] == [ - {"type": "vehicle", "id": id_ordered[0]}, - {"type": "vehicle", "id": id_ordered[1]}, - {"type": "vehicle", "id": id_ordered[2]}] + assert uc.all_received_topics["openWB/counter/get/loadmanagement_prios"] == expected assert uc.all_received_topics["openWB/system/datastore_version"] == [131, 132, 141] assert mock_pub.pub.call_count == 2 # einmal publishen für Upgrade der Datastore-Version