diff --git a/packages/modules/devices/solarmax/solarmax/bat.py b/packages/modules/devices/solarmax/solarmax/bat.py index c15135050e..97e91d9f79 100644 --- a/packages/modules/devices/solarmax/solarmax/bat.py +++ b/packages/modules/devices/solarmax/solarmax/bat.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -from typing import TypedDict, Any +import logging +from typing import TypedDict, Any, Optional +from pymodbus.constants import Endian from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -10,6 +12,8 @@ from modules.common.store import get_bat_value_store from modules.devices.solarmax.solarmax.config import SolarmaxBatSetup +log = logging.getLogger(__name__) + class KwargsDict(TypedDict): device_id: int @@ -30,8 +34,8 @@ def initialize(self) -> None: def update(self) -> None: unit = self.component_config.configuration.modbus_id - power = self.client.read_holding_registers(114, ModbusDataType.INT_32, unit=unit) - soc = self.client.read_holding_registers(122, ModbusDataType.INT_16, unit=unit) + power = self.client.read_input_registers(114, ModbusDataType.INT_32, unit=unit, wordorder=Endian.Little) + soc = self.client.read_input_registers(122, ModbusDataType.INT_16, unit=unit) imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( @@ -42,5 +46,33 @@ def update(self) -> None: ) self.store.set(bat_state) + def set_power_limit(self, power_limit: Optional[int]) -> 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: + log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") + if self.last_mode is not None: + self.__tcp_client.write_registers(142, [0], data_type=ModbusDataType.INT_16, unit=unit) + self.last_mode = None + elif power_limit == 0: + log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") + self.__tcp_client.write_registers(140, [0], data_type=ModbusDataType.INT_16, unit=unit) + self.__tcp_client.write_registers(141, [0], data_type=ModbusDataType.INT_16, unit=unit) + self.__tcp_client.write_registers(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") + self.__tcp_client.write_registers(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)) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") + self.__tcp_client.write_registers(140, [power_value], data_type=ModbusDataType.INT_16, unit=unit) + self.__tcp_client.write_registers(141, [power_value], data_type=ModbusDataType.INT_16, unit=unit) + + def power_limit_controllable(self) -> bool: + return self.component_config.configuration.power_limit_controllable + component_descriptor = ComponentDescriptor(configuration_factory=SolarmaxBatSetup) diff --git a/packages/modules/devices/solarmax/solarmax/config.py b/packages/modules/devices/solarmax/solarmax/config.py index 32596ef821..b0efd51e34 100644 --- a/packages/modules/devices/solarmax/solarmax/config.py +++ b/packages/modules/devices/solarmax/solarmax/config.py @@ -23,11 +23,26 @@ def __init__(self, self.configuration = configuration or SolarmaxConfiguration() -class SolarmaxBatConfiguration: +class SolarmaxMsCounterConfiguration: def __init__(self, modbus_id: int = 1): self.modbus_id = modbus_id +class SolarmaxMsCounterSetup(ComponentSetup[SolarmaxMsCounterConfiguration]): + def __init__(self, + name: str = "Solarmax MAX.STORAGE / MAX.STORAGE Ultimate Zähler", + type: str = "counter_maxstorage", + id: Optional[int] = 0, + configuration: SolarmaxMsCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolarmaxMsCounterConfiguration()) + + +class SolarmaxBatConfiguration: + def __init__(self, modbus_id: int = 1, power_limit_controllable: bool = False): + self.modbus_id = modbus_id + self.power_limit_controllable = power_limit_controllable + + class SolarmaxBatSetup(ComponentSetup[SolarmaxBatConfiguration]): def __init__(self, name: str = "Solarmax MAX.STORAGE / MAX.STORAGE Ultimate Speicher", @@ -37,6 +52,20 @@ def __init__(self, super().__init__(name, type, id, configuration or SolarmaxBatConfiguration()) +class SolarmaxMsInverterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +class SolarmaxMsInverterSetup(ComponentSetup[SolarmaxMsInverterConfiguration]): + def __init__(self, + name: str = "Solarmax MAX.STORAGE / MAX.STORAGE Ultimate Wechselrichter", + type: str = "inverter_maxstorage", + id: int = 0, + configuration: SolarmaxMsInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolarmaxMsInverterConfiguration()) + + class SolarmaxInverterConfiguration: def __init__(self, modbus_id: int = 1): self.modbus_id = modbus_id diff --git a/packages/modules/devices/solarmax/solarmax/counter_maxstorage.py b/packages/modules/devices/solarmax/solarmax/counter_maxstorage.py new file mode 100644 index 0000000000..8d524f4856 --- /dev/null +++ b/packages/modules/devices/solarmax/solarmax/counter_maxstorage.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from pymodbus.constants import Endian +from modules.common.abstract_device import AbstractCounter +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.solarmax.solarmax.config import SolarmaxMsCounterSetup + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class SolarmaxMsCounter(AbstractCounter): + def __init__(self, + component_config: SolarmaxMsCounterSetup, + **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + power = self.client.read_input_registers(118, ModbusDataType.INT_32, unit=unit, wordorder=Endian.Little) * -1 + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + power=power, + imported=imported, + exported=exported + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolarmaxMsCounterSetup) diff --git a/packages/modules/devices/solarmax/solarmax/device.py b/packages/modules/devices/solarmax/solarmax/device.py index 3fb514d09d..75181d9f23 100644 --- a/packages/modules/devices/solarmax/solarmax/device.py +++ b/packages/modules/devices/solarmax/solarmax/device.py @@ -8,9 +8,13 @@ from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.solarmax.solarmax import inverter +from modules.devices.solarmax.solarmax.inverter import SolarmaxInverter from modules.devices.solarmax.solarmax.bat import SolarmaxBat -from modules.devices.solarmax.solarmax.config import ( - Solarmax, SolarmaxBatSetup, SolarmaxConfiguration, SolarmaxInverterSetup) +from modules.devices.solarmax.solarmax.counter_maxstorage import SolarmaxMsCounter +from modules.devices.solarmax.solarmax.inverter_maxstorage import SolarmaxMsInverter +from modules.devices.solarmax.solarmax.config import (Solarmax, SolarmaxConfiguration, + SolarmaxBatSetup, SolarmaxMsCounterSetup, + SolarmaxInverterSetup, SolarmaxMsInverterSetup) log = logging.getLogger(__name__) @@ -24,9 +28,18 @@ def create_bat_component(component_config: SolarmaxBatSetup): def create_inverter_component(component_config: SolarmaxInverterSetup): nonlocal client - return inverter.SolarmaxInverter(component_config, device_id=device_config.id, client=client) + return SolarmaxInverter(component_config, device_id=device_config.id, client=client) - def update_components(components: Iterable[Union[SolarmaxBat, inverter.SolarmaxInverter]]): + def create_inverter_ms_component(component_config: SolarmaxMsInverterSetup): + nonlocal client + return SolarmaxMsInverter(component_config, device_id=device_config.id, client=client) + + def create_counter_ms_component(component_config: SolarmaxMsCounterSetup): + nonlocal client + return SolarmaxMsCounter(component_config, device_id=device_config.id, client=client) + + def update_components(components: Iterable[Union[SolarmaxBat, SolarmaxInverter, + SolarmaxMsCounter, SolarmaxMsInverter]]): nonlocal client with client: for component in components: @@ -43,6 +56,9 @@ def initializer(): component_factory=ComponentFactoryByType( bat=create_bat_component, inverter=create_inverter_component, + counter_maxstorage=create_counter_ms_component, + inverter_maxstorage=create_inverter_ms_component, + ), component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/solarmax/solarmax/inverter_maxstorage.py b/packages/modules/devices/solarmax/solarmax/inverter_maxstorage.py new file mode 100644 index 0000000000..d8d9722380 --- /dev/null +++ b/packages/modules/devices/solarmax/solarmax/inverter_maxstorage.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from pymodbus.constants import Endian +from modules.common.abstract_device import AbstractInverter +from modules.common.component_state import InverterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.solarmax.solarmax.config import SolarmaxMsInverterSetup + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class SolarmaxMsInverter(AbstractInverter): + def __init__(self, + component_config: SolarmaxMsInverterSetup, + **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + self.store = get_inverter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + power = self.client.read_input_registers(120, ModbusDataType.INT_32, unit=unit, wordorder=Endian.Little) * -1 + _, exported = self.sim_counter.sim_count(power) + + inverter_state = InverterState( + power=power, + exported=exported + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolarmaxMsInverterSetup)