diff --git a/data/config/mosquitto/public/default-dynamic-security.json b/data/config/mosquitto/public/default-dynamic-security.json index 10ed378e2f..e9ace61b90 100644 --- a/data/config/mosquitto/public/default-dynamic-security.json +++ b/data/config/mosquitto/public/default-dynamic-security.json @@ -1498,31 +1498,31 @@ }, { "acltype": "publishClientSend", - "topic": "openWB/set/counter/config/home_consumption_source_id", + "topic": "openWB/set/counter/config/consider_less_charging", "priority": 0, "allow": true }, { "acltype": "publishClientSend", - "topic": "openWB/set/counter/config/consider_less_charging", + "topic": "openWB/set/counter/get/hierarchy", "priority": 0, "allow": true }, { "acltype": "publishClientSend", - "topic": "openWB/set/counter/get/hierarchy", + "topic": "openWB/set/counter/+/config/max_power_errorcase", "priority": 0, "allow": true }, { "acltype": "publishClientSend", - "topic": "openWB/set/counter/+/config/max_power_errorcase", + "topic": "openWB/set/counter/+/config/max_currents", "priority": 0, "allow": true }, { "acltype": "publishClientSend", - "topic": "openWB/set/counter/+/config/max_currents", + "topic": "openWB/set/counter/+/config/is_home_consumption_counter", "priority": 0, "allow": true }, @@ -1550,12 +1550,6 @@ "priority": 0, "allow": true }, - { - "acltype": "publishClientReceive", - "topic": "openWB/counter/config/home_consumption_source_id", - "priority": 0, - "allow": true - }, { "acltype": "publishClientReceive", "topic": "openWB/counter/config/consider_less_charging", @@ -1586,6 +1580,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientReceive", + "topic": "openWB/counter/+/config/is_home_consumption_counter", + "priority": 0, + "allow": true + }, { "acltype": "publishClientReceive", "topic": "openWB/counter/+/config/max_total_power", diff --git a/packages/conftest.py b/packages/conftest.py index b58a69d591..b99bb5a9a5 100644 --- a/packages/conftest.py +++ b/packages/conftest.py @@ -189,11 +189,12 @@ def data_() -> None: fault_state=0), config=Mock(spec=PvConfig, max_ac_out=10000)))}) data.data.counter_data.update({ "counter0": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( - spec=CounterGet, currents=[40]*3, power=6200, daily_imported=45000, daily_exported=3000, fault_state=0))), + spec=CounterGet, currents=[40]*3, power=6200, daily_imported=45000, daily_exported=3000, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), "counter6": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( spec=CounterGet, currents=[25, 10, 25], power=13800, daily_imported=20000, daily_exported=0, imported=14000, exported=18000, fault_state=0), - config=Mock(spec=CounterConfig, max_currents=[32]*3), + config=Mock(spec=CounterConfig, max_currents=[32]*3, is_home_consumption_counter=False), set=Mock(spec=CounterSet, raw_currents_left=[31]*3)))}) diff --git a/packages/control/counter.py b/packages/control/counter.py index e7351b3cef..d10d72cbd3 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -27,6 +27,7 @@ def get_counter_default_config(): return {"max_power_errorcase": 7000, "max_currents": [35]*3, "max_total_power": 24000, + "is_home_consumption_counter": False } @@ -38,10 +39,11 @@ class ControlRangeState(Enum): @dataclass class Config: - max_power_errorcase: float = field(default=7000, metadata={"topic": "get/max_power_errorcase"}) + max_power_errorcase: float = field(default=7000, metadata={"topic": "config/max_power_errorcase"}) max_currents: List[float] = field(default_factory=currents_list_factory, metadata={ - "topic": "get/max_currents"}) - max_total_power: float = field(default=0, metadata={"topic": "get/max_total_power"}) + "topic": "config/max_currents"}) + max_total_power: float = field(default=0, metadata={"topic": "config/max_total_power"}) + is_home_consumption_counter: bool = field(default=False, metadata={"topic": "config/is_home_consumption_counter"}) def config_factory() -> Config: diff --git a/packages/control/counter_all.py b/packages/control/counter_all.py index 89f214b773..80efd51164 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -4,13 +4,12 @@ from dataclasses import dataclass, field import logging import re -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, List, Tuple, Union from control import data from control.counter import Counter from dataclass_utils.factories import empty_list_factory from helpermodules.messaging import MessageType, pub_system_message -from helpermodules.pub import Pub from modules.common.component_type import ComponentType, component_type_to_readable_text from modules.common.fault_state import FaultStateLevel from modules.common.simcount import SimCounter @@ -20,8 +19,6 @@ @dataclass class Config: - home_consumption_source_id: Optional[str] = field( - default=None, metadata={"topic": "config/home_consumption_source_id"}) consider_less_charging: bool = field( default=False, metadata={"topic": "config/consider_less_charging"}) @@ -101,15 +98,11 @@ def get_id_evu_counter(self) -> int: def set_home_consumption(self) -> None: try: - self._validate_home_consumption_counter() home_consumption, elements = self._calc_home_consumption() if home_consumption < 0: log.error( f"Ungültiger Hausverbrauch: {home_consumption}W, Berücksichtigte Komponenten neben EVU {elements}") - if self.data.config.home_consumption_source_id is None: - hc_counter_source = self.get_evu_counter_str() - else: - hc_counter_source = f"counter{self.data.config.home_consumption_source_id}" + hc_counter_source = self.get_evu_counter_str() hc_counter_data = data.data.counter_data[hc_counter_source].data if hc_counter_data.get.fault_state == FaultStateLevel.NO_ERROR: hc_counter_data.get.fault_state = FaultStateLevel.WARNING.value @@ -130,49 +123,99 @@ def set_home_consumption(self) -> None: except Exception: log.exception("Fehler in der allgemeinen Zähler-Klasse") - EVU_IS_HC_COUNTER_ERROR = ("Der EVU-Zähler kann nicht als Quelle für den Hausverbrauch verwendet werden. Meist ist " - "der Zähler am EVU-Punkt installiert, dann muss im Lastmanagement unter Hausverbrauch" - " 'von openWB berechnen' ausgewählt werden. Wenn der Zähler im Hausverbrauchszweig " - "installiert ist, einen virtuellen Zähler anlegen und im Lastmanagement ganz links " - "anordnen.") - - def _validate_home_consumption_counter(self): - if self.data.config.home_consumption_source_id is not None: - if self.data.config.home_consumption_source_id == self.get_id_evu_counter(): - hc_counter_data = data.data.counter_data[self.get_evu_counter_str()].data - hc_counter_data.get.fault_state = FaultStateLevel.ERROR.value - hc_counter_data.get.fault_str = self.EVU_IS_HC_COUNTER_ERROR - evu_counter = self.get_id_evu_counter() - Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_state", - hc_counter_data.get.fault_state) - Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_str", - hc_counter_data.get.fault_str) - raise Exception(self.EVU_IS_HC_COUNTER_ERROR) - def _calc_home_consumption(self) -> Tuple[float, List]: - power = 0 - if self.data.config.home_consumption_source_id is None: - id_source = self.get_id_evu_counter() - else: - id_source = self.data.config.home_consumption_source_id - elements_to_sum_up = self.get_elements_for_downstream_calculation(id_source) + hc_all_power = 0 + no_hc_all_power = 0 + no_hc_evu = 0 + evu_id = self.get_id_evu_counter() + elements_to_sum_up = self.get_elements_for_downstream_calculation(evu_id) + + evu_is_HC = data.data.counter_data[f"counter{evu_id}"].data.config.is_home_consumption_counter + for element in elements_to_sum_up: - if element["type"] == ComponentType.CHARGEPOINT.value: - component = data.data.cp_data[f"cp{element['id']}"] + if element["type"] == ComponentType.COUNTER.value: + component = data.data.counter_data[f"counter{element['id']}"] + + _hc_all_power, hc_counter = self._get_home_consumption_counter(element, evu_is_HC=evu_is_HC) + hc_all_power += _hc_all_power + + for counter in hc_counter: + no_hc_all_power += self._get_no_home_consumption(counter) + continue + elif element["type"] == ComponentType.BAT.value: component = data.data.bat_data[f"bat{element['id']}"] - elif element["type"] == ComponentType.COUNTER.value: - component = data.data.counter_data[f"counter{element['id']}"] + elif element["type"] == ComponentType.CHARGEPOINT.value: + component = data.data.cp_data[f"cp{element['id']}"] elif element["type"] == ComponentType.INVERTER.value: component = data.data.pv_data[f"pv{element['id']}"] if component.data.get.fault_state < 2: - power += component.data.get.power + no_hc_evu += component.data.get.power else: log.warning( f"Komponente {element['type']}{component.num} ist im Fehlerzustand und wird nicht berücksichtigt.") - evu = data.data.counter_data[f"counter{id_source}"].data.get.power - return evu - power - self.data.set.smarthome_power_excluded_from_home_consumption, elements_to_sum_up + + evu_power = data.data.counter_data[f"counter{evu_id}"].data.get.power + + if data.data.counter_data[f"counter{evu_id}"].data.config.is_home_consumption_counter: + return (evu_power - no_hc_all_power - no_hc_evu - + self.data.set.smarthome_power_excluded_from_home_consumption), elements_to_sum_up + else: + return (hc_all_power - no_hc_all_power - + self.data.set.smarthome_power_excluded_from_home_consumption), elements_to_sum_up + + def _get_no_home_consumption(self, element) -> float: + # Summiert die Leistung aller Komponenten, die nicht als Hausverbrauch gezählt werden, + # unterhalb des angegebenen Elements. + not_home_consumption = 0 + if element["type"] != ComponentType.COUNTER.value: + # Wenn kein Counter, dann get power davon -> not_home_consumption + if element["type"] == ComponentType.CHARGEPOINT.value: + component = data.data.cp_data[f"cp{element['id']}"] + elif element["type"] == ComponentType.BAT.value: + component = data.data.bat_data[f"bat{element['id']}"] + elif element["type"] == ComponentType.INVERTER.value: + component = data.data.pv_data[f"pv{element['id']}"] + + if component.data.get.fault_state < 2: + not_home_consumption += component.data.get.power + + return not_home_consumption + + # Wenn Counter, dann get not_home_consumption von allen Children + for child in element["children"]: + not_home_consumption += self._get_no_home_consumption(child) + + return not_home_consumption + + def _get_home_consumption_counter(self, elements, evu_is_HC=False) -> Tuple[float, List]: + # Sucht rekursiv HC-Zähler im Teilbaum, summiert deren Leistung + # und gibt eine Liste der gefundenen HC-Zähler zurück. + # -> wenn mehrer HC-Zähler auf der selben Ebene sind + + total_power = 0 + hc_counters = [] + + if elements["type"] == ComponentType.COUNTER.value: + component = data.data.counter_data[f"counter{elements['id']}"] + if component.data.config.is_home_consumption_counter or evu_is_HC: + hc_counters.append(elements) + total_power += component.data.get.power + # Kinder nicht weiter durchsuchen, + # da deren Leistung bereits enthalten ist. + return total_power, hc_counters + else: + # Zähler kein HC -> check Kinder + # Alle durchgehen, falls ein Zähler mehrer Zähler als Kinder hat + for element in elements["children"]: + if element["type"] == ComponentType.COUNTER.value: + power, counters = self._get_home_consumption_counter(element, evu_is_HC=evu_is_HC) + total_power += power + hc_counters.extend(counters) + else: + continue + return total_power, hc_counters def _add_hybrid_bat(self, id: int) -> List: elements = [] diff --git a/packages/control/counter_home_consumption_test.py b/packages/control/counter_home_consumption_test.py index 6303dd63e9..793455715c 100644 --- a/packages/control/counter_home_consumption_test.py +++ b/packages/control/counter_home_consumption_test.py @@ -3,26 +3,69 @@ import pytest from control import data -from packages.conftest import hierarchy_hc_counter, hierarchy_standard, hierarchy_hybrid, hierarchy_nested + + +from control.bat import Bat, BatData +from control.bat import Get as BatGet +from control.bat import Set as BatSet +from control.chargepoint.chargepoint import Chargepoint, ChargepointData +from control.chargepoint.chargepoint_data import Config, Get, Set +from control.counter import Counter, CounterData +from control.counter import Config as CounterConfig +from control.counter import Get as CounterGet from control.counter_all import CounterAll +from control.pv import Pv, PvData +from control.pv import Config as PvConfig +from control.pv import Get as PvGet + +from modules.chargepoints.mqtt.chargepoint_module import ChargepointModule +from modules.common.component_state import ChargepointState +from modules.common.store._api import LoggingValueStore + +from packages.conftest import ( + hierarchy_standard, + hierarchy_hybrid, + hierarchy_nested) from modules.common.fault_state import FaultStateLevel @pytest.mark.parametrize("counter_all", [pytest.param(hierarchy_standard, id="standard"), pytest.param(hierarchy_hybrid, id="hybrid"), - pytest.param(hierarchy_nested, id="nested")]) + pytest.param(hierarchy_nested, id="nested") + ]) def test_calc_home_consumption(counter_all: Callable[[], CounterAll], data_): c = counter_all() home_consumption = c._calc_home_consumption()[0] assert home_consumption == 500 -def test_calc_home_consumption_hc_counter(data_hc_counter_): - c = hierarchy_hc_counter() - c.data.config.home_consumption_source_id = 6 +@pytest.mark.parametrize( + ["counter_all", "expected_home_consumption"], + [ + pytest.param("hierarchy_home_consumption_standard", 0, id="hierarchy_home_consumption_standard"), + pytest.param("hierarchy_home_consumption_hybrid", 500, id="hierarchy_home_consumption_hybrid"), + pytest.param("hierarchy_nested_home_consumption_level_3", 500, id="hierarchy_nested_home_consumption_level_3"), + pytest.param("hierarchy_nested_home_consumption_level_2", + 500, id="hierarchy_nested_home_consumption_level_2"), + pytest.param("hierarchy_home_consumption_only_root", + 1000, id="hierarchy_home_consumption_only_root"), + pytest.param("hierarchy_home_consumption_all", + 1000, id="hierarchy_home_consumption_all"), + pytest.param("hierarchy_nested_home_consumption_multi_level_2", 250, + id="hierarchy_nested_home_consumption_multi_level_2"), + pytest.param("hierarchy_nested_home_consumption_2_hc_childs", 500, + id="hierarchy_nested_home_consumption_2_hc_childs") + ], +) +def test_calc_home_consumption_with_configured_home_consumption_counter( + counter_all: str, + expected_home_consumption: int, + data_home_consumption, +): + c = globals()[counter_all]() home_consumption = c._calc_home_consumption()[0] - assert home_consumption == 1100 + assert home_consumption == expected_home_consumption @pytest.mark.parametrize(["home_consumption", @@ -31,7 +74,7 @@ def test_calc_home_consumption_hc_counter(data_hc_counter_): "expected_invalid_home_consumption"], [pytest.param(500, 0, 500, 0, id="valid home consumption"), pytest.param(-100, 0, 200, 1, id="first invalid home consumption"), - pytest.param(-100, 3, 0, 3, id="invalid home consumption, reset home consumption")]) + pytest.param(-100, 3, 0, 3, id="invalid home consumption, reset home consumption")]) def test_set_home_consumption(home_consumption: int, invalid_home_consumption: int, expected_home_consumption: int, @@ -54,13 +97,367 @@ def test_set_home_consumption(home_consumption: int, assert c.data.set.home_consumption == expected_home_consumption -def test_validate_home_consumption_counter(monkeypatch): +def hierarchy_home_consumption_standard() -> CounterAll: + # counter0 + # | + # - cp4 + # - cp5 + # - cp3 + # - inverter1 + # - bat2 + # counter_8 <-- home consumption counter + # + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 750 + # counter8 = 500 + # Final Home Consumption = 0 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 5, "type": "cp", "children": []}, + {"id": 3, "type": "cp", "children": []}, + {"id": 1, "type": "inverter", "children": []}, + {"id": 2, "type": "bat", "children": []}]}, + {"id": 8, "type": "counter", + "children": []}] + return c + + +def hierarchy_home_consumption_hybrid() -> CounterAll: + # counter0 + # | + # - cp3 + # - cp4 + # - counter8 <-- home consumption counter + # | + # - cp5 + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 250 + # counter8 = 500 + # Final Home Consumption = 500 c = CounterAll() - c.data.config.home_consumption_source_id = 0 - monkeypatch.setattr(c, "get_id_evu_counter", lambda: 0) - monkeypatch.setattr(c, "get_evu_counter_str", lambda: "counter0") + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 4, "type": "cp", "children": []}, + {"id": 8, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_nested_home_consumption_level_3() -> CounterAll: + # counter0 + # | + # - cp3 + # - counter6 + # | + # - cp4 + # - counter8 <-- home consumption counter + # | + # - cp5 + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 250 + # counter6 = 0 + # counter8 = 500 + # Final Home Consumption = 500 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 8, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []} + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_nested_home_consumption_level_2() -> CounterAll: + # counter0 + # | + # - cp3 + # - counter9 <-- home consumption counter + # | + # - cp4 + # - counter10 <-- home consumption counter + # | + # - cp5 + # + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 250 + # coutner9 = 250 + # counter10 = 250 + # Final Home Consumption = 500 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 9, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 10, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []}, + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_home_consumption_only_root() -> CounterAll: + # counter11 <-- home consumption counter + # | + # - cp3 + # - counter6 + # | + # - cp4 + # - counter13 + # | + # - cp5 + # + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter11 = 500 + # coutner6 = 250 + # counter13 = 250 + # Final Home Consumption = 1000 + c = CounterAll() + c.data.get.hierarchy = [{"id": 11, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 13, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []}, + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_home_consumption_all() -> CounterAll: + # counter11 <-- home consumption counter + # | + # - cp3 + # - counter9 <-- home consumption counter + # | + # - cp4 + # - counter10 <-- home consumption counter + # | + # - cp5 + # + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter11 = 500 + # coutner9 = 250 + # counter10 = 250 + # Final Home Consumption = 1000 + c = CounterAll() + c.data.get.hierarchy = [{"id": 11, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 9, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 10, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []}, + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_nested_home_consumption_multi_level_2() -> CounterAll: + # counter0 + # | + # - cp3 + # - counter6 + # | + # - cp4 + # - counter10 <-- home consumption counter + # | + # - cp5 + # - counter14 + # | + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 250 + # coutner6 = 0 + # counter10 = 250 + # counter14 = 250 + # Final Home Consumption = 250 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 10, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 14, "type": "counter", + "children": [ + {"id": 1, "type": "inverter", "children": []}]}, + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +def hierarchy_nested_home_consumption_2_hc_childs() -> CounterAll: + # counter0 + # | + # - cp3 + # - counter6 + # | + # - cp4 + # - counter10 <-- home consumption counter + # | + # - cp5 + # - counter15 <-- home consumption counter + # | + # - inverter1 + # - bat2 + + # UnbekannterVerbraucher/Hausverbrauch am Countern + # counter0 = 250 + # coutner6 = 0 + # counter10 = 250 + # counter15 = 250 + # Final Home Consumption = 500 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 10, "type": "counter", + "children": [ + {"id": 5, "type": "cp", "children": []}]}, + {"id": 15, "type": "counter", + "children": [ + {"id": 1, "type": "inverter", "children": []}]}, + ]}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +@pytest.fixture() +def data_home_consumption() -> None: + data.data_init(Mock()) + data.data.cp_data = { + "cp3": Mock(spec=Chargepoint, data=Mock(spec=ChargepointData, + config=Mock(spec=Config, phase_1=1), + get=Mock(spec=Get, currents=[30, 0, 0], power=6900, + daily_imported=10000, daily_exported=0, imported=56000, + fault_state=0), + set=Mock(spec=Set, loadmanagement_available=True)), + chargepoint_module=Mock(spec=ChargepointModule, + store=Mock(spec=LoggingValueStore, + delegate=Mock(spec=LoggingValueStore, + state=ChargepointState(currents=[30, 0, 0], + power=6900, + plug_state=False, + charge_state=False, + imported=None, + exported=None, + phases_in_use=0))))), + "cp4": Mock(spec=Chargepoint, data=Mock(spec=ChargepointData, + config=Mock(spec=Config, phase_1=2), + get=Mock(spec=Get, currents=[0, 15, 15], power=6900, + daily_imported=10000, daily_exported=0, imported=60000, + fault_state=0), + set=Mock(spec=Set, loadmanagement_available=True)), + chargepoint_module=Mock(spec=ChargepointModule, + store=Mock(spec=LoggingValueStore, + delegate=Mock(spec=LoggingValueStore, + state=ChargepointState(currents=[0, 15, 15], + power=6900, + plug_state=False, + charge_state=False, + imported=None, + exported=None, + phases_in_use=0))))), + "cp5": Mock(spec=Chargepoint, data=Mock(spec=ChargepointData, + config=Mock(spec=Config, phase_1=3), + get=Mock(spec=Get, currents=[10]*3, power=6900, + daily_imported=10000, daily_exported=0, imported=62000, + fault_state=0), + set=Mock(spec=Set, loadmanagement_available=True)), + chargepoint_module=Mock(spec=ChargepointModule, + store=Mock(spec=LoggingValueStore, + delegate=Mock(spec=LoggingValueStore, + state=ChargepointState(currents=[10]*3, + power=6900, + plug_state=False, + charge_state=False, + imported=None, + exported=None, + phases_in_use=0)))))} + data.data.bat_data.update({"bat2": Mock(spec=Bat, num=2, data=Mock(spec=BatData, get=Mock( + spec=BatGet, power=-5000, fault_state=0), + set=Mock(spec=BatSet, power_limit=None)))}) + data.data.pv_data.update({"pv1": Mock(spec=Pv, data=Mock( + spec=PvData, get=Mock(spec=PvGet, power=-10000, fault_state=0), config=Mock(spec=PvConfig, max_ac_out=10000)))}) + data.data.counter_data.update({ + "counter0": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=6450, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=False))), + "counter6": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=4300, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=False))), + "counter7": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=20700, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=False))), + "counter13": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=7150, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=False))), + "counter14": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=-9750, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=False))), - with pytest.raises(Exception) as e: - c._validate_home_consumption_counter() + "counter11": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=6700, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), - assert str(e.value) == CounterAll.EVU_IS_HC_COUNTER_ERROR + "counter8": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=7400, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), + "counter9": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=4300, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), + "counter10": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=7150, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), + "counter15": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, power=-9750, fault_state=0), + config=Mock(spec=CounterConfig, is_home_consumption_counter=True))), + }) diff --git a/packages/helpermodules/create_debug.py b/packages/helpermodules/create_debug.py index cb032927a5..e2aa81f002 100644 --- a/packages/helpermodules/create_debug.py +++ b/packages/helpermodules/create_debug.py @@ -35,8 +35,8 @@ def get_common_data(): ip_address = None try: updateAvailable = subdata.SubData.system_data["system"].data["current_branch_commit"] and \ - subdata.SubData.system_data["system"].data["current_branch_commit"] != \ - subdata.SubData.system_data["system"].data["current_commit"] + subdata.SubData.system_data["system"].data["current_branch_commit"] != \ + subdata.SubData.system_data["system"].data["current_commit"] except Exception: updateAvailable = False @@ -177,7 +177,7 @@ def config_and_state(): f"{component_data.data.config.max_currents} A\n" "--| Counter_Max_Power_Errorcase: " f"{component_data.data.config.max_power_errorcase} W\n") - elif counter_all_data.data.config.home_consumption_source_id == component_data.num: + elif component_data.data.config.is_home_consumption_counter: parsed_data += ("--| Counter_Type: Hausverbrauchszähler\n" "--| Counter_Max_Power: " f"{component_data.data.config.max_total_power} W\n" @@ -251,8 +251,7 @@ def get_hierarchy(hierarchy, level=0): counter_all_data = data.data.counter_all_data if counter_all_data.get_evu_counter_str() == f"counter{component_data.num}": counter_type = ("EVU-Zähler") - elif (counter_all_data.data.config.home_consumption_source_id == - component_data.num): + elif (component_data.data.config.is_home_consumption_counter): counter_type = ("Hausverbrauchszähler") else: counter_type = "Sonstiger Zähler" @@ -461,9 +460,9 @@ def write_to_file(file_handler, func, default: Optional[Any] = None): json_rsp = req.get_http_session().put("https://debughandler.wb-solution.de", data=data, params={ - 'debugemail': debug_email, - 'ticketnumber': ticketnumber, - 'subject': subject + 'debugemail': debug_email, + 'ticketnumber': ticketnumber, + 'subject': subject }, timeout=10).json() diff --git a/packages/helpermodules/measurement_logging/write_log.py b/packages/helpermodules/measurement_logging/write_log.py index fd8d51aa4b..8b1db45e88 100644 --- a/packages/helpermodules/measurement_logging/write_log.py +++ b/packages/helpermodules/measurement_logging/write_log.py @@ -248,8 +248,7 @@ def create_entry(log_type: LogType, sh_log_data: LegacySmartHomeLogData, previou counter_dict = {} for counter in data.data.counter_data.values(): try: - home_consumption_source_id = data.data.counter_all_data.data.config.home_consumption_source_id - if (home_consumption_source_id is None or counter.num != home_consumption_source_id): + if not counter.data.config.is_home_consumption_counter: counter_dict.update( {f"counter{counter.num}": { "imported": counter.data.get.imported, diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 7973936181..e02e36b151 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -933,8 +933,6 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, float, [(0, float("inf"))]) elif "openWB/set/counter/get/hierarchy" in msg.topic: self._validate_value(msg, None) - elif "openWB/set/counter/config/home_consumption_source_id" in msg.topic: - self._validate_value(msg, int) elif "openWB/set/counter/set/simulation" in msg.topic: self._validate_value(msg, "json") elif "/set/consumption_left" in msg.topic: @@ -946,6 +944,8 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): elif ("/config/max_total_power" in msg.topic or "/config/max_power_errorcase" in msg.topic): self._validate_value(msg, int, [(0, float("inf"))]) + elif ("/config/is_home_consumption_counter" in msg.topic): + self._validate_value(msg, bool) elif subdata.SubData.counter_data.get(f"counter{get_index(msg.topic)}"): if ("/get/powers" in msg.topic or "/get/currents" in msg.topic): diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 48d2f72b1e..90d2f560f8 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -58,7 +58,7 @@ class UpdateConfig: - DATASTORE_VERSION = 137 + DATASTORE_VERSION = 138 valid_topic = [ "^openWB/bat/config/bat_control_activated$", @@ -186,7 +186,6 @@ class UpdateConfig: "^openWB/command/todo$", "^openWB/counter/config/consider_less_charging$", - "^openWB/counter/config/home_consumption_source_id$", "^openWB/counter/get/hierarchy$", "^openWB/counter/set/disengageable_smarthome_power$", "^openWB/counter/set/imported_home_consumption$", @@ -213,6 +212,7 @@ class UpdateConfig: "^openWB/counter/[0-9]+/config/max_power_errorcase$", "^openWB/counter/[0-9]+/config/max_currents$", "^openWB/counter/[0-9]+/config/max_total_power$", + "^openWB/counter/[0-9]+/config/is_home_consumption_counter$", "^openWB/general/allow_unencrypted_access$", "^openWB/general/extern$", @@ -588,7 +588,6 @@ class UpdateConfig: ("openWB/chargepoint/template/0", get_chargepoint_template_default()), ("openWB/counter/get/hierarchy", []), ("openWB/counter/config/consider_less_charging", counter_all.Config().consider_less_charging), - ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), ("openWB/vehicle/0/name", "Standard-Fahrzeug"), ("openWB/vehicle/0/color", DEFAULT_COLORS.VEHICLE.value), ("openWB/vehicle/0/info", {"manufacturer": None, "model": None}), @@ -3472,6 +3471,7 @@ def upgrade(topic: str, payload) -> Optional[dict]: self._append_datastore_version(136) def upgrade_datastore_137(self) -> None: + # Update all counters with new Parameter and default wert def upgrade(topic: str, payload) -> Optional[dict]: if re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None: payload = decode_payload(payload) @@ -3481,3 +3481,24 @@ def upgrade(topic: str, payload) -> Optional[dict]: return {topic: payload} self._loop_all_received_topics(upgrade) self._append_datastore_version(137) + + def upgrade_datastore_138(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/counter/[0-9]+/config", topic) is not None: + index = get_index(topic) + if f"openWB/counter/{index}/config/is_home_consumption_counter" not in self.all_received_topics: + is_home_consumption_counter = get_counter_default_config()["is_home_consumption_counter"] + return {f"openWB/counter/{index}/config/is_home_consumption_counter": is_home_consumption_counter} + self._loop_all_received_topics(upgrade) + # Remove old Topic + old_topic = "openWB/counter/config/home_consumption_source_id" + if old_topic in self.all_received_topics: + source_id = decode_payload(self.all_received_topics[old_topic]) + if source_id is not None: + try: + source_id = int(source_id) + except (TypeError, ValueError): + log.warning(f"Invalid '{old_topic}' value: {source_id!r}; skipping migration") + else: + self.__update_topic(f"openWB/counter/{source_id}/config/is_home_consumption_counter", True) + self._append_datastore_version(138)