From db63ea61a5c5e1464b52f37bb9151bd67fdd35f5 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Aug 2026 08:21:39 +0200 Subject: [PATCH 1/5] victron using mode 1; pass dataclass isntead of power_limit --- packages/control/bat.py | 1 + packages/control/bat_all.py | 3 +- packages/control/process.py | 2 +- packages/helpermodules/setdata.py | 2 + packages/helpermodules/update_config.py | 3 +- .../modules/common/configurable_device.py | 5 ++- .../devices/alpha_ess/alpha_ess/bat.py | 9 ++-- .../modules/devices/anker/solarbank/bat.py | 11 ++--- .../modules/devices/batterx/batterx/bat.py | 9 ++-- packages/modules/devices/generic/mqtt/bat.py | 7 +-- .../modules/devices/good_we/good_we/bat.py | 22 +++++----- .../devices/kostal/kostal_plenticore/bat.py | 17 ++++---- .../modules/devices/marstek/venus_c_e/bat.py | 15 ++++--- packages/modules/devices/qcells/qcells/bat.py | 13 +++--- .../devices/sigenergy/sigenergy/bat.py | 7 +-- .../modules/devices/sma/sma_sunny_boy/bat.py | 9 ++-- .../devices/sma/sma_sunny_island/bat.py | 9 ++-- .../devices/solaredge/solaredge/bat.py | 36 +++++++++------- packages/modules/devices/solax/solax/bat.py | 13 +++--- packages/modules/devices/solis/solis/bat.py | 17 ++++---- .../devices/sonnen/sonnenbatterie/bat.py | 9 ++-- .../devices/sungrow/sungrow_ihm/bat.py | 27 ++++++------ .../modules/devices/sungrow/sungrow_sh/bat.py | 22 +++++----- .../modules/devices/varta/varta/bat_modbus.py | 17 ++++---- .../modules/devices/victron/victron/bat.py | 43 +++++++++---------- 25 files changed, 180 insertions(+), 148 deletions(-) diff --git a/packages/control/bat.py b/packages/control/bat.py index f3ae1e7d9f..5331a0a356 100644 --- a/packages/control/bat.py +++ b/packages/control/bat.py @@ -42,6 +42,7 @@ def get_factory() -> Get: @dataclass class Set: power_limit: Optional[int] = field(default=None, metadata={"topic": "set/power_limit"}) + total_limit: Optional[int] = field(default=None, metadata={"topic": "set/total_limit"}) def set_factory() -> Set: diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index d553be7509..6c30fd551c 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -297,7 +297,8 @@ def _set_bat_power_active_control(self, power): log.debug(("Aktive Speichersteuerung: Ladung - " f"Speicher (ID: {bat_component.component_config.id}) " f"laden mit {power_limit} ({factor} x {bat_component_data.get.max_charge_power}) W")) - data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set.power_limit = power_limit + bat_component_data.set.power_limit = power_limit + bat_component_data.set.total_limit = power def setup_bat(self): """ prüft, ob mind ein Speicher vorhanden ist und berechnet die Summen-Topics. diff --git a/packages/control/process.py b/packages/control/process.py index 9fa439b828..e3086e9b09 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -69,7 +69,7 @@ def process_algorithm_results(self) -> None: Thread( target=set_power_limit_wrapper, args=(bat_component, - data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set.power_limit), + data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set), name=f"set power limit {bat_component.component_config.id}")) for action in data.data.io_actions.actions.values(): if isinstance(action, DimmingDirectControl): diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 7c03072896..9722b66efc 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -734,6 +734,8 @@ def process_bat_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, bool) elif "/set/power_limit" in msg.topic: self._validate_value(msg, float) + elif "/set/total_limit" in msg.topic: + self._validate_value(msg, float) else: self.__unknown_topic(msg) else: diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index c35ba3b974..7bc144dee5 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -97,7 +97,8 @@ class UpdateConfig: "^openWB/bat/[0-9]+/get/fault_state$", "^openWB/bat/[0-9]+/get/fault_str$", "^openWB/bat/[0-9]+/get/power_limit_controllable$", - "^openWB/bat/[0-9]+/set/power_limit$", + "^openWB/bat/[0-9]+/set/power_limtit$", + "^openWB/bat/[0-9]+/set/total_limit$", "^openWB/chargepoint/get/power$", "^openWB/chargepoint/get/exported$", diff --git a/packages/modules/common/configurable_device.py b/packages/modules/common/configurable_device.py index 97ceb6e70f..63b2c132c6 100644 --- a/packages/modules/common/configurable_device.py +++ b/packages/modules/common/configurable_device.py @@ -8,6 +8,7 @@ from modules.common.abstract_device import AbstractBat, AbstractDevice from modules.common.component_context import SingleComponentUpdateContext, MultiComponentUpdateContext from modules.common.fault_state import ComponentInfo, FaultState +from control.bat import Set as SetPoint T_DEVICE_CONFIG = TypeVar("T_DEVICE_CONFIG") T_COMPONENT = TypeVar("T_COMPONENT") @@ -130,9 +131,9 @@ def update(self): self.__component_updater(initialized_components, self.error_handler) -def set_power_limit_wrapper(bat_component: AbstractBat, power_limit: Optional[int]): +def set_power_limit_wrapper(bat_component: AbstractBat, setpoint: Optional[SetPoint]): """set_power_limit innerhalb des SingleComponentUpdateContext aufrufen, damit Fehler im fault_state-Handler behandelt werden """ with SingleComponentUpdateContext(bat_component.fault_state, update_always=False): - bat_component.set_power_limit(power_limit) + bat_component.set_power_limit(setpoint) diff --git a/packages/modules/devices/alpha_ess/alpha_ess/bat.py b/packages/modules/devices/alpha_ess/alpha_ess/bat.py index 3604c27608..48c63f2da0 100644 --- a/packages/modules/devices/alpha_ess/alpha_ess/bat.py +++ b/packages/modules/devices/alpha_ess/alpha_ess/bat.py @@ -1,6 +1,6 @@ import logging import time -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -11,6 +11,7 @@ from modules.devices.alpha_ess.alpha_ess.config import AlphaEssBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -62,16 +63,16 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.__modbus_id - if power_limit is None: + if setpoint.power_limit is None: # Kein Powerlimit gefordert, externe Steuerung deaktivieren log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") if self.last_mode is not None: self.__tcp_client.write_register(2127, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit <= 0: + elif setpoint.power_limit <= 0: # AlphaESS kann die Entladung nur über den SoC verhindern (komplette Entladesperre) # Netzladung mit geringen Ziel SoC verhindert auch Entladung (Default 10%) # Zeiten für Netzladung müssen im Wechselrichter aktiviert werden diff --git a/packages/modules/devices/anker/solarbank/bat.py b/packages/modules/devices/anker/solarbank/bat.py index 75d44a6ba3..06a93b5629 100644 --- a/packages/modules/devices/anker/solarbank/bat.py +++ b/packages/modules/devices/anker/solarbank/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Any, Optional, TypedDict +from typing import Any, TypedDict from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -12,6 +12,7 @@ from modules.devices.anker.solarbank.config import Anker, AnkerBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -52,10 +53,10 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.device_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: self.client.write_register(10064, 0, data_type=ModbusDataType.UINT_16, unit=unit) @@ -65,9 +66,9 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.client.write_register(10064, 3, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'limited' - power_value = 0 if power_limit == 0 else int(power_limit) * -1 + power_value = 0 if setpoint.power_limit == 0 else int(setpoint.power_limit) * -1 self.client.write_register(10071, power_value, data_type=ModbusDataType.INT_32, unit=unit) - log.debug("Aktive Batteriesteuerung angefordert, angeforderte Leistung: {power_value} W") + log.debug(f"Aktive Batteriesteuerung angefordert, angeforderte Leistung: {power_value} W") def power_limit_controllable(self) -> bool: return True diff --git a/packages/modules/devices/batterx/batterx/bat.py b/packages/modules/devices/batterx/batterx/bat.py index f3918adf72..6ac1c99cfa 100644 --- a/packages/modules/devices/batterx/batterx/bat.py +++ b/packages/modules/devices/batterx/batterx/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, TypedDict, Any, Optional +from typing import Dict, TypedDict, Any from modules.devices.batterx.batterx.config import BatterXBatSetup from modules.common.abstract_device import AbstractBat @@ -12,6 +12,7 @@ from modules.common import req from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -49,10 +50,10 @@ def update(self, resp: Dict) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: log.debug(f'last_mode: {self.last_mode}') - if power_limit is None: + if setpoint.power_limit is None: # Kein Powerlimit gefordert, externe Steuerung deaktivieren log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") if self.last_mode is not None: @@ -67,7 +68,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: timeout=5 ) self.last_mode = None - elif power_limit <= 0: + elif setpoint.power_limit <= 0: # BatterX kann Entladung nur komplett sperren log.debug("Aktive Batteriesteuerung angestoßen. Setze Entladesperre.") if self.last_mode != 'stop': diff --git a/packages/modules/devices/generic/mqtt/bat.py b/packages/modules/devices/generic/mqtt/bat.py index 61368a2b7f..05fc4d39ec 100644 --- a/packages/modules/devices/generic/mqtt/bat.py +++ b/packages/modules/devices/generic/mqtt/bat.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import Any, Dict, Optional, TypedDict +from typing import Any, Dict, TypedDict from helpermodules.pub import Pub from helpermodules.utils._get_default import get_default @@ -12,6 +12,7 @@ from modules.devices.generic.mqtt.config import MqttBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint class KwargsDict(TypedDict): @@ -55,8 +56,8 @@ def parse_received_topics(value: str): ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: - Pub().pub(f"openWB/set/mqtt/bat/{self.component_config.id}/set/power_limit", power_limit) + def set_power_limit(self, setpoint: SetPoint) -> None: + Pub().pub(f"openWB/set/mqtt/bat/{self.component_config.id}/set/power_limit", setpoint.power_limit) def power_limit_controllable(self) -> bool: return self.component_config.configuration.power_limit_controllable diff --git a/packages/modules/devices/good_we/good_we/bat.py b/packages/modules/devices/good_we/good_we/bat.py index b222867b82..3cca8abd03 100644 --- a/packages/modules/devices/good_we/good_we/bat.py +++ b/packages/modules/devices/good_we/good_we/bat.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any import logging from modules.common import modbus @@ -14,6 +14,7 @@ from modules.devices.good_we.good_we.version import GoodWeVersion from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -73,37 +74,38 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.__modbus_id log.debug(f'last_mode: {self.last_mode}') - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: self.__tcp_client.write_register(47511, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(47512, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': self.__tcp_client.write_register(47511, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(47512, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' - elif power_limit < 0: - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") + elif setpoint.power_limit < 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W " + "entladen für den Hausverbrauch") if self.last_mode != 'discharge': self.__tcp_client.write_register(47511, 3, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. - power_value = int(min(abs(power_limit), 10000)) + power_value = int(min(abs(setpoint.power_limit), 10000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") self.__tcp_client.write_register(47512, power_value, data_type=ModbusDataType.UINT_16, unit=unit) - elif power_limit > 0: + elif setpoint.power_limit > 0: if self.last_mode != 'charge': self.__tcp_client.write_register(47511, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' - power_value = int(min(abs(power_limit), 10000)) - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") + power_value = int(min(abs(setpoint.power_limit), 10000)) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen.") self.__tcp_client.write_register(47512, power_value, data_type=ModbusDataType.UINT_16, unit=unit) def power_limit_controllable(self) -> bool: diff --git a/packages/modules/devices/kostal/kostal_plenticore/bat.py b/packages/modules/devices/kostal/kostal_plenticore/bat.py index 131e4d0f4c..05f2317d1c 100644 --- a/packages/modules/devices/kostal/kostal_plenticore/bat.py +++ b/packages/modules/devices/kostal/kostal_plenticore/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any from pymodbus.constants import Endian from modules.common.abstract_device import AbstractBat @@ -13,6 +13,7 @@ from modules.devices.kostal.kostal_plenticore.config import KostalPlenticoreBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -67,24 +68,24 @@ def update(self) -> None: # Kostal setzt das Register autmatisch nach Timeout zurück auf Eigensteuerung. # Timeout kann im Kostal UI geändert werden. Standardwert 30s - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.modbus_id - if power_limit is None: + if setpoint.power_limit is None: # Keine Registeränderung damit nach Timeout eigenständig zurückgesetzt wird log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") - elif power_limit == 0: + elif setpoint.power_limit == 0: # wiederholt auf Stop setzen damit sich Register nicht zurücksetzt log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") self.client.write_register(1034, 0.0, data_type=ModbusDataType.FLOAT_32, wordorder=self.endianess, unit=unit) - elif power_limit < 0: - power_value = float(abs(power_limit)) + elif setpoint.power_limit < 0: + power_value = float(abs(setpoint.power_limit)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") self.client.write_register(1034, power_value, data_type=ModbusDataType.FLOAT_32, wordorder=self.endianess, unit=unit) - elif power_limit > 0: - power_value = float(abs(power_limit)) * -1 + elif setpoint.power_limit > 0: + power_value = float(abs(setpoint.power_limit)) * -1 log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen") self.client.write_register(1034, power_value, data_type=ModbusDataType.FLOAT_32, wordorder=self.endianess, unit=unit) diff --git a/packages/modules/devices/marstek/venus_c_e/bat.py b/packages/modules/devices/marstek/venus_c_e/bat.py index 0e22812173..54062b5851 100644 --- a/packages/modules/devices/marstek/venus_c_e/bat.py +++ b/packages/modules/devices/marstek/venus_c_e/bat.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import Optional, TypedDict, Any, Union +from typing import TypedDict, Any, Union from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -10,6 +10,7 @@ from modules.devices.marstek.venus_c_e.config import VenusCEBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint class KwargsDict(TypedDict): @@ -53,20 +54,20 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: # Wenn der Speicher die Steuerung der Ladeleistung unterstützt, muss bei Übergabe einer Zahl auf aktive # Speichersteurung umgeschaltet werden, sodass der Speicher mit der übergebenen Leistung lädt/entlädt. Wird # None übergeben, muss der Speicher die Null-Punkt-Ausregelung selbst übernehmen. - if (power_limit is None): + if (setpoint.power_limit is None): self._write_reg(42000, 0x55bb) else: self._write_reg(42000, 0x55aa) - if power_limit < 0: + if setpoint.power_limit < 0: self._write_reg(42010, 2) - self._write_reg(42021, int(min(-power_limit, 2500))) - elif power_limit > 0: + self._write_reg(42021, int(min(-setpoint.power_limit, 2500))) + elif setpoint.power_limit > 0: self._write_reg(42010, 1) - self._write_reg(42020, int(min(power_limit, 2500))) + self._write_reg(42020, int(min(setpoint.power_limit, 2500))) else: self._write_reg(42010, 0) diff --git a/packages/modules/devices/qcells/qcells/bat.py b/packages/modules/devices/qcells/qcells/bat.py index 95e9a4a0d8..30c05d29aa 100644 --- a/packages/modules/devices/qcells/qcells/bat.py +++ b/packages/modules/devices/qcells/qcells/bat.py @@ -13,6 +13,7 @@ from modules.common.store import get_component_value_store from modules.common.utils.peak_filter import PeakFilter from modules.devices.qcells.qcells.config import QCellsBatSetup +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -68,11 +69,11 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.__modbus_id - log.debug(f"QCells set_power_limit: power_limit={power_limit}, last_mode={self.last_mode}") + log.debug(f"QCells set_power_limit: power_limit={setpoint.power_limit}, last_mode={self.last_mode}") - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: with self.client: @@ -85,14 +86,14 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.last_mode = None return - if power_limit < 0: + if setpoint.power_limit < 0: self.last_mode = "discharge" - elif power_limit > 0: + elif setpoint.power_limit > 0: self.last_mode = "charge" else: self.last_mode = "stop" - push_power = self._get_mode4_push_power(int(power_limit)) + push_power = self._get_mode4_push_power(int(setpoint.power_limit)) self._write_mode4(push_power, unit) def _get_mode4_push_power(self, power_limit: int) -> int: diff --git a/packages/modules/devices/sigenergy/sigenergy/bat.py b/packages/modules/devices/sigenergy/sigenergy/bat.py index 6ce460b415..ac3fbb92cb 100644 --- a/packages/modules/devices/sigenergy/sigenergy/bat.py +++ b/packages/modules/devices/sigenergy/sigenergy/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -11,6 +11,7 @@ from modules.devices.sigenergy.sigenergy.config import SigenergyBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -51,11 +52,11 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.component_config.configuration.modbus_id log.debug(f'last_mode: {self.last_mode}') # Steuerung erfolgt über SoC (mit Faktor 10) - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: # Entladesperre ab 5%, Ansonsten Eigenregelung diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat.py b/packages/modules/devices/sma/sma_sunny_boy/bat.py index f9a9804e7b..4ca6ee74b2 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Any, TypedDict, Optional +from typing import Any, TypedDict from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -13,6 +13,7 @@ from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType from modules.devices.sma.sma_sunny_boy.version import SmaBatVersion +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -82,10 +83,10 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.component_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: if self.last_mode is not None: # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren self.__tcp_client.write_register(40151, 803, data_type=ModbusDataType.UINT_32, unit=unit) @@ -95,7 +96,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: # Powerlimit gefordert, externe Steuerung aktivieren, Limit setzen self.__tcp_client.write_register(40151, 802, data_type=ModbusDataType.UINT_32, unit=unit) - power_value = int(power_limit) * -1 + power_value = int(setpoint.power_limit) * -1 self.__tcp_client.write_register(40149, power_value, data_type=ModbusDataType.INT_32, unit=unit) log.debug(f"Aktive Batteriesteuerung vorhanden. Setze externe Steuerung. Leistung: {power_value}") self.last_mode = 'limited' diff --git a/packages/modules/devices/sma/sma_sunny_island/bat.py b/packages/modules/devices/sma/sma_sunny_island/bat.py index 1e37f78bee..62943ad64a 100644 --- a/packages/modules/devices/sma/sma_sunny_island/bat.py +++ b/packages/modules/devices/sma/sma_sunny_island/bat.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any import logging from modules.common import modbus @@ -12,6 +12,7 @@ from modules.devices.sma.sma_sunny_island.config import SmaSunnyIslandBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -48,10 +49,10 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.component_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: if self.last_mode is not None: # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren self.__tcp_client.write_register(40151, 803, data_type=ModbusDataType.UINT_32, unit=unit) @@ -61,7 +62,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: # Powerlimit gefordert, externe Steuerung aktivieren, Limit setzen self.__tcp_client.write_register(40151, 802, data_type=ModbusDataType.UINT_32, unit=unit) - power_value = int(power_limit) * -1 + power_value = int(setpoint.power_limit) * -1 self.__tcp_client.write_register(40149, power_value, data_type=ModbusDataType.INT_32, unit=unit) log.debug("Aktive Batteriesteuerung vorhanden. Setze externe Steuerung. Leistung: {power_value}") self.last_mode = 'limited' diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 16105b9dfc..60a4dbf914 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from enum import IntEnum import logging -from typing import Any, TypedDict, Dict, Union, Optional, Tuple +from typing import Any, TypedDict, Dict, Union, Tuple from pymodbus.constants import Endian from modules.common import modbus @@ -15,6 +15,7 @@ from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -106,7 +107,7 @@ def get_values(self) -> Tuple[float, float]: def get_imported_exported(self, power: float) -> Tuple[float, float]: return self.sim_counter.sim_count(power) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.component_config.configuration.modbus_id battery_index = self.component_config.configuration.battery_index @@ -122,7 +123,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: Registers.STORAGE_CONTROL_MODE, 14, wordorder=Endian.Little, mapping=bulk, unit=unit) log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") - if power_limit is None: # No Bat Control should be used. + if setpoint.power_limit is None: # No Bat Control should be used. if values[Registers.STORAGE_CONTROL_MODE] == CONTROL_MODE_MSC: log.debug(f"Speicher{battery_index}:Keine Steuerung gefordert, bereits deaktiviert.") else: @@ -137,18 +138,20 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self._write_registers(values_to_write, unit) log.debug(f"Speicher{battery_index}:Keine Steuerung gefordert, Steuerung deaktiviert.") - elif power_limit <= 0: # Limit Discharge Mode should be used. + elif setpoint.power_limit <= 0: # Limit Discharge Mode should be used. if (values[Registers.STORAGE_CONTROL_MODE] == CONTROL_MODE_REMOTE and values[Registers.REMOTE_CONTROL_COMMAND_MODE] == REMOTE_CONTROL_COMMAND_MODE_MSC): # Remote Control and Discharge Mode already active. discharge_limit = int(values[Registers.REMOTE_CONTROL_DISCHARGE_LIMIT]) - if discharge_limit not in range(int(abs(power_limit)) - 10, int(abs(power_limit)) + 10): + if discharge_limit not in range(int(abs(setpoint.power_limit)) - 10, + int(abs(setpoint.power_limit)) + 10): # Send Limit only if difference is more than 10W, needed with more than 1 battery. values_to_write = { - Registers.REMOTE_CONTROL_DISCHARGE_LIMIT: int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT)) + Registers.REMOTE_CONTROL_DISCHARGE_LIMIT: int(min(abs(setpoint.power_limit), + MAX_CHARGEDISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) - log.debug(f"Entlade-Limit Speicher{battery_index}: {int(abs(power_limit))}W.") + log.debug(f"Entlade-Limit Speicher{battery_index}: {int(abs(setpoint.power_limit))}W.") else: log.debug(f"Entlade-Limit Speicher{battery_index}: Abweichung unter +/- 10W.") else: # Enable Remote Control and Discharge Mode. @@ -156,23 +159,25 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: Registers.STORAGE_CONTROL_MODE: CONTROL_MODE_REMOTE, Registers.REMOTE_CONTROL_COMMAND_MODE_DEFAULT_REG: REMOTE_CONTROL_COMMAND_MODE_MSC, Registers.REMOTE_CONTROL_COMMAND_MODE: REMOTE_CONTROL_COMMAND_MODE_MSC, - Registers.REMOTE_CONTROL_DISCHARGE_LIMIT: int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT)) + Registers.REMOTE_CONTROL_DISCHARGE_LIMIT: int(min(abs(setpoint.power_limit), + MAX_CHARGEDISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) - log.debug(f"Entlade-Limit aktiviert, Speicher{battery_index}: {int(abs(power_limit))}W.") + log.debug(f"Entlade-Limit aktiviert, Speicher{battery_index}: {int(abs(setpoint.power_limit))}W.") - elif power_limit > 0: # Charge Mode should be used + elif setpoint.power_limit > 0: # Charge Mode should be used if (values[Registers.STORAGE_CONTROL_MODE] == CONTROL_MODE_REMOTE and values[Registers.REMOTE_CONTROL_COMMAND_MODE] == REMOTE_CONTROL_COMMAND_MODE_CHARGE): # Remote Control and Charge Mode already active. charge_limit = int(values[Registers.REMOTE_CONTROL_CHARGE_LIMIT]) - if charge_limit not in range(int(abs(power_limit)) - 10, int(abs(power_limit)) + 10): + if charge_limit not in range(int(abs(setpoint.power_limit)) - 10, int(abs(setpoint.power_limit)) + 10): # Send Limit only if difference is more than 10W. values_to_write = { - Registers.REMOTE_CONTROL_CHARGE_LIMIT: int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT)) + Registers.REMOTE_CONTROL_CHARGE_LIMIT: int(min(abs(setpoint.power_limit), + MAX_CHARGEDISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) - log.debug(f"Ladung Speicher{battery_index}: {int(abs(power_limit))}W.") + log.debug(f"Ladung Speicher{battery_index}: {int(abs(setpoint.power_limit))}W.") else: log.debug(f"Ladung Speicher{battery_index}: Abweichung unter +/- 10W.") else: # Enable Remote Control and Charge Mode. @@ -180,10 +185,11 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: Registers.STORAGE_CONTROL_MODE: CONTROL_MODE_REMOTE, Registers.REMOTE_CONTROL_COMMAND_MODE_DEFAULT_REG: REMOTE_CONTROL_COMMAND_MODE_CHARGE, Registers.REMOTE_CONTROL_COMMAND_MODE: REMOTE_CONTROL_COMMAND_MODE_CHARGE, - Registers.REMOTE_CONTROL_CHARGE_LIMIT: int(min(abs(power_limit), MAX_CHARGEDISCHARGE_LIMIT)) + Registers.REMOTE_CONTROL_CHARGE_LIMIT: int(min(abs(setpoint.power_limit), + MAX_CHARGEDISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) - log.debug(f"Aktivierung Ladung Speicher{battery_index}: {int(abs(power_limit))}W.") + log.debug(f"Aktivierung Ladung Speicher{battery_index}: {int(abs(setpoint.power_limit))}W.") def _write_registers(self, values_to_write: Dict[Registers, Union[int, float]], unit: int) -> None: for address, value in values_to_write.items(): diff --git a/packages/modules/devices/solax/solax/bat.py b/packages/modules/devices/solax/solax/bat.py index 57540a1584..94b9f30839 100644 --- a/packages/modules/devices/solax/solax/bat.py +++ b/packages/modules/devices/solax/solax/bat.py @@ -17,6 +17,7 @@ from modules.devices.solax.solax.version import SolaxVersion from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -64,15 +65,15 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: if self.power_limit_controllable() is False: log.debug("SolaX set_power_limit: aktive Speichersteuerung für diese Version nicht unterstützt") return unit = self.device_config.configuration.modbus_id - log.debug(f"SolaX set_power_limit: power_limit={power_limit}, last_mode={self.last_mode}") + log.debug(f"SolaX set_power_limit: power_limit={setpoint.power_limit}, last_mode={self.last_mode}") - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: with self.__tcp_client: @@ -85,14 +86,14 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.last_mode = None return - if power_limit < 0: + if setpoint.power_limit < 0: self.last_mode = 'discharge' - elif power_limit > 0: + elif setpoint.power_limit > 0: self.last_mode = 'charge' else: self.last_mode = 'stop' - push_power = self._get_mode4_push_power(int(power_limit)) + push_power = self._get_mode4_push_power(int(setpoint.power_limit)) self._write_mode4(push_power, unit) def _get_mode4_push_power(self, power_limit: int) -> int: diff --git a/packages/modules/devices/solis/solis/bat.py b/packages/modules/devices/solis/solis/bat.py index f13c5e0991..bc491fdb9e 100644 --- a/packages/modules/devices/solis/solis/bat.py +++ b/packages/modules/devices/solis/solis/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -13,6 +13,7 @@ from modules.devices.solis.solis.version import SolisVersion from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -60,24 +61,24 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.component_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: self.client.write_register(43135, 0, data_type=ModbusDataType.UINT_16, unit=unit) log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") - elif power_limit == 0: + elif setpoint.power_limit == 0: self.client.write_register(43135, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.client.write_register(43136, 0, data_type=ModbusDataType.UINT_16, unit=unit) log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht geladen/entladen") - elif power_limit < 0: + elif setpoint.power_limit < 0: self.client.write_register(43135, 2, data_type=ModbusDataType.UINT_16, unit=unit) - power_value = int(abs(power_limit) / 10) + power_value = int(abs(setpoint.power_limit) / 10) self.client.write_register(43129, power_value, data_type=ModbusDataType.UINT_16, unit=unit) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") - elif power_limit > 0: + elif setpoint.power_limit > 0: self.client.write_register(43135, 1, data_type=ModbusDataType.UINT_16, unit=unit) - power_value = int(power_limit / 10) + power_value = int(setpoint.power_limit / 10) self.client.write_register(43136, power_value, data_type=ModbusDataType.UINT_16, unit=unit) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen") diff --git a/packages/modules/devices/sonnen/sonnenbatterie/bat.py b/packages/modules/devices/sonnen/sonnenbatterie/bat.py index 84853bc04e..2171e1f8d3 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/bat.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/bat.py @@ -12,6 +12,7 @@ from modules.devices.sonnen.sonnenbatterie.config import SonnenbatterieBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -53,14 +54,14 @@ def initialize(self) -> None: def update(self) -> None: self.store.set(self.api.update_battery(sim_counter=self.sim_counter, peak_filter=self.peak_filter)) - def set_power_limit(self, power_limit: Optional[int]) -> None: - if power_limit is None: + def set_power_limit(self, setpoint: SetPoint) -> None: + if setpoint.power_limit is None: # Wert wird nur einmal gesetzt if self.last_mode is not None: - self.api.set_power_limit(power_limit=power_limit) + self.api.set_power_limit(power_limit=setpoint.power_limit) self.last_mode = None else: - self.api.set_power_limit(power_limit=power_limit) + self.api.set_power_limit(power_limit=setpoint.power_limit) self.last_mode = 'active' def power_limit_controllable(self) -> bool: diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py index 17811a622e..844e338597 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/bat.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Any, Optional, TypedDict +from typing import Any, TypedDict from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -12,6 +12,7 @@ from modules.devices.sungrow.sungrow_ihm.config import SungrowIHMBatSetup, SungrowIHM from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -52,40 +53,42 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.device_config.configuration.modbus_id log.debug(f'last_mode: {self.last_mode}') - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: self.__tcp_client.write_register(8023, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(8024, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(8024, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' - elif power_limit < 0: - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") + elif setpoint.power_limit < 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W " + "entladen für den Hausverbrauch") if self.last_mode != 'discharge': self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(8024, 0xBB, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' - power_value = int(power_limit / 100) - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") + power_value = int(setpoint.power_limit / 100) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W " + "entladen für den Hausverbrauch") self.__tcp_client.write_register(8025, power_value, data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) - elif power_limit > 0: - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + elif setpoint.power_limit > 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W geladen") if self.last_mode != 'charge': self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(8025, 0xAA, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' - power_value = int(power_limit / 100) - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + power_value = int(setpoint.power_limit / 100) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W geladen") self.__tcp_client.write_register(8025, power_value, data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) diff --git a/packages/modules/devices/sungrow/sungrow_sh/bat.py b/packages/modules/devices/sungrow/sungrow_sh/bat.py index cb2f64d3f5..1593278c72 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/bat.py +++ b/packages/modules/devices/sungrow/sungrow_sh/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Any, Optional, TypedDict +from typing import Any, TypedDict from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -13,6 +13,7 @@ from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType from modules.common.store import get_component_value_store +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -118,40 +119,41 @@ def update(self) -> None: ) self.store.set(bat_state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.device_config.configuration.modbus_id log.debug(f'last_mode: {self.last_mode}') - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: self.__tcp_client.write_register(13049, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(13050, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(13050, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' - elif power_limit < 0: - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") + elif setpoint.power_limit < 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W " + "entladen für den Hausverbrauch") if self.last_mode != 'discharge': self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(13050, 0xBB, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. - power_value = int(min(abs(power_limit), 5000)) + power_value = int(min(abs(setpoint.power_limit), 5000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") self.__tcp_client.write_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) - elif power_limit > 0: - log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + elif setpoint.power_limit > 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {setpoint.power_limit} W geladen") if self.last_mode != 'charge': self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(13050, 0xAA, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. - power_value = int(min(power_limit, 5000)) + power_value = int(min(setpoint.power_limit, 5000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen") self.__tcp_client.write_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) diff --git a/packages/modules/devices/varta/varta/bat_modbus.py b/packages/modules/devices/varta/varta/bat_modbus.py index b9484b341f..138223a217 100644 --- a/packages/modules/devices/varta/varta/bat_modbus.py +++ b/packages/modules/devices/varta/varta/bat_modbus.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import TypedDict, Any, Optional +from typing import TypedDict, Any import logging import struct @@ -13,6 +13,7 @@ from modules.devices.varta.varta.config import VartaBatModbusSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -54,11 +55,11 @@ def set_state(self, state: BatState) -> None: state.imported, state.exported = self.sim_counter.sim_count(state.power) self.store.set(state) - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: unit = self.__modbus_id log.debug(f'last_mode: {self.last_mode}') - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: # hier muss die maximale Entladeleistung des Systems einmalig gesetzt werden @@ -68,21 +69,21 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: uint16_value = struct.unpack(">H", struct.pack(">h", max_discharge_w))[0] self.client.write_register(1074, uint16_value, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit < 0: + elif setpoint.power_limit < 0: # Das Register muss kontinuierlich geschrieben werden, da der Speicher # sonst nach 120s die Steuerung aufhebt. - log.debug(f"Aktive Batteriesteuerung. Batterie darf mit {power_limit} W entladen werden " + log.debug(f"Aktive Batteriesteuerung. Batterie darf mit {setpoint.power_limit} W entladen werden " "für den Hausverbrauch") - uint16_value = struct.unpack(">H", struct.pack(">h", power_limit))[0] + uint16_value = struct.unpack(">H", struct.pack(">h", setpoint.power_limit))[0] self.client.write_register(1074, uint16_value, data_type=ModbusDataType.INT_16, unit=unit) self.last_mode = 'discharge' else: # Das Register muss kontinuierlich geschrieben werden, da der Speicher # sonst nach 120s die Steuerung aufhebt. - if power_limit == 0: + if setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung, Speicher wird auf Stop gesetzt.") else: - log.debug(f"Aktive Batteriesteuerung, übergebene Leistung: {power_limit}W. " + log.debug(f"Aktive Batteriesteuerung, übergebene Leistung: {setpoint.power_limit}W. " "Aktive Ladung nicht möglich. Speicher wird auf Stop gesetzt.") self.client.write_register(1074, 0, data_type=ModbusDataType.INT_16, unit=unit) diff --git a/packages/modules/devices/victron/victron/bat.py b/packages/modules/devices/victron/victron/bat.py index 6671ba978c..7680cc3121 100644 --- a/packages/modules/devices/victron/victron/bat.py +++ b/packages/modules/devices/victron/victron/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Any, Optional, TypedDict +from typing import Any, TypedDict from modules.common import modbus from modules.common.abstract_device import AbstractBat @@ -14,6 +14,7 @@ from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType from control import data +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -54,38 +55,38 @@ def update(self) -> None: self.store.set(bat_state) self.current_power = power - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: modbus_id = self.component_config.configuration.modbus_id # Wenn Victron Dynamic ESS aktiv, erfolgt keine weitere Regelung in openWB dynamic_ess_mode = self.__tcp_client.read_holding_registers(5400, ModbusDataType.UINT_16, unit=modbus_id) if dynamic_ess_mode == 1: log.debug("Dynamic ESS Mode ist aktiv, daher erfolgt keine Regelung des Speichers durch openWB") return - - if power_limit is None: + evu_power = data.data.counter_all_data.get_evu_counter().data.get.power + bat_power = self.__tcp_client.read_holding_registers(842, ModbusDataType.INT_16, unit=modbus_id) + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: # ESS Mode 2 und Leistung EVU auf 0kW setzen für Selbstregelung - self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) + self.__tcp_client.write_register(2902, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2716, 0, data_type=ModbusDataType.INT_32, unit=modbus_id) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': # ESS Mode 2 und Discharge Power 0% für externe Steuerung und keine Entladung # Leistung an EVU-Punkt auf 0kW setzen -> Eigenregelung bei laden und Entladen verhindern self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) - self.__tcp_client.write_register(2716, 0, data_type=ModbusDataType.INT_32, unit=modbus_id) self.last_mode = 'stop' - elif power_limit < 0: - evu_power = data.data.counter_all_data.get_evu_counter().data.get.power - set_power = (power_limit - self.current_power) + evu_power + self.__tcp_client.write_register(2716, 0, data_type=ModbusDataType.INT_32, unit=modbus_id) + elif setpoint.power_limit < 0: + grid_setpoint = int(evu_power + setpoint.total_limit - bat_power) log.debug(f"Aktive Batteriesteuerung Victron:" - f"Speicher soll mit {power_limit} W entladen werden. \n" - f"Aktuelle Speicherleistung: {self.current_power} W, EVU-Leistung: {evu_power} W " - f"EVU-Leistung um {power_limit - self.current_power} W anpassen auf {set_power} W") + f"Speicher soll mit {setpoint.power_limit} W entladen werden. \n" + f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {evu_power} W " + f"EVU-Leistung um {setpoint.power_limit - bat_power} W anpassen auf {grid_setpoint} W") if self.last_mode != 'discharge': self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) @@ -93,21 +94,19 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: # Setzen der angestrebten EVU-Leistung, Speicher versucht seine Leistung # anzupassen um den Zielwert zu erreichen - self.__tcp_client.write_register( - 2716, set_power, data_type=ModbusDataType.INT_32, unit=modbus_id) - elif power_limit > 0: - evu_power = data.data.counter_all_data.get_evu_counter().data.get.power - set_power = (power_limit - self.current_power) + evu_power + self.__tcp_client.write_register(2716, grid_setpoint, data_type=ModbusDataType.INT_32, unit=modbus_id) + elif setpoint.power_limit > 0: + grid_setpoint = int(evu_power + setpoint.total_limit - bat_power) log.debug(f"Aktive Batteriesteuerung Victron:" - f"Speicher soll mit {power_limit} W geladen werden. \n" - f"Aktuelle Speicherleistung: {self.current_power} W, EVU-Leistung: {evu_power} W " - f"EVU-Leistung um {power_limit - self.current_power} W anpassen auf {set_power} W") + f"Speicher soll mit {bat_power} W geladen werden. \n" + f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {evu_power} W " + f"EVU-Leistung um {setpoint.power_limit - bat_power} W anpassen auf {grid_setpoint} W") if self.last_mode != 'charge': self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.last_mode = 'charge' self.__tcp_client.write_register( - 2716, set_power, data_type=ModbusDataType.INT_32, unit=modbus_id) + 2716, grid_setpoint, data_type=ModbusDataType.INT_32, unit=modbus_id) def power_limit_controllable(self) -> bool: return True From 8653177a52562ab4952a98980025483f2e905469 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Aug 2026 08:55:38 +0200 Subject: [PATCH 2/5] fix typo --- .../helpermodules/mosquitto_dynsec/missing_role_topics_test.py | 1 + packages/helpermodules/update_config.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py b/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py index b06822ab14..cde9e4a48c 100644 --- a/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py +++ b/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py @@ -20,6 +20,7 @@ def _get_data_path() -> Path: INTERNAL_TOPICS = ['openWB/bat/set/charging_power_left', 'openWB/bat/set/regulate_up', 'openWB/bat/+/set/power_limit', + 'openWB/bat/+/set/total_limit', 'openWB/command/max_id/autolock_plan', 'openWB/command/max_id/charge_template', 'openWB/command/max_id/charge_template_scheduled_plan', diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 7bc144dee5..536f5104e0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -97,7 +97,7 @@ class UpdateConfig: "^openWB/bat/[0-9]+/get/fault_state$", "^openWB/bat/[0-9]+/get/fault_str$", "^openWB/bat/[0-9]+/get/power_limit_controllable$", - "^openWB/bat/[0-9]+/set/power_limtit$", + "^openWB/bat/[0-9]+/set/power_limit$", "^openWB/bat/[0-9]+/set/total_limit$", "^openWB/chargepoint/get/power$", From 4ab4bcdf97b09af5324b96d5c9b62ddc64e65298 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Aug 2026 11:35:33 +0200 Subject: [PATCH 3/5] fix --- packages/control/bat.py | 2 +- packages/control/bat_all.py | 2 +- .../missing_role_topics_test.py | 2 +- packages/helpermodules/setdata.py | 2 +- packages/helpermodules/update_config.py | 2 +- .../modules/devices/victron/victron/bat.py | 19 ++++++++----------- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/control/bat.py b/packages/control/bat.py index 5331a0a356..8e6458c8b0 100644 --- a/packages/control/bat.py +++ b/packages/control/bat.py @@ -42,7 +42,7 @@ def get_factory() -> Get: @dataclass class Set: power_limit: Optional[int] = field(default=None, metadata={"topic": "set/power_limit"}) - total_limit: Optional[int] = field(default=None, metadata={"topic": "set/total_limit"}) + evu_power: Optional[int] = field(default=None, metadata={"topic": "set/evu_power"}) def set_factory() -> Set: diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 6c30fd551c..3772728292 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -298,7 +298,7 @@ def _set_bat_power_active_control(self, power): f"Speicher (ID: {bat_component.component_config.id}) " f"laden mit {power_limit} ({factor} x {bat_component_data.get.max_charge_power}) W")) bat_component_data.set.power_limit = power_limit - bat_component_data.set.total_limit = power + bat_component_data.set.evu_power = data.data.counter_all_data.get_evu_counter().data.get.power def setup_bat(self): """ prüft, ob mind ein Speicher vorhanden ist und berechnet die Summen-Topics. diff --git a/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py b/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py index cde9e4a48c..f823668794 100644 --- a/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py +++ b/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py @@ -20,7 +20,7 @@ def _get_data_path() -> Path: INTERNAL_TOPICS = ['openWB/bat/set/charging_power_left', 'openWB/bat/set/regulate_up', 'openWB/bat/+/set/power_limit', - 'openWB/bat/+/set/total_limit', + 'openWB/bat/+/set/evu_power', 'openWB/command/max_id/autolock_plan', 'openWB/command/max_id/charge_template', 'openWB/command/max_id/charge_template_scheduled_plan', diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 9722b66efc..2d7c2339ee 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -734,7 +734,7 @@ def process_bat_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, bool) elif "/set/power_limit" in msg.topic: self._validate_value(msg, float) - elif "/set/total_limit" in msg.topic: + elif "/set/evu_power" in msg.topic: self._validate_value(msg, float) else: self.__unknown_topic(msg) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 536f5104e0..f0fa9ce058 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -98,7 +98,7 @@ class UpdateConfig: "^openWB/bat/[0-9]+/get/fault_str$", "^openWB/bat/[0-9]+/get/power_limit_controllable$", "^openWB/bat/[0-9]+/set/power_limit$", - "^openWB/bat/[0-9]+/set/total_limit$", + "^openWB/bat/[0-9]+/set/evu_power$", "^openWB/chargepoint/get/power$", "^openWB/chargepoint/get/exported$", diff --git a/packages/modules/devices/victron/victron/bat.py b/packages/modules/devices/victron/victron/bat.py index 7680cc3121..3acb3b15d6 100644 --- a/packages/modules/devices/victron/victron/bat.py +++ b/packages/modules/devices/victron/victron/bat.py @@ -62,7 +62,6 @@ def set_power_limit(self, setpoint: SetPoint) -> None: if dynamic_ess_mode == 1: log.debug("Dynamic ESS Mode ist aktiv, daher erfolgt keine Regelung des Speichers durch openWB") return - evu_power = data.data.counter_all_data.get_evu_counter().data.get.power bat_power = self.__tcp_client.read_holding_registers(842, ModbusDataType.INT_16, unit=modbus_id) if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") @@ -82,31 +81,29 @@ def set_power_limit(self, setpoint: SetPoint) -> None: self.last_mode = 'stop' self.__tcp_client.write_register(2716, 0, data_type=ModbusDataType.INT_32, unit=modbus_id) elif setpoint.power_limit < 0: - grid_setpoint = int(evu_power + setpoint.total_limit - bat_power) + grid_setpoint = int(setpoint.evu_power + setpoint.power_limit - bat_power) log.debug(f"Aktive Batteriesteuerung Victron:" f"Speicher soll mit {setpoint.power_limit} W entladen werden. \n" - f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {evu_power} W " + f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {setpoint.evu_power} W " f"EVU-Leistung um {setpoint.power_limit - bat_power} W anpassen auf {grid_setpoint} W") if self.last_mode != 'discharge': - self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) + self.__tcp_client.write_register(2902, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.last_mode = 'discharge' - # Setzen der angestrebten EVU-Leistung, Speicher versucht seine Leistung # anzupassen um den Zielwert zu erreichen self.__tcp_client.write_register(2716, grid_setpoint, data_type=ModbusDataType.INT_32, unit=modbus_id) elif setpoint.power_limit > 0: - grid_setpoint = int(evu_power + setpoint.total_limit - bat_power) + grid_setpoint = int(setpoint.evu_power + setpoint.power_limit - bat_power) log.debug(f"Aktive Batteriesteuerung Victron:" - f"Speicher soll mit {bat_power} W geladen werden. \n" - f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {evu_power} W " + f"Speicher soll mit {setpoint.power_limit} W geladen werden. \n" + f"Aktuelle Speicherleistung: {bat_power} W, EVU-Leistung: {setpoint.evu_power} W " f"EVU-Leistung um {setpoint.power_limit - bat_power} W anpassen auf {grid_setpoint} W") if self.last_mode != 'charge': - self.__tcp_client.write_register(2902, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) + self.__tcp_client.write_register(2902, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.last_mode = 'charge' - self.__tcp_client.write_register( - 2716, grid_setpoint, data_type=ModbusDataType.INT_32, unit=modbus_id) + self.__tcp_client.write_register(2716, grid_setpoint, data_type=ModbusDataType.INT_32, unit=modbus_id) def power_limit_controllable(self) -> bool: return True From afa90ec8a43e9bd63bd3f09bda439e28b2b61edb Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Aug 2026 11:36:03 +0200 Subject: [PATCH 4/5] remove unused import --- packages/modules/devices/victron/victron/bat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/victron/victron/bat.py b/packages/modules/devices/victron/victron/bat.py index 3acb3b15d6..bb2bc2e2bc 100644 --- a/packages/modules/devices/victron/victron/bat.py +++ b/packages/modules/devices/victron/victron/bat.py @@ -13,7 +13,6 @@ from modules.devices.victron.victron.config import VictronBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType -from control import data from control.bat import Set as SetPoint log = logging.getLogger(__name__) From ddf11b990d48ea29f98522df17a06f9d2549f5e2 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Aug 2026 13:46:05 +0200 Subject: [PATCH 5/5] update comments --- packages/modules/devices/victron/victron/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/victron/victron/bat.py b/packages/modules/devices/victron/victron/bat.py index bb2bc2e2bc..0c3d9e6ede 100644 --- a/packages/modules/devices/victron/victron/bat.py +++ b/packages/modules/devices/victron/victron/bat.py @@ -65,7 +65,7 @@ def set_power_limit(self, setpoint: SetPoint) -> None: if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: - # ESS Mode 2 und Leistung EVU auf 0kW setzen für Selbstregelung + # ESS Mode 1 und grid setpoint auf 0 setzen. Erlaubte Discharge Power 100% self.__tcp_client.write_register(2902, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2702, 100, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(2716, 0, data_type=ModbusDataType.INT_32, unit=modbus_id)