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
6 changes: 6 additions & 0 deletions data/config/mosquitto/public/default-dynamic-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -2994,6 +2994,12 @@
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/counter/set/not_in_home_consumption",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/counter/set/imported_home_consumption",
Expand Down
39 changes: 25 additions & 14 deletions packages/control/counter_all/counter_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any, Dict, List, Tuple

from control import data
from control.consumer.consumer import Consumer
from control.counter_all.counter_all_data import CounterAllData
from control.counter_all.hierarchy import HierarchyMixin
from control.counter_all.loadmanagement_prio import LoadmanagementPrioMixin
Expand Down Expand Up @@ -48,8 +49,9 @@ def get_id_evu_counter(self) -> int:

def set_home_consumption(self) -> None:
try:
home_consumption, elements = self._calc_home_consumption()
home_consumption, not_in_home_consumption, elements = self._calc_home_consumption()
home_consumption = round(home_consumption, 2)
not_in_home_consumption = round(not_in_home_consumption, 2)
if home_consumption < 0:
log.error(
f"Ungültiger Hausverbrauch: {home_consumption}W, Berücksichtigte Komponenten neben EVU {elements}")
Expand All @@ -68,6 +70,7 @@ def set_home_consumption(self) -> None:
home_consumption = 0
else:
self.data.set.invalid_home_consumption = 0
self.data.set.not_in_home_consumption = not_in_home_consumption
self.data.set.home_consumption = home_consumption
imported, _ = self.sim_counter.sim_count(self.data.set.home_consumption)
self.data.set.imported_home_consumption = round(imported, 2)
Expand Down Expand Up @@ -96,12 +99,10 @@ def _get_is_home_consumption(self, counter: Counter, parent_home_consumption: st

return counter.data.config.is_home_consumption_counter

def _get_is_home_consumption_consumer(self, consumer: Any, parent_home_consumption: str) -> str:
consumer_mode = getattr(consumer.data.config, "is_home_consumption_consumer",
CounterMode.AUTO_HOME_CONSUMPTION.value)
if consumer_mode == CounterMode.AUTO_HOME_CONSUMPTION.value:
def _get_is_home_consumption_consumer(self, consumer: Consumer, parent_home_consumption: str) -> str:
if consumer.data.config.is_home_consumption_consumer == CounterMode.AUTO_HOME_CONSUMPTION.value:
return parent_home_consumption
return consumer_mode
return consumer.data.config.is_home_consumption_consumer

def _get_local_power_from_counter(self, element: Dict) -> float:
# Wird nur von Countern aufgerufen
Expand All @@ -121,37 +122,45 @@ def _get_local_power_from_counter(self, element: Dict) -> float:
return local_power

def _calc_home_consumption_from_counter(
self, element: Dict, parent_home_consumption: str) -> float:
self, element: Dict, parent_home_consumption: str) -> Tuple[float, float]:
# Wird nur von Countern aufgerufen
# Bewertet, ob Hausverbrauch oder nicht
# Gibt den Hausverbrauch des Zählers zurück

home_consumption = 0.0
not_home_consumption = 0.0
local_power = self._get_local_power_from_counter(element)

counter = self._get_component(element)
child_home_consumption = self._get_is_home_consumption(counter, parent_home_consumption)

if child_home_consumption == CounterMode.HOME_CONSUMPTION.value:
home_consumption += local_power
elif child_home_consumption == CounterMode.NOT_HOME_CONSUMPTION.value:
not_home_consumption += local_power

for child in element["children"]:
comp = self._get_component(child)

if comp.data.get.fault_state < 2:
if child["type"] == ComponentType.COUNTER.value:
home_consumption += self._calc_home_consumption_from_counter(child, child_home_consumption)
add_home_consumption, add_not_home_consumption = self._calc_home_consumption_from_counter(
child, child_home_consumption)
home_consumption += add_home_consumption
not_home_consumption += add_not_home_consumption
elif child["type"] == ComponentType.CONSUMER.value:
if self._get_is_home_consumption_consumer(
comp, child_home_consumption) == CounterMode.HOME_CONSUMPTION.value:
mode = self._get_is_home_consumption_consumer(comp, child_home_consumption)
if mode == CounterMode.HOME_CONSUMPTION.value:
home_consumption += comp.data.get.power
elif mode == CounterMode.NOT_HOME_CONSUMPTION.value:
not_home_consumption += comp.data.get.power
else:
log.warning(
f"Komponente {element['type']}{comp.num} ist im Fehlerzustand und wird nicht berücksichtigt.")

return home_consumption
return home_consumption, not_home_consumption

def _calc_home_consumption(self) -> Tuple[float, Dict]:
def _calc_home_consumption(self) -> Tuple[float, float, Dict]:
evu_id = self.get_id_evu_counter()

# get_elements_for_downstream_calculation berücksichtigt Hybrid-Batterien
Expand All @@ -167,11 +176,13 @@ def _calc_home_consumption(self) -> Tuple[float, Dict]:
home_consumption = 0.0

# Rekursion startet immer beim EVU-Zähler.
home_consumption = self._calc_home_consumption_from_counter(evu_element, CounterMode.HOME_CONSUMPTION.value)
home_consumption, not_in_home_consumption = self._calc_home_consumption_from_counter(
evu_element, CounterMode.HOME_CONSUMPTION.value)

home_consumption -= self.data.set.smarthome_power_excluded_from_home_consumption
not_in_home_consumption += self.data.set.smarthome_power_excluded_from_home_consumption

return home_consumption, evu_element
return home_consumption, not_in_home_consumption, evu_element
Comment thread
Copilot marked this conversation as resolved.
Resolved

def _add_hybrid_bat(self, id: int) -> List:
elements = []
Expand Down
1 change: 1 addition & 0 deletions packages/control/counter_all/counter_all_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Set:
loadmanagement_active: bool = field(
default=False, metadata={"topic": "set/loadmanagement_active"})
home_consumption: float = field(default=0, metadata={"topic": "set/home_consumption"})
not_in_home_consumption: float = field(default=0, metadata={"topic": "set/not_in_home_consumption"})
smarthome_power_excluded_from_home_consumption: float = field(
default=0,
metadata={"topic": "set/smarthome_power_excluded_from_home_consumption"})
Expand Down
157 changes: 149 additions & 8 deletions packages/control/counter_all/counter_home_consumption_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,32 @@ def test_calc_home_consumption_with_configured_home_consumption_counter(


@pytest.mark.parametrize(["home_consumption",
"not_in_home_consumption",
"invalid_home_consumption",
"initial_not_in_home_consumption",
"expected_home_consumption",
"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")])
"expected_invalid_home_consumption",
"expected_not_in_home_consumption"],
[pytest.param(500, 150, 0, 42, 500, 0, 150, id="valid home consumption"),
pytest.param(-100, 150, 0, 42, 200, 1, 42, id="first invalid home consumption"),
pytest.param(-100, 150, 3, 42, 0, 3, 150,
id="invalid home consumption, reset home consumption")])
def test_set_home_consumption(home_consumption: int,
not_in_home_consumption: int,
invalid_home_consumption: int,
initial_not_in_home_consumption: int,
expected_home_consumption: int,
expected_invalid_home_consumption: int,
expected_not_in_home_consumption: int,
monkeypatch,
data_):
# setup
c = hierarchy_standard()
data.data.counter_data["counter0"].data.get.fault_state = FaultStateLevel.NO_ERROR
c.data.set.invalid_home_consumption = invalid_home_consumption
c.data.set.home_consumption = 200
calc_home_consumption_mock = Mock(return_value=[home_consumption, []])
c.data.set.not_in_home_consumption = initial_not_in_home_consumption
calc_home_consumption_mock = Mock(return_value=[home_consumption, not_in_home_consumption, []])
monkeypatch.setattr(CounterAll, "_calc_home_consumption", calc_home_consumption_mock)

# execution
Expand All @@ -93,16 +101,17 @@ def test_set_home_consumption(home_consumption: int,
# evaluation
assert c.data.set.invalid_home_consumption == expected_invalid_home_consumption
assert c.data.set.home_consumption == expected_home_consumption
assert c.data.set.not_in_home_consumption == expected_not_in_home_consumption


@pytest.mark.parametrize(
["counter_mode", "consumer_mode", "expected_home_consumption"],
[
pytest.param(CounterMode.HOME_CONSUMPTION.value, "auto_home_consumption", 1000,
pytest.param(CounterMode.HOME_CONSUMPTION.value, CounterMode.AUTO_HOME_CONSUMPTION.value, 1000,
id="consumer_auto_inherits_home"),
pytest.param(CounterMode.HOME_CONSUMPTION.value, "no_home_consumption", 700,
pytest.param(CounterMode.HOME_CONSUMPTION.value, CounterMode.NOT_HOME_CONSUMPTION.value, 700,
id="consumer_explicit_no_home"),
pytest.param(CounterMode.NOT_HOME_CONSUMPTION.value, "home_consumption", 300,
pytest.param(CounterMode.NOT_HOME_CONSUMPTION.value, CounterMode.HOME_CONSUMPTION.value, 300,
id="consumer_explicit_home_overrides_parent"),
],
)
Expand Down Expand Up @@ -142,6 +151,138 @@ def test_calc_home_consumption_with_consumer_modes(counter_mode: str,
assert home_consumption == expected_home_consumption


@pytest.mark.parametrize(
["consumer_mode", "parent_mode", "expected_mode"],
[
pytest.param(CounterMode.AUTO_HOME_CONSUMPTION.value, CounterMode.HOME_CONSUMPTION.value,
CounterMode.HOME_CONSUMPTION.value, id="auto_inherits_parent"),
pytest.param(CounterMode.HOME_CONSUMPTION.value, CounterMode.NOT_HOME_CONSUMPTION.value,
CounterMode.HOME_CONSUMPTION.value, id="explicit_home_overrides_parent"),
pytest.param(CounterMode.NOT_HOME_CONSUMPTION.value, CounterMode.HOME_CONSUMPTION.value,
CounterMode.NOT_HOME_CONSUMPTION.value, id="explicit_no_home_overrides_parent"),
],
)
def test_get_is_home_consumption_consumer(consumer_mode: str,
parent_mode: str,
expected_mode: str):
c = CounterAll()
consumer = Mock(data=Mock(config=Mock(is_home_consumption_consumer=consumer_mode)))

mode = c._get_is_home_consumption_consumer(consumer, parent_mode)

assert mode == expected_mode


def test_get_local_power_from_counter_ignores_faulty_child():
data.data_init(Mock())
c = CounterAll()

data.data.counter_data = {
"counter0": Mock(
spec=Counter,
num=0,
data=Mock(
spec=CounterData,
get=Mock(spec=CounterGet, power=1000, fault_state=0),
config=Mock(spec=CounterConfig, is_home_consumption_counter=CounterMode.HOME_CONSUMPTION.value),
),
),
"counter1": Mock(
spec=Counter,
num=1,
data=Mock(
spec=CounterData,
get=Mock(spec=CounterGet, power=300, fault_state=0),
config=Mock(spec=CounterConfig, is_home_consumption_counter=CounterMode.HOME_CONSUMPTION.value),
),
),
"counter2": Mock(
spec=Counter,
num=2,
data=Mock(
spec=CounterData,
get=Mock(spec=CounterGet, power=200, fault_state=2),
config=Mock(spec=CounterConfig, is_home_consumption_counter=CounterMode.HOME_CONSUMPTION.value),
),
),
}

element = {
"id": 0,
"type": "counter",
"children": [
{"id": 1, "type": "counter", "children": []},
{"id": 2, "type": "counter", "children": []},
],
}

local_power = c._get_local_power_from_counter(element)

assert local_power == 700


def test_calc_home_consumption_from_counter_splits_home_and_not_home():
data.data_init(Mock())
c = CounterAll()
c.data.get.hierarchy = [{
"id": 0,
"type": "counter",
"children": [
{"id": 7, "type": "consumer", "children": []},
{
"id": 8,
"type": "counter",
"children": [{"id": 9, "type": "consumer", "children": []}],
},
],
}]

data.data.counter_data = {
"counter0": Mock(
spec=Counter,
num=0,
data=Mock(
spec=CounterData,
get=Mock(spec=CounterGet, power=1000, fault_state=0),
config=Mock(spec=CounterConfig,
is_home_consumption_counter=CounterMode.AUTO_HOME_CONSUMPTION.value),
),
),
"counter8": Mock(
spec=Counter,
num=8,
data=Mock(
spec=CounterData,
get=Mock(spec=CounterGet, power=100, fault_state=0),
config=Mock(spec=CounterConfig,
is_home_consumption_counter=CounterMode.HOME_CONSUMPTION.value),
),
),
}
data.data.consumer_data = {
"consumer7": Mock(
num=7,
data=Mock(
get=Mock(power=400, fault_state=0),
config=Mock(is_home_consumption_consumer=CounterMode.HOME_CONSUMPTION.value),
),
),
"consumer9": Mock(
num=9,
data=Mock(
get=Mock(power=50, fault_state=0),
config=Mock(is_home_consumption_consumer=CounterMode.NOT_HOME_CONSUMPTION.value),
),
),
}

home_consumption, not_home_consumption = c._calc_home_consumption_from_counter(
c.data.get.hierarchy[0], CounterMode.HOME_CONSUMPTION.value)

assert home_consumption == 950
assert not_home_consumption == 50


def hierarchy_home_consumption_standard() -> CounterAll:
# counter0
# |
Expand Down
3 changes: 2 additions & 1 deletion packages/control/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ def _start_consumer(self, consumer: Consumer) -> Optional[Thread]:
bat_soc=data.data.bat_all_data.data.get.soc,
cp_power=data.data.cp_all_data.data.get.power,
evu_power=data.data.counter_all_data.get_evu_counter().data.get.power,
home_consumption=data.data.counter_all_data.data.set.home_consumption,
home_consumption=(data.data.counter_all_data.data.set.home_consumption +
data.data.counter_all_data.data.set.not_in_home_consumption),
pv_power=data.data.pv_all_data.data.get.power
)
return Thread(target=consumer.module.send_values,
Expand Down
5 changes: 5 additions & 0 deletions packages/helpermodules/create_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ def config_and_state():
except Exception:
home_consumption = "Keine Daten"
parsed_data += f"Home_Consumption:\n {home_consumption}\n"
try:
not_in_home_consumption = filter_log_file('mqtt', 'openWB/counter/set/not_in_home_consumption', 5)
except Exception:
not_in_home_consumption = "Keine Daten"
parsed_data += f"Not_In_Home_Consumption:\n {not_in_home_consumption}\n"
with ErrorHandlingContext():
parsed_data += "\n## Charge Points ##\n"
parsed_data += f"CP_All_Power: {data.data.cp_all_data.data.get.power / 1000} kW\n\n"
Expand Down
1 change: 1 addition & 0 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage):
elif "openWB/set/counter/set/invalid_home_consumption" in msg.topic:
self._validate_value(msg, int, [(0, 3)])
elif ("openWB/set/counter/set/home_consumption" in msg.topic or
"openWB/set/counter/set/not_in_home_consumption" in msg.topic or
"openWB/set/counter/set/imported_home_consumption" in msg.topic or
"openWB/set/counter/set/daily_yield_home_consumption" in msg.topic or
"openWB/set/counter/set/disengageable_smarthome_power" in msg.topic):
Expand Down
1 change: 1 addition & 0 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ class UpdateConfig:
"^openWB/counter/set/imported_home_consumption$",
"^openWB/counter/set/invalid_home_consumption$",
"^openWB/counter/set/home_consumption$",
"^openWB/counter/set/not_in_home_consumption$",
"^openWB/counter/set/daily_yield_home_consumption$",
"^openWB/counter/set/simulation$",
"^openWB/counter/[0-9]+/get/voltages$",
Expand Down
Loading