From b1b721036a4052ffa500629d5571d4a3006f0580 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 29 May 2026 08:28:50 +0200 Subject: [PATCH 1/4] fix consideration of max ac out of hybrid systems --- packages/control/bat_all.py | 55 +++++++++++++++++--------------- packages/control/bat_all_test.py | 47 +++++++-------------------- 2 files changed, 40 insertions(+), 62 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 9821b71852..d79dc0e791 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -187,46 +187,49 @@ def calc_power_for_all_components(self): except Exception: log.exception("Fehler im Bat-Modul") - def _get_pv_power_beyond_max_ac_out(self, inverter: Pv) -> float: - """gibt die PV-Leistung zurück, die über der maximalen Ausgangsleistung des Wechselrichters liegt und somit - nicht für die Entladung des Speichers genutzt werden kann.""" - # tested - # Wenn vom PV-Ertrag der Speicher geladen wird, kann diese Leistung bis zur max Ausgangsleistung des WR - # genutzt werden. - if inverter.data.config.max_ac_out > 0: - return max(inverter.data.get.power * -1 - inverter.data.config.max_ac_out, 0) - else: - return 0 + def get_bat_power_of_hybrid_system(self, inverter) -> float: + """ ermittelt die Leistung des Speichers, die über den Wechselrichter fließt, um die Leistung des Speichers + bei einem Hybrid-System zu berücksichtigen, wenn die maximale Ausgangsleistung des Wechselrichters erreicht ist. + """ + bat_power = 0 + try: + children = data.data.counter_all_data.get_entry_of_element(inverter.num)["children"] + if len(children): + hybrid = [] + for c in children: + if c.get("type") == "bat": + hybrid.append(f'bat{c["id"]}') + break + if len(hybrid): + for bat in hybrid: + # nur wenn der Speicher entlädt, fließt Leistung über den WR + if data.data.bat_data[bat].data.get.power < 0: + bat_power += data.data.bat_data[bat].data.get.power + except Exception: + log.exception(f"Fehler im Bat-Modul {inverter.num}") + return bat_power - def _limit_bat_power_discharge(self, required_power): + def _limit_bat_power_discharge(self, required_power) -> float: """begrenzt die für den Algorithmus benötigte Entladeleistung des Speichers, wenn die maximale Ausgangsleistung des WR erreicht ist.""" - pv_power_beyond_max_ac_out = 0 if required_power > 0: # Nur wenn der Speicher entladen werden soll, fließt Leistung durch den WR. + remaining_inverter_ac_out_power = 0 for inverter in data.data.pv_data.values(): try: - pv_power_beyond_max_ac_out += self._get_pv_power_beyond_max_ac_out(inverter) + bat_power = self.get_bat_power_of_hybrid_system(inverter) + inverter_power = max((inverter.data.get.power + bat_power) * -1, 0) + remaining_inverter_ac_out_power += inverter.data.config.max_ac_out - inverter_power + bat_power * -1 except Exception: log.exception(f"Fehler im Bat-Modul {inverter.num}") - # Wenn max_inverter_power_for_bat nur deshalb negativ ist, weil der Speicher aktuell bereits entlädt - # (self.data.get.power < 0) und keine PV-Leistung über der maximalen WR-Ausgangsleistung anliegt - # (pv_power_beyond_max_ac_out == 0), dann würde eine Begrenzung auf 0W die noch verfügbare - # Entladeleistung fälschlicherweise unterdrücken. In diesem Fall wenden wir keine zusätzliche - # Begrenzung durch die WR-Ausgangsleistung an. - if pv_power_beyond_max_ac_out > 0: - max_inverter_power_for_bat = self.data.get.power - pv_power_beyond_max_ac_out - # Negative Werte bedeuten, dass bereits mehr Leistung über den WR fließt, als für den Speicher - # zusätzlich verfügbar ist; in diesem Fall ist keine weitere Entladung möglich. - max_inverter_power_for_bat = max(max_inverter_power_for_bat, 0) - required_power = min(required_power, max_inverter_power_for_bat) + if remaining_inverter_ac_out_power > 0: + required_power = min(required_power, remaining_inverter_ac_out_power) log.debug( f"Verbleibende Speicher-Leistung durch maximale Ausgangsleistung auf {required_power}W begrenzt." ) else: log.debug( - "Speicher-Entladeleistung nicht durch maximale WR-Ausgangsleistung begrenzt, da keine PV-Leistung " - "über der maximalen WR-Ausgangsleistung anliegt.") + "Speicher-Entladeleistung nicht durch maximale WR-Ausgangsleistung begrenzt.") return required_power def _set_bat_power_active_control(self, power): diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index ae7ce91cc3..54ba5abb17 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -27,50 +27,25 @@ def data_fixture() -> None: @pytest.mark.parametrize( - "max_ac_out, power, expected_result", + "bat_power, required_power, pv_power, expected_power", [ - pytest.param(5000, -6000, 1000, id="Leistung überschreitet max_ac_out"), - pytest.param(5000, -4000, 0, id="Leistung liegt unter max_ac_out"), - pytest.param(5000, 0, 0, id="Keine Leistung (power = 0)"), - pytest.param(0, -6000, 0, id="max_ac_out ist 0"), - ], -) -def test_pv_power_beyond_max_ac_out(max_ac_out: int, power: int, expected_result: int): - # Mock für die Pv-Klasse - inverter = Pv(1) - inverter.data.config.max_ac_out = max_ac_out - inverter.data.get.power = power - bat_all = BatAll() - - # Aufruf der zu testenden Funktion - result = bat_all._get_pv_power_beyond_max_ac_out(inverter) - - # Überprüfung des Ergebnisses - assert result == expected_result - - -@pytest.mark.parametrize( - "bat_power, required_power, return_pv_power_beyond_max_ac_out, expected_power", - [ - pytest.param(1000, 1000, 0, 1000, id="exakt max Leistung des WR"), - pytest.param(1000, 1000, 100, 900, id="max Leistung des WR um 100W überschritten"), - pytest.param(1000, 1000, 1100, 0, id="maximale Entladeleistung erreicht"), - pytest.param(3000, 3000, 2000, 1000, id="max Leistung des WR um 2000W überschritten"), - pytest.param(3000, 5000, 2000, 1000, id="max Leistung des WR um 2000W überschritten, " + - "erlaubte Entladeleistung höher als aktuelle Leistung"), - pytest.param(-1000, 1100, 0, 1100, id="Speicher entlädt, soll entladen"), - pytest.param(-1000, -600, 0, -600, id="Speicher entlädt, soll weniger entladen"), - pytest.param(0, 600, 0, 600, id="Speicher ruht, soll entladen"), + pytest.param(-1000, 1000, 0, 1000, id="Leistung verfügbar"), + pytest.param(-4900, 5100, -100, 4900, id="max Leistung des WR um 100W überschritten, Speicher entlädt"), + pytest.param(1000, 1600, -4500, 500, id="Speicher lädt, soll entladen"), ]) def test_limit_bat_power_discharge(bat_power: int, required_power: int, - return_pv_power_beyond_max_ac_out: int, + pv_power: int, expected_power: int, monkeypatch): # setup data.data.pv_data = {"pv2": Pv(2)} - mock_pv_power_beyond_max_ac_out = Mock(return_value=return_pv_power_beyond_max_ac_out) - monkeypatch.setattr(BatAll, "_get_pv_power_beyond_max_ac_out", mock_pv_power_beyond_max_ac_out) + data.data.pv_data["pv2"].data.get.power = pv_power + data.data.pv_data["pv2"].data.config.max_ac_out = 5000 + data.data.bat_data["bat1"] = Bat(1) + data.data.bat_data["bat1"].data.get.power = bat_power + mock_enty_children = Mock(return_value={"children": [{"id": 1, "type": "bat"}]}) + monkeypatch.setattr(data.data.counter_all_data, "get_entry_of_element", mock_enty_children) b = BatAll() b.data.get.power = bat_power From e0959f6cf54806c38c2755fc8af620de577b55cf Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 29 May 2026 08:38:04 +0200 Subject: [PATCH 2/4] type hints --- packages/control/bat_all.py | 6 +++--- packages/control/counter_all.py | 2 +- packages/control/pv.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index d79dc0e791..ccd69f066b 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -187,7 +187,7 @@ def calc_power_for_all_components(self): except Exception: log.exception("Fehler im Bat-Modul") - def get_bat_power_of_hybrid_system(self, inverter) -> float: + def get_bat_power_of_hybrid_system(self, inverter: Pv) -> float: """ ermittelt die Leistung des Speichers, die über den Wechselrichter fließt, um die Leistung des Speichers bei einem Hybrid-System zu berücksichtigen, wenn die maximale Ausgangsleistung des Wechselrichters erreicht ist. """ @@ -195,7 +195,7 @@ def get_bat_power_of_hybrid_system(self, inverter) -> float: try: children = data.data.counter_all_data.get_entry_of_element(inverter.num)["children"] if len(children): - hybrid = [] + hybrid: List[str] = [] for c in children: if c.get("type") == "bat": hybrid.append(f'bat{c["id"]}') @@ -209,7 +209,7 @@ def get_bat_power_of_hybrid_system(self, inverter) -> float: log.exception(f"Fehler im Bat-Modul {inverter.num}") return bat_power - def _limit_bat_power_discharge(self, required_power) -> float: + def _limit_bat_power_discharge(self, required_power: float) -> float: """begrenzt die für den Algorithmus benötigte Entladeleistung des Speichers, wenn die maximale Ausgangsleistung des WR erreicht ist.""" if required_power > 0: diff --git a/packages/control/counter_all.py b/packages/control/counter_all.py index 2eeb156d18..8ea0729967 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -248,7 +248,7 @@ def get_counters_to_check(self, num: int) -> List[str]: self.__get_all_counter_in_branch(self.data.get.hierarchy[0], num) return self.connected_counters - def get_entry_of_element(self, id_to_find: int) -> Dict: + def get_entry_of_element(self, id_to_find: int) -> Dict[str, List[Dict[str, Union[int, str]]]]: item = self.__is_id_in_top_level(id_to_find) if item: return item diff --git a/packages/control/pv.py b/packages/control/pv.py index a7969e41c1..c44f06fd5d 100644 --- a/packages/control/pv.py +++ b/packages/control/pv.py @@ -49,6 +49,6 @@ class PvData: class Pv: - def __init__(self, index): + def __init__(self, index: int): self.data = PvData() self.num = index From 1d341ba10fbef9e645bc0647e02e73bb69583b01 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 29 May 2026 08:42:11 +0200 Subject: [PATCH 3/4] type checker --- packages/control/bat_all_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index 54ba5abb17..4031a555cb 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -37,7 +37,7 @@ def test_limit_bat_power_discharge(bat_power: int, required_power: int, pv_power: int, expected_power: int, - monkeypatch): + monkeypatch: pytest.MonkeyPatch): # setup data.data.pv_data = {"pv2": Pv(2)} data.data.pv_data["pv2"].data.get.power = pv_power @@ -51,7 +51,7 @@ def test_limit_bat_power_discharge(bat_power: int, b.data.get.power = bat_power # execution - power = b._limit_bat_power_discharge(required_power) + power = b._limit_bat_power_discharge(required_power) # pyright: ignore[reportPrivateUsage] # evaluation assert power == expected_power From e4aeb8bc993126ab42d2afed6b30bbd745207f7e Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 29 May 2026 08:45:09 +0200 Subject: [PATCH 4/4] typo --- packages/control/bat_all_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index 4031a555cb..74c3f8dda4 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -44,8 +44,8 @@ def test_limit_bat_power_discharge(bat_power: int, data.data.pv_data["pv2"].data.config.max_ac_out = 5000 data.data.bat_data["bat1"] = Bat(1) data.data.bat_data["bat1"].data.get.power = bat_power - mock_enty_children = Mock(return_value={"children": [{"id": 1, "type": "bat"}]}) - monkeypatch.setattr(data.data.counter_all_data, "get_entry_of_element", mock_enty_children) + mock_entry_children = Mock(return_value={"children": [{"id": 1, "type": "bat"}]}) + monkeypatch.setattr(data.data.counter_all_data, "get_entry_of_element", mock_entry_children) b = BatAll() b.data.get.power = bat_power