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: 18 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

class UpdateConfig:

DATASTORE_VERSION = 79
DATASTORE_VERSION = 80

valid_topic = [
"^openWB/bat/config/configured$",
Expand Down Expand Up @@ -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)
147 changes: 2 additions & 145 deletions packages/modules/common/simcount/_simcounter_store.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,20 @@
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"

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]:
Expand All @@ -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(
Expand Down Expand Up @@ -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()
33 changes: 14 additions & 19 deletions packages/modules/common/store/_counter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import logging
from operator import add
from typing import Optional

from control import data
from helpermodules import compatibility
from helpermodules.phase_mapping import convert_cp_currents_to_evu_currents
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
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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)
5 changes: 4 additions & 1 deletion packages/modules/common/store/_counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
9 changes: 4 additions & 5 deletions packages/modules/devices/generic/virtual/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down