From 7f27665516b72eca31933ca47dc8e22c3fac7ba4 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 22 Apr 2025 10:03:17 +0200 Subject: [PATCH 1/2] add Kaco Tx inverter --- .../modules/devices/kaco/kaco_tx/config.py | 37 ++++++++++++ .../modules/devices/kaco/kaco_tx/device.py | 48 ++++++++++++++++ .../modules/devices/kaco/kaco_tx/inverter.py | 56 +++++++++++++++++++ .../modules/devices/kaco/kaco_tx/scale.py | 27 +++++++++ packages/modules/devices/kaco/vendor.py | 14 +++++ 5 files changed, 182 insertions(+) create mode 100644 packages/modules/devices/kaco/kaco_tx/config.py create mode 100644 packages/modules/devices/kaco/kaco_tx/device.py create mode 100644 packages/modules/devices/kaco/kaco_tx/inverter.py create mode 100644 packages/modules/devices/kaco/kaco_tx/scale.py create mode 100644 packages/modules/devices/kaco/vendor.py diff --git a/packages/modules/devices/kaco/kaco_tx/config.py b/packages/modules/devices/kaco/kaco_tx/config.py new file mode 100644 index 0000000000..0e343f1211 --- /dev/null +++ b/packages/modules/devices/kaco/kaco_tx/config.py @@ -0,0 +1,37 @@ +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class KacoConfiguration: + def __init__(self, + port: int = 502, + ip_address=None): + self.port = port + self.ip_address = ip_address + + +class Kaco: + def __init__(self, + name: str = "Kaco Tx1 & Tx3 Serie", + type: str = "kaco", + id: int = 0, + configuration: KacoConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or KacoConfiguration() + + +class KacoInverterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +class KacoInverterSetup(ComponentSetup[KacoInverterConfiguration]): + def __init__(self, + name: str = "Kaco Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: KacoInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or KacoInverterConfiguration()) diff --git a/packages/modules/devices/kaco/kaco_tx/device.py b/packages/modules/devices/kaco/kaco_tx/device.py new file mode 100644 index 0000000000..f1ff89a6e6 --- /dev/null +++ b/packages/modules/devices/kaco/kaco_tx/device.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable + +from modules.common import modbus +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.kaco.kaco_tx.inverter import KacoInverter +from modules.devices.kaco.kaco_tx.config import (Kaco, KacoInverterSetup) + +log = logging.getLogger(__name__) + +default_unit_id = 85 +synergy_unit_identifier = 160 +reconnect_delay = 1.2 + + +def create_device(device_config: Kaco): + client = None + + def create_inverter_component(component_config: KacoInverterSetup): + nonlocal client + return KacoInverter(component_config, client=client, device_id=device_config.id) + + def update_components(components: Iterable[KacoInverter]): + nonlocal client + with client: + for component in components: + component.update() + + def initializer(): + nonlocal client + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, + device_config.configuration.port, + reconnect_delay=reconnect_delay) + + device = ConfigurableDevice( + device_config=device_config, + initializer=initializer, + component_factory=ComponentFactoryByType( + inverter=create_inverter_component + ), + component_updater=MultiComponentUpdater(update_components) + ) + return device + + +device_descriptor = DeviceDescriptor(configuration_factory=Kaco) diff --git a/packages/modules/devices/kaco/kaco_tx/inverter.py b/packages/modules/devices/kaco/kaco_tx/inverter.py new file mode 100644 index 0000000000..3cc3e78f4f --- /dev/null +++ b/packages/modules/devices/kaco/kaco_tx/inverter.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +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.kaco.kaco_tx.config import KacoInverterSetup +from modules.devices.kaco.kaco_tx.scale import create_scaled_reader + + +class KwargsDict(TypedDict): + client: modbus.ModbusTcpClient_ + device_id: int + + +class KacoInverter(AbstractInverter): + def __init__(self, + component_config: KacoInverterSetup, + **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__tcp_client = self.kwargs['client'] + self.store = get_inverter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self._read_scaled_int16 = create_scaled_reader( + self.__tcp_client, self.component_config.configuration.modbus_id, ModbusDataType.INT_16 + ) + self._read_scaled_int32 = create_scaled_reader( + self.__tcp_client, self.component_config.configuration.modbus_id, ModbusDataType.INT_32 + ) + + def update(self) -> None: + self.store.set(self.read_state()) + + def read_state(self): + # 40084 | Total AC Power | int16 + # 40085 | AC Power scale factor | sunssf + power = self._read_scaled_int16(40084, 1)[0] * -1 + + # 40094 | AC Energy | acc32 + # 40096 | AC Energy scale factor | sunssf + exported = self._read_scaled_int32(40094, 1)[0] + + return InverterState( + power=power, + exported=exported + ) + + +component_descriptor = ComponentDescriptor(configuration_factory=KacoInverterSetup) diff --git a/packages/modules/devices/kaco/kaco_tx/scale.py b/packages/modules/devices/kaco/kaco_tx/scale.py new file mode 100644 index 0000000000..bfe78d2ddd --- /dev/null +++ b/packages/modules/devices/kaco/kaco_tx/scale.py @@ -0,0 +1,27 @@ +import logging +import math +from typing import List + +from modules.common.modbus import ModbusDataType, ModbusTcpClient_, Number + +log = logging.getLogger(__name__) + +# Registers that are not applicable to a meter class return the unsupported value. (e.g. Single Phase +# meters will support only summary and phase A values): + +UINT16_UNSUPPORTED = 0xFFFF + + +def scale_registers(registers: List[Number]) -> List[float]: + log.debug("Registers %s, Scale %s", registers[:-1], registers[-1]) + scale = math.pow(10, registers[-1]) + return [register * scale if register != UINT16_UNSUPPORTED else 0 for register in registers[:-1]] + + +def create_scaled_reader(client: ModbusTcpClient_, modbus_id: int, type: ModbusDataType): + def scaled_reader(address: int, count: int): + return scale_registers( + client.read_holding_registers(address, [type] * count + [ModbusDataType.INT_16], unit=modbus_id) + ) + + return scaled_reader diff --git a/packages/modules/devices/kaco/vendor.py b/packages/modules/devices/kaco/vendor.py new file mode 100644 index 0000000000..f68b3c044e --- /dev/null +++ b/packages/modules/devices/kaco/vendor.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from modules.common.abstract_device import DeviceDescriptor +from modules.devices.vendors import VendorGroup + + +class Vendor: + def __init__(self): + self.type = Path(__file__).parent.name + self.vendor = "Kaco" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) From 611735352b810edd42f6d6f0ee83d0a54fb5562b Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 7 May 2025 14:29:34 +0200 Subject: [PATCH 2/2] remove unused variables --- packages/modules/devices/kaco/kaco_tx/device.py | 4 +--- packages/modules/devices/kaco/kaco_tx/inverter.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/modules/devices/kaco/kaco_tx/device.py b/packages/modules/devices/kaco/kaco_tx/device.py index f1ff89a6e6..6a5b2f3f01 100644 --- a/packages/modules/devices/kaco/kaco_tx/device.py +++ b/packages/modules/devices/kaco/kaco_tx/device.py @@ -10,8 +10,6 @@ log = logging.getLogger(__name__) -default_unit_id = 85 -synergy_unit_identifier = 160 reconnect_delay = 1.2 @@ -20,7 +18,7 @@ def create_device(device_config: Kaco): def create_inverter_component(component_config: KacoInverterSetup): nonlocal client - return KacoInverter(component_config, client=client, device_id=device_config.id) + return KacoInverter(component_config, client=client) def update_components(components: Iterable[KacoInverter]): nonlocal client diff --git a/packages/modules/devices/kaco/kaco_tx/inverter.py b/packages/modules/devices/kaco/kaco_tx/inverter.py index 3cc3e78f4f..d089b3d17f 100644 --- a/packages/modules/devices/kaco/kaco_tx/inverter.py +++ b/packages/modules/devices/kaco/kaco_tx/inverter.py @@ -14,7 +14,6 @@ class KwargsDict(TypedDict): client: modbus.ModbusTcpClient_ - device_id: int class KacoInverter(AbstractInverter):