diff --git a/packages/modules/devices/kostal/kostal_piko_ci/config.py b/packages/modules/devices/kostal/kostal_piko_ci/config.py new file mode 100644 index 0000000000..5cb6b05116 --- /dev/null +++ b/packages/modules/devices/kostal/kostal_piko_ci/config.py @@ -0,0 +1,58 @@ +from typing import Optional + +from helpermodules.auto_str import auto_str +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class KostalPikoCiConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502): + self.ip_address = ip_address + self.port = port + + +class KostalPikoCi: + def __init__(self, + name: str = "Kostal Piko CI", + type: str = "kostal_piko_ci", + id: int = 0, + configuration: KostalPikoCiConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or KostalPikoCiConfiguration() + + +@auto_str +class KostalPikoCiCounterConfiguration: + def __init__(self, modbus_id: int = 75): + self.modbus_id = modbus_id + + +@auto_str +class KostalPikoCiCounterSetup(ComponentSetup[KostalPikoCiCounterConfiguration]): + def __init__(self, + name: str = "Kostal Piko CI Zähler", + type: str = "counter", + id: int = 0, + configuration: KostalPikoCiCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or KostalPikoCiCounterConfiguration()) + + +@auto_str +class KostalPikoCiInverterConfiguration: + def __init__(self, modbus_id: int = 75): + self.modbus_id = modbus_id + + +@auto_str +class KostalPikoCiInverterSetup(ComponentSetup[KostalPikoCiInverterConfiguration]): + def __init__(self, + name: str = "Kostal Piko CI Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: KostalPikoCiInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or KostalPikoCiInverterConfiguration()) diff --git a/packages/modules/devices/kostal/kostal_piko_ci/counter.py b/packages/modules/devices/kostal/kostal_piko_ci/counter.py new file mode 100644 index 0000000000..3552783c8a --- /dev/null +++ b/packages/modules/devices/kostal/kostal_piko_ci/counter.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +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.kostal.kostal_piko_ci.config import KostalPikoCiCounterSetup + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class KostalPikoCiCounter(AbstractCounter): + def __init__(self, component_config: KostalPikoCiCounterSetup, **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.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + + power = self.client.read_holding_registers(252, ModbusDataType.FLOAT_32, unit=unit) + powers = [self.client.read_holding_registers( + reg, ModbusDataType.FLOAT_32, unit=unit) for reg in [224, 234, 244]] + frequency = self.client.read_holding_registers(220, ModbusDataType.FLOAT_32, unit=unit) + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + power=power, + powers=powers, + frequency=frequency, + imported=imported, + exported=exported, + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=KostalPikoCiCounterSetup) diff --git a/packages/modules/devices/kostal/kostal_piko_ci/device.py b/packages/modules/devices/kostal/kostal_piko_ci/device.py new file mode 100644 index 0000000000..85e57d2a1c --- /dev/null +++ b/packages/modules/devices/kostal/kostal_piko_ci/device.py @@ -0,0 +1,50 @@ +#!/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 ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.kostal.kostal_piko_ci.counter import KostalPikoCiCounter +from modules.devices.kostal.kostal_piko_ci.inverter import KostalPikoCiInverter +from modules.devices.kostal.kostal_piko_ci.config import (KostalPikoCi, KostalPikoCiCounterSetup, + KostalPikoCiInverterSetup) + +log = logging.getLogger(__name__) + + +def create_device(device_config: KostalPikoCi): + client = None + + def create_counter_component(component_config: KostalPikoCiCounterSetup): + nonlocal client + return KostalPikoCiCounter(component_config, device_id=device_config.id, client=client) + + def create_inverter_component(component_config: KostalPikoCiInverterSetup): + nonlocal client + return KostalPikoCiInverter(component_config, device_id=device_config.id, client=client) + + def update_components(components: Iterable[Union[KostalPikoCiCounter, KostalPikoCiInverter]]): + nonlocal client + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + component_factory=ComponentFactoryByType( + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=KostalPikoCi) diff --git a/packages/modules/devices/kostal/kostal_piko_ci/inverter.py b/packages/modules/devices/kostal/kostal_piko_ci/inverter.py new file mode 100644 index 0000000000..2965dfd76b --- /dev/null +++ b/packages/modules/devices/kostal/kostal_piko_ci/inverter.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +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.store import get_inverter_value_store +from modules.devices.kostal.kostal_piko_ci.config import KostalPikoCiInverterSetup + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class KostalPikoCiInverter(AbstractInverter): + def __init__(self, component_config: KostalPikoCiInverterSetup, **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.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_holding_registers(172, ModbusDataType.FLOAT_32, unit=unit) * -1 + currents = [self.client.read_holding_registers( + reg, ModbusDataType.FLOAT_32, unit=unit) for reg in [154, 160, 166]] + exported = self.client.read_holding_registers(320, ModbusDataType.FLOAT_32, unit=unit) + + inverter_state = InverterState( + power=power, + currents=currents, + exported=exported + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=KostalPikoCiInverterSetup)