From f3d3d71270a66c5e908f55369332ffa563551000 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 3 Jun 2025 11:55:38 +0200 Subject: [PATCH] consider max ac out for all inverters --- packages/conftest.py | 3 +- packages/control/bat_all.py | 52 ++++------- packages/control/bat_all_test.py | 86 +++++++------------ .../measurement_logging/conftest.py | 3 +- 4 files changed, 50 insertions(+), 94 deletions(-) diff --git a/packages/conftest.py b/packages/conftest.py index a185b7cfc0..bb2cea597f 100644 --- a/packages/conftest.py +++ b/packages/conftest.py @@ -14,6 +14,7 @@ from control.counter import Set as CounterSet 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 helpermodules import hardware_configuration, pub, timecheck from modules.chargepoints.mqtt.chargepoint_module import ChargepointModule @@ -155,7 +156,7 @@ def data_() -> None: 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, daily_exported=6000, exported=27000, currents=None, - fault_state=0)))}) + 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))), diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index c84cd5aed0..cf9d57b59b 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -25,10 +25,9 @@ from control import data from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes -from control.bat import Bat +from control.pv import Pv from helpermodules.constants import NO_ERROR from modules.common.abstract_device import AbstractDevice -from modules.common.fault_state import FaultStateLevel log = logging.getLogger(__name__) @@ -144,48 +143,32 @@ def calc_power_for_all_components(self): except Exception: log.exception("Fehler im Bat-Modul") - def _max_bat_power_hybrid_system(self, battery: Bat) -> float: + def _inverter_limited_power(self, inverter: Pv) -> float: """gibt die maximale Entladeleistung des Speichers zurück, bis die maximale Ausgangsleistung des WR erreicht ist.""" # tested - parent = data.data.counter_all_data.get_entry_of_parent(battery.num) - if parent.get("type") == "inverter": - parent_data = data.data.pv_data[f"pv{parent['id']}"].data - # Wenn vom PV-Ertrag der Speicher geladen wird, kann diese Leistung bis zur max Ausgangsleistung des WR - # genutzt werden. - if parent_data.config.max_ac_out > 0: - max_bat_discharge_power = parent_data.config.max_ac_out + \ - parent_data.get.power + battery.data.get.power - return max_bat_discharge_power, True - else: - battery.data.get.fault_state = FaultStateLevel.ERROR.value - battery.data.get.fault_str = self.ERROR_CONFIG_MAX_AC_OUT - raise ValueError(self.ERROR_CONFIG_MAX_AC_OUT) + # 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: - # Kein Hybrid-WR - # Maximal die Speicher-Leistung als Entladeleistung nutzen, um nicht unnötig Bezug zu erzeugen. - return abs(battery.data.get.power) + 50, False + return 0 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.""" - available_power = 0 - hybrid = False + inverter_limited_power = 0 if required_power > 0: # Nur wenn der Speicher entladen werden soll, fließt Leistung durch den WR. - for battery in data.data.bat_data.values(): + for inverter in data.data.pv_data.values(): try: - available_power_bat, hybrid_bat = self._max_bat_power_hybrid_system(battery) - if hybrid_bat: - hybrid = True - available_power += available_power_bat + inverter_limited_power += self._inverter_limited_power(inverter) except Exception: - log.exception(f"Fehler im Bat-Modul {battery.num}") - if hybrid: - if required_power > available_power: - log.debug(f"Verbleibende Speicher-Leistung durch maximale Ausgangsleistung auf {available_power}W" - " begrenzt.") - return min(required_power, available_power) + 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.") return required_power def setup_bat(self): @@ -332,10 +315,7 @@ def get_power_limit(self): if self.data.set.power_limit is None: power_limit = None else: - power_limit = min(self._max_bat_power_hybrid_system( - data.data.bat_data[f"bat{bat_component.component_config.id}"])[0], remaining_power_limit) - remaining_power_limit -= power_limit - remaining_power_limit = max(remaining_power_limit, 0) + power_limit = self._limit_bat_power_discharge(remaining_power_limit) data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set.power_limit = power_limit diff --git a/packages/control/bat_all_test.py b/packages/control/bat_all_test.py index ddca630a69..73bd804535 100644 --- a/packages/control/bat_all_test.py +++ b/packages/control/bat_all_test.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from typing import List, Optional -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock import pytest from packages.conftest import hierarchy_standard from control import bat_all @@ -27,66 +27,40 @@ def data_fixture() -> None: @pytest.mark.parametrize( - "parent, bat_power, pv_power, expected_power_hybrid", + "max_ac_out, power, expected_result", [ - pytest.param({"id": 6, "type": "counter", "children": [ - {"id": 2, "type": "bat", "children": []}]}, 100, -6400, (150, False), - id="kein Hybrid-System, Speicher wird geladen"), - pytest.param({"id": 6, "type": "counter", "children": [ - {"id": 2, "type": "bat", "children": []}]}, -100, -6400, (150, False), - id="kein Hybrid-System, Speicher wird entladen"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 1200, -6400, (2000, True), - id="Speicher lädt mit 1200W, max 2000W zusätzliche Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 600, -6400, (1400, True), - id="Speicher lädt mit 600W, max 1400W zusätzliche Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 0, -6400, (800, True), - id="Speicher neutral, max 800W Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, -600, -6400, (200, True), - id="Speicher entlädt mit 600W, max 200W Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, -800, -6400, (0, True), - id="Speicher entlädt mit 800W, maximale Entladeleistung des WR erreicht"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 1200, -7200, (1200, True), - id="Speicher lädt mit 1200W, max 1200W zusätzliche Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 600, -7200, (600, True), - id="Speicher lädt mit 600W, max 600W zusätzliche Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 0, -7200, (0, True), - id="Speicher neutral, maximale Entladeleistung des WR erreicht"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 1200, -7800, (600, True), - id="Speicher lädt mit 1200W, max 600W zusätzliche Entladeleistung bis WR max"), - pytest.param({"id": 1, "type": "inverter", "children": []}, 600, -7800, (0, True), - id="Speicher lädt mit 600W, maximale Entladeleistung des WR erreicht"), - ]) -def test_max_bat_power_hybrid_system(parent, bat_power, pv_power, expected_power_hybrid, data_fixture, monkeypatch): - # setup - # pv1-Data: max_ac_out 7200 - mock_get_entry_of_parent = Mock(return_value=parent) - monkeypatch.setattr(data.data.counter_all_data, "get_entry_of_parent", mock_get_entry_of_parent) - data.data.pv_data["pv1"].data.get.power = pv_power - - b = BatAll() - bat2 = Bat(2) - bat2.data.get.power = bat_power - - # execution - power = b._max_bat_power_hybrid_system(bat2) - - # evaluation - assert power == expected_power_hybrid + 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_inverter_limited_power(max_ac_out, power, expected_result): + # 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._inverter_limited_power(inverter) + + # Überprüfung des Ergebnisses + assert result == expected_result @pytest.mark.parametrize( - "required_power, return_max_bat_power_hybrid_system, expected_power", + "required_power, return_inverter_limited_power, expected_power", [ - pytest.param(1000, (1100, True), 1000, id="maximale Entladeleistung nicht erreicht"), - pytest.param(1000, (900, True), 900, id="maximale Entladeleistung erreicht"), - pytest.param(-1000, (10, True), -1000, id="Speicher soll nicht mehr entladen werden"), - pytest.param(1000, (900, False), 1000, id="kein Hybrid-System"), + 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"), ]) -def test_limit_bat_power_discharge(required_power, return_max_bat_power_hybrid_system, expected_power, monkeypatch): +def test_limit_bat_power_discharge(required_power, return_inverter_limited_power, expected_power, monkeypatch): # setup - data.data.bat_data = {"bat2": Bat(2)} - mock_max_bat_power_hybrid_system = Mock(return_value=return_max_bat_power_hybrid_system) - monkeypatch.setattr(BatAll, "_max_bat_power_hybrid_system", mock_max_bat_power_hybrid_system) + 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) b = BatAll() @@ -168,8 +142,8 @@ def test_get_charging_power_left(params: Params, caplog, data_fixture, monkeypat b.data.get.power = params.power data.data.bat_data["bat0"] = b data.data.general_data.data.chargemode_config.pv_charging = params.config - mock__max_bat_power_hybrid_system = Mock(return_value=(params.power, None)) - monkeypatch.setattr(BatAll, "_max_bat_power_hybrid_system", mock__max_bat_power_hybrid_system) + mock_limit_bat_power_discharge = MagicMock(side_effect=lambda x: x) + monkeypatch.setattr(BatAll, "_limit_bat_power_discharge", mock_limit_bat_power_discharge) # execution b_all._get_charging_power_left() diff --git a/packages/helpermodules/measurement_logging/conftest.py b/packages/helpermodules/measurement_logging/conftest.py index 5e1059a91a..17e3209584 100644 --- a/packages/helpermodules/measurement_logging/conftest.py +++ b/packages/helpermodules/measurement_logging/conftest.py @@ -1,6 +1,7 @@ from threading import Event import pytest +from control.bat import Bat from control.chargepoint import chargepoint from control.chargepoint.chargepoint_all import AllChargepoints from control import bat_all, counter, pv_all, pv @@ -10,7 +11,7 @@ @pytest.fixture(autouse=True) def data_module() -> None: data.data_init(Event()) - data.data.bat_data.update({"all": bat_all.BatAll(), "bat2": bat_all.Bat(2)}) + data.data.bat_data.update({"all": bat_all.BatAll(), "bat2": Bat(2)}) data.data.counter_data.update({"counter0": counter.Counter(0)}) data.data.cp_all_data = AllChargepoints() data.data.cp_data.update({"cp4": chargepoint.Chargepoint(