From 7ed60ce99c624e5179306442eeeb7a0b9a7fe86c Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 25 Feb 2025 14:31:08 +0100 Subject: [PATCH 1/3] add registers for Gen1 & Gen2 --- packages/modules/devices/upower/__init__.py | 0 .../modules/devices/upower/upower/__init__.py | 0 packages/modules/devices/upower/upower/bat.py | 53 +++++++++++++++ .../modules/devices/upower/upower/config.py | 66 +++++++++++++++++++ .../modules/devices/upower/upower/counter.py | 57 ++++++++++++++++ .../modules/devices/upower/upower/device.py | 56 ++++++++++++++++ .../modules/devices/upower/upower/inverter.py | 42 ++++++++++++ .../modules/devices/upower/upower/version.py | 6 ++ packages/modules/devices/upower/vendor.py | 14 ++++ 9 files changed, 294 insertions(+) create mode 100644 packages/modules/devices/upower/__init__.py create mode 100644 packages/modules/devices/upower/upower/__init__.py create mode 100644 packages/modules/devices/upower/upower/bat.py create mode 100644 packages/modules/devices/upower/upower/config.py create mode 100644 packages/modules/devices/upower/upower/counter.py create mode 100644 packages/modules/devices/upower/upower/device.py create mode 100644 packages/modules/devices/upower/upower/inverter.py create mode 100644 packages/modules/devices/upower/upower/version.py create mode 100644 packages/modules/devices/upower/vendor.py diff --git a/packages/modules/devices/upower/__init__.py b/packages/modules/devices/upower/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/devices/upower/upower/__init__.py b/packages/modules/devices/upower/upower/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/devices/upower/upower/bat.py b/packages/modules/devices/upower/upower/bat.py new file mode 100644 index 0000000000..a2546743e5 --- /dev/null +++ b/packages/modules/devices/upower/upower/bat.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +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, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.upower.upower.config import UPowerBatSetup +from modules.devices.upower.upower.version import UPowerVersion + + +class UPowerBat(AbstractBat): + def __init__(self, + component_config: Union[Dict, UPowerBatSetup], + version: UPowerVersion, + modbus_id: int) -> None: + self.component_config = dataclass_from_dict(UPowerBatSetup, component_config) + self.__modbus_id = modbus_id + self.version = version + self.store = get_bat_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="speicher") + + def update(self, client: ModbusTcpClient_) -> None: + if self.version == UPowerVersion.GEN_1: + power = client.read_input_registers(30258, ModbusDataType.INT_32, unit=self.__modbus_id) * -1 + soc = client.read_input_registers(33000, ModbusDataType.UINT_16, unit=self.__modbus_id) + imported = client.read_input_registers( + 31108, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 + exported = client.read_input_registers( + 31110, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 + else: + # 1221 Total Bat Power + # 1427 Battery 1 current power + # Bat 1 (additional batteries offset by 50) + power = client.read_input_registers(1427, ModbusDataType.INT_16, unit=self.__modbus_id) + soc = client.read_input_registers(1402, ModbusDataType.UINT_16, unit=self.__modbus_id) / 10 + 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=UPowerBatSetup) diff --git a/packages/modules/devices/upower/upower/config.py b/packages/modules/devices/upower/upower/config.py new file mode 100644 index 0000000000..49dd740a4f --- /dev/null +++ b/packages/modules/devices/upower/upower/config.py @@ -0,0 +1,66 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class UPowerConfiguration: + 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 UPower: + def __init__(self, + name: str = "UPower", + type: str = "upower", + id: int = 0, + configuration: UPowerConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or UPowerConfiguration() + + +class UPowerBatConfiguration: + def __init__(self): + pass + + +class UPowerBatSetup(ComponentSetup[UPowerBatConfiguration]): + def __init__(self, + name: str = "UPower Speicher", + type: str = "bat", + id: int = 0, + configuration: UPowerBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or UPowerBatConfiguration()) + + +class UPowerCounterConfiguration: + def __init__(self): + pass + + +class UPowerCounterSetup(ComponentSetup[UPowerCounterConfiguration]): + def __init__(self, + name: str = "UPower Zähler", + type: str = "counter", + id: int = 0, + configuration: UPowerCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or UPowerCounterConfiguration()) + + +class UPowerInverterConfiguration: + def __init__(self): + pass + + +class UPowerInverterSetup(ComponentSetup[UPowerInverterConfiguration]): + def __init__(self, + name: str = "UPower Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: UPowerInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or UPowerInverterConfiguration()) diff --git a/packages/modules/devices/upower/upower/counter.py b/packages/modules/devices/upower/upower/counter.py new file mode 100644 index 0000000000..2cf22bab87 --- /dev/null +++ b/packages/modules/devices/upower/upower/counter.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +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.upower.upower.config import UPowerCounterSetup +from modules.devices.upower.upower.version import UPowerVersion + + +class UPowerCounter(AbstractCounter): + def __init__(self, + component_config: Union[Dict, UPowerCounterSetup], + version: UPowerVersion, + modbus_id: int) -> None: + self.component_config = dataclass_from_dict(UPowerCounterSetup, component_config) + self.__modbus_id = modbus_id + self.version = version + 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, client: ModbusTcpClient_): + if self.version == UPowerVersion.GEN_1: + power = client.read_holding_registers(1000, ModbusDataType.INT_32, unit=self.__modbus_id) * -1 + frequency = client.read_holding_registers(11015, ModbusDataType.UINT_16, unit=self.__modbus_id) + + powers = [-value for value in client.read_holding_registers( + 10994, [ModbusDataType.INT_32] * 3, unit=self.__modbus_id)] + exported = client.read_holding_registers(31102, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 + imported = client.read_holding_registers(31104, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 + else: + power = client.read_holding_registers(1219, ModbusDataType.INT_16, unit=self.__modbus_id) * -10 + frequency = client.read_holding_registers( + 1759, ModbusDataType.UINT_16, unit=self.__modbus_id) / 100 + + powers = [-10 * value for value in client.read_holding_registers( + 1750, [ModbusDataType.INT_16] * 3, unit=self.__modbus_id + )] + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers, + frequency=frequency + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=UPowerCounterSetup) diff --git a/packages/modules/devices/upower/upower/device.py b/packages/modules/devices/upower/upower/device.py new file mode 100644 index 0000000000..b072a4e68c --- /dev/null +++ b/packages/modules/devices/upower/upower/device.py @@ -0,0 +1,56 @@ +#!/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.upower.upower.bat import UPowerBat +from modules.devices.upower.upower.config import UPower, UPowerBatSetup, UPowerCounterSetup, UPowerInverterSetup +from modules.devices.upower.upower.counter import UPowerCounter +from modules.devices.upower.upower.inverter import UPowerInverter +from modules.devices.upower.upower.version import UPowerVersion + +log = logging.getLogger(__name__) + + +def create_device(device_config: UPower): + def create_bat_component(component_config: UPowerBatSetup): + return UPowerBat(component_config, + UPowerVersion(device_config.configuration.version), + device_config.configuration.modbus_id) + + def create_counter_component(component_config: UPowerCounterSetup): + return UPowerCounter(component_config, + UPowerVersion(device_config.configuration.version), + device_config.configuration.modbus_id) + + def create_inverter_component(component_config: UPowerInverterSetup): + return UPowerInverter(component_config, + UPowerVersion(device_config.configuration.version), + device_config.configuration.modbus_id) + + def update_components(components: Iterable[Union[UPowerBat, UPowerCounter, UPowerInverter]]): + with client as c: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update(c) + + 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=UPower) diff --git a/packages/modules/devices/upower/upower/inverter.py b/packages/modules/devices/upower/upower/inverter.py new file mode 100644 index 0000000000..fa3ff881f6 --- /dev/null +++ b/packages/modules/devices/upower/upower/inverter.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +from typing import Dict, Union +# from pymodbus.constants import Endian + +from dataclass_utils import dataclass_from_dict +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.upower.upower.config import UPowerInverterSetup +from modules.devices.upower.upower.version import UPowerVersion + + +class UPowerInverter(AbstractInverter): + def __init__(self, + component_config: Union[Dict, UPowerInverterSetup], + version: UPowerVersion, + modbus_id: int) -> None: + self.component_config = dataclass_from_dict(UPowerInverterSetup, component_config) + self.__modbus_id = modbus_id + self.version = version + self.store = get_inverter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self, client: ModbusTcpClient_) -> None: + if self.version == UPowerVersion.GEN_1: + power = client.read_holding_registers(11028, ModbusDataType.UINT_32, unit=self.__modbus_id) * -1 + exported = client.read_holding_registers(11020, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 + else: + power = client.read_holding_registers(1220, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 + exported = client.read_holding_registers(1006, ModbusDataType.UINT_32, unit=self.__modbus_id) * 10 + + inverter_state = InverterState( + power=power, + exported=exported + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=UPowerInverterSetup) diff --git a/packages/modules/devices/upower/upower/version.py b/packages/modules/devices/upower/upower/version.py new file mode 100644 index 0000000000..a404e2901f --- /dev/null +++ b/packages/modules/devices/upower/upower/version.py @@ -0,0 +1,6 @@ +from enum import IntEnum + + +class UPowerVersion(IntEnum): + GEN_1 = 1 + GEN_2 = 2 diff --git a/packages/modules/devices/upower/vendor.py b/packages/modules/devices/upower/vendor.py new file mode 100644 index 0000000000..8dee6e8e53 --- /dev/null +++ b/packages/modules/devices/upower/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 = "QCells" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) From c9536fbaa2f7e240489d87e9b8ae7d031b2c99f7 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 26 Feb 2025 08:44:10 +0100 Subject: [PATCH 2/3] add correct vendor --- packages/modules/devices/upower/vendor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/upower/vendor.py b/packages/modules/devices/upower/vendor.py index 8dee6e8e53..988c9192dc 100644 --- a/packages/modules/devices/upower/vendor.py +++ b/packages/modules/devices/upower/vendor.py @@ -7,7 +7,7 @@ class Vendor: def __init__(self): self.type = Path(__file__).parent.name - self.vendor = "QCells" + self.vendor = "UPower" self.group = VendorGroup.VENDORS.value From 499ee220956b1ab5a07c36825a5fbba076de451d Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 18 Mar 2025 11:19:45 +0100 Subject: [PATCH 3/3] add version --- packages/modules/devices/upower/upower/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/upower/upower/config.py b/packages/modules/devices/upower/upower/config.py index 49dd740a4f..64d4148f1d 100644 --- a/packages/modules/devices/upower/upower/config.py +++ b/packages/modules/devices/upower/upower/config.py @@ -5,10 +5,11 @@ class UPowerConfiguration: - def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: int = 502): + def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: int = 502, version: int = 1): self.modbus_id = modbus_id self.ip_address = ip_address self.port = port + self.version = version class UPower: