From e45bddc69f1dcb6498dc7fe1f895c97d2bd0f6ae Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:04:52 +0100 Subject: [PATCH 01/69] 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 5bfdd0c5c65b20758c5740e8bcf3ca7a3ef04fb3 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:05:27 +0100 Subject: [PATCH 02/69] 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 5c0a9c794c86d43298ecb0ec3030873158911fce Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 29 Dec 2025 16:22:10 +0100 Subject: [PATCH 03/69] 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 688dfc09e49d553030bf70724fa15466067418da Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:45:03 +0100 Subject: [PATCH 04/69] 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 75948dc335490a8ac6d2817b11ccc1a89309e807 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:45:29 +0100 Subject: [PATCH 05/69] 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 421f4bb3cf429432b65288fa4881c659efd5b4ff Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 09:57:39 +0100 Subject: [PATCH 06/69] 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 2db2cff759571e69cfa87b4f0ebbfb57caaab21b Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:08:29 +0100 Subject: [PATCH 07/69] 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 ee78075bac5e243eedd565cb919d23e4852dcf45 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:11:13 +0100 Subject: [PATCH 08/69] 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 e87d4c1f32f7631a75c8263164ce512f688a6971 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:13:43 +0100 Subject: [PATCH 09/69] 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 2099823e843da5a0469a3f8e73fd4d8023ccaf6c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:15:35 +0100 Subject: [PATCH 10/69] 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 (95%) diff --git a/packages/modules/devices/sungrow/sungrow/bat.py b/packages/modules/devices/sungrow/sungrow_sh/bat.py similarity index 95% rename from packages/modules/devices/sungrow/sungrow/bat.py rename to packages/modules/devices/sungrow/sungrow_sh/bat.py index cf25bec159..fc5a6a767d 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) @@ -155,4 +155,4 @@ def power_limit_controllable(self) -> bool: return True -component_descriptor = ComponentDescriptor(configuration_factory=SungrowBatSetup) +component_descriptor = ComponentDescriptor(configuration_factory=SungrowSHBatSetup) From d75845d562ef1cc6a9f10521c0f10620ce6d3f93 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:15:53 +0100 Subject: [PATCH 11/69] 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 bcf39bac9c7c5a277065456d700889594549c73e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:17:12 +0100 Subject: [PATCH 12/69] 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 069ec635f5e5fd828c82071825c82b2680925b5e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:17:42 +0100 Subject: [PATCH 13/69] 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 4c7de069bdf9151373c41601a998c6bf2d2ec23f Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:20:17 +0100 Subject: [PATCH 14/69] 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 1dae765f6c6e26b47109fa7003055cb88ed01365 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:26:35 +0100 Subject: [PATCH 15/69] 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 500e395af9a18fac76a0fdb965849b0f6e5f2029 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:29:24 +0100 Subject: [PATCH 16/69] 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 2675be3d913af3881da4e28519682c8642356ba9 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:34:49 +0100 Subject: [PATCH 17/69] 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 0e890801c1809946b4e10639aadad84f82f93d32 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:35:02 +0100 Subject: [PATCH 18/69] 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 0402fda3e455e524b69dd255c61cb307adcef976 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:38:22 +0100 Subject: [PATCH 19/69] 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 1490c5da049e94b114f5a0ff4b25664404de27c8 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:38:58 +0100 Subject: [PATCH 20/69] 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 db300606643aa82d69170854850342171ffddf39 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:46:41 +0100 Subject: [PATCH 21/69] 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 003fe172353d724a59af16c3f2fde9772029840b Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:46:52 +0100 Subject: [PATCH 22/69] 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 83652735cd3c9b374c80bccda15c54a5a3fcbf91 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:50:33 +0100 Subject: [PATCH 23/69] 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 ecb4b827f3f6f8f40b1250c0b8fe6cefdff1648d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:51:27 +0100 Subject: [PATCH 24/69] 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 310ef523c20578f79cfb16afe5eaf36311c29648 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:52:41 +0100 Subject: [PATCH 25/69] 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 38b6172f47be1b849b9886dc36058f34d1e00d6d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 10:57:20 +0100 Subject: [PATCH 26/69] 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 4dc387c7a93780f9bad2e67152c31b426dbec985 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:26:57 +0100 Subject: [PATCH 27/69] 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 1ad4526c2f503d7fbbd5f2e0da7a58cfd1af7bdf Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:27:43 +0100 Subject: [PATCH 28/69] Add charging for SH bat.py --- .../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 fc5a6a767d..0b15be11d4 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_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.__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.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_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.__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.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_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.__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.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_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) + 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_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.__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_register(13051, power_value, data_type=ModbusDataType.UINT_16, unit=unit) + self.__tcp_client.write_registers(13051, [power_value], data_type=ModbusDataType.UINT_16, unit=unit) def power_limit_controllable(self) -> bool: return True From 8e56e1a1a141d100690bc5f5c0770273fb4610a8 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:42:45 +0100 Subject: [PATCH 29/69] 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 e804390f7c1e5c1c108a4ab0e6f6f5dd09883f62 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:42:56 +0100 Subject: [PATCH 30/69] 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 53fb7fa14336b723bfd7ae192e6963e975ee94a0 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:44:46 +0100 Subject: [PATCH 31/69] 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 8c285b55d9921b28c95c2a14c29118ccabe19c50 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:47:18 +0100 Subject: [PATCH 32/69] 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 177f44ac03a606018b0027d01106b98b33aa5126 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:51:32 +0100 Subject: [PATCH 33/69] 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 8dbbcee0dafd1f473f7b3763cb9a030d5d113f6c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 19 Jan 2026 11:52:01 +0100 Subject: [PATCH 34/69] 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 7f2a98ab18e69c97c0845d39214a12739491149c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:07:33 +0100 Subject: [PATCH 35/69] 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 f1979ce35f0528adfcf05a90eaa78826e90f270c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:08:48 +0100 Subject: [PATCH 36/69] 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 6f380668db2ff3bf3a3308d0c1372d372728afae Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 09:10:09 +0100 Subject: [PATCH 37/69] 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 1f5d336346bb5e1d08f4bdb9d82fdfa5833fdf32 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:05:23 +0100 Subject: [PATCH 38/69] 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 c1749511188e006d06c02783efe79bbd37185e0e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:46:09 +0100 Subject: [PATCH 39/69] 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 00c84812b7af717f197e8dc862ea600de67ef290 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:47:12 +0100 Subject: [PATCH 40/69] 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 27b17bfa7d79bbea4ce143aa5e2ca1f0cc97dfd8 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:48:12 +0100 Subject: [PATCH 41/69] 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 2b86f038a934f75d5c750e4057ba8401c2a212bb Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:49:07 +0100 Subject: [PATCH 42/69] 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 6a348ae66a9d636a1bfaaeb7f53ab26805c562b4 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:49:36 +0100 Subject: [PATCH 43/69] 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 4e897acaca6ba3f441a38bdb2d5ca4a7c84035f0 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:50:10 +0100 Subject: [PATCH 44/69] 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 c62f0cc1f07ce402f0e5fc67109f6a1176848792 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:50:56 +0100 Subject: [PATCH 45/69] 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 2c5b9fb725bd74cf41b97e25283db1cbcf3e6ef0 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:51:45 +0100 Subject: [PATCH 46/69] 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 bbb923ee9671ac3a15e4d98f0ef2573902e33415 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:52:20 +0100 Subject: [PATCH 47/69] 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 1566ec108f58dc71807500b1df52bb8b7fe17a64 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:52:37 +0100 Subject: [PATCH 48/69] 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 15d5628d37b45f555fceed80bb4bad40bcb14d56 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:53:20 +0100 Subject: [PATCH 49/69] 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 8295743fd1571480644aaf88c0c1f7f1e4271571 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:53:57 +0100 Subject: [PATCH 50/69] 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 344a01b02b7cfd4d08a7b8c4044540b0f7839a36 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:55:13 +0100 Subject: [PATCH 51/69] 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 588da8ca60163f8b8cb56f20e563cc68d80c574e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:56:36 +0100 Subject: [PATCH 52/69] 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 cbe235509b7c2c17f5aea9217feddfdfe1d3536a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:57:32 +0100 Subject: [PATCH 53/69] 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 36721064bf12d9b1b2a8793f5904ffa5c253a6cd Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 10:58:16 +0100 Subject: [PATCH 54/69] 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 8be3764d33a085567f1519340f0a6d2c7a3d4e6c Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:00:10 +0100 Subject: [PATCH 55/69] 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 2dd18eff5f7b1d9917f3221923ab5d792ca9cc30 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:01:52 +0100 Subject: [PATCH 56/69] 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 c9a8af094d34759c4956c264c623bb28dfdd847b Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 29 Jan 2026 11:42:47 +0100 Subject: [PATCH 57/69] 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 29e9a48941..023ab2e38a 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -44,7 +44,7 @@ from control.optional_data import OcppConfig 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 ed1f161b4e10702165b528381911f35e4caf9353 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Feb 2026 13:59:41 +0100 Subject: [PATCH 58/69] 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 358f628eb23151a6013f251d1dd28fbdfc355989 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Feb 2026 14:01:00 +0100 Subject: [PATCH 59/69] 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: From 650792e29c80924e08e9db55b502eb9adeacb24d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 9 Feb 2026 12:12:51 +0100 Subject: [PATCH 60/69] Flake8 --- packages/modules/devices/sungrow/sungrow_ihm/bat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sungrow/sungrow_ihm/bat.py b/packages/modules/devices/sungrow/sungrow_ihm/bat.py index 302ea96058..a32c35d0b6 100644 --- a/packages/modules/devices/sungrow/sungrow_ihm/bat.py +++ b/packages/modules/devices/sungrow/sungrow_ihm/bat.py @@ -74,7 +74,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: 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_register(8025, power_value, data_type=ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) + 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': @@ -84,7 +84,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: power_value = int(power_limit / 100) log.debug(f"Aktive Batteriesteuerung. Batterie wird mit {power_limit} W geladen") self.__tcp_client.write_register(8025, power_value, data_type=ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) + wordorder=Endian.Little, unit=unit) def power_limit_controllable(self) -> bool: return True From 8b6cb0df3656d2e7ac035aad9fae09f881fe4515 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 24 Feb 2026 12:21:45 +0100 Subject: [PATCH 61/69] Flake8 --- packages/helpermodules/update_config.py | 211 +++++++++++------------- 1 file changed, 97 insertions(+), 114 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 023ab2e38a..a51586dc0b 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 112 + DATASTORE_VERSION = 110 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -213,7 +213,6 @@ class UpdateConfig: "^openWB/counter/[0-9]+/config/max_currents$", "^openWB/counter/[0-9]+/config/max_total_power$", - "^openWB/general/allow_unencrypted_access$", "^openWB/general/extern$", "^openWB/general/extern_display_mode$", "^openWB/general/charge_log_data_config$", @@ -510,7 +509,6 @@ class UpdateConfig: "^openWB/system/device/[0-9]+/component/[0-9]+/simulation/timestamp_present$", "^openWB/system/device/[0-9]+/config$", "^openWB/system/device/module_update_completed$", - "^openWB/system/hostname$", "^openWB/system/io/[0-9]+/config$", "^openWB/system/ip_address$", "^openWB/system/lastlivevaluesJson$", @@ -518,33 +516,6 @@ class UpdateConfig: "^openWB/system/mqtt/valid_partner_ids$", "^openWB/system/release_train$", "^openWB/system/secondary_auto_update$", - "^openWB/system/security/user_management_active$", - "^openWB/system/security/access_allowed$", - "^openWB/system/security/access/Settings$", - "^openWB/system/security/access/Status$", - "^openWB/system/security/access/ChargeLog$", - "^openWB/system/security/access/Chart$", - "^openWB/system/security/access/GeneralConfiguration$", - "^openWB/system/security/access/DisplayConfiguration$", - "^openWB/system/security/access/IdentificationConfiguration$", - "^openWB/system/security/access/GeneralChargeConfiguration$", - "^openWB/system/security/access/SurplusChargeConfiguration$", - "^openWB/system/security/access/ActiveBatControlConfiguration$", - "^openWB/system/security/access/HardwareInstallation$", - "^openWB/system/security/access/LoadManagementConfiguration$", - "^openWB/system/security/access/ChargePointInstallation$", - "^openWB/system/security/access/VehicleConfiguration$", - "^openWB/system/security/access/IoConfiguration$", - "^openWB/system/security/access/LegacySmartHomeConfiguration$", - "^openWB/system/security/access/InstallAssistant$", - "^openWB/system/security/access/CloudConfiguration$", - "^openWB/system/security/access/MqttBridgeConfiguration$", - "^openWB/system/security/access/DebugConfiguration$", - "^openWB/system/security/access/Support$", - "^openWB/system/security/access/DataManagement$", - "^openWB/system/security/access/SecurityConfiguration$", - "^openWB/system/security/access/SystemConfiguration$", - "^openWB/system/security/access/LegalSettings$", "^openWB/system/time$", "^openWB/system/update_in_progress$", "^openWB/system/usage_terms_acknowledged$", @@ -582,7 +553,6 @@ class UpdateConfig: ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", min_current=10))), ("openWB/vehicle/template/charge_template/0", get_charge_template_default()), - ("openWB/general/allow_unencrypted_access", True), ("openWB/general/charge_log_data_config", get_default_charge_log_columns()), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), ("openWB/general/chargemode_config/pv_charging/bat_power_discharge", 1000), @@ -649,42 +619,11 @@ class UpdateConfig: ("openWB/system/usage_terms_acknowledged", False), ("openWB/system/debug_level", 30), ("openWB/system/device/module_update_completed", True), - ("openWB/system/hostname", "unknown"), ("openWB/system/ip_address", "unknown"), ("openWB/system/mqtt/valid_partner_ids", []), ("openWB/system/release_train", "master"), ("openWB/system/secondary_auto_update", True), ("openWB/system/serial_number", get_serial_number()), - ("openWB/system/security/user_management_active", False), - # the following topics in openWB/system/security/ must default to True! - # ACLs will restrict access to this topics if user management is active so that the UI can distinguish - # between "no access" (no topic received) and "access" (topic received) - ("openWB/system/security/access_allowed", True), - ("openWB/system/security/access/Settings", True), - ("openWB/system/security/access/Status", True), - ("openWB/system/security/access/ChargeLog", True), - ("openWB/system/security/access/Chart", True), - ("openWB/system/security/access/GeneralConfiguration", True), - ("openWB/system/security/access/DisplayConfiguration", True), - ("openWB/system/security/access/IdentificationConfiguration", True), - ("openWB/system/security/access/GeneralChargeConfiguration", True), - ("openWB/system/security/access/SurplusChargeConfiguration", True), - ("openWB/system/security/access/ActiveBatControlConfiguration", True), - ("openWB/system/security/access/HardwareInstallation", True), - ("openWB/system/security/access/LoadManagementConfiguration", True), - ("openWB/system/security/access/ChargePointInstallation", True), - ("openWB/system/security/access/VehicleConfiguration", True), - ("openWB/system/security/access/IoConfiguration", True), - ("openWB/system/security/access/LegacySmartHomeConfiguration", True), - ("openWB/system/security/access/InstallAssistant", True), - ("openWB/system/security/access/CloudConfiguration", True), - ("openWB/system/security/access/MqttBridgeConfiguration", True), - ("openWB/system/security/access/DebugConfiguration", True), - ("openWB/system/security/access/Support", True), - ("openWB/system/security/access/DataManagement", True), - ("openWB/system/security/access/SecurityConfiguration", True), - ("openWB/system/security/access/SystemConfiguration", True), - ("openWB/system/security/access/LegalSettings", True), ) invalid_topic = ( # Tuple: (Regex, callable) @@ -2795,67 +2734,111 @@ def upgrade(topic: str, payload) -> None: self._append_datastore_version(108) def upgrade_datastore_109(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if re.search("^openWB/bat/[0-9]+/get/power$", topic) is not None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/bat/[0-9]+/get/power", topic) is not None: index = get_index(topic) - new_topics = {} # add new topics for battery control: # openWB/bat/[0-9]+/get/max_charge_power => 0 # openWB/bat/[0-9]+/get/max_discharge_power => 0 if f"openWB/bat/{index}/get/max_charge_power" not in self.all_received_topics: - new_topics[f"openWB/bat/{index}/get/max_charge_power"] = 0 + self.__update_topic(f"openWB/bat/{index}/get/max_charge_power", 0) if f"openWB/bat/{index}/get/max_discharge_power" not in self.all_received_topics: - new_topics[f"openWB/bat/{index}/get/max_discharge_power"] = 0 - return new_topics if new_topics else None + self.__update_topic(f"openWB/bat/{index}/get/max_discharge_power", 0) self._loop_all_received_topics(upgrade) self._append_datastore_version(109) def upgrade_datastore_110(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if re.search("openWB/chargepoint/[0-9]+/config", topic) is not None: - config = decode_payload(payload) - if config.get("type") == "openwb_dc_adapter": - config["configuration"]["user"] = None - config["configuration"]["password"] = None - ip_address = config["configuration"].pop("ip_address") - config["configuration"]["url"] = f'http://{ip_address}/connect.php' - return {topic: config} - self._loop_all_received_topics(upgrade) - self._append_datastore_version(110) + # 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_raw = device.get("configuration", {}).get("version") + old_version = None + if old_raw is None: + old_version = None + else: + try: + if isinstance(old_raw, Version): + old_version = int(old_raw) + else: + old_version = int(old_raw) + except Exception: + old_version = None + + 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 - def upgrade_datastore_111(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - # add "userManagementSupported" flag if display theme "cards" is selected - if re.search("openWB/optional/int_display/theme", topic) is not None: - configuration_payload = decode_payload(payload) - configuration_payload.update( - {"userManagementSupported": True if configuration_payload.get("type") == "cards" else False}) - return {topic: configuration_payload} - # add "userManagementSupported" flag if web theme "koala" is selected - if re.search("openWB/general/web_theme", topic) is not None: - configuration_payload = decode_payload(payload) - configuration_payload.update( - {"userManagementSupported": True if configuration_payload.get("type") == "koala" else False}) - return {topic: configuration_payload} - self._loop_all_received_topics(upgrade) - self._append_datastore_version(111) + changed = False - def upgrade_datastore_112(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if re.search("openWB/vehicle/[0-9]+/soc_module/config", topic) is not None: - payload = decode_payload(payload) - if payload.get("type") == "bmwbc": - pub_system_message( - {}, - "Die Schnittstelle des bisherigen BMW-Moduls wurde eingestellt und in openWB entfernt. Bitte " - "beachte, dass Du ohne die Konfiguration eines anderen Fahrzeug-Moduls kein SoC-basiertes " - "Laden nutzen kannst.
Unsere Fahrzeug-Module werden von der Community entwickelt. Wenn du " - "also ein BMW-Fahrer bist und gerne ein neues BMW-Modul in openWB programmieren möchtest, " - "findest Du im Forum " - "weitere Informationen.", - MessageType.INFO, - ) - return {topic: NO_MODULE} - run_command(['pip', 'uninstall', 'bimmer_connected', '-y'], process_exception=True) - self._loop_all_received_topics(upgrade) - self._append_datastore_version(112) + 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: + # sungrow_sg: remove version key if present + if "version" in device["configuration"]: + device["configuration"].pop("version", None) + changed = True + else: + # sungrow_sh: set numeric version if different + 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 afffe3dad2270761b63f46273cd0b59b36486fa6 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 24 Feb 2026 12:26:37 +0100 Subject: [PATCH 62/69] flake8 --- packages/helpermodules/update_config.py | 170 ++++++++++++------------ 1 file changed, 87 insertions(+), 83 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index a51586dc0b..6cc72547ab 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -2747,98 +2747,102 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(109) - def upgrade_datastore_110(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_raw = device.get("configuration", {}).get("version") + 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": + # inline normalization: handle Version enum, ints, numeric strings + old_raw = device.get("configuration", {}).get("version") + old_version = None + if old_raw is None: old_version = None - if old_raw is None: + else: + try: + # Version enum -> int + if isinstance(old_raw, Version): + old_version = int(old_raw) + else: + # numeric-like: int or numeric string + old_version = int(old_raw) + except Exception: old_version = None - else: - try: - if isinstance(old_raw, Version): - old_version = int(old_raw) - else: - old_version = int(old_raw) - except Exception: - old_version = None + 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 - 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 - changed = False + if device.get("type") != new_type: + device["type"] = new_type + changed = True - 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 "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 new_version is None: - # sungrow_sg: remove version key if present - if "version" in device["configuration"]: - device["configuration"].pop("version", None) - 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: - # sungrow_sh: set numeric version if different - 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_system_message( + device, + (f"Die Sungrow-Geräte-Konfiguration wurde aktualisiert: Gerät " + f"'{device_name}' auf Typ '{new_type}'."), + MessageType.INFO, ) - 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) + + self._loop_all_received_topics(upgrade) + self._append_datastore_version(108) From 41a967f75f1a810d6540284291dc593c28edebf2 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 24 Feb 2026 12:28:02 +0100 Subject: [PATCH 63/69] Change upgrade_datastore version to 110 --- 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 6cc72547ab..b74800f096 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -2747,7 +2747,7 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(109) - def upgrade_datastore_108(self) -> None: + def upgrade_datastore_110(self) -> None: """ Migrate old single 'sungrow' devices into new modules: - 'sungrow_sg' for SG family (no version field) From ca79b7bdd7e83aeed29cefcc9d17f2dda2712f9d Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 24 Feb 2026 15:06:15 +0100 Subject: [PATCH 64/69] Update datastore version from 108 to 110 --- 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 b74800f096..607053d474 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -2845,4 +2845,4 @@ def upgrade(topic: str, payload) -> None: ) self._loop_all_received_topics(upgrade) - self._append_datastore_version(108) + self._append_datastore_version(110) From 7017ad91aa4941e9f3311c4fd325cb50e7030364 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Mar 2026 16:17:44 +0100 Subject: [PATCH 65/69] Update update_config.py --- packages/helpermodules/update_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 607053d474..e7fbb4bdd0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 110 + DATASTORE_VERSION = 111 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2747,7 +2747,7 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(109) - def upgrade_datastore_110(self) -> None: + def upgrade_datastore_111(self) -> None: """ Migrate old single 'sungrow' devices into new modules: - 'sungrow_sg' for SG family (no version field) From 90d7127e9f1e7edadd0bcef370cabac12dce6b2f Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Mar 2026 16:25:46 +0100 Subject: [PATCH 66/69] Update update_config.py --- packages/helpermodules/update_config.py | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index e7fbb4bdd0..95d419ae9b 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 111 + DATASTORE_VERSION = 112 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2747,7 +2747,37 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(109) + def upgrade_datastore_110(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/chargepoint/[0-9]+/config", topic) is not None: + config = decode_payload(payload) + if config.get("type") == "openwb_dc_adapter": + config["configuration"]["user"] = None + config["configuration"]["password"] = None + ip_address = config["configuration"].pop("ip_address") + config["configuration"]["url"] = f'http://{ip_address}/connect.php' + return {topic: config} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(110) + def upgrade_datastore_111(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + # add "userManagementSupported" flag if display theme "cards" is selected + if re.search("openWB/optional/int_display/theme", topic) is not None: + configuration_payload = decode_payload(payload) + configuration_payload.update( + {"userManagementSupported": True if configuration_payload.get("type") == "cards" else False}) + return {topic: configuration_payload} + # add "userManagementSupported" flag if web theme "koala" is selected + if re.search("openWB/general/web_theme", topic) is not None: + configuration_payload = decode_payload(payload) + configuration_payload.update( + {"userManagementSupported": True if configuration_payload.get("type") == "koala" else False}) + return {topic: configuration_payload} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(111) + + def upgrade_datastore_112(self) -> None: """ Migrate old single 'sungrow' devices into new modules: - 'sungrow_sg' for SG family (no version field) From 0f03ab89d8115d09af6475e81544b17d853cc203 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Thu, 5 Mar 2026 16:36:08 +0100 Subject: [PATCH 67/69] Update update_config.py --- packages/helpermodules/update_config.py | 73 +++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 95d419ae9b..77fced24a0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -213,6 +213,7 @@ class UpdateConfig: "^openWB/counter/[0-9]+/config/max_currents$", "^openWB/counter/[0-9]+/config/max_total_power$", + "^openWB/general/allow_unencrypted_access$", "^openWB/general/extern$", "^openWB/general/extern_display_mode$", "^openWB/general/charge_log_data_config$", @@ -509,6 +510,7 @@ class UpdateConfig: "^openWB/system/device/[0-9]+/component/[0-9]+/simulation/timestamp_present$", "^openWB/system/device/[0-9]+/config$", "^openWB/system/device/module_update_completed$", + "^openWB/system/hostname$", "^openWB/system/io/[0-9]+/config$", "^openWB/system/ip_address$", "^openWB/system/lastlivevaluesJson$", @@ -516,6 +518,33 @@ class UpdateConfig: "^openWB/system/mqtt/valid_partner_ids$", "^openWB/system/release_train$", "^openWB/system/secondary_auto_update$", + "^openWB/system/security/user_management_active$", + "^openWB/system/security/access_allowed$", + "^openWB/system/security/access/Settings$", + "^openWB/system/security/access/Status$", + "^openWB/system/security/access/ChargeLog$", + "^openWB/system/security/access/Chart$", + "^openWB/system/security/access/GeneralConfiguration$", + "^openWB/system/security/access/DisplayConfiguration$", + "^openWB/system/security/access/IdentificationConfiguration$", + "^openWB/system/security/access/GeneralChargeConfiguration$", + "^openWB/system/security/access/SurplusChargeConfiguration$", + "^openWB/system/security/access/ActiveBatControlConfiguration$", + "^openWB/system/security/access/HardwareInstallation$", + "^openWB/system/security/access/LoadManagementConfiguration$", + "^openWB/system/security/access/ChargePointInstallation$", + "^openWB/system/security/access/VehicleConfiguration$", + "^openWB/system/security/access/IoConfiguration$", + "^openWB/system/security/access/LegacySmartHomeConfiguration$", + "^openWB/system/security/access/InstallAssistant$", + "^openWB/system/security/access/CloudConfiguration$", + "^openWB/system/security/access/MqttBridgeConfiguration$", + "^openWB/system/security/access/DebugConfiguration$", + "^openWB/system/security/access/Support$", + "^openWB/system/security/access/DataManagement$", + "^openWB/system/security/access/SecurityConfiguration$", + "^openWB/system/security/access/SystemConfiguration$", + "^openWB/system/security/access/LegalSettings$", "^openWB/system/time$", "^openWB/system/update_in_progress$", "^openWB/system/usage_terms_acknowledged$", @@ -553,6 +582,7 @@ class UpdateConfig: ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", min_current=10))), ("openWB/vehicle/template/charge_template/0", get_charge_template_default()), + ("openWB/general/allow_unencrypted_access", True), ("openWB/general/charge_log_data_config", get_default_charge_log_columns()), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), ("openWB/general/chargemode_config/pv_charging/bat_power_discharge", 1000), @@ -619,11 +649,42 @@ class UpdateConfig: ("openWB/system/usage_terms_acknowledged", False), ("openWB/system/debug_level", 30), ("openWB/system/device/module_update_completed", True), + ("openWB/system/hostname", "unknown"), ("openWB/system/ip_address", "unknown"), ("openWB/system/mqtt/valid_partner_ids", []), ("openWB/system/release_train", "master"), ("openWB/system/secondary_auto_update", True), ("openWB/system/serial_number", get_serial_number()), + ("openWB/system/security/user_management_active", False), + # the following topics in openWB/system/security/ must default to True! + # ACLs will restrict access to this topics if user management is active so that the UI can distinguish + # between "no access" (no topic received) and "access" (topic received) + ("openWB/system/security/access_allowed", True), + ("openWB/system/security/access/Settings", True), + ("openWB/system/security/access/Status", True), + ("openWB/system/security/access/ChargeLog", True), + ("openWB/system/security/access/Chart", True), + ("openWB/system/security/access/GeneralConfiguration", True), + ("openWB/system/security/access/DisplayConfiguration", True), + ("openWB/system/security/access/IdentificationConfiguration", True), + ("openWB/system/security/access/GeneralChargeConfiguration", True), + ("openWB/system/security/access/SurplusChargeConfiguration", True), + ("openWB/system/security/access/ActiveBatControlConfiguration", True), + ("openWB/system/security/access/HardwareInstallation", True), + ("openWB/system/security/access/LoadManagementConfiguration", True), + ("openWB/system/security/access/ChargePointInstallation", True), + ("openWB/system/security/access/VehicleConfiguration", True), + ("openWB/system/security/access/IoConfiguration", True), + ("openWB/system/security/access/LegacySmartHomeConfiguration", True), + ("openWB/system/security/access/InstallAssistant", True), + ("openWB/system/security/access/CloudConfiguration", True), + ("openWB/system/security/access/MqttBridgeConfiguration", True), + ("openWB/system/security/access/DebugConfiguration", True), + ("openWB/system/security/access/Support", True), + ("openWB/system/security/access/DataManagement", True), + ("openWB/system/security/access/SecurityConfiguration", True), + ("openWB/system/security/access/SystemConfiguration", True), + ("openWB/system/security/access/LegalSettings", True), ) invalid_topic = ( # Tuple: (Regex, callable) @@ -2734,16 +2795,18 @@ def upgrade(topic: str, payload) -> None: self._append_datastore_version(108) def upgrade_datastore_109(self) -> None: - def upgrade(topic: str, payload) -> None: - if re.search("openWB/bat/[0-9]+/get/power", topic) is not None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("^openWB/bat/[0-9]+/get/power$", topic) is not None: index = get_index(topic) + new_topics = {} # add new topics for battery control: # openWB/bat/[0-9]+/get/max_charge_power => 0 # openWB/bat/[0-9]+/get/max_discharge_power => 0 if f"openWB/bat/{index}/get/max_charge_power" not in self.all_received_topics: - self.__update_topic(f"openWB/bat/{index}/get/max_charge_power", 0) + new_topics[f"openWB/bat/{index}/get/max_charge_power"] = 0 if f"openWB/bat/{index}/get/max_discharge_power" not in self.all_received_topics: - self.__update_topic(f"openWB/bat/{index}/get/max_discharge_power", 0) + new_topics[f"openWB/bat/{index}/get/max_discharge_power"] = 0 + return new_topics if new_topics else None self._loop_all_received_topics(upgrade) self._append_datastore_version(109) @@ -2875,4 +2938,4 @@ def upgrade(topic: str, payload) -> None: ) self._loop_all_received_topics(upgrade) - self._append_datastore_version(110) + self._append_datastore_version(112) From b800baf2da7a1328f44bcea71c092f12e8bc4dcb Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Mon, 9 Mar 2026 22:20:48 +0100 Subject: [PATCH 68/69] Update update_config.py --- packages/helpermodules/update_config.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 77fced24a0..7fcaf4aa5e 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 112 + DATASTORE_VERSION = 113 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2841,6 +2841,26 @@ def upgrade(topic: str, payload) -> Optional[dict]: self._append_datastore_version(111) def upgrade_datastore_112(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/vehicle/[0-9]+/soc_module/config", topic) is not None: + payload = decode_payload(payload) + if payload.get("type") == "bmwbc": + pub_system_message( + {}, + "Die Schnittstelle des bisherigen BMW-Moduls wurde eingestellt und in openWB entfernt. Bitte " + "beachte, dass Du ohne die Konfiguration eines anderen Fahrzeug-Moduls kein SoC-basiertes " + "Laden nutzen kannst.
Unsere Fahrzeug-Module werden von der Community entwickelt. Wenn du " + "also ein BMW-Fahrer bist und gerne ein neues BMW-Modul in openWB programmieren möchtest, " + "findest Du im Forum " + "weitere Informationen.", + MessageType.INFO, + ) + return {topic: NO_MODULE} + run_command(['pip', 'uninstall', 'bimmer_connected', '-y'], process_exception=True) + self._loop_all_received_topics(upgrade) + self._append_datastore_version(112) + + def upgrade_datastore_113(self) -> None: """ Migrate old single 'sungrow' devices into new modules: - 'sungrow_sg' for SG family (no version field) @@ -2938,4 +2958,4 @@ def upgrade(topic: str, payload) -> None: ) self._loop_all_received_topics(upgrade) - self._append_datastore_version(112) + self._append_datastore_version(113) From 31dc51524dbdd241fa6d900876aed2e4acf0125a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Fri, 13 Mar 2026 15:01:23 +0100 Subject: [PATCH 69/69] Update packages/helpermodules/update_config.py Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> --- 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 7fcaf4aa5e..cde98efd95 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -2874,7 +2874,7 @@ def upgrade_datastore_113(self) -> None: 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: + if re.search(r"^openWB/system/device/[0-9]+/config$", topic) is not None: device = decode_payload(payload) if device.get("type") == "sungrow": # inline normalization: handle Version enum, ints, numeric strings