diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 648125d8e2..11adb6f014 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -54,7 +54,7 @@ class UpdateConfig: - DATASTORE_VERSION = 79 + DATASTORE_VERSION = 80 valid_topic = [ "^openWB/bat/config/configured$", @@ -2082,3 +2082,20 @@ def upgrade(topic: str, payload) -> Optional[dict]: break self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 79) + + def upgrade_datastore_79(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + # Simcount-Topic löschen, damit ein neuer Simcount mit dem akutellen Zählerstand des virtuellen Zählers + # gestartet wird. Akutell ist nur der feste Verbrauch im Simcount. + if re.search("openWB/system/device/[0-9]+/config", topic) is not None: + device_config = decode_payload(payload) + if device_config.get("type") == "virtual": + for component_topic, component_payload in self.all_received_topics.items(): + if re.search(f"openWB/system/device/{device_config['id']}/component/[0-9]+/config", + component_topic): + component_config = decode_payload(component_payload) + if "counter" == component_config["type"]: + Pub().pub((f"openWB/system/device/{device_config['id']}/component/" + f"{component_config['id']}/simulation"), "") + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 80) diff --git a/packages/modules/common/simcount/_simcounter_store.py b/packages/modules/common/simcount/_simcounter_store.py index 2c0cb14158..9f5700c287 100644 --- a/packages/modules/common/simcount/_simcounter_store.py +++ b/packages/modules/common/simcount/_simcounter_store.py @@ -1,18 +1,13 @@ import logging from abc import abstractmethod -from enum import Enum -from queue import Queue, Empty from typing import Optional -from paho.mqtt.client import Client as MqttClient, MQTTMessage from control import data -from helpermodules import pub, compatibility +from helpermodules import pub from helpermodules.utils.topic_parser import get_index, get_second_index from modules.common.component_type import type_to_topic_mapping from modules.common.simcount.simcounter_state import SimCounterState -from modules.common.store import ramdisk_write, ramdisk_read_float -from modules.common.store.ramdisk.io import RamdiskReadError POSTFIX_EXPORT = "watt0neg" POSTFIX_IMPORT = "watt0pos" @@ -20,98 +15,6 @@ log = logging.getLogger(__name__) -def get_serial(): - try: - with open('/proc/cpuinfo', 'r') as f: - for line in f: - if line[0:6] == 'Serial': - return line[10:26] - except Exception: - log.exception("Could not read serial from cpuinfo") - - return "0000000000000000" - - -def read_mqtt_topic(topic: str) -> Optional[str]: - """Reads and returns the first message received for the specified topic. - - Returns None if no value is received before timeout""" - - def on_message(_client, _userdata, message: MQTTMessage): - queue.put(message.payload.decode("utf-8")) - - def on_connect(*_args): - client.subscribe(topic) - - queue = Queue(1) # type: Queue[str] - client = MqttClient("openWB-simcounter-" + get_serial()) - try: - client.on_connect = on_connect - client.on_message = on_message - client.connect("localhost", 1883) - client.loop_start() - return queue.get(block=True, timeout=.5) - except Empty: - return None - finally: - client.disconnect() - - -class SimCountPrefix(Enum): - PV = ("pv", None, "pvkwh") - PV2 = ("pv2", None, "pvkwh2") - BEZUG = ("evu", "bezugkwh", "einspeisungkwh") - SPEICHER = ("housebattery", "speicherikwh", "speicherekwh") - - def __init__(self, topic: str, import_file: Optional[str], export_file: str): - self.topic = topic - self.import_file = import_file - self.export_file = export_file - - def read_import(self) -> float: - return self.__read_file(self.import_file) - - def read_export(self) -> float: - return self.__read_file(self.export_file) - - @staticmethod - def __read_file(file: str) -> float: - if file is None: - return 0.0 - try: - result = ramdisk_read_float(file) - log.info("Found counter reading <%g Wh> in file <%s>", result, file) - return result - except FileNotFoundError: - return 0.0 - - -def restore_value(prefix_str: str, postfix: str) -> float: - prefix = SimCountPrefix[prefix_str.upper()] - # topic = "openWB/" + prefix.topic + ("/WHImported_temp" if postfix == POSTFIX_IMPORT else "/WHExport_temp") - if prefix.topic == "pv2": - topic = "openWB/pv" + ("/WH2Imported_temp" if postfix == POSTFIX_IMPORT else "/WH2Export_temp") - else: - topic = "openWB/" + prefix.topic + ("/WHImported_temp" if postfix == POSTFIX_IMPORT else "/WHExport_temp") - mqtt_value = read_mqtt_topic(topic) - log.info("read from broker: %s=%s", topic, mqtt_value) - if mqtt_value is None: - result = None - else: - try: - # MQTT-Value is in Watt-seconds for historic reasons -> / 3600 - result = float(mqtt_value) / 3600 - except ValueError: - log.warning("Value <%s> from topic <%s> is not a valid number", mqtt_value, topic) - result = None - - if result is None: - result = (prefix.read_import if postfix == POSTFIX_IMPORT else prefix.read_export)() - - ramdisk_write(prefix_str + postfix, result) - return result - - class SimCounterStore: @abstractmethod def load(self, prefix: str, topic: str) -> Optional[SimCounterState]: @@ -126,52 +29,6 @@ def initialize(self, prefix: str, topic: str, power: float, timestamp: float) -> pass -class SimCounterStoreRamdisk(SimCounterStore): - def initialize(self, prefix: str, topic: str, power: float, timestamp: float) -> SimCounterState: - result = SimCounterState( - timestamp, power, restore_value(prefix, POSTFIX_IMPORT), restore_value(prefix, POSTFIX_EXPORT) - ) - self.save(prefix, topic, result) - return result - - def load(self, prefix: str, topic: str) -> Optional[SimCounterState]: - try: - timestamp = ramdisk_read_float(prefix + "sec0") - except FileNotFoundError: - return None - - def read_or_restore(postfix: str) -> float: - try: - # ramdisk value is in watt-seconds for historic reasons -> / 3600 - return ramdisk_read_float(prefix + postfix) / 3600 - except (FileNotFoundError, RamdiskReadError) as e: - log.warning("Read from ramdisk failed: %s. Attempting restore from broker", e) - return restore_value(prefix, postfix) - - return SimCounterState( - timestamp=timestamp, - power=ramdisk_read_float(prefix + "wh0"), - imported=read_or_restore(POSTFIX_IMPORT), - # abs() weil runs/simcount.py speichert das Zwischenergebnis des Exports negativ ab: - exported=abs(read_or_restore(POSTFIX_EXPORT)), - ) - - def save(self, prefix: str, topic: str, state: SimCounterState): - topic = SimCountPrefix[prefix.upper()].topic - ramdisk_write(prefix + "sec0", state.timestamp) - ramdisk_write(prefix + "wh0", state.power) - - # For historic reasons, the SimCount stored state uses Watt-seconds instead of Watt-hours -> * 3600: - ramdisk_write(prefix + POSTFIX_IMPORT, state.imported * 3600) - ramdisk_write(prefix + POSTFIX_EXPORT, state.exported * 3600) - if topic == "pv2": - pub.pub_single("openWB/pv/WH2Imported_temp", state.imported * 3600, no_json=True) - pub.pub_single("openWB/pv/WH2Export_temp", state.exported * 3600, no_json=True) - else: - pub.pub_single("openWB/" + topic + "/WHImported_temp", state.imported * 3600, no_json=True) - pub.pub_single("openWB/" + topic + "/WHExport_temp", state.exported * 3600, no_json=True) - - class SimCounterStoreBroker(SimCounterStore): def initialize(self, prefix: str, topic: str, power: float, timestamp: float) -> SimCounterState: state = SimCounterState(timestamp, power, imported=restore_last_energy( @@ -209,4 +66,4 @@ def restore_last_energy(topic: str, value: str): def get_sim_counter_store() -> SimCounterStore: - return SimCounterStoreRamdisk() if compatibility.is_ramdisk_in_use() else SimCounterStoreBroker() + return SimCounterStoreBroker() diff --git a/packages/modules/common/store/_counter.py b/packages/modules/common/store/_counter.py index 4626673987..8ddff768a1 100644 --- a/packages/modules/common/store/_counter.py +++ b/packages/modules/common/store/_counter.py @@ -1,5 +1,6 @@ import logging from operator import add +from typing import Optional from control import data from helpermodules import compatibility @@ -7,6 +8,7 @@ from modules.common.component_state import CounterState from modules.common.component_type import ComponentType from modules.common.fault_state import FaultState +from modules.common.simcount._simcounter import SimCounter from modules.common.store import ValueStore from modules.common.store._api import LoggingValueStore from modules.common.store._broker import pub_to_broker @@ -53,9 +55,13 @@ def update(self): class PurgeCounterState: - def __init__(self, delegate: LoggingValueStore, add_child_values: bool = False) -> None: + def __init__(self, + delegate: LoggingValueStore, + add_child_values: bool = False, + simcounter: Optional[SimCounter] = None) -> None: self.delegate = delegate self.add_child_values = add_child_values + self.sim_counter = simcounter def set(self, state: CounterState) -> None: self.delegate.set(state) @@ -69,8 +75,6 @@ def calc_virtual(self, state: CounterState) -> CounterState: if self.add_child_values: self.currents = state.currents if state.currents else [0.0]*3 self.power = state.power - self.imported = state.imported - self.exported = state.exported self.incomplete_currents = False def add_current_power(element): @@ -85,13 +89,6 @@ def add_current_power(element): self.incomplete_currents = True self.power += element.power - def add_imported_exported(element): - self.imported += element.imported - self.exported += element.exported - - def add_exported(element): - self.exported += element.exported - counter_all = data.data.counter_all_data elements = counter_all.get_elements_for_downstream_calculation(self.delegate.delegate.num) for element in elements: @@ -110,30 +107,28 @@ def add_exported(element): f" {chargepoint.data.config.name} an die Phasen des EVU Zählers " "angegeben werden.") self.power += chargepoint_state.power - self.imported += chargepoint_state.imported else: component = get_component_obj_by_id(element['id']) add_current_power(component.store.delegate.delegate.state) - if element["type"] == ComponentType.INVERTER.value: - add_exported(component.store.delegate.delegate.state) - else: - add_imported_exported(component.store.delegate.delegate.state) except Exception: log.exception(f"Fehler beim Hinzufügen der Werte für Element {element}") + imported, exported = self.sim_counter.sim_count(self.power) if self.incomplete_currents: self.currents = None return CounterState(currents=self.currents, power=self.power, - exported=self.exported, - imported=self.imported) + exported=exported, + imported=imported) else: return state -def get_counter_value_store(component_num: int, add_child_values: bool = False) -> PurgeCounterState: +def get_counter_value_store(component_num: int, + add_child_values: bool = False, + simcounter: Optional[SimCounter] = None) -> PurgeCounterState: if compatibility.is_ramdisk_in_use(): delegate = CounterValueStoreRamdisk() else: delegate = CounterValueStoreBroker(component_num) - return PurgeCounterState(LoggingValueStore(delegate), add_child_values) + return PurgeCounterState(LoggingValueStore(delegate), add_child_values, simcounter) diff --git a/packages/modules/common/store/_counter_test.py b/packages/modules/common/store/_counter_test.py index e4afc4cbfa..4617e1b107 100644 --- a/packages/modules/common/store/_counter_test.py +++ b/packages/modules/common/store/_counter_test.py @@ -12,6 +12,7 @@ from control.counter_all import CounterAll from modules.chargepoints.mqtt.chargepoint_module import ChargepointModule from modules.common.component_state import BatState, ChargepointState, CounterState, InverterState +from modules.common.simcount._simcounter import SimCounter from modules.common.store import _counter from modules.common.store._api import LoggingValueStore from modules.common.store._battery import BatteryValueStoreBroker, PurgeBatteryState @@ -123,7 +124,9 @@ def mock_data_nested(): def test_calc_virtual(params: Params, monkeypatch): # setup params.mock_data() - purge = PurgeCounterState(delegate=Mock(delegate=Mock(num=0)), add_child_values=True) + purge = PurgeCounterState(delegate=Mock(delegate=Mock(num=0)), + add_child_values=True, + simcounter=SimCounter(0, 0, prefix="bezug")) mock_comp_obj = Mock(side_effect=params.mock_comp) monkeypatch.setattr(_counter, "get_component_obj_by_id", mock_comp_obj) diff --git a/packages/modules/devices/generic/virtual/counter.py b/packages/modules/devices/generic/virtual/counter.py index 6311a9c46a..295483a25c 100644 --- a/packages/modules/devices/generic/virtual/counter.py +++ b/packages/modules/devices/generic/virtual/counter.py @@ -22,15 +22,14 @@ def __init__(self, component_config: VirtualCounterSetup, **kwargs: Any) -> None def initialize(self) -> None: self.__device_id: int = self.kwargs['device_id'] self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") - self.store = get_counter_value_store(self.component_config.id, add_child_values=True) + self.store = get_counter_value_store( + self.component_config.id, add_child_values=True, simcounter=self.sim_counter) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self): - imported, exported = self.sim_counter.sim_count(self.component_config.configuration.external_consumption) - counter_state = CounterState( - imported=imported, - exported=exported, + imported=None, + exported=None, power=self.component_config.configuration.external_consumption, currents=[self.component_config.configuration.external_consumption/3/230]*3 )