diff --git a/docs/samples/sample_modbus/sample_modbus/bat.py b/docs/samples/sample_modbus/sample_modbus/bat.py index 3a2fd9370b..68571314b9 100644 --- a/docs/samples/sample_modbus/sample_modbus/bat.py +++ b/docs/samples/sample_modbus/sample_modbus/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from enum import IntEnum -from typing import Optional, TypedDict, Any +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.sample_modbus.sample_modbus.config import SampleBatSetup 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): @@ -79,11 +80,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: # 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. - self.client.write_register(reg, power_limit) + self.client.write_register(reg, setpoint.power_limit) # Wenn der Speicher keine Steuerung der Ladeleistung unterstützt pass diff --git a/docs/samples/sample_request_by_component/sample_request_by_component/bat.py b/docs/samples/sample_request_by_component/sample_request_by_component/bat.py index d226cff211..87205a655e 100644 --- a/docs/samples/sample_request_by_component/sample_request_by_component/bat.py +++ b/docs/samples/sample_request_by_component/sample_request_by_component/bat.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import Optional, TypedDict, Any +from typing import TypedDict, Any from modules.common import req from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -10,6 +10,7 @@ from modules.devices.sample_request_by_component.sample_request_by_component.config import SampleBatSetup 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): @@ -45,11 +46,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: # 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. - self.client.write_register(reg, power_limit) + self.client.write_register(reg, setpoint.power_limit) # Wenn der Speicher keine Steuerung der Ladeleistung unterstützt pass diff --git a/docs/samples/sample_request_by_device/sample_request_by_device/bat.py b/docs/samples/sample_request_by_device/sample_request_by_device/bat.py index 9251c9cf63..c13e082063 100644 --- a/docs/samples/sample_request_by_device/sample_request_by_device/bat.py +++ b/docs/samples/sample_request_by_device/sample_request_by_device/bat.py @@ -9,6 +9,7 @@ from modules.devices.sample_request_by_device.sample_request_by_device.config import SampleBatSetup 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): @@ -42,11 +43,11 @@ def update(self, response) -> 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. - self.client.write_register(reg, power_limit) + self.client.write_register(reg, setpoint.power_limit) # Wenn der Speicher keine Steuerung der Ladeleistung unterstützt pass diff --git a/packages/control/bat.py b/packages/control/bat.py index f3ae1e7d9f..8e6458c8b0 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"}) + 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 af3cab264e..bd878b13ab 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -295,7 +295,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.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/control/process.py b/packages/control/process.py index edacc92f49..dc99f545f2 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -24,6 +24,7 @@ from modules.io_actions.controllable_consumers.dimming_direct_control.api import DimmingDirectControl from modules.io_actions.generator_systems.stepwise_control.api_eebus import StepwiseControlEebus from modules.io_actions.generator_systems.stepwise_control.api_io import StepwiseControlIo +from dataclasses import replace log = logging.getLogger(__name__) control_command_log = logging.getLogger("steuve_control_command") @@ -71,11 +72,14 @@ def process_algorithm_results(self) -> None: log.exception("Fehler im Process-Modul für Ladepunkt "+str(cp)) if data.data.bat_all_data.data.set.set_limit: for bat_component in get_bat_components_by_controllability()[0]: + setpoint = replace( + data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set + ) modules_threads.append( Thread( target=set_power_limit_wrapper, args=(bat_component, - data.data.bat_data[f"bat{bat_component.component_config.id}"].data.set.power_limit), + setpoint), name=f"set power limit {bat_component.component_config.id}")) for consumer in data.data.consumer_data.values(): try: diff --git a/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py b/packages/helpermodules/mosquitto_dynsec/missing_role_topics_test.py index 6b1b3b3c79..e5a1a35a27 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/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 439ea6ca81..153fcdd78d 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -743,6 +743,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/evu_power" 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 7bf8f3c703..74cf8b37aa 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -105,6 +105,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/evu_power$", "^openWB/chargepoint/get/power$", "^openWB/chargepoint/get/exported$", diff --git a/packages/modules/common/abstract_device.py b/packages/modules/common/abstract_device.py index 562e93cc20..920cba295a 100644 --- a/packages/modules/common/abstract_device.py +++ b/packages/modules/common/abstract_device.py @@ -1,5 +1,11 @@ +from __future__ import annotations from abc import abstractmethod -from typing import Optional, Type +from typing import Optional, Type, Protocol + + +class SetPoint(Protocol): + power_limit: Optional[int] + evu_power: Optional[int] class AbstractDevice: @@ -31,7 +37,7 @@ def update(self, *kwargs) -> None: pass @abstractmethod - def set_power_limit(self, power_limit: Optional[int]) -> None: + def set_power_limit(self, setpoint: SetPoint) -> None: # power limit None heißt, auf maximale Speicherleistung setzen = Speicher-Begrenzung aufheben pass diff --git a/packages/modules/common/configurable_device.py b/packages/modules/common/configurable_device.py index 97ceb6e70f..e31366f77c 100644 --- a/packages/modules/common/configurable_device.py +++ b/packages/modules/common/configurable_device.py @@ -1,6 +1,6 @@ import inspect import logging -from typing import Optional, TypeVar, Generic, Dict, Any, Callable, Iterable, List +from typing import TypeVar, Generic, Dict, Any, Callable, Iterable, List from dataclass_utils import dataclass_from_dict from helpermodules import timecheck @@ -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: 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/fox_ess/fox_ess_h3_smart/bat.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/bat.py index 302c8bbb5a..02162f7590 100644 --- a/packages/modules/devices/fox_ess/fox_ess_h3_smart/bat.py +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/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 @@ -12,6 +12,7 @@ from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3SmartBatSetup 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__) @@ -50,17 +51,17 @@ 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: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: # remote control disabled, mode self use (1) self.__tcp_client.write_register(46001, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(49203, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: # remote control enabled, watchdog 60s, active power 0W, mode FeedIn Priority (2) log.debug("Aktive Batteriesteuerung. FoxEss H3 Smart wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': @@ -69,26 +70,26 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.__tcp_client.write_register(49203, 2, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' self.__tcp_client.write_register(46003, 0, data_type=ModbusDataType.UINT_16, unit=unit) - elif power_limit < 0: + elif setpoint.power_limit < 0: # remote control enabled, watchdog 60s, active power 0W, mode Backup (3) log.debug(f"Aktive Batteriesteuerung FoxEss H3 Smart:" - f"Speicher soll mit {power_limit} W entladen werden") + f"Speicher soll mit {setpoint.power_limit} W entladen werden") if self.last_mode != 'discharge': self.__tcp_client.write_register(46001, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(46002, 60, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(49203, 3, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' - self.__tcp_client.write_register(46003, -power_limit, data_type=ModbusDataType.UINT_16, unit=unit) - elif power_limit > 0: + self.__tcp_client.write_register(46003, -setpoint.power_limit, data_type=ModbusDataType.UINT_16, unit=unit) + elif setpoint.power_limit > 0: # remote control enabled, watchdog 60s, active power 0W, mode Backup (3) log.debug(f"Aktive Batteriesteuerung FoxEss H3 Smart:" - f"Speicher soll mit {power_limit} W geladen werden") + f"Speicher soll mit {setpoint.power_limit} W geladen werden") if self.last_mode != 'charge': self.__tcp_client.write_register(46001, 1, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(46002, 60, data_type=ModbusDataType.UINT_16, unit=unit) self.__tcp_client.write_register(49203, 3, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' - self.__tcp_client.write_register(46003, -power_limit, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(46003, -setpoint.power_limit, data_type=ModbusDataType.UINT_16, unit=unit) def power_limit_controllable(self) -> bool: return True 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/huawei/huawei_smartlogger/bat.py b/packages/modules/devices/huawei/huawei_smartlogger/bat.py index 29cafc17f8..bba4984fa0 100644 --- a/packages/modules/devices/huawei/huawei_smartlogger/bat.py +++ b/packages/modules/devices/huawei/huawei_smartlogger/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 import modbus from modules.common.abstract_device import AbstractBat @@ -13,6 +13,7 @@ from modules.devices.huawei.huawei_smartlogger.config import Huawei_SmartloggerBatSetup 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__) @@ -50,14 +51,14 @@ 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: modbus_id = self.component_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Speicher") if self.last_mode is not None: self.__tcp_client.write_register(47100, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("Aktive Batteriesteuerung Huawei Smartlogger. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': self.last_mode = 'stop' @@ -66,26 +67,28 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.__tcp_client.write_register(47246, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47083, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47249, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) - elif power_limit < 0: + elif setpoint.power_limit < 0: log.debug(f"Aktive Batteriesteuerung Huawei Smartlogger:" - f"Speicher soll mit {power_limit} W entladen werden.") + f"Speicher soll mit {setpoint.power_limit} W entladen werden.") if self.last_mode != 'discharge': self.last_mode = 'discharge' # discharge self.__tcp_client.write_register(47100, 2, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47246, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47083, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) - self.__tcp_client.write_register(47249, -power_limit, data_type=ModbusDataType.UINT_16, unit=modbus_id) - elif power_limit > 0: + self.__tcp_client.write_register(47249, -setpoint.power_limit, + data_type=ModbusDataType.UINT_16, unit=modbus_id) + elif setpoint.power_limit > 0: log.debug(f"Aktive Batteriesteuerung Huawei Smartlogger:" - f"Speicher soll mit {power_limit} W geladen werden.") + f"Speicher soll mit {setpoint.power_limit} W geladen werden.") if self.last_mode != 'charge': self.last_mode = 'charge' # charge self.__tcp_client.write_register(47100, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47246, 0, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47083, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) - self.__tcp_client.write_register(47247, power_limit, data_type=ModbusDataType.UINT_16, unit=modbus_id) + self.__tcp_client.write_register(47247, setpoint.power_limit, + data_type=ModbusDataType.UINT_16, unit=modbus_id) self.__tcp_client.write_register(47087, 1, data_type=ModbusDataType.UINT_16, unit=modbus_id) 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 83f9a6f62a..c18b89d5ca 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__) @@ -65,24 +66,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=Endian.Little, 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=Endian.Little, 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=Endian.Little, 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 c022117f21..d639cd74d2 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/mtec/mtec/bat.py b/packages/modules/devices/mtec/mtec/bat.py index 4f4354db63..9f1969089e 100644 --- a/packages/modules/devices/mtec/mtec/bat.py +++ b/packages/modules/devices/mtec/mtec/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 @@ -12,6 +12,7 @@ from modules.devices.mtec.mtec.config import MTecBatSetup 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__) @@ -56,21 +57,21 @@ 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: modbus_id = self.component_config.configuration.modbus_id - if power_limit is None: + if setpoint.power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Speicher") if self.last_mode is not None: self.__tcp_client.write_register(50000, 257, data_type=ModbusDataType.UINT_16, 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': self.__tcp_client.write_register(50000, 258, data_type=ModbusDataType.UINT_16, unit=modbus_id) self.last_mode = 'stop' - elif power_limit > 0: + elif setpoint.power_limit > 0: log.debug(f"Aktive Batteriesteuerung M-Tec:" - f"Speicher soll mit {power_limit} W geladen werden. " + f"Speicher soll mit {setpoint.power_limit} W geladen werden. " "kann aber nur mit maximaler Leistung laden") if self.last_mode != 'charge': self.__tcp_client.write_register(50000, 259, data_type=ModbusDataType.UINT_16, unit=modbus_id) 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/saxpower/saxpower/bat.py b/packages/modules/devices/saxpower/saxpower/bat.py index 132e490028..09fef92596 100644 --- a/packages/modules/devices/saxpower/saxpower/bat.py +++ b/packages/modules/devices/saxpower/saxpower/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 import modbus from modules.common.abstract_device import AbstractBat @@ -13,6 +13,7 @@ from modules.devices.saxpower.saxpower.config import SaxpowerBatSetup 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,28 +53,28 @@ 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, erlaubte Entladeleistung auf Maximalwert setzen max_power = 4600 log.debug("Saxpower: Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") if self.last_mode is not None: self.__tcp_client.write_register(43, max_power, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None - elif power_limit == 0: + elif setpoint.power_limit == 0: # Erlaubte Entladeleistung auf 0 setzen log.debug("Saxpower: Aktive Batteriesteuerung angestoßen. Setze Entladesperre.") if self.last_mode != 'stop': self.__tcp_client.write_register(43, 0, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' - elif power_limit < 0: + elif setpoint.power_limit < 0: # Erlaubte Entladeleistung auf power_limit setzen log.debug("Saxpower: Aktive Batteriesteuerung angestoßen. Erlaubte Entladeleistung " - f"auf {power_limit}W setzen.") + f"auf {setpoint.power_limit}W setzen.") if self.last_mode != 'stop': - self.__tcp_client.write_register(43, power_limit, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(43, setpoint.power_limit, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'stop' else: # Aktive Ladung 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/solarmax/solarmax/bat.py b/packages/modules/devices/solarmax/solarmax/bat.py index e643bfc587..d1fc3c7c86 100644 --- a/packages/modules/devices/solarmax/solarmax/bat.py +++ b/packages/modules/devices/solarmax/solarmax/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.solarmax.solarmax.config import SolarmaxBatSetup 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__) @@ -50,28 +51,29 @@ 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}') # reg 142 is automatically reset every 60s so needs to be written continuously - 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(142, 0, data_type=ModbusDataType.INT_16, unit=unit) self.last_mode = None - elif power_limit >= 0: + elif setpoint.power_limit >= 0: # Solarmax kann nicht aktiv laden log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") self.client.write_register(140, 0, data_type=ModbusDataType.INT_16, unit=unit) self.client.write_register(141, 0, data_type=ModbusDataType.INT_16, unit=unit) self.client.write_register(142, 1, data_type=ModbusDataType.INT_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") self.client.write_register(142, 1, data_type=ModbusDataType.INT_16, unit=unit) self.last_mode = 'discharge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. - power_value = int(min(abs(power_limit), 7000)) + power_value = int(min(abs(setpoint.power_limit), 7000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") self.client.write_register(140, power_value, data_type=ModbusDataType.INT_16, unit=unit) self.client.write_register(141, power_value, data_type=ModbusDataType.INT_16, unit=unit) 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/sunenergyxt/sunenergyxt/SunEnergyXT500_bat_test.py b/packages/modules/devices/sunenergyxt/sunenergyxt/SunEnergyXT500_bat_test.py index b0967122dc..bdf9356396 100644 --- a/packages/modules/devices/sunenergyxt/sunenergyxt/SunEnergyXT500_bat_test.py +++ b/packages/modules/devices/sunenergyxt/sunenergyxt/SunEnergyXT500_bat_test.py @@ -7,6 +7,7 @@ SunEnergyXTBatSetup, SunEnergyXTConfiguration, ) +from control.bat import Set # --------------------------------------------------------------------------- @@ -122,7 +123,7 @@ def test_none_sets_automatic_mode(self, requests_mock): bat = _make_bat() requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(None) + bat.set_power_limit(Set(power_limit=None)) assert requests_mock.last_request.json() == {"state": {"MM": 1, "GS": 0}} @@ -131,7 +132,7 @@ def test_zero_stops_discharge(self, requests_mock): bat = _make_bat() requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(0) + bat.set_power_limit(Set(power_limit=0)) assert requests_mock.last_request.json() == {"state": {"MM": 0, "GS": 0}} @@ -141,7 +142,7 @@ def test_negative_discharges(self, requests_mock): bat._gs_max = 800 requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(-500) + bat.set_power_limit(Set(power_limit=-500)) assert requests_mock.last_request.json() == {"state": {"MM": 0, "GS": 500}} @@ -151,7 +152,7 @@ def test_positive_charges(self, requests_mock): bat._gs_max = 800 requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(600) + bat.set_power_limit(Set(power_limit=600)) assert requests_mock.last_request.json() == {"state": {"MM": 0, "GS": -600}} @@ -161,7 +162,7 @@ def test_discharge_capped_at_gs_max(self, requests_mock): bat._gs_max = 800 requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(-9999) # Weit über Limit + bat.set_power_limit(Set(power_limit=-9999)) # Weit über Limit payload = requests_mock.last_request.json() assert payload["state"]["GS"] == 800 @@ -172,7 +173,7 @@ def test_charge_capped_at_gs_max(self, requests_mock): bat._gs_max = 2400 requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(9999) # Weit über Limit + bat.set_power_limit(Set(power_limit=9999)) # Weit über Limit payload = requests_mock.last_request.json() assert payload["state"]["GS"] == -2400 @@ -183,7 +184,7 @@ def test_gs_max_reflects_pro_model(self, requests_mock): bat._gs_max = 2400 # Wie nach update() mit IS=2400 requests_mock.post(f"{BASE_URL}/write", json={}) - bat.set_power_limit(-2400) + bat.set_power_limit(Set(power_limit=-2400)) payload = requests_mock.last_request.json() assert payload["state"]["GS"] == 2400 diff --git a/packages/modules/devices/sunenergyxt/sunenergyxt/bat.py b/packages/modules/devices/sunenergyxt/sunenergyxt/bat.py index c2dd8418ce..e88c05831b 100644 --- a/packages/modules/devices/sunenergyxt/sunenergyxt/bat.py +++ b/packages/modules/devices/sunenergyxt/sunenergyxt/bat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """SunEnergyXT 500 Series – openWB Batteriespeicher-Modul.""" import logging -from typing import Any, Optional +from typing import Any from modules.common import req from modules.common.abstract_device import AbstractBat @@ -12,6 +12,7 @@ from modules.common.store import get_component_value_store from modules.common.utils.peak_filter import PeakFilter from modules.devices.sunenergyxt.sunenergyxt.config import SunEnergyXT, SunEnergyXTBatSetup +from control.bat import Set as SetPoint log = logging.getLogger(__name__) @@ -73,19 +74,19 @@ def update(self) -> None: self.store.set(bat_state) log.debug("SunEnergyXT: SoC=%d%%,BP=%.0fW, IS=%dW (gs_max)", soc, power, self._gs_max) - 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: log.debug("SunEnergyXT: Automatik (MM=1, GS=0)") self._write(MM=1, GS=0) - elif power_limit == 0: + elif setpoint.power_limit == 0: log.debug("SunEnergyXT: Entladung gesperrt (MM=0, GS=0)") self._write(MM=0, GS=0) - elif power_limit < 0: - p = int(min(abs(power_limit), self._gs_max)) + elif setpoint.power_limit < 0: + p = int(min(abs(setpoint.power_limit), self._gs_max)) log.debug("SunEnergyXT: Entladen mit %dW (gs_max=%dW)", p, self._gs_max) self._write(MM=0, GS=p) else: - p = int(min(power_limit, self._gs_max)) + p = int(min(setpoint.power_limit, self._gs_max)) log.debug("SunEnergyXT: Laden mit %dW (gs_max=%dW)", p, self._gs_max) self._write(MM=0, GS=-p) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py index 17811a622e..f3b4043061 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,39 @@ 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) 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.__tcp_client.write_register(8024, 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) 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 ab3f3d0815..0af6604083 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__) @@ -117,40 +118,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..1f7039a64d 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 @@ -13,7 +13,7 @@ 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__) @@ -54,60 +54,56 @@ 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: + if setpoint.power_limit is None or (setpoint.power_limit != 0 and setpoint.evu_power 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) + # 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) 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: + bat_power = self.__tcp_client.read_holding_registers(842, ModbusDataType.INT_16, unit=modbus_id) + grid_setpoint = int(setpoint.evu_power + setpoint.power_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: {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, 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: + bat_power = self.__tcp_client.read_holding_registers(842, ModbusDataType.INT_16, unit=modbus_id) + grid_setpoint = int(setpoint.evu_power + setpoint.power_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 {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, set_power, 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