From 2077551adb9debb2bdf0264160c21a6c5b32cfe3 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:04:52 +0100 Subject: [PATCH 01/60] Create __init__.py --- packages/modules/devices/sungrow/sungrow_ihm/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/__init__.py b/packages/modules/devices/sungrow/sungrow_ihm/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/__init__.py @@ -0,0 +1 @@ + From 00022fb3095a1fc61505f2d1e092f3334a1d19ba Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:05:27 +0100 Subject: [PATCH 02/60] Delete packages/modules/devices/sungrow/sungrow_ihm/__init__.py --- packages/modules/devices/sungrow/sungrow_ihm/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/modules/devices/sungrow/sungrow_ihm/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/__init__.py b/packages/modules/devices/sungrow/sungrow_ihm/__init__.py deleted file mode 100644 index 8b13789179..0000000000 --- a/packages/modules/devices/sungrow/sungrow_ihm/__init__.py +++ /dev/null @@ -1 +0,0 @@ - From 0922a7e438b52b5b548bf1baf1c1931f05479b03 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:22:10 +0100 Subject: [PATCH 03/60] Add SungrowIHMCounter --- .../devices/sungrow/sungrow_ihm/counter.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/counter.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/counter.py b/packages/modules/devices/sungrow/sungrow_ihm/counter.py new file mode 100644 index 0000000000..38bd515a0b --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/counter.py @@ -0,0 +1,57 @@ +#!/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 Endian, ModbusDataType, ModbusTcpClient_ +from modules.common.simcount._simcounter import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.sungrow.sungrow_ihm.config import Sungrow, SungrowIHMCounterSetup + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: Sungrow + + +class SungrowIHMCounter(AbstractCounter): + def __init__(self, component_config: SungrowIHMCounterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: Sungrow = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="evu") + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + + def update(self, pv_power: float): + unit = self.device_config.configuration.modbus_id + power = self.__tcp_client.read_input_registers(8156, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + powers = self.__tcp_client.read_input_registers(8558, [ModbusDataType.UINT_32] * 3, + wordorder=Endian.Little, unit=unit) + frequency = self.__tcp_client.read_input_registers(8557, ModbusDataType.UINT_16, unit=unit) / 10 + voltages = self.__tcp_client.read_input_registers(8554, [ModbusDataType.UINT_16] * 3, + wordorder=Endian.Little, unit=unit) + + voltages = [value / 10 for value in voltages] + + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers, + voltages=voltages, + frequency=frequency, + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowIHMCounterSetup) From a26de1ce9dc0867ba775554afb372b3924a45d47 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:45:03 +0100 Subject: [PATCH 04/60] Add device.py for Sungrow SG --- .../devices/sungrow/sungrow_sg/device.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_sg/device.py diff --git a/packages/modules/devices/sungrow/sungrow_sg/device.py b/packages/modules/devices/sungrow/sungrow_sg/device.py new file mode 100644 index 0000000000..7bdf4c44ad --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sg/device.py @@ -0,0 +1,48 @@ +#!/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 import modbus +from modules.devices.sungrow.sungrow_sg.config import SungrowSG, SungrowSGCounterSetup, SungrowSGInverterSetup +from modules.devices.sungrow.sungrow_sg.counter import SungrowSGCounter +from modules.devices.sungrow.sungrow_sg.inverter import SungrowSGInverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: SungrowSG): + client = None + + def create_counter_component(component_config: SungrowSGCounterSetup): + nonlocal client + return SungrowSGCounter(component_config, device_config=device_config, client=client) + + def create_inverter_component(component_config: SungrowSGInverterSetup): + nonlocal client + return SungrowSGInverter(component_config, device_config=device_config, client=client) + + def update_components(components: Iterable[Union[SungrowSGCounter, SungrowSGInverter]]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = modbus.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=SungrowSG) From 4e9f377455101a0c78f898e69776e34340480242 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:45:29 +0100 Subject: [PATCH 05/60] Add files via upload --- packages/modules/devices/sungrow/sungrow_sg/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/modules/devices/sungrow/sungrow_sg/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_sg/__init__.py b/packages/modules/devices/sungrow/sungrow_sg/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From fe809ef9bf38130e069b9dbfe96a9f618cdbe70a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:57:39 +0100 Subject: [PATCH 06/60] Add config.py --- .../devices/sungrow/sungrow_sg/config.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_sg/config.py diff --git a/packages/modules/devices/sungrow/sungrow_sg/config.py b/packages/modules/devices/sungrow/sungrow_sg/config.py new file mode 100644 index 0000000000..fb1bdafe9e --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sg/config.py @@ -0,0 +1,56 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from modules.devices.sungrow.sungrow_sg.version import Version +from ..vendor import vendor_descriptor + + +class SungrowSGConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +class SungrowSG: + def __init__(self, + name: str = "Sungrow SG", + type: str = "sungrow_sg", + id: int = 0, + configuration: SungrowSGConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or SungrowSGConfiguration() + + +class SungrowSGCounterConfiguration: + def __init__(self): + pass + + +class SungrowSGCounterSetup(ComponentSetup[SungrowSGCounterConfiguration]): + def __init__(self, + name: str = "Sungrow SG Zähler", + type: str = "counter", + id: int = 0, + configuration: SungrowSGCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowSGCounterConfiguration()) + + +class SungrowSGInverterConfiguration: + def __init__(self): + pass + + +class SungrowSGInverterSetup(ComponentSetup[SungrowSGInverterConfiguration]): + def __init__(self, + name: str = "Sungrow SG Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: SungrowSGInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowSGInverterConfiguration()) From d6d4cd0d456d4f34c6e066301fd396f7d131bbef Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:08:29 +0100 Subject: [PATCH 07/60] Add counter.py --- .../devices/sungrow/sungrow_sg/counter.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_sg/counter.py diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py new file mode 100644 index 0000000000..bbe9cf14b9 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -0,0 +1,70 @@ +#!/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 Endian, ModbusDataType, ModbusTcpClient_ +from modules.common.simcount._simcounter import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.sungrow.sungrow_sg.config import SungrowSG, SungrowSGCounterSetup +from modules.devices.sungrow.sungrow_sg.version import Version + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowSG + + +class SungrowSGCounter(AbstractCounter): + def __init__(self, component_config: SungrowSGCounterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowSG = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="evu") + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.fault_text = "Dieser Sungrow Zähler liefert von Werk aus (entgegen der Dokumentation) "\ + "keine Leistung der einzelnen Phasen. "\ + "Das Lastmanagement ist daher nur anhand der Gesamtleistung (nicht phasenbasiert) möglich." + + def update(self, pv_power: float): + unit = self.device_config.configuration.modbus_id + power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) + + try: + powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, + wordorder=Endian.Little, unit=unit) + except Exception: + powers = None + self.fault_state.no_error(self.fault_text) + + frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 + + power_factor = self.__tcp_client.read_input_registers(5034, ModbusDataType.INT_16, unit=unit) / 1000 + + voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, + wordorder=Endian.Little, unit=unit) + + voltages = [value / 10 for value in voltages] + + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers, + voltages=voltages, + frequency=frequency, + power_factors=[power_factor] * 3 + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSGCounterSetup) From 42e3c270e1464f74984c53d3e3d87c573afc22ec Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:11:13 +0100 Subject: [PATCH 08/60] Add inverter.py --- .../devices/sungrow/sungrow_sg/inverter.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_sg/inverter.py diff --git a/packages/modules/devices/sungrow/sungrow_sg/inverter.py b/packages/modules/devices/sungrow/sungrow_sg/inverter.py new file mode 100644 index 0000000000..ff7dbe6dd5 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sg/inverter.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +from typing import Any, TypedDict + +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, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.sungrow.sungrow_sg.config import SungrowSGInverterSetup, SungrowSG +from modules.devices.sungrow.sungrow_sg.version import Version + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowSG + + +class SungrowSGInverter(AbstractInverter): + def __init__(self, component_config: SungrowSGInverterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowSG = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.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) -> float: + unit = self.device_config.configuration.modbus_id + + power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * -1 + + currents = self.__tcp_client.read_input_registers(5021, [ModbusDataType.INT_16]*3, unit=unit) + + currents = [value * -0.1 for value in currents] + + imported, exported = self.sim_counter.sim_count(power, dc_power) + + inverter_state = InverterState( + power=power, + dc_power=dc_power, + currents=currents, + imported=imported, + exported=exported + ) + self.store.set(inverter_state) + return power + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSGInverterSetup) From c3dff646f7babce5cb5ad90449315a8610f26347 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:13:43 +0100 Subject: [PATCH 09/60] Delete packages/modules/devices/sungrow/sungrow/modbus.md --- .../modules/devices/sungrow/sungrow/modbus.md | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 packages/modules/devices/sungrow/sungrow/modbus.md diff --git a/packages/modules/devices/sungrow/sungrow/modbus.md b/packages/modules/devices/sungrow/sungrow/modbus.md deleted file mode 100644 index 04a50389d2..0000000000 --- a/packages/modules/devices/sungrow/sungrow/modbus.md +++ /dev/null @@ -1,44 +0,0 @@ -# Modbus Adressen für Sungrow SH\* und SG\* Wechselrichter - -## Datengrundlage - -* TI_20230918_Communication Protocol of Residential and Commercial PV Grid-connected Inverter_V1.1.58_EN.pdf -* TI_20231019_Communication Protocol of Residential Hybrid Inverter_V1.1.2_EN.pdf -* modbus_finder.py an SH10RT-V112 (LAN) Firmware SAPPHIRE-H_B001.V000.P005-20231027 -* modbus_finder.py an SH10RT-V112 (WiNet-S) Firmware WINET-SV200.001.00.P023 -* modbus_finder.py an SG10RT (WiNet-S) Firmware BERYL-S_B000.V000.P039-20230626 / WINET-SV200.001.00.P023 - -## Werte - -| Wert | SH_LAN | SH_WiNet | SG_WiNet | Einheit | Typ | Bemerkung | -|-------------------------------------|--------|----------|---------------|---------|----------------|------------------------------------------------------------------| -| WR: Zähler inkl. Batterieentladung | 5003 | 5003 | -- | 0.1 kWh | UINT_32 mixed | Delta zu 'WR: Zähler Gesamtertrag' ist entladene **Netz**energie | -| WR: Zähler Gesamtertrag | 5660 | -- | 5143 | 0.1 kWh | UINT_32 mixed | Wirkleistungszähler, abweichend zu Gesamt-PV-Stromerzeugung | -| WR: AC Ausgangsspannung Phase A | 5018 | 5018 | 5018 | 0.1 V | UINT_16 little | Unterscheidet sich pro WR (nicht vom Meter gemessen) | -| WR: AC Ausgangsspannung Phase B | 5019 | 5019 | 5019 | 0.1 V | UINT_16 little | Unterscheidet sich pro WR (nicht vom Meter gemessen) | -| WR: AC Ausgangsspannung Phase C | 5020 | 5020 | 5020 | 0.1 V | UINT_16 little | Unterscheidet sich pro WR (nicht vom Meter gemessen) | -| WR: Akt. DC Bruttoleistung | 5016 | 5016 | 5016 | 1 W | INT_32 mixed | | -| WR: Akt. AC Wirkleistung | 13033 | 13033 | -- | 1 W | INT_32 mixed | ggf. Speicherladung addieren für effektive PV-Leistung | -| WR: Akt. AC Wirkleistung | 5030 | -- | 5030 | 1 W | INT_32 mixed | 5030 "altes" Register, 13033 bevorzugt für SH Versionen | -| WR: Akt. Leistungsfluss | 13000 | 13000 | -- | ja/nein | 8-bit bitmask | (v.r.) Bit0: PV-Erzeugung, Bit1: Batt. lädt, Bit2: Batt. entlädt | -| BAT: Akt. Leistung (ein-/ausgehend) | 13021 | 13021 | -- | 1 W | UINT16 little | Immer positiv, bei Be- und Entladung. WR Leistungsfluss beachten | -| BAT: SoC | 13022 | 13022 | -- | 0.1 % | UINT16 little | | -| BAT: Zähler Ladung von PV | 13012 | 13012 | -- | 0.1 kWh | UINT32 mixed | | -| BAT: Zähler Gesamtladung | 13026 | 13026 | -- | 0.1 kWh | UINT32 mixed | | -| BAT: Zähler Gesamtentladung | 13040 | 13040 | -- | 0.1 kWh | UINT32 mixed | | -| Netz: Akt. Wirkleistung | 13009 | 13009 | -- | -1 W | INT_32 mixed | | -| Netz: Akt. Wirkleistung | -- | -- | ? 5090 ? 5082 | 1 W | INT_32 mixed | | -| Netz: Akt. Frequenz | 5035 | -- | 5035 | 0.1 Hz | UINT_16 little | | -| Netz: Akt. Frequenz | -- | 5035 | -- | 0.01 Hz | UINT_16 little | | -| Netz: Akt. Leistungsfaktor | 5034 | 5034 | 5034 | 0.001 | INT_16 little | Nur über alle Phasen vorhanden | -| Netz: Zähler Netzentnahme | 13036 | 13036 | -- | 0.1 kWh | UINT_32 mixed | | -| Netz: Zähler Einspeisung | 13045 | 13045 | -- | 0.1 kWh | UINT_32 mixed | | -| Meter: AC Wirkleistung Phase A | 5602 | 5602 | 5084 | 1 W | INT_32 mixed | Im Unterschied zu 13009 Vorzeichen korrekt | -| Meter: AC Wirkleistung Phase B | 5604 | 5604 | 5086 | 1 W | INT_32 mixed | Im Unterschied zu 13009 Vorzeichen korrekt | -| Meter: AC Wirkleistung Phase C | 5606 | 5606 | 5088 | 1 W | INT_32 mixed | Im Unterschied zu 13009 Vorzeichen korrekt | -| Meter: AC Spannung Phase A | 5740 | -- | -- | 0.1 V | UINT_16 little | | -| Meter: AC Spannung Phase B | 5741 | -- | -- | 0.1 V | UINT_16 little | | -| Meter: AC Spannung Phase C | 5742 | -- | -- | 0.1 V | UINT_16 little | | -| Meter: AC Strom Phase A | 5743 | -- | -- | 0.01 A | UINT_16 little | Immer positiv, auch bei Einspeisung | -| Meter: AC Strom Phase B | 5744 | -- | -- | 0.01 A | UINT_16 little | Immer positiv, auch bei Einspeisung | -| Meter: AC Strom Phase C | 5745 | -- | -- | 0.01 A | UINT_16 little | Immer positiv, auch bei Einspeisung | From a6c0ba354e5d38a66e0a2d289e145942b39c85e4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:15:35 +0100 Subject: [PATCH 10/60] Move to SungrowSH bat.py --- .../devices/sungrow/{sungrow => sungrow_sh}/bat.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename packages/modules/devices/sungrow/{sungrow => sungrow_sh}/bat.py (94%) diff --git a/packages/modules/devices/sungrow/sungrow/bat.py b/packages/modules/devices/sungrow/sungrow_sh/bat.py similarity index 94% rename from packages/modules/devices/sungrow/sungrow/bat.py rename to packages/modules/devices/sungrow/sungrow_sh/bat.py index ef99e62237..9217d28b8b 100644 --- a/packages/modules/devices/sungrow/sungrow/bat.py +++ b/packages/modules/devices/sungrow/sungrow_sh/bat.py @@ -9,24 +9,24 @@ from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store -from modules.devices.sungrow.sungrow.config import SungrowBatSetup, Sungrow -from modules.devices.sungrow.sungrow.registers import RegMode +from modules.devices.sungrow.sungrow_sh.config import SungrowSHBatSetup, SungrowSH +from modules.devices.sungrow.sungrow_sh.registers import RegMode log = logging.getLogger(__name__) class KwargsDict(TypedDict): client: ModbusTcpClient_ - device_config: Sungrow + device_config: SungrowSH -class SungrowBat(AbstractBat): - def __init__(self, component_config: SungrowBatSetup, **kwargs: Any) -> None: +class SungrowSHBat(AbstractBat): + def __init__(self, component_config: SungrowSHBatSetup, **kwargs: Any) -> None: self.component_config = component_config self.kwargs: KwargsDict = kwargs def initialize(self) -> None: - self.device_config: Sungrow = self.kwargs['device_config'] + self.device_config: SungrowSH = self.kwargs['device_config'] self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) @@ -145,4 +145,4 @@ def power_limit_controllable(self) -> bool: return True -component_descriptor = ComponentDescriptor(configuration_factory=SungrowBatSetup) +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSHBatSetup) From 973af4fc3ec8ea98118f2867e9b491b75786984e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:15:53 +0100 Subject: [PATCH 11/60] Add files via upload --- packages/modules/devices/sungrow/sungrow_sh/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/modules/devices/sungrow/sungrow_sh/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_sh/__init__.py b/packages/modules/devices/sungrow/sungrow_sh/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 2c18f98857a65feae091b9fa927e1272f7e2d50e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:17:12 +0100 Subject: [PATCH 12/60] Move to SungrowSH version.py --- .../devices/sungrow/{sungrow => sungrow_sh}/version.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename packages/modules/devices/sungrow/{sungrow => sungrow_sh}/version.py (51%) diff --git a/packages/modules/devices/sungrow/sungrow/version.py b/packages/modules/devices/sungrow/sungrow_sh/version.py similarity index 51% rename from packages/modules/devices/sungrow/sungrow/version.py rename to packages/modules/devices/sungrow/sungrow_sh/version.py index 539705095e..57a66e8f47 100644 --- a/packages/modules/devices/sungrow/sungrow/version.py +++ b/packages/modules/devices/sungrow/sungrow_sh/version.py @@ -3,6 +3,4 @@ class Version(IntEnum): SH = 0 - SG = 1 - SG_winet_dongle = 2 - SH_winet_dongle = 3 + SH_winet_dongle = 1 From 280dc8523d869d231a1974f76276f52c25392944 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:17:42 +0100 Subject: [PATCH 13/60] Move to SungrowSH registers.py --- .../modules/devices/sungrow/{sungrow => sungrow_sh}/registers.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/modules/devices/sungrow/{sungrow => sungrow_sh}/registers.py (100%) diff --git a/packages/modules/devices/sungrow/sungrow/registers.py b/packages/modules/devices/sungrow/sungrow_sh/registers.py similarity index 100% rename from packages/modules/devices/sungrow/sungrow/registers.py rename to packages/modules/devices/sungrow/sungrow_sh/registers.py From 896ac366f93b53e45f3a2d5117758adfd465529c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:20:17 +0100 Subject: [PATCH 14/60] Move to SungrowSH inverter.py --- .../devices/sungrow/sungrow/inverter.py | 66 ------------------- .../devices/sungrow/sungrow_sh/inverter.py | 56 ++++++++++++++++ 2 files changed, 56 insertions(+), 66 deletions(-) delete mode 100644 packages/modules/devices/sungrow/sungrow/inverter.py create mode 100644 packages/modules/devices/sungrow/sungrow_sh/inverter.py diff --git a/packages/modules/devices/sungrow/sungrow/inverter.py b/packages/modules/devices/sungrow/sungrow/inverter.py deleted file mode 100644 index db2d5a3807..0000000000 --- a/packages/modules/devices/sungrow/sungrow/inverter.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -from typing import Any, TypedDict - -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, Endian, ModbusTcpClient_ -from modules.common.simcount import SimCounter -from modules.common.store import get_inverter_value_store -from modules.devices.sungrow.sungrow.config import SungrowInverterSetup, Sungrow -from modules.devices.sungrow.sungrow.version import Version - - -class KwargsDict(TypedDict): - client: ModbusTcpClient_ - device_config: Sungrow - - -class SungrowInverter(AbstractInverter): - def __init__(self, component_config: SungrowInverterSetup, **kwargs: Any) -> None: - self.component_config = component_config - self.kwargs: KwargsDict = kwargs - - def initialize(self) -> None: - self.device_config: Sungrow = self.kwargs['device_config'] - self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] - self.sim_counter = SimCounter(self.device_config.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) -> float: - unit = self.device_config.configuration.modbus_id - - if self.device_config.configuration.version in (Version.SH, Version.SH_winet_dongle): - power = self.__tcp_client.read_input_registers(13033, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * -1 - - currents = self.__tcp_client.read_input_registers(13030, [ModbusDataType.INT_16]*3, unit=unit) - currents = [value * -0.1 for value in currents] - - elif self.device_config.configuration.version in (Version.SG, Version.SG_winet_dongle): - power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * -1 - - currents = self.__tcp_client.read_input_registers(5021, [ModbusDataType.INT_16]*3, unit=unit) - currents = [value * -0.1 for value in currents] - - imported, exported = self.sim_counter.sim_count(power, dc_power) - - inverter_state = InverterState( - power=power, - dc_power=dc_power, - currents=currents, - imported=imported, - exported=exported - ) - self.store.set(inverter_state) - return power - - -component_descriptor = ComponentDescriptor(configuration_factory=SungrowInverterSetup) diff --git a/packages/modules/devices/sungrow/sungrow_sh/inverter.py b/packages/modules/devices/sungrow/sungrow_sh/inverter.py new file mode 100644 index 0000000000..2f1c5a28b1 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sh/inverter.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +from typing import Any, TypedDict + +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, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.sungrow.sungrow_sh.config import SungrowSHInverterSetup, SungrowSH + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowSH + + +class SungrowSHInverter(AbstractInverter): + def __init__(self, component_config: SungrowSHInverterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowSH = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.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) -> float: + unit = self.device_config.configuration.modbus_id + + power = self.__tcp_client.read_input_registers(13033, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * -1 + + currents = self.__tcp_client.read_input_registers(13030, [ModbusDataType.INT_16]*3, unit=unit) + + currents = [value * -0.1 for value in currents] + + imported, exported = self.sim_counter.sim_count(power, dc_power) + + inverter_state = InverterState( + power=power, + dc_power=dc_power, + currents=currents, + imported=imported, + exported=exported + ) + self.store.set(inverter_state) + return power + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSHInverterSetup) From 9a2a2ebd13ea0faa0a532ed694086609fce8c3d4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:26:35 +0100 Subject: [PATCH 15/60] Move to SungrowSH counter.py --- .../{sungrow => sungrow_sh}/counter.py | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) rename packages/modules/devices/sungrow/{sungrow => sungrow_sh}/counter.py (61%) diff --git a/packages/modules/devices/sungrow/sungrow/counter.py b/packages/modules/devices/sungrow/sungrow_sh/counter.py similarity index 61% rename from packages/modules/devices/sungrow/sungrow/counter.py rename to packages/modules/devices/sungrow/sungrow_sh/counter.py index 67e6cf849d..b6cd3eaca3 100644 --- a/packages/modules/devices/sungrow/sungrow/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sh/counter.py @@ -8,22 +8,22 @@ from modules.common.modbus import Endian, ModbusDataType, ModbusTcpClient_ from modules.common.simcount._simcounter import SimCounter from modules.common.store import get_counter_value_store -from modules.devices.sungrow.sungrow.config import Sungrow, SungrowCounterSetup -from modules.devices.sungrow.sungrow.version import Version +from modules.devices.sungrow.sungrow_sh.config import SungrowSH, SungrowSHCounterSetup +from modules.devices.sungrow.sungrow_sh.version import Version class KwargsDict(TypedDict): client: ModbusTcpClient_ - device_config: Sungrow + device_config: SungrowSH -class SungrowCounter(AbstractCounter): - def __init__(self, component_config: SungrowCounterSetup, **kwargs: Any) -> None: +class SungrowSHCounter(AbstractCounter): + def __init__(self, component_config: SungrowSHCounterSetup, **kwargs: Any) -> None: self.component_config = component_config self.kwargs: KwargsDict = kwargs def initialize(self) -> None: - self.device_config: Sungrow = self.kwargs['device_config'] + self.device_config: SungrowSH = self.kwargs['device_config'] self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="evu") self.store = get_counter_value_store(self.component_config.id) @@ -34,29 +34,15 @@ def initialize(self) -> None: def update(self, pv_power: float): unit = self.device_config.configuration.modbus_id - if self.device_config.configuration.version in (Version.SH, Version.SH_winet_dongle): - power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - try: - powers = self.__tcp_client.read_input_registers(5602, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) - except Exception: - powers = None - self.fault_state.no_error(self.fault_text) - else: - if pv_power != 0: - power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) - else: - power = self.__tcp_client.read_input_registers(5090, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) - try: - powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) - except Exception: - powers = None - self.fault_state.no_error(self.fault_text) - + power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + try: + powers = self.__tcp_client.read_input_registers(5602, [ModbusDataType.INT_32] * 3, + wordorder=Endian.Little, unit=unit) + except Exception: + powers = None + self.fault_state.no_error(self.fault_text) + frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 if self.device_config.configuration.version == Version.SH_winet_dongle: # On WiNet-S, the frequency accuracy is higher by one place @@ -89,4 +75,4 @@ def update(self, pv_power: float): self.store.set(counter_state) -component_descriptor = ComponentDescriptor(configuration_factory=SungrowCounterSetup) +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSHCounterSetup) From 80a858a0c220d9ef002fddca8f85aadf8f03776e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:29:24 +0100 Subject: [PATCH 16/60] Move to SungrowSH config.py --- .../sungrow/{sungrow => sungrow_sh}/config.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) rename packages/modules/devices/sungrow/{sungrow => sungrow_sh}/config.py (52%) diff --git a/packages/modules/devices/sungrow/sungrow/config.py b/packages/modules/devices/sungrow/sungrow_sh/config.py similarity index 52% rename from packages/modules/devices/sungrow/sungrow/config.py rename to packages/modules/devices/sungrow/sungrow_sh/config.py index bc112dd867..d035b27de9 100644 --- a/packages/modules/devices/sungrow/sungrow/config.py +++ b/packages/modules/devices/sungrow/sungrow_sh/config.py @@ -1,72 +1,72 @@ from typing import Optional from modules.common.component_setup import ComponentSetup -from modules.devices.sungrow.sungrow.version import Version +from modules.devices.sungrow.sungrow_sh.version import Version from ..vendor import vendor_descriptor -class SungrowConfiguration: +class SungrowSHConfiguration: def __init__(self, ip_address: Optional[str] = None, port: int = 502, modbus_id: int = 1, - version: Version = Version.SG): + version: Version = Version.SH): self.ip_address = ip_address self.port = port self.modbus_id = modbus_id self.version = version -class Sungrow: +class SungrowSH: def __init__(self, - name: str = "Sungrow", - type: str = "sungrow", + name: str = "Sungrow SH", + type: str = "sungrow_sh", id: int = 0, configuration: SungrowConfiguration = None) -> None: self.name = name self.type = type self.vendor = vendor_descriptor.configuration_factory().type self.id = id - self.configuration = configuration or SungrowConfiguration() + self.configuration = configuration or SungrowSHConfiguration() -class SungrowBatConfiguration: +class SungrowSHBatConfiguration: def __init__(self): pass -class SungrowBatSetup(ComponentSetup[SungrowBatConfiguration]): +class SungrowSHBatSetup(ComponentSetup[SungrowSHBatConfiguration]): def __init__(self, - name: str = "Sungrow Speicher", + name: str = "Sungrow SH Speicher", type: str = "bat", id: int = 0, - configuration: SungrowBatConfiguration = None) -> None: - super().__init__(name, type, id, configuration or SungrowBatConfiguration()) + configuration: SungrowSHBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowSHBatConfiguration()) -class SungrowCounterConfiguration: +class SungrowSHCounterConfiguration: def __init__(self): pass -class SungrowCounterSetup(ComponentSetup[SungrowCounterConfiguration]): +class SungrowSHCounterSetup(ComponentSetup[SungrowSHCounterConfiguration]): def __init__(self, - name: str = "Sungrow Zähler", + name: str = "Sungrow SH Zähler", type: str = "counter", id: int = 0, - configuration: SungrowCounterConfiguration = None) -> None: - super().__init__(name, type, id, configuration or SungrowCounterConfiguration()) + configuration: SungrowSHCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowSHCounterConfiguration()) -class SungrowInverterConfiguration: +class SungrowSHInverterConfiguration: def __init__(self): pass -class SungrowInverterSetup(ComponentSetup[SungrowInverterConfiguration]): +class SungrowSHInverterSetup(ComponentSetup[SungrowSHInverterConfiguration]): def __init__(self, - name: str = "Sungrow Wechselrichter", + name: str = "Sungrow SH Wechselrichter", type: str = "inverter", id: int = 0, - configuration: SungrowInverterConfiguration = None) -> None: - super().__init__(name, type, id, configuration or SungrowInverterConfiguration()) + configuration: SungrowSHInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowSHInverterConfiguration()) From e8b18db181b9221d45674c4c8d19235bd690480a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:34:49 +0100 Subject: [PATCH 17/60] Move to SungrowSH device.py --- .../modules/devices/sungrow/sungrow/device.py | 61 ------------------- .../devices/sungrow/sungrow_sh/device.py | 54 ++++++++++++++++ 2 files changed, 54 insertions(+), 61 deletions(-) delete mode 100644 packages/modules/devices/sungrow/sungrow/device.py create mode 100644 packages/modules/devices/sungrow/sungrow_sh/device.py diff --git a/packages/modules/devices/sungrow/sungrow/device.py b/packages/modules/devices/sungrow/sungrow/device.py deleted file mode 100644 index 69e5ed1e3a..0000000000 --- a/packages/modules/devices/sungrow/sungrow/device.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -import logging -from typing import Iterable, Union - -from modules.common import modbus -from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater -from modules.devices.sungrow.sungrow.bat import SungrowBat -from modules.devices.sungrow.sungrow.config import Sungrow, SungrowBatSetup, SungrowCounterSetup, SungrowInverterSetup -from modules.devices.sungrow.sungrow.counter import SungrowCounter -from modules.devices.sungrow.sungrow.inverter import SungrowInverter - -log = logging.getLogger(__name__) - - -def create_device(device_config: Sungrow): - client = None - - def create_bat_component(component_config: SungrowBatSetup): - nonlocal client - return SungrowBat(component_config, device_config=device_config, client=client) - - def create_counter_component(component_config: SungrowCounterSetup): - nonlocal client - return SungrowCounter(component_config, device_config=device_config, client=client) - - def create_inverter_component(component_config: SungrowInverterSetup): - nonlocal client - return SungrowInverter(component_config, device_config=device_config, client=client) - - def update_components(components: Iterable[Union[SungrowBat, SungrowCounter, SungrowInverter]]): - pv_power = 0 - nonlocal client - with client: - for component in components: - if isinstance(component, SungrowInverter): - pv_power = component.update() - for component in components: - if isinstance(component, SungrowCounter): - component.update(pv_power) - for component in components: - if isinstance(component, SungrowBat): - component.update() - - def initializer(): - nonlocal client - client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) - - return ConfigurableDevice( - device_config=device_config, - initializer=initializer, - 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=Sungrow) diff --git a/packages/modules/devices/sungrow/sungrow_sh/device.py b/packages/modules/devices/sungrow/sungrow_sh/device.py new file mode 100644 index 0000000000..8a9344c342 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_sh/device.py @@ -0,0 +1,54 @@ +#!/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 import modbus +from modules.devices.sungrow.sungrow_sh.bat import SungrowSHBat +from modules.devices.sungrow.sungrow_sh.config import SungrowSH, SungrowSHBatSetup, SungrowSHCounterSetup, SungrowSHInverterSetup +from modules.devices.sungrow.sungrow_sh.counter import SungrowSHCounter +from modules.devices.sungrow.sungrow_sh.inverter import SungrowSHInverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: SungrowSH): + client = None + + def create_bat_component(component_config: SungrowSHBatSetup): + nonlocal client + return SungrowSHBat(component_config, device_config=device_config, client=client) + + def create_counter_component(component_config: SungrowSHCounterSetup): + nonlocal client + return SungrowSHCounter(component_config, device_config=device_config, client=client) + + def create_inverter_component(component_config: SungrowSHInverterSetup): + nonlocal client + return SungrowSHInverter(component_config, device_config=device_config, client=client) + + def update_components(components: Iterable[Union[SungrowSHBat, SungrowSHCounter, SungrowSHInverter]]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + 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=SungrowSH) From bc603ed4177145ea3cbd15297457b4b3ce8ef24d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:35:02 +0100 Subject: [PATCH 18/60] Delete packages/modules/devices/sungrow/sungrow/__init__.py --- packages/modules/devices/sungrow/sungrow/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/modules/devices/sungrow/sungrow/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow/__init__.py b/packages/modules/devices/sungrow/sungrow/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 45a072af013ac4e74f98c1e8b7488af24176b55e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:38:22 +0100 Subject: [PATCH 19/60] Remove unused parameter Removed pv_power parameter from update method. --- packages/modules/devices/sungrow/sungrow_sh/counter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/counter.py b/packages/modules/devices/sungrow/sungrow_sh/counter.py index b6cd3eaca3..d4b1079576 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sh/counter.py @@ -32,7 +32,7 @@ def initialize(self) -> None: "keine Leistung der einzelnen Phasen. "\ "Das Lastmanagement ist daher nur anhand der Gesamtleistung (nicht phasenbasiert) möglich." - def update(self, pv_power: float): + def update(self): unit = self.device_config.configuration.modbus_id power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 From 5b83bd80da26598167f8a04c50cfa3af7b34e6e7 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:38:58 +0100 Subject: [PATCH 20/60] Remove unused parameter Removed pv_power parameter from update method. --- packages/modules/devices/sungrow/sungrow_sg/counter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py index bbe9cf14b9..2a1a94930a 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -32,7 +32,7 @@ def initialize(self) -> None: "keine Leistung der einzelnen Phasen. "\ "Das Lastmanagement ist daher nur anhand der Gesamtleistung (nicht phasenbasiert) möglich." - def update(self, pv_power: float): + def update(self): unit = self.device_config.configuration.modbus_id power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) From 7dea7194724fc5717fb8b82c2bf9e9d1a6ca75e5 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:46:41 +0100 Subject: [PATCH 21/60] Refactor SungrowIHM counter.py --- .../devices/sungrow/sungrow_ihm/counter.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/counter.py b/packages/modules/devices/sungrow/sungrow_ihm/counter.py index 38bd515a0b..e539adf4b0 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/counter.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/counter.py @@ -8,12 +8,12 @@ from modules.common.modbus import Endian, ModbusDataType, ModbusTcpClient_ from modules.common.simcount._simcounter import SimCounter from modules.common.store import get_counter_value_store -from modules.devices.sungrow.sungrow_ihm.config import Sungrow, SungrowIHMCounterSetup +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHM, SungrowIHMCounterSetup class KwargsDict(TypedDict): client: ModbusTcpClient_ - device_config: Sungrow + device_config: SungrowIHM class SungrowIHMCounter(AbstractCounter): @@ -22,7 +22,7 @@ def __init__(self, component_config: SungrowIHMCounterSetup, **kwargs: Any) -> N self.kwargs: KwargsDict = kwargs def initialize(self) -> None: - self.device_config: Sungrow = self.kwargs['device_config'] + self.device_config: SungrowIHM = self.kwargs['device_config'] self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="evu") self.store = get_counter_value_store(self.component_config.id) @@ -32,12 +32,15 @@ def initialize(self) -> None: def update(self, pv_power: float): unit = self.device_config.configuration.modbus_id power = self.__tcp_client.read_input_registers(8156, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 + wordorder=Endian.Little, unit=unit) * -10 + powers = self.__tcp_client.read_input_registers(8558, [ModbusDataType.UINT_32] * 3, wordorder=Endian.Little, unit=unit) + frequency = self.__tcp_client.read_input_registers(8557, ModbusDataType.UINT_16, unit=unit) / 10 - voltages = self.__tcp_client.read_input_registers(8554, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) + + voltages = self.__tcp_client.read_input_registers(8554, [ModbusDataType.UINT_16] * 3, + wordorder=Endian.Little, unit=unit) voltages = [value / 10 for value in voltages] From b983fb54f6bb789a3b6f543f052b080e34a43a35 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:46:52 +0100 Subject: [PATCH 22/60] Add files via upload --- packages/modules/devices/sungrow/sungrow_ihm/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/__init__.py b/packages/modules/devices/sungrow/sungrow_ihm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 76b2b4ad3b2da07ea48a06050d5f9bd10499c5a4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:50:33 +0100 Subject: [PATCH 23/60] Add Sungrow IHM config.py --- .../devices/sungrow/sungrow_ihm/config.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/config.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/config.py b/packages/modules/devices/sungrow/sungrow_ihm/config.py new file mode 100644 index 0000000000..bddad7ee1a --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/config.py @@ -0,0 +1,70 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from modules.devices.sungrow.sungrow_ihm.version import Version +from ..vendor import vendor_descriptor + + +class SungrowIHMConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +class SungrowIHM: + def __init__(self, + name: str = "Sungrow iHomeManager", + type: str = "sungrow_ihm", + id: int = 0, + configuration: SungrowConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or SungrowIHMConfiguration() + + +class SungrowIHMBatConfiguration: + def __init__(self): + pass + + +class SungrowIHMBatSetup(ComponentSetup[SungrowIHMBatConfiguration]): + def __init__(self, + name: str = "Sungrow iHM Speicher", + type: str = "bat", + id: int = 0, + configuration: SungrowIHMBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowIHMBatConfiguration()) + + +class SungrowIHMCounterConfiguration: + def __init__(self): + pass + + +class SungrowIHMCounterSetup(ComponentSetup[SungrowIHMCounterConfiguration]): + def __init__(self, + name: str = "Sungrow iHM Zähler", + type: str = "counter", + id: int = 0, + configuration: SungrowIHMCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowIHMCounterConfiguration()) + + +class SungrowIHMInverterConfiguration: + def __init__(self): + pass + + +class SungrowIHMInverterSetup(ComponentSetup[SungrowIHMInverterConfiguration]): + def __init__(self, + name: str = "Sungrow iHM Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: SungrowIHMInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowIHMInverterConfiguration()) From 22fce2041a0cf8696afb1b92fe0dc509c0e08b9d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:51:27 +0100 Subject: [PATCH 24/60] Update configuration types in Sungrow classes --- packages/modules/devices/sungrow/sungrow_ihm/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/config.py b/packages/modules/devices/sungrow/sungrow_ihm/config.py index bddad7ee1a..c9544c8129 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/config.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/config.py @@ -20,7 +20,7 @@ def __init__(self, name: str = "Sungrow iHomeManager", type: str = "sungrow_ihm", id: int = 0, - configuration: SungrowConfiguration = None) -> None: + configuration: SungrowIHMConfiguration = None) -> None: self.name = name self.type = type self.vendor = vendor_descriptor.configuration_factory().type @@ -35,10 +35,10 @@ def __init__(self): class SungrowIHMBatSetup(ComponentSetup[SungrowIHMBatConfiguration]): def __init__(self, - name: str = "Sungrow iHM Speicher", + name: str = " iHM Speicher", type: str = "bat", id: int = 0, - configuration: SungrowIHMBatConfiguration = None) -> None: + configuration: IHMBatConfiguration = None) -> None: super().__init__(name, type, id, configuration or SungrowIHMBatConfiguration()) From 5fcfc0e0dd0bb3a15cb1dc507b99458ec25d31cf Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:52:41 +0100 Subject: [PATCH 25/60] Add Sungrow IHM device.py --- .../devices/sungrow/sungrow_ihm/device.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/device.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/device.py b/packages/modules/devices/sungrow/sungrow_ihm/device.py new file mode 100644 index 0000000000..0c42bfd0d6 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/device.py @@ -0,0 +1,54 @@ +#!/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 import modbus +from modules.devices.sungrow.sungrow_ihm.bat import SungrowIHMBat +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHM, SungrowIHMBatSetup, SungrowIHMCounterSetup, SungrowIHMInverterSetup +from modules.devices.sungrow.sungrow_ihm.counter import SungrowIHMCounter +from modules.devices.sungrow.sungrow_ihm.inverter import SungrowIHMInverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: SungrowIHM): + client = None + + def create_bat_component(component_config: SungrowIHMBatSetup): + nonlocal client + return SungrowIHMBat(component_config, device_config=device_config, client=client) + + def create_counter_component(component_config: SungrowIHMCounterSetup): + nonlocal client + return SungrowIHMCounter(component_config, device_config=device_config, client=client) + + def create_inverter_component(component_config: SungrowIHMInverterSetup): + nonlocal client + return SungrowIHMInverter(component_config, device_config=device_config, client=client) + + def update_components(components: Iterable[Union[SungrowIHMBat, SungrowIHMCounter, SungrowIHMInverter]]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + 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=SungrowIHM) From bf89fc42b122da8afdb5d053063f1ecde5a22ee3 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:57:20 +0100 Subject: [PATCH 26/60] Add Sungrow IHM inverter.py --- .../devices/sungrow/sungrow_ihm/inverter.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/inverter.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/inverter.py b/packages/modules/devices/sungrow/sungrow_ihm/inverter.py new file mode 100644 index 0000000000..f823973c92 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/inverter.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +from typing import Any, TypedDict + +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, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHMInverterSetup, SungrowIHM + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowIHM + + +class SungrowIHMInverter(AbstractInverter): + def __init__(self, component_config: SungrowIHMInverterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowIHM = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.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) -> float: + unit = self.device_config.configuration.modbus_id + + power = self.__tcp_client.read_input_registers(8154, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -10 + + imported, exported = self.sim_counter.sim_count(power) + + inverter_state = InverterState( + power=power, + imported=imported, + exported=exported + ) + self.store.set(inverter_state) + return power + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowIHMInverterSetup) From 23217f685e2a2d3ee6881aac631bcf5924aa28ef Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:26:57 +0100 Subject: [PATCH 27/60] Add Sungrow IHM bat.py --- .../devices/sungrow/sungrow_ihm/bat.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_ihm/bat.py diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py new file mode 100644 index 0000000000..6bdf56504d --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_ihm/bat.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +import logging +from typing import Any, Optional, TypedDict + +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, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHMBatSetup, SungrowIHM + +log = logging.getLogger(__name__) + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowIHM + + +class SungrowIHMBat(AbstractBat): + def __init__(self, component_config: SungrowIHMBatSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowIHM = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.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)) + self.last_mode = 'Undefined' + + def update(self) -> None: + unit = self.device_config.configuration.modbus_id + soc = int(self.__tcp_client.read_input_registers(8162, ModbusDataType.UINT_16, unit=unit) / 10) + + bat_power = self.__tcp_client.read_input_registers(8160, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -10 + + imported, exported = self.sim_counter.sim_count(bat_power) + + bat_state = BatState( + power=bat_power, + soc=soc, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + def set_power_limit(self, power_limit: Optional[int]) -> None: + unit = self.device_config.configuration.modbus_id + log.debug(f'last_mode: {self.last_mode}') + + if power_limit is None: + log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") + if self.last_mode is not None: + self.__tcp_client.write_registers(8023, [1], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(8024, [0xCC], data_type=ModbusDataType.UINT_16, unit=unit) + self.last_mode = None + elif power_limit == 0: + log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") + if self.last_mode != 'stop': + self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(8024, [0xCC], data_type=ModbusDataType.UINT_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") + if self.last_mode != 'discharge': + self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(8024, [0xBB], data_type=ModbusDataType.UINT_16, unit=unit) + self.last_mode = 'discharge' + power_value = int(power_limit / 100) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") + self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) + elif power_limit > 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + if self.last_mode != 'charge': + self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(8025, [0xAA], data_type=ModbusDataType.UINT_16, unit=unit) + self.last_mode = 'charge' + power_value = int(power_limit / 100) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) + + def power_limit_controllable(self) -> bool: + return True + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowIHMBatSetup) From 4a4475ec2c0d4321be88a06643ee5098555a4096 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:27:43 +0100 Subject: [PATCH 28/60] Add charging for SH bat.py --- packages/modules/devices/sungrow/sungrow_sh/bat.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/modules/devices/sungrow/sungrow_sh/bat.py b/packages/modules/devices/sungrow/sungrow_sh/bat.py index 9217d28b8b..0b15be11d4 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/bat.py +++ b/packages/modules/devices/sungrow/sungrow_sh/bat.py @@ -140,6 +140,16 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: power_value = int(min(abs(power_limit), 5000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") self.__tcp_client.write_registers(13051, [power_value], data_type=ModbusDataType.UINT_16, unit=unit) + elif power_limit > 0: + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") + if self.last_mode != 'charge': + self.__tcp_client.write_registers(13049, [2], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(13050, [0xAA], data_type=ModbusDataType.UINT_16, unit=unit) + self.last_mode = 'charge' + # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. + power_value = int(min(power_limit, 5000)) + log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen") + self.__tcp_client.write_registers(13051, [power_value], data_type=ModbusDataType.UINT_16, unit=unit) def power_limit_controllable(self) -> bool: return True From 1899a11565be40ee0ef1ceea705952b48264a8f3 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:42:45 +0100 Subject: [PATCH 29/60] Implement Sungrow Micro Inverter --- .../devices/sungrow/sungrow_micro/inverter.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_micro/inverter.py diff --git a/packages/modules/devices/sungrow/sungrow_micro/inverter.py b/packages/modules/devices/sungrow/sungrow_micro/inverter.py new file mode 100644 index 0000000000..4061f80384 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_micro/inverter.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +from typing import Any, TypedDict + +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, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.sungrow.sungrow_micro.config import SungrowMicroInverterSetup, SungrowMicro +from modules.devices.sungrow.sungrow_micro.version import Version + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + device_config: SungrowMicro + + +class SungrowMicroInverter(AbstractInverter): + def __init__(self, component_config: SungrowMicroInverterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.device_config: SungrowMicro = self.kwargs['device_config'] + self.__tcp_client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.device_config.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) -> float: + unit = self.device_config.configuration.modbus_id + + power = self.__tcp_client.read_input_registers(32213, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * -1 + + imported, exported = self.sim_counter.sim_count(power) + + inverter_state = InverterState( + power=power, + imported=imported, + exported=exported + ) + self.store.set(inverter_state) + return power + + +component_descriptor = ComponentDescriptor(configuration_factory=SungrowMicroInverterSetup) From ca53d61d55aa0b82316f955a75939ce56ab5f11e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:42:56 +0100 Subject: [PATCH 30/60] Add files via upload --- packages/modules/devices/sungrow/sungrow_micro/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/modules/devices/sungrow/sungrow_micro/__init__.py diff --git a/packages/modules/devices/sungrow/sungrow_micro/__init__.py b/packages/modules/devices/sungrow/sungrow_micro/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From e8a7705841def8bbd18a9512ead422b6c80b6c1b Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:44:46 +0100 Subject: [PATCH 31/60] Implement Sungrow Micro device configuration --- .../devices/sungrow/sungrow_micro/device.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_micro/device.py diff --git a/packages/modules/devices/sungrow/sungrow_micro/device.py b/packages/modules/devices/sungrow/sungrow_micro/device.py new file mode 100644 index 0000000000..27593e5f35 --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_micro/device.py @@ -0,0 +1,42 @@ +#!/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 import modbus +from modules.devices.sungrow.sungrow_micro.config import SungrowMicro, SungrowMicroInverterSetup +from modules.devices.sungrow.sungrow_micro.inverter import SungrowMicroInverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: SungrowMicro): + client = None + + def create_inverter_component(component_config: SungrowMicroInverterSetup): + nonlocal client + return SungrowMicroInverter(component_config, device_config=device_config, client=client) + + def update_components(components: Iterable[Union[SungrowMicroInverter]]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + component_factory=ComponentFactoryByType( + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=SungrowMicro) From 0b5529e1412171215788b622499d1075a0dd2ac6 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:47:18 +0100 Subject: [PATCH 32/60] Add SungrowMicro and configuration classes --- .../devices/sungrow/sungrow_micro/config.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/modules/devices/sungrow/sungrow_micro/config.py diff --git a/packages/modules/devices/sungrow/sungrow_micro/config.py b/packages/modules/devices/sungrow/sungrow_micro/config.py new file mode 100644 index 0000000000..3ef287f2fa --- /dev/null +++ b/packages/modules/devices/sungrow/sungrow_micro/config.py @@ -0,0 +1,42 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from modules.devices.sungrow.sungrow_micro.version import Version +from ..vendor import vendor_descriptor + + +class SungrowMicroConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +class SungrowMicro: + def __init__(self, + name: str = "Sungrow Microwechselrichter", + type: str = "sungrow_micro", + id: int = 0, + configuration: SungrowMicroConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or SungrowMicroConfiguration() + + +class SungrowMicroInverterConfiguration: + def __init__(self): + pass + + +class SungrowMicroInverterSetup(ComponentSetup[SungrowMicroInverterConfiguration]): + def __init__(self, + name: str = "Sungrow Microwechselrichter SxxxxS", + type: str = "inverter", + id: int = 0, + configuration: SungrowMicroInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SungrowMicroInverterConfiguration()) From 58e7a34e0ffa0bd5cbf600d7a24ae596cc1c9278 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:51:32 +0100 Subject: [PATCH 33/60] Update inverter.py --- packages/modules/devices/sungrow/sungrow_micro/inverter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_micro/inverter.py b/packages/modules/devices/sungrow/sungrow_micro/inverter.py index 4061f80384..dc72a8d7ac 100644 --- a/packages/modules/devices/sungrow/sungrow_micro/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_micro/inverter.py @@ -9,7 +9,6 @@ from modules.common.simcount import SimCounter from modules.common.store import get_inverter_value_store from modules.devices.sungrow.sungrow_micro.config import SungrowMicroInverterSetup, SungrowMicro -from modules.devices.sungrow.sungrow_micro.version import Version class KwargsDict(TypedDict): From 47fa7d6586bac1c6bed2ce9c3ae85389026c8c7a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:52:01 +0100 Subject: [PATCH 34/60] Remove version import from inverter.py --- packages/modules/devices/sungrow/sungrow_sg/inverter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/inverter.py b/packages/modules/devices/sungrow/sungrow_sg/inverter.py index ff7dbe6dd5..a96ff0feb8 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/inverter.py @@ -9,7 +9,6 @@ from modules.common.simcount import SimCounter from modules.common.store import get_inverter_value_store from modules.devices.sungrow.sungrow_sg.config import SungrowSGInverterSetup, SungrowSG -from modules.devices.sungrow.sungrow_sg.version import Version class KwargsDict(TypedDict): From 9d95a9aa48f0938a95fe65b4977624e2cc92e049 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:07:33 +0100 Subject: [PATCH 35/60] Reorder import statements in counter.py --- packages/modules/devices/sungrow/sungrow_sh/counter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/counter.py b/packages/modules/devices/sungrow/sungrow_sh/counter.py index d4b1079576..c3956e9bc7 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sh/counter.py @@ -5,8 +5,8 @@ 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 Endian, ModbusDataType, ModbusTcpClient_ -from modules.common.simcount._simcounter import SimCounter +from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.sungrow.sungrow_sh.config import SungrowSH, SungrowSHCounterSetup from modules.devices.sungrow.sungrow_sh.version import Version From 4f9d00eb5da9432b961f0abfeb865712ab1385f9 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:08:48 +0100 Subject: [PATCH 36/60] Reorder import statements in counter.py --- packages/modules/devices/sungrow/sungrow_sg/counter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py index 2a1a94930a..73c3174766 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -5,11 +5,10 @@ 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 Endian, ModbusDataType, ModbusTcpClient_ -from modules.common.simcount._simcounter import SimCounter +from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.sungrow.sungrow_sg.config import SungrowSG, SungrowSGCounterSetup -from modules.devices.sungrow.sungrow_sg.version import Version class KwargsDict(TypedDict): From b418460d367014f29abafb34e5b8882269791bb4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:10:09 +0100 Subject: [PATCH 37/60] Reorder import statements in counter.py --- packages/modules/devices/sungrow/sungrow_ihm/counter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/counter.py b/packages/modules/devices/sungrow/sungrow_ihm/counter.py index e539adf4b0..ac41939cb8 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/counter.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/counter.py @@ -5,8 +5,8 @@ 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 Endian, ModbusDataType, ModbusTcpClient_ -from modules.common.simcount._simcounter import SimCounter +from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ +from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.sungrow.sungrow_ihm.config import SungrowIHM, SungrowIHMCounterSetup From fbebc266068013c8c97fe1e57f11f3e74a5889e2 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:05:23 +0100 Subject: [PATCH 38/60] Update config.py --- packages/modules/devices/sungrow/sungrow_ihm/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/config.py b/packages/modules/devices/sungrow/sungrow_ihm/config.py index c9544c8129..428f791274 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/config.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/config.py @@ -8,8 +8,8 @@ class SungrowIHMConfiguration: def __init__(self, ip_address: Optional[str] = None, - port: int = 502, - modbus_id: int = 1): + port: int = 503, + modbus_id: int = 247): self.ip_address = ip_address self.port = port self.modbus_id = modbus_id From 617864cabb30a83115191710b029aab3b2409921 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:46:09 +0100 Subject: [PATCH 39/60] Flake8 --- packages/modules/devices/sungrow/sungrow_micro/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_micro/config.py b/packages/modules/devices/sungrow/sungrow_micro/config.py index 3ef287f2fa..b2bf9fcc99 100644 --- a/packages/modules/devices/sungrow/sungrow_micro/config.py +++ b/packages/modules/devices/sungrow/sungrow_micro/config.py @@ -1,7 +1,6 @@ from typing import Optional from modules.common.component_setup import ComponentSetup -from modules.devices.sungrow.sungrow_micro.version import Version from ..vendor import vendor_descriptor From cc8a13a4212ec6a8710df6a544491b84e75db4a2 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:47:12 +0100 Subject: [PATCH 40/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sh/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/device.py b/packages/modules/devices/sungrow/sungrow_sh/device.py index 8a9344c342..e5d6c47a78 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/device.py +++ b/packages/modules/devices/sungrow/sungrow_sh/device.py @@ -7,7 +7,8 @@ from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common import modbus from modules.devices.sungrow.sungrow_sh.bat import SungrowSHBat -from modules.devices.sungrow.sungrow_sh.config import SungrowSH, SungrowSHBatSetup, SungrowSHCounterSetup, SungrowSHInverterSetup +from modules.devices.sungrow.sungrow_sh.config import SungrowSH, SungrowSHBatSetup +from modules.devices.sungrow.sungrow_sh.config import SungrowSHCounterSetup, SungrowSHInverterSetup from modules.devices.sungrow.sungrow_sh.counter import SungrowSHCounter from modules.devices.sungrow.sungrow_sh.inverter import SungrowSHInverter From f6d04b25afb3d6726fa518377ad9390086da4b7c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:48:12 +0100 Subject: [PATCH 41/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sh/inverter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/inverter.py b/packages/modules/devices/sungrow/sungrow_sh/inverter.py index 2f1c5a28b1..acaf859403 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_sh/inverter.py @@ -31,13 +31,13 @@ def initialize(self) -> None: def update(self) -> float: unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(13033, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(13033, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 - dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, + dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) * -1 currents = self.__tcp_client.read_input_registers(13030, [ModbusDataType.INT_16]*3, unit=unit) - + currents = [value * -0.1 for value in currents] imported, exported = self.sim_counter.sim_count(power, dc_power) From 0b2fd7ba064ad9d9b1dfe7f1c3989df8fc8c76c1 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:49:07 +0100 Subject: [PATCH 42/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sh/counter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/counter.py b/packages/modules/devices/sungrow/sungrow_sh/counter.py index c3956e9bc7..d1ad8e4d9e 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sh/counter.py @@ -34,15 +34,15 @@ def initialize(self) -> None: def update(self): unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 try: - powers = self.__tcp_client.read_input_registers(5602, [ModbusDataType.INT_32] * 3, + powers = self.__tcp_client.read_input_registers(5602, [ModbusDataType.INT_32] * 3, wordorder=Endian.Little, unit=unit) except Exception: powers = None self.fault_state.no_error(self.fault_text) - + frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 if self.device_config.configuration.version == Version.SH_winet_dongle: # On WiNet-S, the frequency accuracy is higher by one place From e906b7a18d274d95210e1c30d5928bf3226627a4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:49:36 +0100 Subject: [PATCH 43/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sh/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/config.py b/packages/modules/devices/sungrow/sungrow_sh/config.py index d035b27de9..6a1e8b1def 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/config.py +++ b/packages/modules/devices/sungrow/sungrow_sh/config.py @@ -22,7 +22,7 @@ def __init__(self, name: str = "Sungrow SH", type: str = "sungrow_sh", id: int = 0, - configuration: SungrowConfiguration = None) -> None: + configuration: SungrowSHConfiguration = None) -> None: self.name = name self.type = type self.vendor = vendor_descriptor.configuration_factory().type From 7054eb25ff466737e7824c97af505ffbfd5fed49 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:50:10 +0100 Subject: [PATCH 44/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sg/inverter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/inverter.py b/packages/modules/devices/sungrow/sungrow_sg/inverter.py index a96ff0feb8..344c17a3f9 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/inverter.py @@ -31,13 +31,13 @@ def initialize(self) -> None: def update(self) -> float: unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 - dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, + dc_power = self.__tcp_client.read_input_registers(5016, ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) * -1 currents = self.__tcp_client.read_input_registers(5021, [ModbusDataType.INT_16]*3, unit=unit) - + currents = [value * -0.1 for value in currents] imported, exported = self.sim_counter.sim_count(power, dc_power) From cc1fb65dc972480e7d43b165c581bbac6d994d6c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:50:56 +0100 Subject: [PATCH 45/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sg/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/config.py b/packages/modules/devices/sungrow/sungrow_sg/config.py index fb1bdafe9e..a6bf4a3b76 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/config.py +++ b/packages/modules/devices/sungrow/sungrow_sg/config.py @@ -1,7 +1,6 @@ from typing import Optional from modules.common.component_setup import ComponentSetup -from modules.devices.sungrow.sungrow_sg.version import Version from ..vendor import vendor_descriptor From 175e3726f0b38b86b433249a5d5737dba9e31a0e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:51:45 +0100 Subject: [PATCH 46/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/bat.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py index 6bdf56504d..fff8494b7f 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/bat.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/bat.py @@ -36,11 +36,11 @@ def update(self) -> None: unit = self.device_config.configuration.modbus_id soc = int(self.__tcp_client.read_input_registers(8162, ModbusDataType.UINT_16, unit=unit) / 10) - bat_power = self.__tcp_client.read_input_registers(8160, ModbusDataType.INT_32, + bat_power = self.__tcp_client.read_input_registers(8160, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -10 imported, exported = self.sim_counter.sim_count(bat_power) - + bat_state = BatState( power=bat_power, soc=soc, @@ -73,7 +73,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.last_mode = 'discharge' power_value = int(power_limit / 100) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") - self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) elif power_limit > 0: log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") @@ -83,7 +83,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.last_mode = 'charge' power_value = int(power_limit / 100) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") - self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) def power_limit_controllable(self) -> bool: From 6cf24b84e0cc454ae06028553e0d8a637c1517e9 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:52:20 +0100 Subject: [PATCH 47/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/device.py b/packages/modules/devices/sungrow/sungrow_ihm/device.py index 0c42bfd0d6..4619549b00 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/device.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/device.py @@ -7,7 +7,8 @@ from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common import modbus from modules.devices.sungrow.sungrow_ihm.bat import SungrowIHMBat -from modules.devices.sungrow.sungrow_ihm.config import SungrowIHM, SungrowIHMBatSetup, SungrowIHMCounterSetup, SungrowIHMInverterSetup +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHM, SungrowIHMBatSetup +from modules.devices.sungrow.sungrow_ihm.config import SungrowIHMCounterSetup, SungrowIHMInverterSetup from modules.devices.sungrow.sungrow_ihm.counter import SungrowIHMCounter from modules.devices.sungrow.sungrow_ihm.inverter import SungrowIHMInverter From 93a16c894d4c7779ce935039398c6b47d7eb6ec1 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:52:37 +0100 Subject: [PATCH 48/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/inverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/inverter.py b/packages/modules/devices/sungrow/sungrow_ihm/inverter.py index f823973c92..eaa705b9fa 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/inverter.py @@ -31,7 +31,7 @@ def initialize(self) -> None: def update(self) -> float: unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(8154, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(8154, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -10 imported, exported = self.sim_counter.sim_count(power) From f5bd378856e84785193f1529f625ca5da25a7123 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:53:20 +0100 Subject: [PATCH 49/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/counter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/counter.py b/packages/modules/devices/sungrow/sungrow_ihm/counter.py index ac41939cb8..ea504be18e 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/counter.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/counter.py @@ -28,8 +28,7 @@ def initialize(self) -> None: self.store = get_counter_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - - def update(self, pv_power: float): + def update(self): unit = self.device_config.configuration.modbus_id power = self.__tcp_client.read_input_registers(8156, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -10 From dd5dc39371397bd307cf38fc373ca30b490d10fb Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:53:57 +0100 Subject: [PATCH 50/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/counter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/counter.py b/packages/modules/devices/sungrow/sungrow_ihm/counter.py index ea504be18e..17dbaf150b 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/counter.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/counter.py @@ -32,13 +32,13 @@ def update(self): unit = self.device_config.configuration.modbus_id power = self.__tcp_client.read_input_registers(8156, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -10 - + powers = self.__tcp_client.read_input_registers(8558, [ModbusDataType.UINT_32] * 3, wordorder=Endian.Little, unit=unit) - + frequency = self.__tcp_client.read_input_registers(8557, ModbusDataType.UINT_16, unit=unit) / 10 - - voltages = self.__tcp_client.read_input_registers(8554, [ModbusDataType.UINT_16] * 3, + + voltages = self.__tcp_client.read_input_registers(8554, [ModbusDataType.UINT_16] * 3, wordorder=Endian.Little, unit=unit) voltages = [value / 10 for value in voltages] From f02a5e90d609c43e2b891ea924c005b04e8a78fd Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:55:13 +0100 Subject: [PATCH 51/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/config.py b/packages/modules/devices/sungrow/sungrow_ihm/config.py index 428f791274..32c362a57e 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/config.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/config.py @@ -1,7 +1,6 @@ from typing import Optional from modules.common.component_setup import ComponentSetup -from modules.devices.sungrow.sungrow_ihm.version import Version from ..vendor import vendor_descriptor From 6ef982557c4dc6f8cef9f75f31e8d1d7ac7438e1 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:56:36 +0100 Subject: [PATCH 52/60] Flake8 --- packages/modules/devices/sungrow/sungrow_micro/inverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_micro/inverter.py b/packages/modules/devices/sungrow/sungrow_micro/inverter.py index dc72a8d7ac..9eb479e4b7 100644 --- a/packages/modules/devices/sungrow/sungrow_micro/inverter.py +++ b/packages/modules/devices/sungrow/sungrow_micro/inverter.py @@ -31,7 +31,7 @@ def initialize(self) -> None: def update(self) -> float: unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(32213, ModbusDataType.UINT_32, + power = self.__tcp_client.read_input_registers(32213, ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) * -1 imported, exported = self.sim_counter.sim_count(power) From 185345ffdb18e4b5627769cd31c59eb71e0ed68c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:57:32 +0100 Subject: [PATCH 53/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sg/counter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py index 73c3174766..93eb3502f7 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -39,9 +39,9 @@ def update(self): try: powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, wordorder=Endian.Little, unit=unit) - except Exception: - powers = None - self.fault_state.no_error(self.fault_text) + except Exception: + powers = None + self.fault_state.no_error(self.fault_text) frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 From 19635cb7c7d1d557f24af32e2c337844bac7912e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:58:16 +0100 Subject: [PATCH 54/60] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/config.py b/packages/modules/devices/sungrow/sungrow_ihm/config.py index 32c362a57e..c66e690824 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/config.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/config.py @@ -37,7 +37,7 @@ def __init__(self, name: str = " iHM Speicher", type: str = "bat", id: int = 0, - configuration: IHMBatConfiguration = None) -> None: + configuration: SungrowIHMBatConfiguration = None) -> None: super().__init__(name, type, id, configuration or SungrowIHMBatConfiguration()) From 87f2b38561453f6a898a69fe8cd8f015e8662d2b Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:00:10 +0100 Subject: [PATCH 55/60] Flake8 --- .../modules/devices/sungrow/sungrow_sg/counter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py index 93eb3502f7..1b66db2185 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -33,21 +33,21 @@ def initialize(self) -> None: def update(self): unit = self.device_config.configuration.modbus_id - power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) try: - powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) + powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, + wordorder=Endian.Little, unit=unit) except Exception: powers = None self.fault_state.no_error(self.fault_text) frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 - + power_factor = self.__tcp_client.read_input_registers(5034, ModbusDataType.INT_16, unit=unit) / 1000 - - voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, + + voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, wordorder=Endian.Little, unit=unit) voltages = [value / 10 for value in voltages] From 92fc3e2d5da876f9b68c96874a4bbffe750df88c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:01:52 +0100 Subject: [PATCH 56/60] Flake8 --- packages/modules/devices/sungrow/sungrow_sg/counter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sungrow/sungrow_sg/counter.py b/packages/modules/devices/sungrow/sungrow_sg/counter.py index 1b66db2185..593ef19c10 100644 --- a/packages/modules/devices/sungrow/sungrow_sg/counter.py +++ b/packages/modules/devices/sungrow/sungrow_sg/counter.py @@ -38,7 +38,7 @@ def update(self): try: powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) + wordorder=Endian.Little, unit=unit) except Exception: powers = None self.fault_state.no_error(self.fault_text) From 50a1f89035436d9064e36df771f308e58ee18a87 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:42:47 +0100 Subject: [PATCH 57/60] Update update_config.py --- packages/helpermodules/update_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 15530c627d..a7269d5270 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -44,7 +44,7 @@ from control.optional_data import Ocpp from modules.common.abstract_vehicle import GeneralVehicleConfig from modules.common.component_type import ComponentType -from modules.devices.sungrow.sungrow.version import Version +from modules.devices.sungrow.sungrow_sh.version import Version from modules.display_themes.cards.config import CardsDisplayTheme from modules.io_actions.controllable_consumers.ripple_control_receiver.config import RippleControlReceiverSetup from modules.web_themes.koala.config import KoalaWebTheme From fd1ab5f0094b661210209569550b5093a91b3a68 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Fri, 30 Jan 2026 17:27:03 +0100 Subject: [PATCH 58/60] Upgrade datastore version to 108 and add migration for Sungrow --- packages/helpermodules/update_config.py | 79 ++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index a7269d5270..23e0484feb 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 107 + DATASTORE_VERSION = 108 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2701,3 +2701,80 @@ def upgrade(topic: str, payload) -> None: return {topic: provider} self._loop_all_received_topics(upgrade) self._append_datastore_version(107) + + def upgrade_datastore_108(self) -> None: + """ + Migrate old single 'sungrow' devices into new modules: + - 'sungrow_sg' for SG family (no version field) + - 'sungrow_sh' for SH family (keeps numeric version 0 or 1) + + Old version mapping: + * 0 -> sungrow_sh, version 0 + * 1 -> sungrow_sg (remove version) + * 2 -> sungrow_sg (remove version) + * 3 -> sungrow_sh, version 1 + Default for missing/unknown old version: sungrow_sg (no version). + """ + def upgrade(topic: str, payload) -> None: + if re.search(r"^openWB/system/device/[0-9]+$", topic) is not None: + device = decode_payload(payload) + if device.get("type") == "sungrow": + old_version = device.get("configuration", {}).get("version") + new_type = "sungrow_sg" + new_version = None + if old_version == 0: + new_type = "sungrow_sh" + new_version = 0 + elif old_version in (1, 2): + new_type = "sungrow_sg" + new_version = None + elif old_version == 3: + new_type = "sungrow_sh" + new_version = 1 + else: + new_type = "sungrow_sg" + new_version = None + changed = False + if device.get("type") != new_type: + device["type"] = new_type + changed = True + if "configuration" not in device or device["configuration"] is None: + device["configuration"] = {} + if new_version is None: + if "version" in device["configuration"]: + device["configuration"].pop("version", None) + changed = True + else: + if device["configuration"].get("version") != new_version: + device["configuration"]["version"] = new_version + changed = True + if changed: + device_name = device.get("name") + device_id = device.get("id") + log.info( + f"Upgrading sungrow device {device_name!r} (id={device_id}) -> " + f"type='{new_type}'" + + (f", version={new_version}" if new_version is not None else ", no version") + ) + Pub().pub(topic, device) + if new_version is not None: + try: + version_name = Version(new_version).name + except Exception: + version_name = str(new_version) + pub_system_message( + device, + (f"Die Konfiguration von '{device_name}' wurde aktualisiert. " + f"Bitte in den Geräteeinstellungen sicherstellen, dass Version " + f"'{version_name}' korrekt ist"), + MessageType.INFO, + ) + else: + pub_system_message( + device, + (f"Die Sungrow-Geräte-Konfiguration wurde aktualisiert: Gerät " + f"'{device_name}' auf Typ '{new_type}'."), + MessageType.INFO, + ) + self._loop_all_received_topics(upgrade) + self._append_datastore_version(108) From 1002955df97ebb0cb5703c45600af855afaf5640 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Feb 2026 13:59:41 +0100 Subject: [PATCH 59/60] Refactor write_registers to write_register calls --- .../modules/devices/sungrow/sungrow_sh/bat.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_sh/bat.py b/packages/modules/devices/sungrow/sungrow_sh/bat.py index 0b15be11d4..fc5a6a767d 100644 --- a/packages/modules/devices/sungrow/sungrow_sh/bat.py +++ b/packages/modules/devices/sungrow/sungrow_sh/bat.py @@ -121,35 +121,35 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: - self.__tcp_client.write_registers(13049, [0], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(13050, [0xCC], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13049, 0, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13050, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None elif power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': - self.__tcp_client.write_registers(13049, [2], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(13050, [0xCC], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13050, 0xCC, data_type=ModbusDataType.UINT_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") if self.last_mode != 'discharge': - self.__tcp_client.write_registers(13049, [2], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(13050, [0xBB], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13050, 0xBB, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. power_value = int(min(abs(power_limit), 5000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W entladen für den Hausverbrauch") - self.__tcp_client.write_registers(13051, [power_value], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) elif power_limit > 0: log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") if self.last_mode != 'charge': - self.__tcp_client.write_registers(13049, [2], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(13050, [0xAA], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13049, 2, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13050, 0xAA, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' # Die maximale Entladeleistung begrenzen auf 5000W, maximaler Wertebereich Modbusregister. power_value = int(min(power_limit, 5000)) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_value} W geladen") - self.__tcp_client.write_registers(13051, [power_value], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) def power_limit_controllable(self) -> bool: return True From 0a6db8a7d37dd053829a5a331fadac562d9d12d2 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Feb 2026 14:01:00 +0100 Subject: [PATCH 60/60] Refactor write_registers calls to write_register --- .../devices/sungrow/sungrow_ihm/bat.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py index fff8494b7f..302ea96058 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/bat.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/bat.py @@ -56,34 +56,34 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if power_limit is None: log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") if self.last_mode is not None: - self.__tcp_client.write_registers(8023, [1], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(8024, [0xCC], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8023, 1, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8024, 0xCC, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = None elif power_limit == 0: log.debug("Aktive Batteriesteuerung. Batterie wird auf Stop gesetzt und nicht entladen") if self.last_mode != 'stop': - self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(8024, [0xCC], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8024, 0xCC, data_type=ModbusDataType.UINT_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") if self.last_mode != 'discharge': - self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(8024, [0xBB], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8024, 0xBB, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'discharge' power_value = int(power_limit / 100) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W entladen für den Hausverbrauch") - self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + self.__tcp_client.write_register(8025, power_value, data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) elif power_limit > 0: log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") if self.last_mode != 'charge': - self.__tcp_client.write_registers(8023, [5], data_type=ModbusDataType.UINT_16, unit=unit) - self.__tcp_client.write_registers(8025, [0xAA], data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8023, 5, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_register(8025, 0xAA, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'charge' power_value = int(power_limit / 100) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") - self.__tcp_client.write_registers(8025, [power_value], data_type=ModbusDataType.UINT_32, + self.__tcp_client.write_register(8025, power_value, data_type=ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) def power_limit_controllable(self) -> bool: