Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sungrow modbus: reading more / correct adresses to complete counter and inverter values #1537

Merged
merged 25 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fdf938d
Updated sungrow modbus addresses, moved version from counter to devic…
Mantelinho Jan 11, 2024
2077262
Fixed wrong Endian import
Mantelinho Jan 11, 2024
37a97ae
Fixed WiNet-S frequency accuracy
Mantelinho Jan 11, 2024
6a1d0a4
Merge remote-tracking branch 'origin/master' into patch_sungrow_modbus
Mantelinho Apr 10, 2024
e8286ad
Reading powers per phase now, added more registers to modbus.md docum…
Mantelinho Apr 10, 2024
d66fc9b
Merge branch 'openWB:master' into patch_sungrow_modbus
Mantelinho Apr 10, 2024
0724d21
Added load power register 13007 for SH_WiNet, so all versions deliver…
Mantelinho Apr 11, 2024
a91f0f4
Merge remote-tracking branch 'origin/patch_sungrow_modbus' into patch…
Mantelinho Apr 11, 2024
e16d57c
Using register 13033 instead of 13007 to have accurate data in retrof…
Mantelinho Apr 11, 2024
7ce082c
Merge branch 'openWB:master' into patch_sungrow_modbus
Mantelinho Apr 12, 2024
efa81f8
Preserving original version numbers
Mantelinho Apr 12, 2024
1a1f2fe
Using SimCounter instead of battery counters due to too low resolution.
Mantelinho Apr 12, 2024
ad40a90
Added update_config.py procedure to move Sungrow device version from …
Mantelinho Apr 12, 2024
d5ca8fb
Merge branch 'openWB:master' into patch_sungrow_modbus
Mantelinho Apr 12, 2024
0646331
Merge remote-tracking branch 'origin/patch_sungrow_modbus' into patch…
Mantelinho Apr 12, 2024
a7e8dba
Merge remote-tracking branch 'origin/master' into patch_sungrow_modbus
Mantelinho Apr 15, 2024
37cf51a
Merge branch 'openWB:master' into patch_sungrow_modbus
Mantelinho Apr 16, 2024
6233bc7
Fixed PEP 8 errors
Mantelinho Apr 16, 2024
0bd8805
Fixed PEP 8 errors
Mantelinho Apr 16, 2024
7db04c8
Merge remote-tracking branch 'origin/patch_sungrow_modbus' into patch…
Mantelinho Apr 16, 2024
9aacdae
Fixed PEP 8 errors
Mantelinho Apr 16, 2024
65e14f1
Added system message in case of Sungrow configuration update
Mantelinho Apr 16, 2024
61b0c2d
Merge remote-tracking branch 'origin/master' into patch_sungrow_modbus
Mantelinho Apr 18, 2024
9ce2ea1
Datastore update now includes hierarchy update for Sungrow hybrid inv…
Mantelinho Apr 18, 2024
f5d0961
Fixed Flake8 f-string complaint
Mantelinho Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 39 additions & 5 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


class UpdateConfig:
DATASTORE_VERSION = 43
DATASTORE_VERSION = 44
valid_topic = [
"^openWB/bat/config/configured$",
"^openWB/bat/set/charging_power_left$",
Expand Down Expand Up @@ -191,9 +191,7 @@ class UpdateConfig:
"^openWB/general/chargemode_config/pv_charging/phases_to_use$",
"^openWB/general/chargemode_config/pv_charging/min_bat_soc$",
"^openWB/general/chargemode_config/pv_charging/bat_power_discharge$",
"^openWB/general/chargemode_config/pv_charging/bat_power_discharge_active$",
"^openWB/general/chargemode_config/pv_charging/bat_power_reserve$",
"^openWB/general/chargemode_config/pv_charging/bat_power_reserve_active$",
"^openWB/general/chargemode_config/retry_failed_phase_switches$",
"^openWB/general/chargemode_config/scheduled_charging/phases_to_use$",
"^openWB/general/chargemode_config/instant_charging/phases_to_use$",
Expand Down Expand Up @@ -419,10 +417,8 @@ class UpdateConfig:
("openWB/general/chargemode_config/instant_charging/phases_to_use", 3),
("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value),
("openWB/general/chargemode_config/pv_charging/bat_power_discharge", 1000),
("openWB/general/chargemode_config/pv_charging/bat_power_discharge_active", False),
("openWB/general/chargemode_config/pv_charging/min_bat_soc", 50),
("openWB/general/chargemode_config/pv_charging/bat_power_reserve", 200),
("openWB/general/chargemode_config/pv_charging/bat_power_reserve_active", False),
("openWB/general/chargemode_config/pv_charging/control_range", [0, 230]),
("openWB/general/chargemode_config/pv_charging/switch_off_threshold", 50),
("openWB/general/chargemode_config/pv_charging/switch_off_delay", 60),
Expand Down Expand Up @@ -1439,3 +1435,41 @@ def upgrade(topic: str, payload) -> Optional[dict]:
payload) > 0}
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 43)

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)
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)
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:
# 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_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)
Mantelinho marked this conversation as resolved.
Show resolved Hide resolved

self._loop_all_received_topics(upgrade)
Pub().pub("openWB/system/datastore_version", 44)
12 changes: 5 additions & 7 deletions packages/modules/devices/sungrow/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,23 @@
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)
Expand Down
12 changes: 9 additions & 3 deletions packages/modules/devices/sungrow/config.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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]):
Expand Down
51 changes: 29 additions & 22 deletions packages/modules/devices/sungrow/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,67 @@
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))
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,
wordorder=Endian.Little, unit=unit)
else:
power = self.__tcp_client.read_input_registers(5090, ModbusDataType.INT_32,
wordorder=Endian.Little, unit=unit)
powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_32] * 3,
wordorder=Endian.Little, unit=unit)

# 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
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_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

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
frequency=frequency,
power_factors=[power_factor] * 3
)
self.store.set(counter_state)

Expand Down
15 changes: 9 additions & 6 deletions packages/modules/devices/sungrow/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,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: " +
Expand Down Expand Up @@ -84,23 +84,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,
Expand All @@ -113,11 +115,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()


Expand Down
24 changes: 13 additions & 11 deletions packages/modules/devices/sungrow/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,31 @@
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
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
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
else:
power = self.__tcp_client.read_input_registers(5030, ModbusDataType.INT_32,
wordorder=Endian.Little, unit=unit) * -1

_, exported = self.sim_counter.sim_count(power)

Expand Down