diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 0afc0726de..a6b9e28edf 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -24,7 +24,6 @@ from control import data from control.bat import Bat from helpermodules.constants import NO_ERROR -from helpermodules.pub import Pub from modules.common.fault_state import FaultStateLevel log = logging.getLogger(__name__) @@ -38,7 +37,7 @@ class BatConsiderationMode(Enum): @dataclass class Config: - configured: bool = False + configured: bool = field(default=False, metadata={"topic": "config/configured"}) def config_factory() -> Config: @@ -47,14 +46,14 @@ def config_factory() -> Config: @dataclass class Get: - soc: float = field(default=0, metadata={"topic": "get/soc", "mutable_by_algorithm": True}) - daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported", "mutable_by_algorithm": True}) - daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported", "mutable_by_algorithm": True}) - fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str", "mutable_by_algorithm": True}) - fault_state: int = field(default=0, metadata={"topic": "get/fault_state", "mutable_by_algorithm": True}) - imported: float = field(default=0, metadata={"topic": "get/imported", "mutable_by_algorithm": True}) - exported: float = field(default=0, metadata={"topic": "get/exported", "mutable_by_algorithm": True}) - power: float = field(default=0, metadata={"topic": "get/power", "mutable_by_algorithm": True}) + soc: float = field(default=0, metadata={"topic": "get/soc"}) + daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported"}) + daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) + fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"}) + fault_state: int = field(default=0, metadata={"topic": "get/fault_state"}) + imported: float = field(default=0, metadata={"topic": "get/imported"}) + exported: float = field(default=0, metadata={"topic": "get/exported"}) + power: float = field(default=0, metadata={"topic": "get/power"}) def get_factory() -> Get: @@ -63,8 +62,9 @@ def get_factory() -> Get: @dataclass class Set: - charging_power_left: float = 0 - regulate_up: bool = False + charging_power_left: float = field( + default=0, metadata={"topic": "set/charging_power_left"}) + regulate_up: bool = field(default=False, metadata={"topic": "set/regulate_up"}) def set_factory() -> Set: @@ -90,7 +90,6 @@ def calc_power_for_all_components(self): try: if len(data.data.bat_data) >= 1: self.data.config.configured = True - Pub().pub("openWB/set/bat/config/configured", self.data.config.configured) # Summe für alle konfigurierten Speicher bilden exported = 0 imported = 0 @@ -128,7 +127,6 @@ def calc_power_for_all_components(self): self.data.get.soc = 0 else: self.data.config.configured = False - Pub().pub("openWB/set/bat/config/configured", self.data.config.configured) except Exception: log.exception("Fehler im Bat-Modul") @@ -148,10 +146,6 @@ def _max_bat_power_hybrid_system(self, battery: Bat) -> float: else: battery.data.get.fault_state = FaultStateLevel.ERROR.value battery.data.get.fault_str = self.ERROR_CONFIG_MAX_AC_OUT - Pub().pub(f"openWB/set/bat/{battery.num}/get/fault_state", - battery.data.get.fault_state) - Pub().pub(f"openWB/set/bat/{battery.num}/get/fault_str", - battery.data.get.fault_str) raise ValueError(self.ERROR_CONFIG_MAX_AC_OUT) else: # Kein Hybrid-WR @@ -193,8 +187,6 @@ def setup_bat(self): else: self.data.set.charging_power_left = 0 self.data.get.power = 0 - Pub().pub("openWB/set/bat/set/charging_power_left", self.data.set.charging_power_left) - Pub().pub("openWB/set/bat/set/regulate_up", self.data.set.regulate_up) except Exception: log.exception("Fehler im Bat-Modul") diff --git a/packages/control/chargepoint/control_parameter.py b/packages/control/chargepoint/control_parameter.py index fc151b9e7b..de8daa2ed4 100644 --- a/packages/control/chargepoint/control_parameter.py +++ b/packages/control/chargepoint/control_parameter.py @@ -9,50 +9,28 @@ @dataclass class ControlParameter: - chargemode: Chargemode_enum = field( - default=Chargemode_enum.STOP, - metadata={"topic": "control_parameter/chargemode", "mutable_by_algorithm": True}) - current_plan: Optional[str] = field( - default=None, - metadata={"topic": "control_parameter/current_plan", "mutable_by_algorithm": True}) - failed_phase_switches: int = field( - default=0, - metadata={"topic": "control_parameter/failed_phase_switches", "mutable_by_algorithm": True}) + chargemode: Chargemode_enum = field(default=Chargemode_enum.STOP, metadata={ + "topic": "control_parameter/chargemode"}) + current_plan: Optional[str] = field(default=None, metadata={"topic": "control_parameter/current_plan"}) + failed_phase_switches: int = field(default=0, metadata={"topic": "control_parameter/failed_phase_switches"}) imported_at_plan_start: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/imported_at_plan_start", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/imported_at_plan_start"}) imported_instant_charging: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/imported_instant_charging", "mutable_by_algorithm": True}) - limit: Optional[LimitingValue] = field( - default=None, - metadata={"topic": "control_parameter/limit", "mutable_by_algorithm": True}) - phases: int = field( - default=0, - metadata={"topic": "control_parameter/phases", "mutable_by_algorithm": True}) - prio: bool = field( - default=False, - metadata={"topic": "control_parameter/prio", "mutable_by_algorithm": True}) - required_current: float = field( - default=0, - metadata={"topic": "control_parameter/required_current", "mutable_by_algorithm": True}) - required_currents: List[float] = field( - default_factory=currents_list_factory) - state: ChargepointState = field( - default=ChargepointState.NO_CHARGING_ALLOWED, - metadata={"topic": "control_parameter/state", "mutable_by_algorithm": True}) - submode: Chargemode_enum = field( - default=Chargemode_enum.STOP, - metadata={"topic": "control_parameter/submode", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/imported_instant_charging"}) + limit: Optional[LimitingValue] = field(default=None, metadata={"topic": "control_parameter/limit"}) + phases: int = field(default=0, metadata={"topic": "control_parameter/phases"}) + prio: bool = field(default=False, metadata={"topic": "control_parameter/prio"}) + required_current: float = field(default=0, metadata={"topic": "control_parameter/required_current"}) + required_currents: List[float] = field(default_factory=currents_list_factory) + state: ChargepointState = field(default=ChargepointState.NO_CHARGING_ALLOWED, + metadata={"topic": "control_parameter/state"}) + submode: Chargemode_enum = field(default=Chargemode_enum.STOP, metadata={"topic": "control_parameter/submode"}) timestamp_auto_phase_switch: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_auto_phase_switch", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_auto_phase_switch"}) timestamp_perform_phase_switch: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_perform_phase_switch", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_perform_phase_switch"}) timestamp_switch_on_off: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_switch_on_off", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_switch_on_off"}) def control_parameter_factory() -> ControlParameter: diff --git a/packages/control/prepare.py b/packages/control/prepare.py index 2bbc9ca383..ee32f486ee 100644 --- a/packages/control/prepare.py +++ b/packages/control/prepare.py @@ -18,6 +18,8 @@ def setup_algorithm(self) -> None: """ bereitet die Daten für den Algorithmus vor und startet diesen. """ try: + data.data.pv_all_data.calc_power_for_all_components() + data.data.bat_all_data.calc_power_for_all_components() for cp in data.data.cp_data.values(): cp.setup_values_at_start() data.data.bat_all_data.setup_bat() diff --git a/packages/helpermodules/changed_values_handler.py b/packages/helpermodules/changed_values_handler.py index 2a8b093255..fe62b60b50 100644 --- a/packages/helpermodules/changed_values_handler.py +++ b/packages/helpermodules/changed_values_handler.py @@ -14,9 +14,8 @@ # In den Metadaten wird unter dem Key der Topic-Suffix ab "openWB/ev/2/" angegeben. Der Topic-Prefix ("openWB/ev/2/") # wird automatisch ermittelt. -# Der Key mutable_by_algorithm ist True, wenn die Werte im Algorithmus geändert werden können. Nur diese Werte werden -# automatisiert gepublished. Werte, die an anderer Stelle gepublished werden, wie zB Zählerstände, werden mit False -# gekennzeichnet. Um eine Dokumentation der Topics zu erhalten, sollten die Metadaten dennoch ausgefüllt werden. +# Der Kontextmanager muss immer verwendet werden, wenn in den Funktionen Werte geändert werden, die nicht gepublished +# werden. # Metadaten werden nur für Felder erzeugt, die gepublished werden sollen, dh bei ganzen Klassen für das Feld der # jeweiligen Klasse. Wenn Werte aus einer instanziierten Klasse gepublished werden sollen, erhält die übergeordnete # Klasse keine Metadaten (siehe Beispiel unten). @@ -34,8 +33,8 @@ # @dataclass # class SampleNested: -# parameter1: bool = field(default=False, metadata={"topic": "get/nested1", "mutable_by_algorithm": True}) -# parameter2: int = field(default=0, metadata={"topic": "get/nested2", "mutable_by_algorithm": True}) +# parameter1: bool = field(default=False, metadata={"topic": "get/nested1"}) +# parameter2: int = field(default=0, metadata={"topic": "get/nested2"}) # def sample_nested() -> SampleNested: @@ -48,12 +47,12 @@ # diese Klasse eingetragen. Die Felder der Konfigurationsklasse bekommen keine Metadaten, da diese nicht einzeln # gepublished werden. # sample_field_class: SampleClass = field( -# default_factory=sample_class, metadata={"topic": "get/field_class", "mutable_by_algorithm": True}) -# sample_field_int: int = field(default=0, metadata={"topic": "get/field_int", "mutable_by_algorithm": True}) +# default_factory=sample_class, metadata={"topic": "get/field_class"}) +# sample_field_int: int = field(default=0, metadata={"topic": "get/field_int"}) # sample_field_immutable: float = field( -# default=0, metadata={"topic": "get/field_immutable", "mutable_by_algorithm": False}) +# default=0, metadata={"topic": "get/field_immutable"}) # sample_field_list: List = field(default_factory=currents_list_factory, metadata={ -# "topic": "get/field_list", "mutable_by_algorithm": True}) +# "topic": "get/field_list"}) # # Bei verschachtelten Klassen, wo der zu publishende Wert auf einer tieferen Ebene liegt, werden nur für den zu # publishenden Wert Metadaten erzeugt. # sample_field_nested: SampleNested = field(default_factory=sample_nested) @@ -99,7 +98,7 @@ def _update_value(self, topic_prefix, data_inst_previous, data_inst): if isinstance(previous_value, Enum): previous_value = previous_value.value if hasattr(f, "metadata"): - if f.metadata.get("mutable_by_algorithm"): + if f.metadata.get("topic"): if isinstance(value, (str, int, float, Dict, List, Tuple)): if previous_value != value: changed = True @@ -123,3 +122,15 @@ def _update_value(self, topic_prefix, data_inst_previous, data_inst): self._update_value(topic_prefix, previous_value, value) except Exception as e: log.exception(e) + + +class ChangedValuesContext: + def __init__(self, event_module_update_completed: threading.Event): + self.changed_values_handler = ChangedValuesHandler(event_module_update_completed) + + def __enter__(self): + self.changed_values_handler.store_initial_values() + + def __exit__(self, exception_type, exception, exception_traceback) -> bool: + self.changed_values_handler.pub_changed_values() + return False diff --git a/packages/helpermodules/changed_values_handler_test.py b/packages/helpermodules/changed_values_handler_test.py index d42524dbe4..2e9225f435 100644 --- a/packages/helpermodules/changed_values_handler_test.py +++ b/packages/helpermodules/changed_values_handler_test.py @@ -23,8 +23,8 @@ def sample_class() -> SampleClass: @dataclass class SampleNested: - parameter1: bool = field(default=False, metadata={"topic": "get/nested1", "mutable_by_algorithm": True}) - parameter2: int = field(default=0, metadata={"topic": "get/nested2", "mutable_by_algorithm": True}) + parameter1: bool = field(default=False, metadata={"topic": "get/nested1"}) + parameter2: int = field(default=0, metadata={"topic": "get/nested2"}) def sample_nested() -> SampleNested: @@ -41,25 +41,25 @@ def sample_tuple_factory() -> Tuple: @dataclass class SampleData: - sample_field_bool: bool = field(default=False, metadata={"topic": "get/field_bool", "mutable_by_algorithm": True}) + sample_field_bool: bool = field(default=False, metadata={"topic": "get/field_bool"}) sample_field_class: SampleClass = field( - default_factory=sample_class, metadata={"topic": "get/field_class", "mutable_by_algorithm": True}) + default_factory=sample_class, metadata={"topic": "get/field_class"}) sample_field_dict: Dict = field(default_factory=sample_dict_factory, metadata={ - "topic": "get/field_dict", "mutable_by_algorithm": True}) + "topic": "get/field_dict"}) sample_field_enum: ChargepointState = field(default=ChargepointState.CHARGING_ALLOWED, metadata={ - "topic": "get/field_enum", "mutable_by_algorithm": True}) - sample_field_float: float = field(default=0, metadata={"topic": "get/field_float", "mutable_by_algorithm": True}) - sample_field_int: int = field(default=0, metadata={"topic": "get/field_int", "mutable_by_algorithm": True}) + "topic": "get/field_enum"}) + sample_field_float: float = field(default=0, metadata={"topic": "get/field_float"}) + sample_field_int: int = field(default=0, metadata={"topic": "get/field_int"}) sample_field_immutable: float = field( - default=0, metadata={"topic": "get/field_immutable", "mutable_by_algorithm": False}) + default=0, metadata={"topic": "get/field_immutable"}) sample_field_list: List = field(default_factory=currents_list_factory, metadata={ - "topic": "get/field_list", "mutable_by_algorithm": True}) + "topic": "get/field_list"}) sample_field_nested: SampleNested = field(default_factory=sample_nested) sample_field_none: Optional[str] = field( - default="Hi", metadata={"topic": "get/field_none", "mutable_by_algorithm": True}) - sample_field_str: str = field(default="Hi", metadata={"topic": "get/field_str", "mutable_by_algorithm": True}) + default="Hi", metadata={"topic": "get/field_none"}) + sample_field_str: str = field(default="Hi", metadata={"topic": "get/field_str"}) sample_field_tuple: Tuple = field(default_factory=sample_tuple_factory, metadata={ - "topic": "get/field_tuple", "mutable_by_algorithm": True}) + "topic": "get/field_tuple"}) @dataclass @@ -83,7 +83,6 @@ class Params: expected_pub_call=("openWB/get/field_float", 2.5)), Params(name="change int", sample_data=SampleData(sample_field_int=2), expected_pub_call=("openWB/get/field_int", 2)), - Params(name="immutable", sample_data=SampleData(sample_field_immutable=2), expected_calls=0), Params(name="change list", sample_data=SampleData(sample_field_list=[ 10, 0, 0]), expected_pub_call=("openWB/get/field_list", [10, 0, 0])), Params(name="change nested", sample_data=SampleData(sample_field_nested=SampleNested( diff --git a/packages/helpermodules/measurement_logging/update_yields_test.py b/packages/helpermodules/measurement_logging/update_yields_test.py index 4411cd23af..35a3fd7b81 100644 --- a/packages/helpermodules/measurement_logging/update_yields_test.py +++ b/packages/helpermodules/measurement_logging/update_yields_test.py @@ -1,5 +1,4 @@ -import pytest - +from control import data from helpermodules.measurement_logging.update_yields import update_module_yields @@ -8,28 +7,17 @@ def test_update_module_yields(daily_log_totals, mock_pub): [update_module_yields(type, daily_log_totals) for type in ("bat", "counter", "cp", "pv")] # evaluation - expected = { - "openWB/set/bat/2/get/daily_imported": 0.0, - "openWB/set/bat/2/get/daily_exported": 550.0, - "openWB/set/counter/0/get/daily_imported": 1492.0, - "openWB/set/counter/0/get/daily_exported": 0.0, - "openWB/set/chargepoint/get/daily_imported": 1920.0, - "openWB/set/chargepoint/get/daily_exported": 0.0, - "openWB/set/chargepoint/4/get/daily_imported": 384.0, - "openWB/set/chargepoint/4/get/daily_exported": 0.0, - "openWB/set/chargepoint/5/get/daily_imported": 192.0, - "openWB/set/chargepoint/5/get/daily_exported": 0.0, - "openWB/set/chargepoint/6/get/daily_imported": 0.0, - "openWB/set/chargepoint/6/get/daily_exported": 0.0, - "openWB/set/pv/get/daily_exported": 251.0, - "openWB/set/pv/1/get/daily_exported": 251.0} - for topic, value in expected.items(): - for call in mock_pub.mock_calls: - try: - if call.args[0] == topic: - assert value == call.args[1] - break - except IndexError: - pass - else: - pytest.fail(f"Topic {topic} is missing") + data.data.bat_data["bat2"].data.get.daily_imported = 0.0 + data.data.bat_data["bat2"].data.get.daily_exported = 550.0 + data.data.counter_data["counter0"].data.get.daily_imported = 1492.0 + data.data.counter_data["counter0"].data.get.daily_exported = 0.0 + data.data.cp_all_data.data.get.daily_imported = 1920.0 + data.data.cp_all_data.data.get.daily_exported = 0.0 + data.data.cp_data["cp4"].data.get.daily_imported = 384.0 + data.data.cp_data["cp4"].data.get.daily_exported = 0.0 + data.data.cp_data["cp5"].data.get.daily_imported = 192.0 + data.data.cp_data["cp5"].data.get.daily_exported = 0.0 + data.data.cp_data["cp6"].data.get.daily_imported = 0.0 + data.data.cp_data["cp6"].data.get.daily_exported = 0.0 + data.data.pv_all_data.data.get.daily_exported = 251.0 + data.data.pv_data["pv1"].data.get.daily_exported = 251.0 diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index abcb4072f9..3c13a3d105 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -403,6 +403,7 @@ class UpdateConfig: "^openWB/system/version$", ] default_topic = ( + ("openWB/bat/config/configured", False), ("openWB/bat/get/fault_state", 0), ("openWB/bat/get/fault_str", NO_ERROR), ("openWB/chargepoint/get/power", 0), diff --git a/packages/main.py b/packages/main.py index ed9cf2925b..91c21a2505 100755 --- a/packages/main.py +++ b/packages/main.py @@ -11,7 +11,7 @@ from threading import Thread from control.chargelog.chargelog import calculate_charge_cost -from helpermodules.changed_values_handler import ChangedValuesHandler +from helpermodules.changed_values_handler import ChangedValuesContext from helpermodules.measurement_logging.update_yields import update_daily_yields, update_pv_monthly_yearly_yields from helpermodules.measurement_logging.write_log import LogType, save_log from modules import loadvars @@ -52,23 +52,21 @@ def handler_with_control_interval(): if (data.data.general_data.data.control_interval / 10) == self.interval_counter: data.data.copy_data() loadvars_.get_values() - changed_values_handler.pub_changed_values() wait_for_module_update_completed(loadvars_.event_module_update_completed, "openWB/set/system/device/module_update_completed") data.data.copy_data() - changed_values_handler.store_initial_values() - self.heartbeat = True - if data.data.system_data["system"].data["perform_update"]: - data.data.system_data["system"].perform_update() - return - elif data.data.system_data["system"].data["update_in_progress"]: - log.info("Regelung pausiert, da ein Update durchgeführt wird.") - event_global_data_initialized.set() - prep.setup_algorithm() - control.calc_current() - proc.process_algorithm_results() - data.data.graph_data.pub_graph_data() - changed_values_handler.pub_changed_values() + with ChangedValuesContext(loadvars_.event_module_update_completed): + self.heartbeat = True + if data.data.system_data["system"].data["perform_update"]: + data.data.system_data["system"].perform_update() + return + elif data.data.system_data["system"].data["update_in_progress"]: + log.info("Regelung pausiert, da ein Update durchgeführt wird.") + event_global_data_initialized.set() + prep.setup_algorithm() + control.calc_current() + proc.process_algorithm_results() + data.data.graph_data.pub_graph_data() self.interval_counter = 1 else: self.interval_counter = self.interval_counter + 1 @@ -86,14 +84,13 @@ def handler5MinAlgorithm(self): ausführt, die nur alle 5 Minuten ausgeführt werden müssen. """ try: - changed_values_handler.store_initial_values() - totals = save_log(LogType.DAILY) - update_daily_yields(totals) - update_pv_monthly_yearly_yields() - data.data.general_data.grid_protection() - data.data.optional_data.et_get_prices() - data.data.counter_all_data.validate_hierarchy() - changed_values_handler.pub_changed_values() + with ChangedValuesContext(loadvars_.event_module_update_completed): + totals = save_log(LogType.DAILY) + update_daily_yields(totals) + update_pv_monthly_yearly_yields() + data.data.general_data.grid_protection() + data.data.optional_data.et_get_prices() + data.data.counter_all_data.validate_hierarchy() except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -126,8 +123,8 @@ def handler5Min(self): general_internal_chargepoint_handler.event_start.set() else: general_internal_chargepoint_handler.internal_chargepoint_handler.heartbeat = False - - sub.system_data["system"].update_ip_address() + with ChangedValuesContext(loadvars_.event_module_update_completed): + sub.system_data["system"].update_ip_address() except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -154,8 +151,9 @@ def handler_random_nightly(self): @exit_after(10) def handler_hour(self): try: - for cp in data.data.cp_data.values(): - calculate_charge_cost(cp) + with ChangedValuesContext(loadvars_.event_module_update_completed): + for cp in data.data.cp_data.values(): + calculate_charge_cost(cp) except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -193,7 +191,6 @@ def schedule_jobs(): prep = prepare.Prepare() general_internal_chargepoint_handler = GeneralInternalChargepointHandler() rfid = RfidReader() - changed_values_handler = ChangedValuesHandler(loadvars_.event_module_update_completed) event_ev_template = threading.Event() event_ev_template.set() event_charge_template = threading.Event() @@ -254,7 +251,6 @@ def schedule_jobs(): event_update_config_completed.wait(300) Pub().pub("openWB/set/system/boot_done", True) Path(Path(__file__).resolve().parents[1]/"ramdisk"/"bootdone").touch() - changed_values_handler.store_initial_values() schedule_jobs() except Exception: log.exception("Fehler im Main-Modul") diff --git a/packages/modules/loadvars.py b/packages/modules/loadvars.py index ba23a85db1..f59a391210 100644 --- a/packages/modules/loadvars.py +++ b/packages/modules/loadvars.py @@ -31,8 +31,6 @@ def get_values(self) -> None: thread_handler(self._get_general(), data.data.general_data.data.control_interval/3) thread_handler(self._set_general(), data.data.general_data.data.control_interval/3) wait_for_module_update_completed(self.event_module_update_completed, topic) - data.data.pv_all_data.calc_power_for_all_components() - data.data.bat_all_data.calc_power_for_all_components() except Exception: log.exception("Fehler im loadvars-Modul")