From 7022e9a04199db381a6f4eb19491354f0e1b1ca2 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 27 Mar 2026 09:40:24 +0100 Subject: [PATCH 1/5] hybrid system: fix consideration of max out power --- packages/control/bat_all.py | 14 +++++++------- packages/control/bat_all_test.py | 27 ++++++++++++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index c5d43b96cc..140204506f 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -179,7 +179,7 @@ def calc_power_for_all_components(self): except Exception: log.exception("Fehler im Bat-Modul") - def _inverter_limited_power(self, inverter: Pv) -> float: + def _get_pv_power_beyond_max_ac_out(self, inverter: Pv) -> float: """gibt die maximale Entladeleistung des Speichers zurück, bis die maximale Ausgangsleistung des WR erreicht ist.""" # tested @@ -193,18 +193,18 @@ def _inverter_limited_power(self, inverter: Pv) -> float: def _limit_bat_power_discharge(self, required_power): """begrenzt die für den Algorithmus benötigte Entladeleistung des Speichers, wenn die maximale Ausgangsleistung des WR erreicht ist.""" - inverter_limited_power = 0 + pv_power_beyond_max_ac_out = 0 if required_power > 0: # Nur wenn der Speicher entladen werden soll, fließt Leistung durch den WR. for inverter in data.data.pv_data.values(): try: - inverter_limited_power += self._inverter_limited_power(inverter) + pv_power_beyond_max_ac_out += self._get_pv_power_beyond_max_ac_out(inverter) except Exception: log.exception(f"Fehler im Bat-Modul {inverter.num}") - if inverter_limited_power > 0: - required_power = max(required_power-inverter_limited_power, 0) - log.debug(f"Verbleibende Speicher-Leistung durch maximale Ausgangsleistung auf {required_power}W" - " begrenzt.") + max_inverter_power_for_bat = self.data.get.power - pv_power_beyond_max_ac_out + required_power = max(min(required_power, max_inverter_power_for_bat), 0) + log.debug(f"Verbleibende Speicher-Leistung durch maximale Ausgangsleistung auf {required_power}W" + " 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 ca3d98f972..8e3b30220c 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -35,7 +35,7 @@ def data_fixture() -> None: pytest.param(0, -6000, 0, id="max_ac_out ist 0"), ], ) -def test_inverter_limited_power(max_ac_out, power, expected_result): +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 @@ -43,26 +43,35 @@ def test_inverter_limited_power(max_ac_out, power, expected_result): bat_all = BatAll() # Aufruf der zu testenden Funktion - result = bat_all._inverter_limited_power(inverter) + result = bat_all._get_pv_power_beyond_max_ac_out(inverter) # Überprüfung des Ergebnisses assert result == expected_result @pytest.mark.parametrize( - "required_power, return_inverter_limited_power, expected_power", + "bat_power, required_power, return_pv_power_beyond_max_ac_out, expected_power", [ - pytest.param(1000, 0, 1000, id="maximale Entladeleistung nicht erreicht"), - pytest.param(1000, 100, 900, id="maximale Entladeleistung erreicht"), - pytest.param(-1000, 10, -1000, id="Speicher soll nicht mehr entladen werden"), + 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, -1000, 10, -1000, id="Speicher soll nicht mehr entladen werden"), ]) -def test_limit_bat_power_discharge(required_power, return_inverter_limited_power, expected_power, monkeypatch): +def test_limit_bat_power_discharge(bat_power: int, + required_power: int, + return_pv_power_beyond_max_ac_out: int, + expected_power: int, + monkeypatch): # setup data.data.pv_data = {"pv2": Pv(2)} - mock_inverter_limited_power = Mock(return_value=return_inverter_limited_power) - monkeypatch.setattr(BatAll, "_inverter_limited_power", mock_inverter_limited_power) + 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) b = BatAll() + b.data.get.power = bat_power # execution power = b._limit_bat_power_discharge(required_power) From e513fb8b28f73c30986dedf331dbe213e7a6e9be Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 27 Mar 2026 10:41:39 +0100 Subject: [PATCH 2/5] review --- packages/control/bat_all.py | 22 +++++++++++++++++----- packages/control/bat_all_test.py | 5 +++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 140204506f..46d79c7836 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -180,8 +180,8 @@ def calc_power_for_all_components(self): log.exception("Fehler im Bat-Modul") def _get_pv_power_beyond_max_ac_out(self, inverter: Pv) -> float: - """gibt die maximale Entladeleistung des Speichers zurück, bis die maximale Ausgangsleistung des WR erreicht - ist.""" + """gibt die PV-Leistung zurück, die über der maximalen Ausgangsleistung des Wechselrichters liegt und somit + nicht für die Entadung 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. @@ -202,9 +202,21 @@ def _limit_bat_power_discharge(self, required_power): except Exception: log.exception(f"Fehler im Bat-Modul {inverter.num}") max_inverter_power_for_bat = self.data.get.power - pv_power_beyond_max_ac_out - required_power = max(min(required_power, max_inverter_power_for_bat), 0) - log.debug(f"Verbleibende Speicher-Leistung durch maximale Ausgangsleistung auf {required_power}W" - " begrenzt.") + # 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 not (max_inverter_power_for_bat < 0 and self.data.get.power < 0 and pv_power_beyond_max_ac_out == 0): + required_power = max(min(required_power, max_inverter_power_for_bat), 0) + 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 der " + "Speicher bereits entlädt und keine PV-Leistung über der maximalen WR-Ausgangsleistung anliegt." + ) 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 8e3b30220c..d2cab97c7f 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -16,7 +16,7 @@ from modules.devices.generic.mqtt.config import MqttBatSetup -@pytest.fixture +@pytest.fixture(autouse=True) def data_fixture() -> None: data.data_init(Mock()) data.data.general_data = General() @@ -58,7 +58,8 @@ def test_pv_power_beyond_max_ac_out(max_ac_out: int, power: int, expected_result 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, -1000, 10, -1000, id="Speicher soll nicht mehr entladen werden"), + 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"), ]) def test_limit_bat_power_discharge(bat_power: int, required_power: int, From 49899a94aaba6c6508cc5a24e7cadcd780655b15 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 27 Mar 2026 10:54:26 +0100 Subject: [PATCH 3/5] review --- packages/control/bat_all.py | 13 ++++++++----- packages/control/bat_all_test.py | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 46d79c7836..471544b455 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -207,16 +207,19 @@ def _limit_bat_power_discharge(self, required_power): # (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 not (max_inverter_power_for_bat < 0 and self.data.get.power < 0 and pv_power_beyond_max_ac_out == 0): - required_power = max(min(required_power, max_inverter_power_for_bat), 0) + 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) 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 der " - "Speicher bereits entlädt und keine PV-Leistung über der maximalen WR-Ausgangsleistung anliegt." - ) + "Speicher-Entladeleistung nicht durch maximale WR-Ausgangsleistung begrenzt, da keine PV-Leistung " + "über der maximalen WR-Ausgangsleistung anliegt.") 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 d2cab97c7f..5b87c1828f 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -60,6 +60,7 @@ def test_pv_power_beyond_max_ac_out(max_ac_out: int, power: int, expected_result "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"), ]) def test_limit_bat_power_discharge(bat_power: int, required_power: int, From 7573986afeedc8d1ce08d81eebd40757acc4c492 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 27 Mar 2026 10:55:29 +0100 Subject: [PATCH 4/5] typo --- packages/control/bat_all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 471544b455..c742ae28eb 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -181,7 +181,7 @@ def calc_power_for_all_components(self): 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 Entadung des Speichers genutzt werden kann.""" + 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. From 2f3f3ec34f1cd0ec02916d7014a8fac73801419f Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 27 Mar 2026 11:35:10 +0100 Subject: [PATCH 5/5] review --- packages/control/bat_all.py | 1 - packages/control/bat_all_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index c742ae28eb..d05b359d3a 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -201,7 +201,6 @@ def _limit_bat_power_discharge(self, required_power): pv_power_beyond_max_ac_out += self._get_pv_power_beyond_max_ac_out(inverter) except Exception: log.exception(f"Fehler im Bat-Modul {inverter.num}") - max_inverter_power_for_bat = self.data.get.power - pv_power_beyond_max_ac_out # 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 diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index 5b87c1828f..0b43a7560d 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -18,7 +18,6 @@ @pytest.fixture(autouse=True) def data_fixture() -> None: - data.data_init(Mock()) data.data.general_data = General() data.data.cp_all_data = Mock(spec=AllChargepoints, data=Mock( spec=AllChargepointData, get=Mock(spec=AllGet, power=0)))