Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/control/counter_all/counter_all_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import Callable, Dict, Generator, List, Protocol, Tuple, Union
from typing import Callable, Dict, Generator, List, Optional, Protocol, Tuple, Union

from control.chargepoint.chargepoint import Chargepoint
from control.consumer.usage import ConsumerUsage
Expand Down Expand Up @@ -94,6 +94,11 @@ def hierarchy_add_item_below(self, new_id: int, new_type: ComponentType, id_to_f
def hierarchy_add_item_below_evu(self, new_id: int, new_type: ComponentType) -> None: ...
def hierarchy_remove_item(self, id_to_find: int, keep_children: bool = True) -> None: ...

def update_linked_counter_hierarchy(self,
consumer_id: int,
old_counter_id: Optional[int],
new_counter_id: Optional[int]) -> bool: ...


class LoadmanagementPrioProtocol(Protocol):
@property
Expand Down
49 changes: 49 additions & 0 deletions packages/control/counter_all/hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,55 @@


class HierarchyMixin:
def _is_entry_of_type(self: HierarchyProtocol, id_to_find: int, type_to_find: ComponentType) -> bool:
entry = self.get_entry_of_element(id_to_find)
return bool(entry) and entry.get("type") == type_to_find.value

def _get_parent_counter_id_of_consumer(self: HierarchyProtocol, consumer_id: int) -> Optional[int]:
parent = self.get_entry_of_parent(consumer_id)
if not parent or parent.get("type") != ComponentType.COUNTER.value:
return None
return parent["id"]

def _add_unlinked_counter_below_consumer_parent_if_needed(self: HierarchyProtocol,
consumer_id: int,
counter_id: int) -> bool:
if self._is_entry_of_type(counter_id, ComponentType.COUNTER):
return False
parent_counter_id = self._get_parent_counter_id_of_consumer(consumer_id)
if parent_counter_id is None:
return False
self.hierarchy_add_item_below(counter_id, ComponentType.COUNTER, parent_counter_id)
return True

def _remove_linked_counter_from_hierarchy_if_present(self: HierarchyProtocol, counter_id: int) -> bool:
counter_entry = self.get_entry_of_element(counter_id)
if not counter_entry or counter_entry.get("type") != ComponentType.COUNTER.value:
return False
parent_counter_id = self.get_entry_of_parent(counter_id).get("id")
if parent_counter_id is None:
return False
self.hierarchy_remove_item(counter_id)
return True

def update_linked_counter_hierarchy(self: HierarchyProtocol,
consumer_id: int,
old_counter_id: Optional[int],
new_counter_id: Optional[int]) -> bool:
hierarchy_changed = False

if not self._is_entry_of_type(consumer_id, ComponentType.CONSUMER):
return False

if new_counter_id is not None:
hierarchy_changed = self._remove_linked_counter_from_hierarchy_if_present(new_counter_id)

if old_counter_id is not None and old_counter_id != new_counter_id:
if self._add_unlinked_counter_below_consumer_parent_if_needed(consumer_id, old_counter_id):
hierarchy_changed = True
Comment on lines +57 to +59

return hierarchy_changed

def get_all_elements_without_children(self: HierarchyProtocol, id: int) -> List[Dict]:
self.childless = []
self._get_all_elements_without_children_recursive(self.get_entry_of_element(id))
Expand Down
9 changes: 9 additions & 0 deletions packages/helpermodules/subdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,16 @@ def process_consumer_topic(self, client: mqtt.Client, var: Dict[str, Consumer],
elif re.search("openWB/consumer/[0-9]+/set", msg.topic) is not None:
self.set_json_payload_class(var["consumer"+index].data.set, msg)
elif re.search("openWB/consumer/[0-9]+/extra_meter", msg.topic) is not None:
old_extra_meter = var[f"consumer{index}"].data.extra_meter
self.set_json_payload_class(var[f"consumer{index}"].data, msg)
if self.event_subdata_initialized.is_set() and old_extra_meter != var[
f"consumer{index}"].data.extra_meter:
if self.counter_all_data.update_linked_counter_hierarchy(
int(index),
old_extra_meter,
var[f"consumer{index}"].data.extra_meter):
Pub().pub("openWB/set/counter/get/hierarchy",
self.counter_all_data.data.get.hierarchy)
elif re.search("openWB/consumer/[0-9]+/usage$", msg.topic) is not None:
usage = dataclass_from_dict(Usage, decode_payload(msg.payload))
var[f"consumer{index}"].data.usage = usage
Expand Down
92 changes: 92 additions & 0 deletions packages/helpermodules/subdata_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,95 @@ def test_process_consumer_usage_does_not_update_loadmanagement_prios_on_startup(
assert consumers["consumer2"].data.usage.type == ConsumerUsage.METER_ONLY
assert counter_all_data.data.get.loadmanagement_prios == [{"type": "consumer", "id": 2}]
mock_pub.pub.assert_not_called()


def test_process_consumer_extra_meter_removes_linked_counter_from_hierarchy(monkeypatch: pytest.MonkeyPatch,
mock_pub: Mock):
subdata = SubData.__new__(SubData)
subdata.event_subdata_initialized = Mock()
subdata.event_subdata_initialized.is_set.return_value = True
counter_all_data = CounterAll()
counter_all_data.data.get.hierarchy = [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 5, "type": "counter", "children": []},
],
}]
monkeypatch.setattr(SubData, "counter_all_data", counter_all_data)
consumers = {"consumer2": Consumer(2)}
msg = Mock(topic="openWB/consumer/2/extra_meter", payload=b"5")

subdata.process_consumer_topic(Mock(), consumers, msg)

assert consumers["consumer2"].data.extra_meter == 5
assert counter_all_data.data.get.hierarchy == [{
"id": 0,
"type": "counter",
"children": [{"id": 2, "type": "consumer", "children": []}],
}]
mock_pub.pub.assert_called_once_with("openWB/set/counter/get/hierarchy", counter_all_data.data.get.hierarchy)


def test_process_consumer_extra_meter_adds_unlinked_counter_to_hierarchy(monkeypatch: pytest.MonkeyPatch,
mock_pub: Mock):
subdata = SubData.__new__(SubData)
subdata.event_subdata_initialized = Mock()
subdata.event_subdata_initialized.is_set.return_value = True
counter_all_data = CounterAll()
counter_all_data.data.get.hierarchy = [{
"id": 0,
"type": "counter",
"children": [{"id": 2, "type": "consumer", "children": []}],
}]
monkeypatch.setattr(SubData, "counter_all_data", counter_all_data)
consumers = {"consumer2": Consumer(2)}
consumers["consumer2"].data.extra_meter = 5
msg = Mock(topic="openWB/consumer/2/extra_meter", payload=b"null")

subdata.process_consumer_topic(Mock(), consumers, msg)

assert consumers["consumer2"].data.extra_meter is None
assert counter_all_data.data.get.hierarchy == [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 5, "type": "counter", "children": []},
],
}]
mock_pub.pub.assert_called_once_with("openWB/set/counter/get/hierarchy", counter_all_data.data.get.hierarchy)


def test_process_consumer_extra_meter_switches_linked_counter_in_hierarchy(monkeypatch: pytest.MonkeyPatch,
mock_pub: Mock):
subdata = SubData.__new__(SubData)
subdata.event_subdata_initialized = Mock()
subdata.event_subdata_initialized.is_set.return_value = True
counter_all_data = CounterAll()
counter_all_data.data.get.hierarchy = [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 6, "type": "counter", "children": []},
],
}]
monkeypatch.setattr(SubData, "counter_all_data", counter_all_data)
consumers = {"consumer2": Consumer(2)}
consumers["consumer2"].data.extra_meter = 5
msg = Mock(topic="openWB/consumer/2/extra_meter", payload=b"6")

subdata.process_consumer_topic(Mock(), consumers, msg)

assert consumers["consumer2"].data.extra_meter == 6
assert counter_all_data.data.get.hierarchy == [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 5, "type": "counter", "children": []},
],
}]
mock_pub.pub.assert_called_once_with("openWB/set/counter/get/hierarchy", counter_all_data.data.get.hierarchy)
42 changes: 41 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

class UpdateConfig:

DATASTORE_VERSION = 150
DATASTORE_VERSION = 151

valid_topic = [
"^openWB/bat/config/bat_control_activated$",
Expand Down Expand Up @@ -3932,3 +3932,43 @@ def upgrade(topic: str, payload) -> Optional[dict]:

self._loop_all_received_topics(upgrade)
self._append_datastore_version(150)

def upgrade_datastore_151(self):
"""Bereits verknüpfte Verbraucher-Zähler aus der Hierarchie entfernen."""
hierarchy_topic = "openWB/counter/get/hierarchy"
hierarchy = decode_payload(self.all_received_topics.get(hierarchy_topic))
if not isinstance(hierarchy, list) or not hierarchy:
self._append_datastore_version(151)
return

linked_counter_ids = {
int(extra_meter_id)
for topic, payload in self.all_received_topics.items()
if re.search(r"^openWB/consumer/[0-9]+/extra_meter$", topic) is not None
for extra_meter_id in [decode_payload(payload)]
if extra_meter_id is not None
}

if not linked_counter_ids:
self._append_datastore_version(151)
return

migrated_hierarchy = copy.deepcopy(hierarchy)
_counter_all = counter_all.CounterAll()
_counter_all.data.get.hierarchy = migrated_hierarchy
hierarchy_changed = False

for linked_counter_id in linked_counter_ids:
counter_entry = _counter_all.get_entry_of_element(linked_counter_id)
if not counter_entry or counter_entry.get("type") != ComponentType.COUNTER.value:
continue
parent_entry = _counter_all.get_entry_of_parent(linked_counter_id)
if not parent_entry or parent_entry.get("type") != ComponentType.COUNTER.value:
continue
_counter_all.hierarchy_remove_item(linked_counter_id)
hierarchy_changed = True

if hierarchy_changed:
self.__update_topic(hierarchy_topic, _counter_all.data.get.hierarchy)

self._append_datastore_version(151)
31 changes: 31 additions & 0 deletions packages/helpermodules/update_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,34 @@ def test_upgrade_datastore_150_migrates_fixed_hours_weekdays(mock_pub):
assert "openWB/optional/ep/flexible_tariff/provider" in updated_topics
assert "openWB/optional/ep/grid_fee/provider" in updated_topics
assert "openWB/system/datastore_version" in updated_topics


def test_upgrade_datastore_151_removes_linked_extra_meter_counters_from_hierarchy(mock_pub: Mock):
uc = UpdateConfig()
uc.all_received_topics = {
"openWB/consumer/2/extra_meter": 5,
"openWB/consumer/3/extra_meter": None,
"openWB/counter/get/hierarchy": [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 5, "type": "counter", "children": []},
{"id": 6, "type": "counter", "children": []},
],
}],
"openWB/system/datastore_version": list(range(151)),
}

uc.upgrade_datastore_151()

assert uc.all_received_topics["openWB/counter/get/hierarchy"] == [{
"id": 0,
"type": "counter",
"children": [
{"id": 2, "type": "consumer", "children": []},
{"id": 6, "type": "counter", "children": []},
],
}]
assert uc.all_received_topics["openWB/system/datastore_version"] == list(range(152))
assert mock_pub.pub.call_count == 2
Loading