diff --git a/packages/helpermodules/changed_values_handler.py b/packages/helpermodules/changed_values_handler.py index 0853b90011..c30da83f89 100644 --- a/packages/helpermodules/changed_values_handler.py +++ b/packages/helpermodules/changed_values_handler.py @@ -1,11 +1,10 @@ -from dataclasses import fields, is_dataclass +from copy import deepcopy +from dataclasses import dataclass, fields, is_dataclass from enum import Enum import logging -from threading import Event -from typing import Dict, List, Tuple +from typing import Any, Dict, List, Optional, Tuple from control import data -from control.data import Data from dataclass_utils import asdict from helpermodules.pub import Pub @@ -64,43 +63,73 @@ # self.data = SampleData() +@dataclass +class ChangedValuesSnapshot: + bat_all_data: Any + cp_all_data: Any + consumer_all_data: Any + counter_all_data: Any + optional_data: Any + pv_all_data: Any + cp_data: Dict[str, Any] + bat_data: Dict[str, Any] + counter_data: Dict[str, Any] + consumer_data: Dict[str, Any] + pv_data: Dict[str, Any] + + class ChangedValuesHandler: - def __init__(self, event_module_update_completed: Event) -> None: - self.prev_data: Data = Data(event_module_update_completed) + def __init__(self) -> None: + self.prev_data: Optional[ChangedValuesSnapshot] = None def store_initial_values(self): try: - # speichern der Daten zum Zyklus-Beginn, um später die geänderten Werte zu ermitteln - self.prev_data.copy_data() + # Snapshot des Arbeitszustands, den der Handler anschließend verändert. + current_data = data.data + self.prev_data = ChangedValuesSnapshot( + bat_all_data=deepcopy(current_data.bat_all_data.data), + cp_all_data=deepcopy(current_data.cp_all_data.data.get), + consumer_all_data=deepcopy(current_data.consumer_all_data.data), + counter_all_data=deepcopy(current_data.counter_all_data.data), + optional_data=deepcopy(current_data.optional_data.data), + pv_all_data=deepcopy(current_data.pv_all_data.data), + cp_data={key: deepcopy(value.data) for key, value in current_data.cp_data.items()}, + bat_data={key: deepcopy(value.data) for key, value in current_data.bat_data.items()}, + counter_data={key: deepcopy(value.data) for key, value in current_data.counter_data.items()}, + consumer_data={key: deepcopy(value.data) for key, value in current_data.consumer_data.items()}, + pv_data={key: deepcopy(value.data) for key, value in current_data.pv_data.items()}, + ) except Exception as e: log.exception(e) def pub_changed_values(self): try: + if self.prev_data is None: + return # veröffentlichen der geänderten Werte - self._update_value("openWB/set/bat/", self.prev_data.bat_all_data.data, data.data.bat_all_data.data) - self._update_value("openWB/set/chargepoint/", self.prev_data.cp_all_data.data.get, + self._update_value("openWB/set/bat/", self.prev_data.bat_all_data, data.data.bat_all_data.data) + self._update_value("openWB/set/chargepoint/", self.prev_data.cp_all_data, data.data.cp_all_data.data.get) - self._update_value("openWB/set/consumer/", self.prev_data.consumer_all_data.data, + self._update_value("openWB/set/consumer/", self.prev_data.consumer_all_data, data.data.consumer_all_data.data) - self._update_value("openWB/set/counter/", self.prev_data.counter_all_data.data, + self._update_value("openWB/set/counter/", self.prev_data.counter_all_data, data.data.counter_all_data.data) - self._update_value("openWB/set/optional/", self.prev_data.optional_data.data, + self._update_value("openWB/set/optional/", self.prev_data.optional_data, data.data.optional_data.data) - self._update_value("openWB/set/pv/", self.prev_data.pv_all_data.data, + self._update_value("openWB/set/pv/", self.prev_data.pv_all_data, data.data.pv_all_data.data) for key, value in data.data.cp_data.items(): - self._update_value(f"openWB/set/chargepoint/{value.num}/", self.prev_data.cp_data[key].data, value.data) + self._update_value(f"openWB/set/chargepoint/{value.num}/", self.prev_data.cp_data[key], value.data) for key, value in data.data.bat_data.items(): - self._update_value(f"openWB/set/bat/{value.num}/", self.prev_data.bat_data[key].data, value.data) + self._update_value(f"openWB/set/bat/{value.num}/", self.prev_data.bat_data[key], value.data) for key, value in data.data.counter_data.items(): self._update_value(f"openWB/set/counter/{value.num}/", - self.prev_data.counter_data[key].data, value.data) + self.prev_data.counter_data[key], value.data) for key, value in data.data.consumer_data.items(): self._update_value(f"openWB/set/consumer/{value.num}/", - self.prev_data.consumer_data[key].data, value.data) + self.prev_data.consumer_data[key], value.data) for key, value in data.data.pv_data.items(): - self._update_value(f"openWB/set/pv/{value.num}/", self.prev_data.pv_data[key].data, value.data) + self._update_value(f"openWB/set/pv/{value.num}/", self.prev_data.pv_data[key], value.data) # chargepoint, ev template, autolock, time and scheduled charging plans mutable_by_algorithm immer false except Exception as e: log.exception(e) @@ -146,8 +175,8 @@ def _update_value(self, topic_prefix, data_inst_previous, data_inst): class ChangedValuesContext: - def __init__(self, event_module_update_completed: Event): - self.changed_values_handler = ChangedValuesHandler(event_module_update_completed) + def __init__(self): + self.changed_values_handler = ChangedValuesHandler() def __enter__(self): self.changed_values_handler.store_initial_values() diff --git a/packages/helpermodules/changed_values_handler_test.py b/packages/helpermodules/changed_values_handler_test.py index 48c13a4ba3..439193a77b 100644 --- a/packages/helpermodules/changed_values_handler_test.py +++ b/packages/helpermodules/changed_values_handler_test.py @@ -1,5 +1,6 @@ from dataclasses import dataclass, field from enum import IntEnum +from types import SimpleNamespace from typing import Dict, List, Optional, Tuple from unittest.mock import Mock @@ -7,6 +8,7 @@ from dataclass_utils import asdict from dataclass_utils.factories import currents_list_factory +from control import data from helpermodules.changed_values_handler import ChangedValuesHandler NONE_TYPE = type(None) @@ -106,12 +108,40 @@ class Params: @pytest.mark.parametrize("params", cases, ids=[c.name for c in cases]) def test_update_value(params: Params, mock_pub: Mock, monkeypatch): # setup - handler = ChangedValuesHandler(Mock()) + handler = ChangedValuesHandler() # execution handler._update_value("openWB/", SampleData(), params.sample_data) # evaluation - assert len(mock_pub.method_calls) - 1 == params.expected_calls + assert len(mock_pub.method_calls) == params.expected_calls if params.expected_calls > 0: - assert mock_pub.method_calls[1].args == params.expected_pub_call + assert mock_pub.method_calls[0].args == params.expected_pub_call + + +def test_store_initial_values_snapshots_current_data(monkeypatch): + # setup + source = SimpleNamespace( + bat_all_data=SimpleNamespace(data={"value": [1]}), + cp_all_data=SimpleNamespace(data=SimpleNamespace(get={"value": [2]})), + consumer_all_data=SimpleNamespace(data={"value": [3]}), + counter_all_data=SimpleNamespace(data={"value": [4]}), + optional_data=SimpleNamespace(data={"value": [5]}), + pv_all_data=SimpleNamespace(data={"value": [6]}), + cp_data={"cp0": SimpleNamespace(data={"value": [7]})}, + bat_data={}, + counter_data={}, + consumer_data={}, + pv_data={}, + ) + monkeypatch.setattr(data, "data", source, raising=False) + handler = ChangedValuesHandler() + + # execution + handler.store_initial_values() + source.cp_data["cp0"].data["value"].append(8) + + # evaluation + assert handler.prev_data is not None + assert handler.prev_data.cp_data["cp0"] == {"value": [7]} + assert handler.prev_data.cp_all_data == {"value": [2]} diff --git a/packages/main.py b/packages/main.py index a9ad7ae912..1b007c92ca 100755 --- a/packages/main.py +++ b/packages/main.py @@ -141,7 +141,7 @@ def handler_with_control_interval(): wait_for_module_update_completed(loadvars_.event_module_update_completed, "openWB/set/system/device/module_update_completed") data.data.copy_data() - with ChangedValuesContext(loadvars_.event_module_update_completed): + with ChangedValuesContext(): self.heartbeat = True if data.data.system_data["system"].data["perform_update"]: data.data.system_data["system"].perform_update() @@ -180,7 +180,8 @@ def handler5MinAlgorithm(self): ausführt, die nur alle 5 Minuten ausgeführt werden müssen. """ try: - with ChangedValuesContext(loadvars_.event_module_update_completed): + data.data.copy_data() + with ChangedValuesContext(): entries = save_log() daily_totals = update_daily_yields(entries) if daily_totals is not None: @@ -233,6 +234,7 @@ def handler5Min(self): @__with_handler_lock(error_threshold=60) def handler_midnight(self): try: + data.data.copy_data() today = timecheck.create_timestamp_YYYYMMDD() previous_day = timecheck.get_relative_date_string(today, day_offset=-1) save_daily_source_totals(previous_day) @@ -245,7 +247,7 @@ def handler_midnight(self): thread_errors_path = Path(Path(__file__).resolve().parents[1]/"ramdisk"/"thread_errors.log") with thread_errors_path.open("w") as f: f.write("") - with ChangedValuesContext(loadvars_.event_module_update_completed): + with ChangedValuesContext(): for consumer in data.data.consumer_data.values(): consumer.midnight_handler() except Exception: