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
19 changes: 10 additions & 9 deletions packages/modules/common/store/_consumer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from control import data
from modules.common.component_state import ConsumerState
from modules.common.component_type import ComponentType
from modules.common.store import ValueStore
from modules.common.store._api import LoggingValueStore
from modules.common.store._broker import pub_to_broker
from modules.common.utils.component_parser import get_component_obj_by_id
from modules.common.utils.component_parser import get_hierarchy_obj_by_id


class ConsumerValueStoreBroker(ValueStore[ConsumerState]):
Expand Down Expand Up @@ -43,15 +44,15 @@ def update(self) -> None:
extra_meter_id = data.data.consumer_data[f"consumer{self.delegate.delegate.num}"].data.extra_meter
if extra_meter_id is not None:
try:
component = get_component_obj_by_id(extra_meter_id)
component_state = component.store.delegate.delegate.state
consumer = get_hierarchy_obj_by_id(extra_meter_id, ComponentType.COUNTER.value)
consumer_state = consumer.store.delegate.delegate.state
self.set(ConsumerState(
power=component_state.power,
imported=component_state.imported,
exported=component_state.exported,
voltages=component_state.voltages,
currents=component_state.currents,
powers=component_state.powers,
power=consumer_state.power,
imported=consumer_state.imported,
exported=consumer_state.exported,
voltages=consumer_state.voltages,
currents=consumer_state.currents,
powers=consumer_state.powers,
))
except Exception:
raise Exception(f"Fehler beim Auslesen des Verbrauchszählers {extra_meter_id} "
Expand Down
24 changes: 20 additions & 4 deletions packages/modules/common/store/_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from modules.common.store import ValueStore
from modules.common.store._api import LoggingValueStore
from modules.common.store._broker import pub_to_broker
from modules.common.utils.component_parser import get_component_obj_by_id
from modules.common.utils.component_parser import get_hierarchy_obj_by_id

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -87,11 +87,20 @@ def _add_values(self, element, calc_imported_exported: bool):
self.exported += element.exported
self.power += element.power

def _get_assigned_extra_meter_ids(self) -> set:
assigned_extra_meter_ids = set()
for consumer in data.data.consumer_data.values():
extra_meter_id = consumer.data.extra_meter
if extra_meter_id is not None:
assigned_extra_meter_ids.add(extra_meter_id)
return assigned_extra_meter_ids

def calc_consumers(self, elements: Dict, calc_imported_exported: bool = False) -> CounterState:
assigned_extra_meter_ids = self._get_assigned_extra_meter_ids()
for element in elements:
try:
if element["type"] == ComponentType.CHARGEPOINT.value:
chargepoint = data.data.cp_data[f"cp{element['id']}"]
chargepoint = get_hierarchy_obj_by_id(element["id"], element["type"])
chargepoint_state = chargepoint.chargepoint_module.store.delegate.state
try:
self.currents = list(map(add,
Expand All @@ -107,8 +116,15 @@ def calc_consumers(self, elements: Dict, calc_imported_exported: bool = False) -
if calc_imported_exported:
self.imported += chargepoint_state.imported
self.exported += chargepoint_state.exported
elif element["type"] == ComponentType.CONSUMER.value:
consumer = get_hierarchy_obj_by_id(element["id"], element["type"])
consumer_state = consumer.module.store.delegate.delegate.state
self._add_values(consumer_state, calc_imported_exported)
elif element["type"] == ComponentType.COUNTER.value and element["id"] in assigned_extra_meter_ids:
log.debug(f"Zähler counter{element['id']} wird übersprungen, da er als separater Zähler "
"einem Verbraucher zugeordnet ist.")
else:
component = get_component_obj_by_id(element['id'])
component = get_hierarchy_obj_by_id(element["id"], element["type"])
self._add_values(component.store.delegate.delegate.state, calc_imported_exported)
except Exception:
log.exception(f"Fehler beim Hinzufügen der Werte für Element {element}")
Expand All @@ -131,7 +147,7 @@ def calc_uncounted_consumption(self) -> CounterState:
Dazu wird der Zählerstand des übergeordneten Zählers herangezogen und davon die
Werte aller anderen untergeordneten Komponenten abgezogen."""
parent_id = data.data.counter_all_data.get_entry_of_parent(self.delegate.delegate.num)["id"]
parent_component = get_component_obj_by_id(parent_id)
parent_component = get_hierarchy_obj_by_id(parent_id, ComponentType.COUNTER.value)
if "counter" not in parent_component.component_config.type:
raise Exception("Die übergeordnete Komponente des virtuellen Zählers muss ein Zähler sein.")
if parent_component.store.add_child_values:
Expand Down
96 changes: 91 additions & 5 deletions packages/modules/common/store/_counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,15 @@ def test_calc_virtual(params: Params, monkeypatch):
purge = PurgeCounterState(delegate=Mock(delegate=Mock(num=0)),
add_child_values=True,
simcounter=SimCounter(0, 0, ComponentType.COUNTER))
mock_comp_obj = Mock(side_effect=params.mock_comp)
monkeypatch.setattr(_counter, "get_component_obj_by_id", mock_comp_obj)
original_get_hierarchy_obj_by_id = _counter.get_hierarchy_obj_by_id
mock_comp_obj_iter = iter(params.mock_comp)

def mock_get_hierarchy_obj_by_id(component_id, component_type):
if component_type == ComponentType.CHARGEPOINT.value:
return original_get_hierarchy_obj_by_id(component_id, component_type)
return next(mock_comp_obj_iter)

monkeypatch.setattr(_counter, "get_hierarchy_obj_by_id", mock_get_hierarchy_obj_by_id)

# execution
state = purge.calc_virtual(CounterState(power=-5001, currents=[7.25]*3, exported=200, imported=100))
Expand Down Expand Up @@ -228,14 +235,16 @@ def test_calc_uncounted_consumption(monkeypatch):
)
)

def mock_get_component_obj_by_id(component_id):
original_get_hierarchy_obj_by_id = _counter.get_hierarchy_obj_by_id

def mock_get_hierarchy_obj_by_id(component_id, component_type):
if component_id == 0: # Parent counter
return parent_counter_component
elif component_id == 2: # Regular counter
return regular_counter_component
return None
return original_get_hierarchy_obj_by_id(component_id, component_type)

monkeypatch.setattr(_counter, "get_component_obj_by_id", mock_get_component_obj_by_id)
monkeypatch.setattr(_counter, "get_hierarchy_obj_by_id", mock_get_hierarchy_obj_by_id)

virtual_counter_purge = PurgeCounterState(
delegate=Mock(delegate=Mock(num=3)),
Expand All @@ -261,3 +270,80 @@ def mock_get_component_obj_by_id(component_id):
)

assert vars(result_state) == vars(expected_state)


def test_calc_consumers_skips_assigned_extra_meter_counter(monkeypatch):
# setup
data.data.counter_all_data.data.get.hierarchy = [
{
"id": 0,
"type": "counter",
"children": [
{"id": 1, "type": "consumer", "children": []},
{"id": 2, "type": "counter", "children": []},
],
}
]
elements = [
{"id": 1, "type": "consumer", "children": []},
{"id": 2, "type": "counter", "children": []},
]

consumer_state = Mock(
power=1000,
currents=[1.0, 1.0, 1.0],
imported=10,
exported=0,
)
consumer_obj = Mock(
data=Mock(extra_meter=2),
module=Mock(
store=Mock(
delegate=Mock(
delegate=Mock(state=consumer_state)
)
)
)
)
data.data.consumer_data["consumer1"] = consumer_obj

counter_state = CounterState(
power=2000,
currents=[2.0, 2.0, 2.0],
imported=20,
exported=0,
)
counter_component = Mock(
store=Mock(
delegate=Mock(
delegate=Mock(state=counter_state)
)
)
)

def mock_get_hierarchy_obj_by_id(component_id, component_type):
if component_id == 1 and component_type == ComponentType.CONSUMER.value:
return consumer_obj
if component_id == 2 and component_type == ComponentType.COUNTER.value:
return counter_component
raise ValueError(f"unexpected lookup: {component_type}{component_id}")

monkeypatch.setattr(_counter, "get_hierarchy_obj_by_id", mock_get_hierarchy_obj_by_id)

purge = PurgeCounterState(delegate=Mock(delegate=Mock(num=0)),
add_child_values=True,
simcounter=SimCounter(0, 0, ComponentType.COUNTER))
purge.currents = [0.0, 0.0, 0.0]
purge.power = 0
purge.imported = 0
purge.exported = 0
purge.incomplete_currents = False

# execution
result_state = purge.calc_consumers(elements, calc_imported_exported=True)

# evaluation
assert result_state.power == 1000
assert result_state.currents == [1.0, 1.0, 1.0]
assert result_state.imported == 10
assert result_state.exported == 0
24 changes: 16 additions & 8 deletions packages/modules/common/utils/component_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from control import data
from modules.common.abstract_device import AbstractDevice
from modules.common.abstract_io import AbstractIoDevice
from modules.common.component_type import ComponentType
from modules.common.component_type import type_to_topic_mapping
log = logging.getLogger(__name__)

Expand Down Expand Up @@ -60,12 +61,19 @@ def get_finished_component_obj_by_id(id: int, not_finished_threads: List[str]) -
return None


def get_component_obj_by_id(id: int) -> Optional[Any]:
for item in data.data.system_data.values():
if isinstance(item, AbstractDevice):
for comp in item.components.values():
if comp.component_config.id == id:
return comp
def get_hierarchy_obj_by_id(id: int, element_type: str) -> Any:
obj = None
if element_type == ComponentType.CHARGEPOINT.value:
obj = data.data.cp_data.get(f"cp{id}")
elif element_type == ComponentType.CONSUMER.value:
obj = data.data.consumer_data.get(f"consumer{id}")
elif element_type in (ComponentType.BAT.value, ComponentType.COUNTER.value, ComponentType.INVERTER.value):
for item in data.data.system_data.values():
if isinstance(item, AbstractDevice):
for comp in item.components.values():
if comp.component_config.id == id:
obj = comp
else:
log.error(f"Element {id} konnte keinem Gerät zugeordnet werden.")
return None
raise ValueError(f"Element {id} vom Typ {element_type} konnte nicht aufgelöst werden.")
if obj is not None:
return obj
14 changes: 12 additions & 2 deletions packages/modules/devices/generic/virtual/counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,24 @@ def test_virtual_counter(mock_pub: Mock, params):
[pytest.param(mock_comp_obj_inv_bat, hierarchy_standard, id="standard"),
pytest.param(mock_comp_obj_inv_bat, hierarchy_hybrid, id="hybrid"),
pytest.param(mock_comp_obj_counter_inv_bat, hierarchy_nested, id="nested")])
def test_virtual_counter_hierarchies(mock, counter_all: Callable[[], CounterAll], data_, mock_pub: Mock, monkeypatch):
def test_virtual_counter_hierarchies(mock,
counter_all: Callable[[], CounterAll],
data_,
mock_pub: Mock,
monkeypatch: pytest.MonkeyPatch):
# setup
virtual_counter = counter.VirtualCounter(VirtualCounterSetup(
id=0, configuration=VirtualCounterConfiguration(external_consumption=0)), device_id=0)
virtual_counter.initialize()
data.data.counter_all_data = counter_all()
mock_comp_obj = Mock(side_effect=mock)
monkeypatch.setattr(_counter, "get_component_obj_by_id", mock_comp_obj)

def mock_get_hierarchy_obj_by_id(component_id: int, component_type: str):
if component_type == "cp":
return data.data.cp_data[f"cp{component_id}"]
return mock_comp_obj(component_id, component_type)

monkeypatch.setattr(_counter, "get_hierarchy_obj_by_id", mock_get_hierarchy_obj_by_id)

# execution
virtual_counter.update()
Expand Down