From fdf938d24ed29306fab076d40a90adae2da81c43 Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 11 Jan 2024 16:35:45 +0100 Subject: [PATCH 01/15] Updated sungrow modbus addresses, moved version from counter to device level --- packages/modules/devices/sungrow/bat.py | 19 ++++--- packages/modules/devices/sungrow/config.py | 12 +++-- packages/modules/devices/sungrow/counter.py | 36 ++++++------- packages/modules/devices/sungrow/device.py | 15 +++--- packages/modules/devices/sungrow/inverter.py | 39 +++++++++----- packages/modules/devices/sungrow/modbus.md | 32 ++++++++++++ packages/modules/devices/sungrow/version.py | 5 +- packages/tools/modbus_finder.py | 54 ++++++++++++++++++++ 8 files changed, 158 insertions(+), 54 deletions(-) create mode 100644 packages/modules/devices/sungrow/modbus.md create mode 100644 packages/tools/modbus_finder.py diff --git a/packages/modules/devices/sungrow/bat.py b/packages/modules/devices/sungrow/bat.py index 94060b426e..e695c72797 100644 --- a/packages/modules/devices/sungrow/bat.py +++ b/packages/modules/devices/sungrow/bat.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 from typing import Dict, Union +from pymodbus.constants import Endian + from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.component_state import BatState @@ -9,33 +11,34 @@ from modules.common.modbus import ModbusDataType from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store -from modules.devices.sungrow.config import SungrowBatSetup +from modules.devices.sungrow.config import SungrowBatSetup, Sungrow class SungrowBat: def __init__(self, - device_id: int, - device_modbus_id: int, + device_config: Union[Dict, Sungrow], component_config: Union[Dict, SungrowBatSetup], tcp_client: modbus.ModbusTcpClient_) -> None: - self.__device_id = device_id - self.__device_modbus_id = device_modbus_id + self.device_config = device_config self.component_config = dataclass_from_dict(SungrowBatSetup, component_config) self.__tcp_client = tcp_client - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + 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)) def update(self) -> None: - unit = self.__device_modbus_id + unit = self.device_config.configuration.modbus_id soc = int(self.__tcp_client.read_input_registers(13022, ModbusDataType.INT_16, unit=unit) / 10) resp = self.__tcp_client.delegate.read_input_registers(13000, 1, unit=unit) binary = bin(resp.registers[0])[2:].zfill(8) power = self.__tcp_client.read_input_registers(13021, ModbusDataType.INT_16, unit=unit) + imported = self.__tcp_client.read_input_registers(13026, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 + exported = self.__tcp_client.read_input_registers(13040, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 if binary[5] == "1": power = power * -1 - imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, soc=soc, diff --git a/packages/modules/devices/sungrow/config.py b/packages/modules/devices/sungrow/config.py index 2fb6a668cd..2d9adfab4b 100644 --- a/packages/modules/devices/sungrow/config.py +++ b/packages/modules/devices/sungrow/config.py @@ -1,13 +1,19 @@ from typing import Optional from modules.common.component_setup import ComponentSetup +from modules.devices.sungrow.version import Version class SungrowConfiguration: - def __init__(self, ip_address: Optional[str] = None, port: int = 502, modbus_id: int = 1): + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1, + version: Version = Version.SG): self.ip_address = ip_address self.port = port self.modbus_id = modbus_id + self.version = version class Sungrow: @@ -37,8 +43,8 @@ def __init__(self, class SungrowCounterConfiguration: - def __init__(self, version=1): - self.version = version + def __init__(self): + pass class SungrowCounterSetup(ComponentSetup[SungrowCounterConfiguration]): diff --git a/packages/modules/devices/sungrow/counter.py b/packages/modules/devices/sungrow/counter.py index 6d1c58f428..c480613828 100644 --- a/packages/modules/devices/sungrow/counter.py +++ b/packages/modules/devices/sungrow/counter.py @@ -9,34 +9,31 @@ from modules.common.modbus import ModbusDataType, Endian from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store -from modules.devices.sungrow.config import SungrowCounterSetup +from modules.devices.sungrow.config import SungrowCounterSetup, Sungrow from modules.devices.sungrow.version import Version class SungrowCounter: def __init__(self, - device_id: int, - device_modbus_id: int, + device_config: Union[Dict, Sungrow], component_config: Union[Dict, SungrowCounterSetup], tcp_client: modbus.ModbusTcpClient_) -> None: - self.__device_id = device_id - self.__device_modbus_id = device_modbus_id + self.device_config = device_config self.component_config = dataclass_from_dict(SungrowCounterSetup, component_config) self.__tcp_client = tcp_client - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="bezug") 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_modbus_id - if self.component_config.configuration.version == Version.SH: + 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 - # no valid data for powers per phase - # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_16] * 3, - # wordorder=Endian.Little, unit=unit) - # powers = [power / 10 for power in powers] - # log.info("power: " + str(power) + " powers?: " + str(powers)) + imported = self.__tcp_client.read_input_registers(13036, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 + exported = self.__tcp_client.read_input_registers(13045, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 else: if pv_power != 0: power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, @@ -44,25 +41,22 @@ def update(self, pv_power: float): else: power = self.__tcp_client.read_input_registers(5090, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) + imported, exported = self.sim_counter.sim_count(power) - # no valid data for powers per phase - # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.UINT_16] * 3, - # wordorder=Endian.Little, unit=unit) - # powers = [power / 10 for power in powers] - # log.info("power: " + str(power) + " powers?: " + str(powers)) 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 + # These are actually output voltages of the inverter: voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, wordorder=Endian.Little, unit=unit) voltages = [voltage / 10 for voltage in voltages] - imported, exported = self.sim_counter.sim_count(power) - counter_state = CounterState( imported=imported, exported=exported, power=power, voltages=voltages, - frequency=frequency + frequency=frequency, + power_factors=[power_factor] * 3 ) self.store.set(counter_state) diff --git a/packages/modules/devices/sungrow/device.py b/packages/modules/devices/sungrow/device.py index be3de2246e..1ada084b27 100644 --- a/packages/modules/devices/sungrow/device.py +++ b/packages/modules/devices/sungrow/device.py @@ -47,7 +47,7 @@ def add_component(self, COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory, component_config) if component_type in self.COMPONENT_TYPE_TO_CLASS: self.components["component" + str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, self.device_config.configuration.modbus_id, component_config, self.client)) + self.device_config, component_config, self.client)) else: raise Exception( "illegal component type " + component_type + ". Allowed values: " + @@ -85,23 +85,25 @@ def update(self) -> None: def read_legacy(ip_address: str, port: int, modbus_id: int, + version: int, component_config: dict): device_config = Sungrow() device_config.configuration.ip_address = ip_address device_config.configuration.port = port device_config.configuration.modbus_id = modbus_id + device_config.configuration.version = Version(version) dev = Device(device_config) dev.add_component(component_config) dev.update() -def read_legacy_bat(ip_address: str, port: int, modbus_id: int): - read_legacy(ip_address, port, modbus_id, bat.component_descriptor.configuration_factory(id=None)) +def read_legacy_bat(ip_address: str, port: int, modbus_id: int, version: int): + read_legacy(ip_address, port, modbus_id, version, bat.component_descriptor.configuration_factory(id=None)) def read_legacy_counter(ip_address: str, port: int, modbus_id: int, version: int): - read_legacy(ip_address, port, modbus_id, counter.component_descriptor.configuration_factory( - id=None, configuration=SungrowCounterConfiguration(version=Version(version)))) + read_legacy(ip_address, port, modbus_id, version, counter.component_descriptor.configuration_factory( + id=None, configuration=SungrowCounterConfiguration())) def read_legacy_inverter(ip_address: str, @@ -114,11 +116,12 @@ def read_legacy_inverter(ip_address: str, device_config.configuration.ip_address = ip_address device_config.configuration.port = port device_config.configuration.modbus_id = modbus_id + device_config.configuration.version = Version(version) dev = Device(device_config) dev.add_component(inverter.component_descriptor.configuration_factory(id=num)) if read_counter == 1: dev.add_component(counter.component_descriptor.configuration_factory( - id=None, configuration=SungrowCounterConfiguration(version=Version(version)))) + id=None, configuration=SungrowCounterConfiguration())) dev.update() diff --git a/packages/modules/devices/sungrow/inverter.py b/packages/modules/devices/sungrow/inverter.py index 40908325de..a8fcc49e76 100644 --- a/packages/modules/devices/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/inverter.py @@ -1,39 +1,50 @@ #!/usr/bin/env python3 from typing import Dict, Union +from pymodbus.constants import Endian + from dataclass_utils import dataclass_from_dict from modules.common import modbus 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 +from modules.common.modbus import ModbusDataType from modules.common.simcount import SimCounter from modules.common.store import get_inverter_value_store -from modules.devices.sungrow.config import SungrowInverterSetup +from modules.devices.sungrow.config import SungrowInverterSetup, Sungrow +from modules.devices.sungrow.version import Version class SungrowInverter: def __init__(self, - device_id: int, - device_modbus_id: int, + device_config: Union[Dict, Sungrow], component_config: Union[Dict, SungrowInverterSetup], tcp_client: modbus.ModbusTcpClient_) -> None: - self.__device_id = device_id - self.__device_modbus_id = device_modbus_id + self.device_config = device_config self.component_config = dataclass_from_dict(SungrowInverterSetup, component_config) self.__tcp_client = tcp_client - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + 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_modbus_id - power = self.__tcp_client.read_input_registers(5016, - ModbusDataType.UINT_32, - wordorder=Endian.Little, - unit=unit) * -1 - - _, exported = self.sim_counter.sim_count(power) + unit = self.device_config.configuration.modbus_id + + if self.device_config.configuration.version == Version.SH: + power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + exported = self.__tcp_client.read_input_registers(5660, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 + elif self.device_config.configuration.version == Version.SH_winet_dongle: + # Not recommended to use the SH WiNet-S-Dongle, but if, this is the most accurate data: + power = self.__tcp_client.read_input_registers(5016, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + _, exported = self.sim_counter.sim_count(power) + else: + power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 + exported = self.__tcp_client.read_input_registers(5143, ModbusDataType.UINT_32, + wordorder=Endian.Little, unit=unit) * 100 inverter_state = InverterState( power=power, diff --git a/packages/modules/devices/sungrow/modbus.md b/packages/modules/devices/sungrow/modbus.md new file mode 100644 index 0000000000..cd54cf002a --- /dev/null +++ b/packages/modules/devices/sungrow/modbus.md @@ -0,0 +1,32 @@ +# Modbus Adressen für Sungrow SH* und SG* Wechselrichter + +## Datengrundlage: +* TI_20230918_Communication Protocol of Residential and Commerical 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.P020 +* modbus_finder.py an SG10RT (WiNet-S) Firmware BERYL-S_B000.V000.P039-20230626 / WINET-SV200.001.00.P020 + +## 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 | 5030 | -- | 5030 | 1 W | INT_32 mixed | | +| 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 | | diff --git a/packages/modules/devices/sungrow/version.py b/packages/modules/devices/sungrow/version.py index 6e3607756b..e1aa85d25e 100644 --- a/packages/modules/devices/sungrow/version.py +++ b/packages/modules/devices/sungrow/version.py @@ -3,5 +3,6 @@ class Version(IntEnum): SH = 0 - SG = 1 - SG_winet_dongle = 2 + SH_winet_dongle = 1 + SG = 2 + SG_winet_dongle = 3 diff --git a/packages/tools/modbus_finder.py b/packages/tools/modbus_finder.py new file mode 100644 index 0000000000..76788c3990 --- /dev/null +++ b/packages/tools/modbus_finder.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import sys +import time +from typing import Callable + +from pymodbus.constants import Endian + +from modules.common import modbus + + +def try_read(function: Callable, **kwargs) -> str: + try: + return str(function(**kwargs)) + except Exception as e: + return "--" + + +host = sys.argv[1] +port = int(sys.argv[2]) +slave_id = int(sys.argv[3]) +start = int(sys.argv[4]) +end = int(sys.argv[5]) +func = int(sys.argv[6]) + +print(time.strftime("%Y-%m-%d %H:%M:%S modbus-finder")) +print("Parameter:") +print("Host: " + host) +print("Port: " + str(port)) +print("Modbus ID: " + str(slave_id)) +print("Startadresse: " + str(start)) +print("Endadresse: " + str(end)) +print("Funktion: " + str(func) + "\n") +try: + client = modbus.ModbusTcpClient_(host, port=port) + function: Callable + if func == 4: + function = client.read_input_registers + elif func == 3: + function = client.read_holding_registers + else: + print("unsupported function code: " + str(func)) + exit(1) + + print("Address;INT_16;UINT_16;INT_32;UINT_32") + for address in range(start, end): + resp_INT_16 = try_read(function, address=address, types=modbus.ModbusDataType.INT_16, unit=slave_id) + resp_UINT_16 = try_read(function, address=address, types=modbus.ModbusDataType.UINT_16, unit=slave_id) + resp_INT_32 = try_read(function, address=address, types=modbus.ModbusDataType.INT_32, wordorder=Endian.Little, + unit=slave_id) + resp_UINT_32 = try_read(function, address=address, types=modbus.ModbusDataType.UINT_32, wordorder=Endian.Little, + unit=slave_id) + print(f"{address};{resp_INT_16};{resp_UINT_16};{resp_INT_32};{resp_UINT_32}") +except Exception as e: + print("Exception " + str(e)) From 2077262608848db745de0db24d7bd5ce51b25b1f Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 11 Jan 2024 17:02:22 +0100 Subject: [PATCH 02/15] Fixed wrong Endian import --- packages/modules/devices/sungrow/bat.py | 4 +--- packages/modules/devices/sungrow/inverter.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sungrow/bat.py b/packages/modules/devices/sungrow/bat.py index e695c72797..927ec765dc 100644 --- a/packages/modules/devices/sungrow/bat.py +++ b/packages/modules/devices/sungrow/bat.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 from typing import Dict, Union -from pymodbus.constants import Endian - from dataclass_utils import dataclass_from_dict from modules.common import modbus 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 +from modules.common.modbus import ModbusDataType, Endian from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.sungrow.config import SungrowBatSetup, Sungrow diff --git a/packages/modules/devices/sungrow/inverter.py b/packages/modules/devices/sungrow/inverter.py index a8fcc49e76..f959fc8f91 100644 --- a/packages/modules/devices/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/inverter.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 from typing import Dict, Union -from pymodbus.constants import Endian - from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.component_state import InverterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.modbus import ModbusDataType +from modules.common.modbus import ModbusDataType, Endian from modules.common.simcount import SimCounter from modules.common.store import get_inverter_value_store from modules.devices.sungrow.config import SungrowInverterSetup, Sungrow From 37a97aee9871e3de57037aa3308e2f5f7f5e487c Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 11 Jan 2024 17:02:53 +0100 Subject: [PATCH 03/15] Fixed WiNet-S frequency accuracy --- packages/modules/devices/sungrow/counter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/modules/devices/sungrow/counter.py b/packages/modules/devices/sungrow/counter.py index c480613828..9ba8804d5a 100644 --- a/packages/modules/devices/sungrow/counter.py +++ b/packages/modules/devices/sungrow/counter.py @@ -44,6 +44,10 @@ def update(self, pv_power: float): imported, exported = self.sim_counter.sim_count(power) 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 + frequency /= 10 + power_factor = self.__tcp_client.read_input_registers(5034, ModbusDataType.INT_16, unit=unit) / 1000 # These are actually output voltages of the inverter: voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, From e8286adad2151cd12faabaf14ab6d0d0ff8fef4f Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Wed, 10 Apr 2024 15:04:58 +0200 Subject: [PATCH 04/15] Reading powers per phase now, added more registers to modbus.md documentation --- packages/modules/devices/sungrow/counter.py | 27 +++++++++++++------- packages/modules/devices/sungrow/inverter.py | 12 +++------ packages/modules/devices/sungrow/modbus.md | 9 +++++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/modules/devices/sungrow/counter.py b/packages/modules/devices/sungrow/counter.py index 9ba8804d5a..04d20a10ae 100644 --- a/packages/modules/devices/sungrow/counter.py +++ b/packages/modules/devices/sungrow/counter.py @@ -30,10 +30,8 @@ def update(self, pv_power: float): 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 - imported = self.__tcp_client.read_input_registers(13036, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 - exported = self.__tcp_client.read_input_registers(13045, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 + powers = self.__tcp_client.read_input_registers(5602, [ModbusDataType.INT_32] * 3, + wordorder=Endian.Little, unit=unit) else: if pv_power != 0: power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, @@ -41,7 +39,8 @@ def update(self, pv_power: float): else: power = self.__tcp_client.read_input_registers(5090, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) - imported, exported = self.sim_counter.sim_count(power) + powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3, + wordorder=Endian.Little, unit=unit) frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 if self.device_config.configuration.version == Version.SH_winet_dongle: @@ -49,15 +48,25 @@ def update(self, pv_power: float): frequency /= 10 power_factor = self.__tcp_client.read_input_registers(5034, ModbusDataType.INT_16, unit=unit) / 1000 - # These are actually output voltages of the inverter: - voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) - voltages = [voltage / 10 for voltage in voltages] + + if self.device_config.configuration.version == Version.SH: + # SH (LAN) provides accurate values from meter + voltages = self.__tcp_client.read_input_registers(5740, [ModbusDataType.UINT_16] * 3, + wordorder=Endian.Little, unit=unit) + else: + # These are actually output voltages of the inverter: + 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 diff --git a/packages/modules/devices/sungrow/inverter.py b/packages/modules/devices/sungrow/inverter.py index f959fc8f91..85048cbe76 100644 --- a/packages/modules/devices/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/inverter.py @@ -28,21 +28,15 @@ def __init__(self, def update(self) -> float: unit = self.device_config.configuration.modbus_id - if self.device_config.configuration.version == Version.SH: - power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - exported = self.__tcp_client.read_input_registers(5660, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 - elif self.device_config.configuration.version == Version.SH_winet_dongle: + if self.device_config.configuration.version == Version.SH_winet_dongle: # Not recommended to use the SH WiNet-S-Dongle, but if, this is the most accurate data: power = self.__tcp_client.read_input_registers(5016, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 - _, exported = self.sim_counter.sim_count(power) else: power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 - exported = self.__tcp_client.read_input_registers(5143, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 + + _, exported = self.sim_counter.sim_count(power) inverter_state = InverterState( power=power, diff --git a/packages/modules/devices/sungrow/modbus.md b/packages/modules/devices/sungrow/modbus.md index cd54cf002a..ed79f24d58 100644 --- a/packages/modules/devices/sungrow/modbus.md +++ b/packages/modules/devices/sungrow/modbus.md @@ -30,3 +30,12 @@ | 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 0724d2129acf79dfbf72d9f052ebf53596804d46 Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 11 Apr 2024 09:10:17 +0200 Subject: [PATCH 05/15] Added load power register 13007 for SH_WiNet, so all versions deliver accurate data for load now --- packages/modules/devices/sungrow/inverter.py | 5 ++--- packages/modules/devices/sungrow/modbus.md | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sungrow/inverter.py b/packages/modules/devices/sungrow/inverter.py index 85048cbe76..2da0b242b3 100644 --- a/packages/modules/devices/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/inverter.py @@ -28,9 +28,8 @@ def __init__(self, def update(self) -> float: unit = self.device_config.configuration.modbus_id - if self.device_config.configuration.version == Version.SH_winet_dongle: - # Not recommended to use the SH WiNet-S-Dongle, but if, this is the most accurate data: - power = self.__tcp_client.read_input_registers(5016, ModbusDataType.INT_32, + if self.device_config.configuration.version in (Version.SH, Version.SH_winet_dongle): + power = self.__tcp_client.read_input_registers(13007, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 else: power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, diff --git a/packages/modules/devices/sungrow/modbus.md b/packages/modules/devices/sungrow/modbus.md index ed79f24d58..875b4c0277 100644 --- a/packages/modules/devices/sungrow/modbus.md +++ b/packages/modules/devices/sungrow/modbus.md @@ -4,8 +4,8 @@ * TI_20230918_Communication Protocol of Residential and Commerical 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.P020 -* modbus_finder.py an SG10RT (WiNet-S) Firmware BERYL-S_B000.V000.P039-20230626 / WINET-SV200.001.00.P020 +* 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 | @@ -16,7 +16,8 @@ | 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 | 5030 | -- | 5030 | 1 W | INT_32 mixed | | +| WR: Akt. AC Wirkleistung | 13007 | 13007 | 5030 | 1 W | INT_32 mixed | 13007 und 5030 sind gleich für SH_LAN, 5030 etwas verzögert | +| WR: Akt. AC Wirkleistung | 5030 | 13007 | 5030 | 1 W | INT_32 mixed | 13007 verfügbar über SH_*, 5030 über SH_LAN und SG_WiNet | | 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 | | From e16d57cfd0cfb05f78687194a0266c8368a357f5 Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 11 Apr 2024 13:47:01 +0200 Subject: [PATCH 06/15] Using register 13033 instead of 13007 to have accurate data in retrofit scenarios --- packages/modules/devices/sungrow/inverter.py | 2 +- packages/modules/devices/sungrow/modbus.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/inverter.py b/packages/modules/devices/sungrow/inverter.py index 2da0b242b3..23199f9cf4 100644 --- a/packages/modules/devices/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/inverter.py @@ -29,7 +29,7 @@ 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(13007, ModbusDataType.INT_32, + power = self.__tcp_client.read_input_registers(13033, ModbusDataType.INT_32, wordorder=Endian.Little, unit=unit) * -1 else: power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32, diff --git a/packages/modules/devices/sungrow/modbus.md b/packages/modules/devices/sungrow/modbus.md index 875b4c0277..35a07e588b 100644 --- a/packages/modules/devices/sungrow/modbus.md +++ b/packages/modules/devices/sungrow/modbus.md @@ -16,8 +16,8 @@ | 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 | 13007 | 13007 | 5030 | 1 W | INT_32 mixed | 13007 und 5030 sind gleich für SH_LAN, 5030 etwas verzögert | -| WR: Akt. AC Wirkleistung | 5030 | 13007 | 5030 | 1 W | INT_32 mixed | 13007 verfügbar über SH_*, 5030 über SH_LAN und SG_WiNet | +| 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 | | From efa81f81723175931c0cd2b715fa74a5340b590f Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Fri, 12 Apr 2024 12:49:52 +0200 Subject: [PATCH 07/15] Preserving original version numbers Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> --- packages/modules/devices/sungrow/version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sungrow/version.py b/packages/modules/devices/sungrow/version.py index e1aa85d25e..539705095e 100644 --- a/packages/modules/devices/sungrow/version.py +++ b/packages/modules/devices/sungrow/version.py @@ -3,6 +3,6 @@ class Version(IntEnum): SH = 0 - SH_winet_dongle = 1 - SG = 2 - SG_winet_dongle = 3 + SG = 1 + SG_winet_dongle = 2 + SH_winet_dongle = 3 From 1a1f2fe7485abd41d172afb609e82b80510c4daf Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Fri, 12 Apr 2024 15:13:09 +0200 Subject: [PATCH 08/15] Using SimCounter instead of battery counters due to too low resolution. --- packages/modules/devices/sungrow/bat.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/modules/devices/sungrow/bat.py b/packages/modules/devices/sungrow/bat.py index 000e5f9f0a..8b96f6c4f3 100644 --- a/packages/modules/devices/sungrow/bat.py +++ b/packages/modules/devices/sungrow/bat.py @@ -30,13 +30,10 @@ def update(self) -> None: resp = self.__tcp_client._delegate.read_input_registers(13000, 1, unit=unit) binary = bin(resp.registers[0])[2:].zfill(8) power = self.__tcp_client.read_input_registers(13021, ModbusDataType.INT_16, unit=unit) - imported = self.__tcp_client.read_input_registers(13026, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 - exported = self.__tcp_client.read_input_registers(13040, ModbusDataType.UINT_32, - wordorder=Endian.Little, unit=unit) * 100 if binary[5] == "1": power = power * -1 + imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, soc=soc, From ad40a905ea47b65ccabd340649431ac57de2caed Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Fri, 12 Apr 2024 15:14:53 +0200 Subject: [PATCH 09/15] Added update_config.py procedure to move Sungrow device version from counter to parent --- packages/helpermodules/update_config.py | 46 ++++++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index cad2364f21..92b002154f 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -37,7 +37,7 @@ class UpdateConfig: - DATASTORE_VERSION = 41 + DATASTORE_VERSION = 42 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/set/charging_power_left$", @@ -1365,17 +1365,17 @@ def upgrade(topic: str, payload) -> None: if re.search("openWB/system/device/[0-9]+", topic) is not None: payload = decode_payload(payload) # Module mit separater Modbus ID für jede Komponente - if payload.get("name") == "Sma Sunny Boy/Tripower Speicher"\ - and "modbus_id" not in payload["configuration"]: + if payload.get("name") == "Sma Sunny Boy/Tripower Speicher" \ + and "modbus_id" not in payload["configuration"]: payload["configuration"].update({"modbus_id": 3}) - if payload.get("name") == "Sma Sunny Boy Smart Energy Speicher"\ - and "modbus_id" not in payload["configuration"]: + if payload.get("name") == "Sma Sunny Boy Smart Energy Speicher" \ + and "modbus_id" not in payload["configuration"]: payload["configuration"].update({"modbus_id": 3}) - if payload.get("name") == "Sma Sunny Boy/Tripower Zähler"\ - and "modbus_id" not in payload["configuration"]: + if payload.get("name") == "Sma Sunny Boy/Tripower Zähler" \ + and "modbus_id" not in payload["configuration"]: payload["configuration"].update({"modbus_id": 3}) - if payload.get("name") == "Sma Sunny Boy/Tripower Wechselrichter"\ - and "modbus_id" not in payload["configuration"]: + if payload.get("name") == "Sma Sunny Boy/Tripower Wechselrichter" \ + and "modbus_id" not in payload["configuration"]: if payload.get("configuration").get("version") == 1: payload["configuration"].update({"modbus_id": 1}) elif payload.get("configuration").get("version") == 2: @@ -1405,3 +1405,31 @@ def upgrade(topic: str, payload) -> None: Pub().pub(topic, payload) self._loop_all_received_topics(upgrade) Pub().pub("openWB/system/datastore_version", 41) + + def upgrade_datastore_41(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/system/device/[0-9]+", topic) is not None: + payload = decode_payload(payload) + if payload.get("type") == "sungrow" and "version" not in payload["configuration"]: + index = get_index(topic) + # Assume an SG_WiNet version as default if no battery or counter found: + version = 2 + for other_topic, other_payload in self.all_received_topics.items(): + if re.search(f"openWB/system/device/{index}/component", other_topic) is not None: + child_component_payload = decode_payload(other_payload) + if child_component_payload.get("type") == "counter" and "version" in \ + child_component_payload["configuration"]: + version = child_component_payload['configuration']['version'] + log.debug(f"Version {version} found at counter for sungrow device {index}") + # Pre-defined value found, stop component search + break + elif child_component_payload.get("type") == "bat": + # Assume a hybrid inverter since a battery is attached, take SH_WiNet as default + # since it is more compatible than SH_LAN + version = 3 + log.debug(f"Setting version {version} for sungrow device {index}") + payload["configuration"].update({"version": version}) + Pub().pub(topic, payload) + + self._loop_all_received_topics(upgrade) + Pub().pub("openWB/system/datastore_version", 42) From 6233bc70370972f98bba83dc374f6e919d46924a Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Tue, 16 Apr 2024 11:02:00 +0200 Subject: [PATCH 10/15] Fixed PEP 8 errors --- packages/helpermodules/update_config.py | 7 ++++--- packages/modules/devices/sungrow/bat.py | 2 +- packages/tools/modbus_finder.py | 11 ++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 996691eba6..175f3583f7 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -1448,15 +1448,16 @@ def upgrade(topic: str, payload) -> None: if re.search(f"openWB/system/device/{index}/component", other_topic) is not None: child_component_payload = decode_payload(other_payload) if child_component_payload.get("type") == "counter" and "version" in \ - child_component_payload["configuration"]: + child_component_payload["configuration"]: version = child_component_payload['configuration']['version'] log.debug(f"Version {version} found at counter for sungrow device {index}") # Pre-defined value found, stop component search break elif child_component_payload.get("type") == "bat": - # Assume a hybrid inverter since a battery is attached, take SH_WiNet as default - # since it is more compatible than SH_LAN + # Assume a hybrid inverter since a battery is attached version = 3 + # Assume an SH_WiNet connection if SH_x has been set before, this is more compatible than SH_LAN: + version = 3 if version == 0 else version log.debug(f"Setting version {version} for sungrow device {index}") payload["configuration"].update({"version": version}) Pub().pub(topic, payload) diff --git a/packages/modules/devices/sungrow/bat.py b/packages/modules/devices/sungrow/bat.py index 8b96f6c4f3..dc579b7ebb 100644 --- a/packages/modules/devices/sungrow/bat.py +++ b/packages/modules/devices/sungrow/bat.py @@ -6,7 +6,7 @@ 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 +from modules.common.modbus import ModbusDataType from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.sungrow.config import SungrowBatSetup, Sungrow diff --git a/packages/tools/modbus_finder.py b/packages/tools/modbus_finder.py index 76788c3990..156ce4b0bb 100644 --- a/packages/tools/modbus_finder.py +++ b/packages/tools/modbus_finder.py @@ -3,16 +3,21 @@ import time from typing import Callable +import pymodbus from pymodbus.constants import Endian from modules.common import modbus def try_read(function: Callable, **kwargs) -> str: + result = "--" try: - return str(function(**kwargs)) - except Exception as e: - return "--" + result = str(function(**kwargs)) + except pymodbus.exceptions.ConnectionException as cex: + # Can happen on concurrent access, retry once + result = str(function(**kwargs)) + finally: + return result host = sys.argv[1] From 0bd8805bb01ff7047a6edb40a6f8aa43653676c6 Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Tue, 16 Apr 2024 11:02:00 +0200 Subject: [PATCH 11/15] Fixed PEP 8 errors --- packages/helpermodules/update_config.py | 7 ++++--- packages/modules/devices/sungrow/bat.py | 2 +- packages/tools/modbus_finder.py | 11 ++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 996691eba6..175f3583f7 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -1448,15 +1448,16 @@ def upgrade(topic: str, payload) -> None: if re.search(f"openWB/system/device/{index}/component", other_topic) is not None: child_component_payload = decode_payload(other_payload) if child_component_payload.get("type") == "counter" and "version" in \ - child_component_payload["configuration"]: + child_component_payload["configuration"]: version = child_component_payload['configuration']['version'] log.debug(f"Version {version} found at counter for sungrow device {index}") # Pre-defined value found, stop component search break elif child_component_payload.get("type") == "bat": - # Assume a hybrid inverter since a battery is attached, take SH_WiNet as default - # since it is more compatible than SH_LAN + # Assume a hybrid inverter since a battery is attached version = 3 + # Assume an SH_WiNet connection if SH_x has been set before, this is more compatible than SH_LAN: + version = 3 if version == 0 else version log.debug(f"Setting version {version} for sungrow device {index}") payload["configuration"].update({"version": version}) Pub().pub(topic, payload) diff --git a/packages/modules/devices/sungrow/bat.py b/packages/modules/devices/sungrow/bat.py index 8b96f6c4f3..dc579b7ebb 100644 --- a/packages/modules/devices/sungrow/bat.py +++ b/packages/modules/devices/sungrow/bat.py @@ -6,7 +6,7 @@ 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 +from modules.common.modbus import ModbusDataType from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.sungrow.config import SungrowBatSetup, Sungrow diff --git a/packages/tools/modbus_finder.py b/packages/tools/modbus_finder.py index 76788c3990..2b575a8b3a 100644 --- a/packages/tools/modbus_finder.py +++ b/packages/tools/modbus_finder.py @@ -3,16 +3,21 @@ import time from typing import Callable +import pymodbus from pymodbus.constants import Endian from modules.common import modbus def try_read(function: Callable, **kwargs) -> str: + result = "--" try: - return str(function(**kwargs)) - except Exception as e: - return "--" + result = str(function(**kwargs)) + except pymodbus.exceptions.ConnectionException: + # Can happen on concurrent access, retry once + result = str(function(**kwargs)) + finally: + return result host = sys.argv[1] From 9aacdaeb6a4058697e0e17fee1b69da8be78abdf Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Tue, 16 Apr 2024 11:42:51 +0200 Subject: [PATCH 12/15] Fixed PEP 8 errors --- packages/tools/modbus_finder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/modbus_finder.py b/packages/tools/modbus_finder.py index 156ce4b0bb..2b575a8b3a 100644 --- a/packages/tools/modbus_finder.py +++ b/packages/tools/modbus_finder.py @@ -13,7 +13,7 @@ def try_read(function: Callable, **kwargs) -> str: result = "--" try: result = str(function(**kwargs)) - except pymodbus.exceptions.ConnectionException as cex: + except pymodbus.exceptions.ConnectionException: # Can happen on concurrent access, retry once result = str(function(**kwargs)) finally: From 65e14f1d74daf3567857c65c1522453656efbc9c Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Tue, 16 Apr 2024 13:26:09 +0200 Subject: [PATCH 13/15] Added system message in case of Sungrow configuration update --- packages/helpermodules/update_config.py | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 175f3583f7..7c5bd2c94e 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -1442,8 +1442,8 @@ def upgrade(topic: str, payload) -> None: payload = decode_payload(payload) if payload.get("type") == "sungrow" and "version" not in payload["configuration"]: index = get_index(topic) - # Assume an SG_WiNet version as default if no battery or counter found: - version = 2 + version = None + has_battery = False for other_topic, other_payload in self.all_received_topics.items(): if re.search(f"openWB/system/device/{index}/component", other_topic) is not None: child_component_payload = decode_payload(other_payload) @@ -1451,16 +1451,25 @@ def upgrade(topic: str, payload) -> None: child_component_payload["configuration"]: version = child_component_payload['configuration']['version'] log.debug(f"Version {version} found at counter for sungrow device {index}") - # Pre-defined value found, stop component search - break elif child_component_payload.get("type") == "bat": - # Assume a hybrid inverter since a battery is attached - version = 3 - # Assume an SH_WiNet connection if SH_x has been set before, this is more compatible than SH_LAN: - version = 3 if version == 0 else version + has_battery = True + + if has_battery or version == 0: + # Assume an SH_WiNet if hybrid inverter, this is compatible to SH_LAN (but not vice versa) + version = 3 + elif not version: + # Assume an SG_WiNet version as default if no battery or version from counter found + version = 2 log.debug(f"Setting version {version} for sungrow device {index}") payload["configuration"].update({"version": version}) - Pub().pub(topic, payload) + Pub().pub(topic, payload) + + pub_system_message(payload, "Die Konfiguration von Sungrow Geräten wurde aktualisiert. \ + Bitte die Version in den Geräteeinstellungen überprüfen", MessageType.WARNING) + if has_battery: + pub_system_message(payload, "Das Auslesen von Sungrow Hybrid-Wechselrichtern wurde \ + aktualisiert. Bitte den Speicher im Lastmanagement innerhalb des zugehörigen \ + Hybrid-Wechselrichters anordnen", MessageType.WARNING) self._loop_all_received_topics(upgrade) Pub().pub("openWB/system/datastore_version", 44) From 9ce2ea17fbc5ae70361053e945a17207d194a09e Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Thu, 18 Apr 2024 12:41:31 +0200 Subject: [PATCH 14/15] Datastore update now includes hierarchy update for Sungrow hybrid inverters with battery --- packages/helpermodules/update_config.py | 66 +++++++++++++++---------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 7c5bd2c94e..ba15052ac9 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -28,6 +28,8 @@ from control import ev from control.general import Prices from modules.common.abstract_vehicle import GeneralVehicleConfig +from modules.common.component_type import ComponentType +from modules.devices.sungrow.version import Version from modules.display_themes.cards.config import CardsDisplayTheme from modules.ripple_control_receivers.gpio.config import GpioRcr from modules.web_themes.standard_legacy.config import StandardLegacyWebTheme @@ -1439,37 +1441,49 @@ def upgrade(topic: str, payload) -> Optional[dict]: def upgrade_datastore_43(self) -> None: def upgrade(topic: str, payload) -> None: if re.search("openWB/system/device/[0-9]+", topic) is not None: - payload = decode_payload(payload) - if payload.get("type") == "sungrow" and "version" not in payload["configuration"]: - index = get_index(topic) + device = decode_payload(payload) + if device.get("type") == "sungrow" and "version" not in device["configuration"]: + device_id = get_index(topic) + device_name = device.get("name") version = None - has_battery = False + inverter_id = None + battery_id = None + hierarchy = None for other_topic, other_payload in self.all_received_topics.items(): - if re.search(f"openWB/system/device/{index}/component", other_topic) is not None: - child_component_payload = decode_payload(other_payload) - if child_component_payload.get("type") == "counter" and "version" in \ - child_component_payload["configuration"]: - version = child_component_payload['configuration']['version'] - log.debug(f"Version {version} found at counter for sungrow device {index}") - elif child_component_payload.get("type") == "bat": - has_battery = True - - if has_battery or version == 0: + if re.search(f"openWB/system/device/{device_id}/component/[0-9]+", other_topic) is not None: + component = decode_payload(other_payload) + component_id = int(get_second_index(other_topic)) + if component.get("type") == ComponentType.COUNTER.value \ + and "version" in component["configuration"]: + version = component['configuration']['version'] + log.debug(f"Version {version} found for device {device_id} '{device_name}'") + elif component.get("type") == ComponentType.BAT.value: + battery_id = component_id + elif component.get("type") == ComponentType.INVERTER.value: + inverter_id = component_id + elif re.search(f"openWB/counter/get/hierarchy", other_topic) is not None: + hierarchy = decode_payload(other_payload) + + if battery_id or version == Version.SH: # Assume an SH_WiNet if hybrid inverter, this is compatible to SH_LAN (but not vice versa) - version = 3 + version = Version.SH_winet_dongle elif not version: # Assume an SG_WiNet version as default if no battery or version from counter found - version = 2 - log.debug(f"Setting version {version} for sungrow device {index}") - payload["configuration"].update({"version": version}) - Pub().pub(topic, payload) - - pub_system_message(payload, "Die Konfiguration von Sungrow Geräten wurde aktualisiert. \ - Bitte die Version in den Geräteeinstellungen überprüfen", MessageType.WARNING) - if has_battery: - pub_system_message(payload, "Das Auslesen von Sungrow Hybrid-Wechselrichtern wurde \ - aktualisiert. Bitte den Speicher im Lastmanagement innerhalb des zugehörigen \ - Hybrid-Wechselrichters anordnen", MessageType.WARNING) + version = Version.SG_winet_dongle + log.debug(f"Setting version {version} for device {device_id} '{device_name}'") + device["configuration"].update({"version": version}) + Pub().pub(topic, device) + pub_system_message(device, f"Die Konfiguration von '{device_name}' wurde aktualisiert. " + f"Bitte in den Geräteeinstellungen sicherstellen, dass Version " + f"'{Version(version).name}' korrekt ist", MessageType.INFO) + + if battery_id and inverter_id and hierarchy: + _counter_all = counter_all.CounterAll() + _counter_all.data.get.hierarchy = hierarchy + _counter_all.hierarchy_remove_item(battery_id) + _counter_all.hierarchy_add_item_below(battery_id, ComponentType.BAT, inverter_id) + log.debug(f"Moved battery {battery_id} below inverter {inverter_id} in hierarchy") + Pub().pub("openWB/counter/get/hierarchy", _counter_all.data.get.hierarchy) self._loop_all_received_topics(upgrade) Pub().pub("openWB/system/datastore_version", 44) From f5d09616271a82cb16af4b9a7481a92f16b47ecd Mon Sep 17 00:00:00 2001 From: Mantelinho Date: Fri, 19 Apr 2024 10:03:19 +0200 Subject: [PATCH 15/15] Fixed Flake8 f-string complaint --- 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 ba15052ac9..eb122d6521 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -1461,7 +1461,7 @@ def upgrade(topic: str, payload) -> None: battery_id = component_id elif component.get("type") == ComponentType.INVERTER.value: inverter_id = component_id - elif re.search(f"openWB/counter/get/hierarchy", other_topic) is not None: + elif re.search("openWB/counter/get/hierarchy", other_topic) is not None: hierarchy = decode_payload(other_payload) if battery_id or version == Version.SH: