diff --git a/packages/modules/devices/solax/solax_gen5/__init__.py b/packages/modules/devices/solax/solax_gen5/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/devices/solax/solax_gen5/bat.py b/packages/modules/devices/solax/solax_gen5/bat.py new file mode 100644 index 0000000000..209c499c69 --- /dev/null +++ b/packages/modules/devices/solax/solax_gen5/bat.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +from modules.common import modbus +from modules.common.abstract_device import AbstractBat +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.solax.solax_gen5.config import SolaxGen5BatSetup + + +class SolaxGen5Bat(AbstractBat): + def __init__(self, + device_id: int, + component_config: Union[Dict, SolaxGen5BatSetup], + tcp_client: modbus.ModbusTcpClient_, + modbus_id: int) -> None: + self.__device_id = device_id + self.__modbus_id = modbus_id + self.component_config = dataclass_from_dict(SolaxGen5BatSetup, component_config) + self.__tcp_client = tcp_client + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + with self.__tcp_client: + power_bat1 = self.__tcp_client.read_input_registers(22, ModbusDataType.INT_16, unit=self.__modbus_id) + power_bat2 = self.__tcp_client.read_input_registers(297, ModbusDataType.INT_16, unit=self.__modbus_id) + power = power_bat1 + power_bat2 + try: + soc = self.__tcp_client.read_input_registers(302, ModbusDataType.UINT_16, unit=self.__modbus_id) + except Exception: + soc = self.__tcp_client.read_input_registers(28, ModbusDataType.UINT_16, unit=self.__modbus_id) + + try: + imported = self.__tcp_client.read_input_registers(35, + ModbusDataType.UINT_16, unit=self.__modbus_id) * 100 + exported = self.__tcp_client.read_input_registers(32, + ModbusDataType.UINT_16, unit=self.__modbus_id) * 100 + except Exception: + imported, exported = self.sim_counter.sim_count(power) + + bat_state = BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5BatSetup) diff --git a/packages/modules/devices/solax/solax_gen5/config.py b/packages/modules/devices/solax/solax_gen5/config.py new file mode 100644 index 0000000000..c698718216 --- /dev/null +++ b/packages/modules/devices/solax/solax_gen5/config.py @@ -0,0 +1,66 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class SolaxGen5Configuration: + def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: int = 502): + self.modbus_id = modbus_id + self.ip_address = ip_address + self.port = port + + +class SolaxGen5: + def __init__(self, + name: str = "Solax Gen5", + type: str = "solax_gen5", + id: int = 0, + configuration: SolaxGen5Configuration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or SolaxGen5Configuration() + + +class SolaxGen5BatConfiguration: + def __init__(self): + pass + + +class SolaxGen5BatSetup(ComponentSetup[SolaxGen5BatConfiguration]): + def __init__(self, + name: str = "Solax Speicher", + type: str = "bat", + id: int = 0, + configuration: SolaxGen5BatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolaxGen5BatConfiguration()) + + +class SolaxGen5CounterConfiguration: + def __init__(self): + pass + + +class SolaxGen5CounterSetup(ComponentSetup[SolaxGen5CounterConfiguration]): + def __init__(self, + name: str = "Solax Zähler", + type: str = "counter", + id: int = 0, + configuration: SolaxGen5CounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolaxGen5CounterConfiguration()) + + +class SolaxGen5InverterConfiguration: + def __init__(self): + pass + + +class SolaxGen5InverterSetup(ComponentSetup[SolaxGen5InverterConfiguration]): + def __init__(self, + name: str = "Solax Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: SolaxGen5InverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolaxGen5InverterConfiguration()) diff --git a/packages/modules/devices/solax/solax_gen5/counter.py b/packages/modules/devices/solax/solax_gen5/counter.py new file mode 100644 index 0000000000..3c222fcc36 --- /dev/null +++ b/packages/modules/devices/solax/solax_gen5/counter.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +from typing import Dict, Union +from pymodbus.constants import Endian + +from dataclass_utils import dataclass_from_dict +from modules.common import modbus +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 +from modules.common.store import get_counter_value_store +from modules.devices.solax.solax_gen5.config import SolaxGen5CounterSetup + + +class SolaxGen5Counter(AbstractCounter): + def __init__(self, + device_id: int, + component_config: Union[Dict, SolaxGen5CounterSetup], + tcp_client: modbus.ModbusTcpClient_, + modbus_id: int) -> None: + + self.component_config = dataclass_from_dict(SolaxGen5CounterSetup, component_config) + self.__modbus_id = modbus_id + self.__tcp_client = tcp_client + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self): + with self.__tcp_client: + power = self.__tcp_client.read_input_registers(70, ModbusDataType.INT_32, wordorder=Endian.Little, + unit=self.__modbus_id) * -1 + frequency = self.__tcp_client.read_input_registers(7, ModbusDataType.UINT_16, unit=self.__modbus_id) / 100 + voltages = [value / 10 + for value in self.__tcp_client.read_input_registers( + 202, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id + )] + + currents = [(65535 - value) / 10 + for value in self.__tcp_client.read_input_registers( + 206, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id + )] + + power_factors = [value / 100 + for value in self.__tcp_client.read_input_registers( + 197, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id + )] + + powers = [-value for value in self.__tcp_client.read_input_registers( + 130, [ModbusDataType.INT_32] * 3, wordorder=Endian.Little, unit=self.__modbus_id + )] + + exported, imported = [value * 10 + for value in self.__tcp_client.read_input_registers( + 72, [ModbusDataType.UINT_32] * 2, wordorder=Endian.Little, unit=self.__modbus_id + )] + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers, + frequency=frequency, + voltages=voltages, + currents=currents, + power_factors=power_factors + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5CounterSetup) diff --git a/packages/modules/devices/solax/solax_gen5/device.py b/packages/modules/devices/solax/solax_gen5/device.py new file mode 100644 index 0000000000..5937be40d7 --- /dev/null +++ b/packages/modules/devices/solax/solax_gen5/device.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable, Union + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.solax.solax_gen5.bat import SolaxGen5Bat +from modules.devices.solax.solax_gen5.config import SolaxGen5, SolaxGen5BatSetup, SolaxGen5CounterSetup +from modules.devices.solax.solax_gen5.config import SolaxGen5InverterSetup +from modules.devices.solax.solax_gen5.counter import SolaxGen5Counter +from modules.devices.solax.solax_gen5.inverter import SolaxGen5Inverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: SolaxGen5): + def create_bat_component(component_config: SolaxGen5BatSetup): + return SolaxGen5Bat(device_config.id, component_config, client, device_config.configuration.modbus_id) + + def create_counter_component(component_config: SolaxGen5CounterSetup): + return SolaxGen5Counter(device_config.id, component_config, client, device_config.configuration.modbus_id) + + def create_inverter_component(component_config: SolaxGen5InverterSetup): + return SolaxGen5Inverter(device_config.id, component_config, client, device_config.configuration.modbus_id) + + def update_components(components: Iterable[Union[SolaxGen5Bat, SolaxGen5Counter, SolaxGen5Inverter]]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=SolaxGen5) diff --git a/packages/modules/devices/solax/solax_gen5/inverter.py b/packages/modules/devices/solax/solax_gen5/inverter.py new file mode 100644 index 0000000000..e4157d947e --- /dev/null +++ b/packages/modules/devices/solax/solax_gen5/inverter.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from typing import Dict, Union +from pymodbus.constants import Endian + +from dataclass_utils import dataclass_from_dict +from modules.common import modbus +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 +from modules.common.store import get_inverter_value_store +from modules.devices.solax.solax_gen5.config import SolaxGen5InverterSetup + + +class SolaxGen5Inverter(AbstractInverter): + def __init__(self, + device_id: int, + component_config: Union[Dict, SolaxGen5InverterSetup], + tcp_client: modbus.ModbusTcpClient_, + modbus_id: int) -> None: + self.component_config = dataclass_from_dict(SolaxGen5InverterSetup, component_config) + self.__modbus_id = modbus_id + self.__tcp_client = tcp_client + 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: + with self.__tcp_client: + power_pv1 = self.__tcp_client.read_input_registers(10, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 + power_pv2 = self.__tcp_client.read_input_registers(11, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 + power_pv3 = self.__tcp_client.read_input_registers(292, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 + power_temp = (power_pv1, power_pv2, power_pv3) + power = sum(power_temp) + exported = self.__tcp_client.read_input_registers(82, ModbusDataType.UINT_32, wordorder=Endian.Little, + unit=self.__modbus_id) * 100 + + inverter_state = InverterState( + power=power, + exported=exported + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5InverterSetup)