diff --git a/CODEOWNERS b/CODEOWNERS
index 2cd8f3d1728..3fc7762cb3c 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -219,6 +219,7 @@
/bundles/org.openhab.binding.modbus.sbc/ @fwolter
/bundles/org.openhab.binding.modbus.stiebeleltron/ @pail23
/bundles/org.openhab.binding.modbus.studer/ @giovannimirulla
+/bundles/org.openhab.binding.modbus.sungrow/ @soenkekueper
/bundles/org.openhab.binding.modbus.sunspec/ @mrbig
/bundles/org.openhab.binding.monopriceaudio/ @mlobstein
/bundles/org.openhab.binding.mpd/ @stefanroellin
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index e0a5201f678..49192aef099 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1086,6 +1086,11 @@
org.openhab.binding.modbus.studer
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.modbus.sungrow
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.modbus.sunspec
diff --git a/bundles/org.openhab.binding.modbus.sungrow/NOTICE b/bundles/org.openhab.binding.modbus.sungrow/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.modbus.sungrow/README.md b/bundles/org.openhab.binding.modbus.sungrow/README.md
new file mode 100644
index 00000000000..79a87f2cf34
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/README.md
@@ -0,0 +1,174 @@
+# Modbus Sungrow Binding
+
+This binding integrates the sungrow inverters into openHAB.
+It is based on the Sungrow specification "Communication Protocol of Residential Hybrid Inverter V1.0.23", which can be found here: https://github.com/bohdan-s/SunGather/issues/36.
+
+## Supported Inverters
+
+As defined within the spec mentioned above the following inverters are supported, but not all are tested yet:
+
+- SH3K6
+- SH4K6
+- SH5K-20
+- SH5K-V13
+- SH3K6-30
+- SH4K6-30
+- SH5K-30
+- SH3.0RS
+- SH3.6RS
+- SH4.0RS
+- SH5.0RS
+- SH6.0RS
+- SH5.0RT
+- SH6.0RT
+- SH8.0RT
+- SH10RT
+
+## Supported Things
+
+The binding supports only one thing:
+
+- `sungrow-inverter`: The sungrow inverter
+
+## Preparation
+
+The data from the inverter is read via Modbus. So you need to configure a Modbus Serial Slave `serial` or Modbus TCP Slave `tcp` as bridge first.
+If you are using a Modbus TCP Slave and the WiNet-S Communication Module please ensure:
+
+- that you have the correct IP-Address of your WiNet-S Device
+- that Modbus is enabled within the Communication Module
+- that you've the correct port number
+- that the white list is disabled or your openHAB instance IP is listed
+
+Enabling modbus and whitelist setting can be done in WiNet-S Web-UI as shown below:
+
+
+## Thing Configuration
+
+Once you've configured the Modbus TCP Slave or Modbus Serial Slave as Bridge you can configure the Sungrow inverter thing.
+You just have to select the configured bridge and optional configure the polling interval.
+
+### Sungrow Inverter (`sungrow-inverter`)
+
+| Name | Type | Description | Default | Required | Advanced |
+|--------------|---------|--------------------------------------|---------|----------|----------|
+| pollInterval | integer | Interval the device is polled in ms. | 5000 | yes | no |
+
+## Channels
+
+The `sungrow-inverter` thing has channels that serve the current state of the sungrow inverter, as you are used to from the iSolareCloud Website and App.
+
+| Channel Type ID | Item Type | Description | Advanced | Channel Group |
+|------------------------------------|--------------------------|---------------------------------------|-----------|---------------------|
+| sg-internal-temperature | Number:Temperature | Internal Temperature | yes | Overview |
+| sg-total-dc-power | Number:Power | Total DC Power | no | Overview |
+| sg-phase-a-voltage | Number:ElectricPotential | Phase A Voltage | yes | Overview |
+| sg-phase-b-voltage | Number:ElectricPotential | Phase B Voltage | yes | Overview |
+| sg-phase-c-voltage | Number:ElectricPotential | Phase C Voltage | yes | Overview |
+| sg-daily-pv-generation | Number:Energy | Daily PV Generation | no | Overview |
+| sg-total-pv-generation | Number:Energy | Total PV Generation | no | Overview |
+| sg-reactive-power | Number:Power | Reactive Power | yes | Overview |
+| sg-power-factor | Number:Dimensionless | Power Factor | yes | Overview |
+| sg-phase-a-current | Number:ElectricCurrent | Phase A Current | yes | Overview |
+| sg-phase-b-current | Number:ElectricCurrent | Phase B Current | yes | Overview |
+| sg-phase-c-current | Number:ElectricCurrent | Phase C Current | yes | Overview |
+| sg-total-active-power | Number:Power | Total Active Power | no | Overview |
+| sg-grid-frequency | Number:Frequency | Grid Frequency | yes | Overview |
+| sg-mppt1-voltage | Number:ElectricPotential | MPPT1 Voltage | yes | MPPT Information |
+| sg-mppt1-current | Number:ElectricCurrent | MPPT1 Current | yes | MPPT Information |
+| sg-mppt2-voltage | Number:ElectricPotential | MPPT2 Voltage | yes | MPPT Information |
+| sg-mppt2-current | Number:ElectricCurrent | MPPT2 Current | yes | MPPT Information |
+| sg-daily-battery-charge | Number:Energy | Daily Battery Charge | no | Battery Information |
+| sg-total-battery-charge | Number:Energy | Total Battery Charge | no | Battery Information |
+| sg-battery-voltage | Number:ElectricPotential | Battery Voltage | yes | Battery Information |
+| sg-battery-current | Number:ElectricCurrent | Battery Current | yes | Battery Information |
+| sg-battery-power | Number:Power | Battery Power | no | Battery Information |
+| sg-battery-level | Number:Dimensionless | Battery Level | no | Battery Information |
+| sg-battery-healthy | Number:Dimensionless | Battery Healthy | no | Battery Information |
+| sg-battery-temperature | Number:Temperature | Battery Temperature | no | Battery Information |
+| sg-daily-battery-discharge-energy | Number:Energy | Daily Battery Discharge Energy | no | Battery Information |
+| sg-total-battery-discharge-energy | Number:Energy | Total Battery Discharge Energy | no | Battery Information |
+| sg-battery-capacity | Number:Energy | Battery Capacity | no | Battery Information |
+| sg-daily-charge-energy | Number:Energy | Daily Charge Energy | no | Battery Information |
+| sg-total-charge-energy | Number:Energy | Total Charge Energy | no | Battery Information |
+| sg-daily-import-energy | Number:Energy | Daily Import Energy | no | Grid Information |
+| sg-total-import-energy | Number:Energy | Total Import Energy | no | Grid Information |
+| sg-daily-export-energy | Number:Energy | Daily Export Energy | no | Grid Information |
+| sg-total-export-energy | Number:Energy | Total Export Energy | no | Grid Information |
+| sg-daily-export-power-from-pv | Number:Power | Daily Export Power from PV | no | Grid Information |
+| sg-total-export-energy-from-pv | Number:Energy | Total Export Energy from PV | no | Grid Information |
+| sg-export-power | Number:Power | Export Power | no | Grid Information |
+| sg-load-power | Number:Power | Load Power | no | Load Information |
+| sg-daily-direct-energy-consumption | Number:Energy | Daily Direct Energy Consumption | no | Load Information |
+| sg-total-direct-energy-consumption | Number:Energy | Total Direct Energy Consumption | no | Load Information |
+| sg-self-consumption-today | Number:Dimensionless | Self Consumption Today | no | Load Information |
+
+## Full Example
+
+This example shows how to configure a sungrow inverter connected via modbus and uses the most common channels.
+
+_sungrow.things_
+
+```java
+Bridge modbus:tcp:sungrowBridge [ host="10.0.0.2", port=502, id=1, enableDiscovery=false ] {
+ Thing sungrow-inverter sungrowInverter "Sungrow Inverter" [ pollInterval=5000 ]
+}
+```
+
+_sungrow.items_
+
+```java
+// Groups
+Group sungrowInverter "Sungrow Inverter" ["Inverter"]
+Group overview "Overview" (sungrowInverter)
+Group batteryInformation "Battery information" (sungrowInverter)
+Group gridInformation "Grid information" (sungrowInverter)
+Group loadInformation "Load information" (sungrowInverter)
+
+// Overview
+Number:Power total_active_power "Total Active Power" (overview) ["Measurement", "Power"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-overview#sg-total-active-power"}
+Number:Power total_dc_power "Total DC Power" (overview) ["Measurement", "Power"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-overview#sg-total-dc-power"}
+Number:Energy daily_pv_generation "Daily PV Generation" (overview) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-overview#sg-daily-pv-generation"}
+Number:Energy total_pv_generation "Total PV Generation" (overview) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-overview#sg-total-pv-generation"}
+
+// Battery information
+Number:Power battery_power "Battery Power" (batteryInformation) ["Measurement", "Power"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-battery-information#sg-battery-power"}
+Number:Dimensionless battery_level "Battery Level" (batteryInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-battery-information#sg-battery-level"}
+Number:Energy daily_charge_energy "Daily Battery Charge Energy" (batteryInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-battery-information#sg-daily-charge-energy"}
+Number:Energy daily_discharge_energy "Daily Battery Discharge Energy" (batteryInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-battery-information#sg-daily-battery-discharge-energy"}
+
+// Grid information
+Number:Power export_power "Export Power" (gridInformation) ["Measurement", "Power"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-grid-information#sg-export-power"}
+Number:Energy daily_export_energy "Daily Export Energy" (gridInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-grid-information#sg-daily-export-energy"}
+Number:Energy daily_import_energy "Daily Import Energy" (gridInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-grid-information#sg-daily-import-energy"}
+
+// Load information
+Number:Power load_power "Load Power" (loadInformation) ["Measurement", "Power"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-load-information#sg-load-power"}
+Number:Energy daily_direct_energy_consumption "Daily Direct Energy Consumption" (loadInformation) ["Measurement", "Energy"] {channel="modbus:sungrow-inverter:sungrowBridge:sungrowInverter:sg-load-information#sg-daily-direct-energy-consumption"}
+```
+
+_sungrow.sitemap_
+
+```perl
+sitemap sungrow label="Sungrow Binding"
+{
+ Frame {
+ Text item=total_active_power
+ Text item=total_dc_power
+ Text item=daily_pv_generation
+ Text item=total_pv_generation
+
+ Text item=battery_power
+ Text item=battery_level
+ Text item=daily_charge_energy
+ Text item=daily_discharge_energy
+
+ Text item=export_power
+ Text item=daily_export_energy
+ Text item=daily_import_energy
+
+ Text item=load_power
+ Text item=daily_direct_energy_consumption
+ }
+}
+```
diff --git a/bundles/org.openhab.binding.modbus.sungrow/doc/WiNet-S_Modbus.PNG b/bundles/org.openhab.binding.modbus.sungrow/doc/WiNet-S_Modbus.PNG
new file mode 100644
index 00000000000..942273fdc08
Binary files /dev/null and b/bundles/org.openhab.binding.modbus.sungrow/doc/WiNet-S_Modbus.PNG differ
diff --git a/bundles/org.openhab.binding.modbus.sungrow/pom.xml b/bundles/org.openhab.binding.modbus.sungrow/pom.xml
new file mode 100644
index 00000000000..63dbb502dd4
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.2.0-SNAPSHOT
+
+
+ org.openhab.binding.modbus.sungrow
+
+ openHAB Add-ons :: Bundles :: Modbus Sungrow Binding
+
+
+
+ org.openhab.addons.bundles
+ org.openhab.binding.modbus
+ ${project.version}
+ provided
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ConversionConstants.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ConversionConstants.java
new file mode 100644
index 00000000000..9d7298bd317
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ConversionConstants.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Constants for converting values.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+final class ConversionConstants {
+
+ private ConversionConstants() {
+ }
+
+ /**
+ * Multiplicand for 0.1.
+ */
+ static final BigDecimal DIV_BY_TEN = new BigDecimal(BigInteger.ONE, 1);
+
+ /**
+ * Value conversion from Celsius to Kelvin.
+ */
+ static final Function CELSIUS_TO_KELVIN = (BigDecimal celsius) -> celsius
+ .add(new BigDecimal(273.15f));
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowBindingConstants.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowBindingConstants.java
new file mode 100644
index 00000000000..1b1abaeacb3
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowBindingConstants.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.modbus.ModbusBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link ModbusSungrowBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+public class ModbusSungrowBindingConstants {
+
+ /**
+ * ThingType-ID for Inverter.
+ */
+ public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(ModbusBindingConstants.BINDING_ID,
+ "sungrow-inverter");
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowHandlerFactory.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowHandlerFactory.java
new file mode 100644
index 00000000000..d0a547cd180
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/ModbusSungrowHandlerFactory.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import static org.openhab.binding.modbus.sungrow.internal.ModbusSungrowBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link ModbusSungrowHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.sungrow", service = ThingHandlerFactory.class)
+public class ModbusSungrowHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_INVERTER);
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_INVERTER.equals(thingTypeUID)) {
+ return new SungrowInverterHandler(thing);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterConfiguration.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterConfiguration.java
new file mode 100644
index 00000000000..88965f9c520
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterConfiguration.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SungrowInverterConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+public class SungrowInverterConfiguration {
+
+ public int pollInterval;
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterHandler.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterHandler.java
new file mode 100644
index 00000000000..5b1d55f7934
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterHandler.java
@@ -0,0 +1,185 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.modbus.handler.BaseModbusThingHandler;
+import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
+import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
+import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
+import org.openhab.core.io.transport.modbus.ModbusConstants;
+import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
+import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SungrowInverterHandler} is responsible for reading the modbus values of the
+ * sungrow inverter.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+public class SungrowInverterHandler extends BaseModbusThingHandler {
+
+ @NonNullByDefault
+ private static final class ModbusRequest {
+
+ private final Deque registers;
+ private final ModbusReadRequestBlueprint blueprint;
+
+ public ModbusRequest(Deque registers, int slaveId) {
+ this.registers = registers;
+ this.blueprint = initReadRequest(registers, slaveId);
+ }
+
+ private ModbusReadRequestBlueprint initReadRequest(Deque registers, int slaveId) {
+ int firstRegister = registers.getFirst().getRegisterNumber();
+ int lastRegister = registers.getLast().getRegisterNumber();
+ int length = lastRegister - firstRegister + registers.getLast().getRegisterCount();
+ assert length <= ModbusConstants.MAX_REGISTERS_READ_COUNT;
+
+ return new ModbusReadRequestBlueprint( //
+ slaveId, //
+ ModbusReadFunctionCode.READ_INPUT_REGISTERS, //
+ firstRegister - 1, //
+ length, //
+ TRIES //
+ );
+ }
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(SungrowInverterHandler.class);
+
+ private static final int TRIES = 1;
+ private List modbusRequests = new ArrayList<>();
+
+ public SungrowInverterHandler(Thing thing) {
+ super(thing);
+ }
+
+ /**
+ * Splits the SungrowInverterRegisters into multiple ModbusRequest, to ensure the max request size.
+ */
+ private List buildRequests() {
+ final List requests = new ArrayList<>();
+ Deque currentRequest = new ArrayDeque<>();
+ int currentRequestFirstRegister = 0;
+
+ for (SungrowInverterRegisters channel : SungrowInverterRegisters.values()) {
+
+ if (currentRequest.isEmpty()) {
+ currentRequest.add(channel);
+ currentRequestFirstRegister = channel.getRegisterNumber();
+ } else {
+ int sizeWithRegisterAdded = channel.getRegisterNumber() - currentRequestFirstRegister
+ + channel.getRegisterCount();
+ if (sizeWithRegisterAdded > ModbusConstants.MAX_REGISTERS_READ_COUNT) {
+ requests.add(new ModbusRequest(currentRequest, getSlaveId()));
+ currentRequest = new ArrayDeque<>();
+
+ currentRequest.add(channel);
+ currentRequestFirstRegister = channel.getRegisterNumber();
+ } else {
+ currentRequest.add(channel);
+ }
+ }
+ }
+
+ if (!currentRequest.isEmpty()) {
+ requests.add(new ModbusRequest(currentRequest, getSlaveId()));
+ }
+ logger.debug("Created {} modbus request templates.", requests.size());
+ return requests;
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType && !this.modbusRequests.isEmpty()) {
+ for (ModbusRequest request : this.modbusRequests) {
+ submitOneTimePoll( //
+ request.blueprint, //
+ (AsyncModbusReadResult result) -> this.readSuccessful(request, result), //
+ this::readError //
+ );
+ }
+ }
+ }
+
+ @Override
+ public void modbusInitialize() {
+ final SungrowInverterConfiguration config = getConfigAs(SungrowInverterConfiguration.class);
+
+ if (config.pollInterval <= 0) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Invalid poll interval: " + config.pollInterval);
+ return;
+ }
+
+ this.updateStatus(ThingStatus.UNKNOWN);
+
+ this.modbusRequests = this.buildRequests();
+
+ for (ModbusRequest request : modbusRequests) {
+ registerRegularPoll( //
+ request.blueprint, //
+ config.pollInterval, //
+ 0, //
+ (AsyncModbusReadResult result) -> this.readSuccessful(request, result), //
+ this::readError //
+ );
+ }
+ }
+
+ private void readSuccessful(ModbusRequest request, AsyncModbusReadResult result) {
+ result.getRegisters().ifPresent(registers -> {
+ if (getThing().getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ int firstRegister = request.registers.getFirst().getRegisterNumber();
+
+ for (SungrowInverterRegisters channel : request.registers) {
+ int index = channel.getRegisterNumber() - firstRegister;
+
+ ModbusBitUtilities.extractStateFromRegisters(registers, index, channel.getType())
+ .map(channel::createState).ifPresent(v -> updateState(createChannelUid(channel), v));
+ }
+ });
+ }
+
+ private void readError(AsyncModbusFailure error) {
+ this.logger.debug("Failed to get modbus data", error.getCause());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Failed to retrieve data: " + error.getCause().getMessage());
+ }
+
+ private ChannelUID createChannelUid(SungrowInverterRegisters register) {
+ return new ChannelUID( //
+ thing.getUID(), //
+ "sg-" + register.getChannelGroup(), //
+ "sg-" + register.getChannelName() //
+ );
+ }
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegisters.java b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegisters.java
new file mode 100644
index 00000000000..12517febfb7
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegisters.java
@@ -0,0 +1,255 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import static org.openhab.core.io.transport.modbus.ModbusConstants.ValueType.INT16;
+import static org.openhab.core.io.transport.modbus.ModbusConstants.ValueType.INT32_SWAP;
+import static org.openhab.core.io.transport.modbus.ModbusConstants.ValueType.UINT16;
+import static org.openhab.core.io.transport.modbus.ModbusConstants.ValueType.UINT32_SWAP;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.function.Function;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link SungrowInverterRegisters} is responsible for defining Modbus registers and their units.
+ *
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+public enum SungrowInverterRegisters {
+
+ // the following register numbers are 1-based. They need to be converted before sending them on the wire.
+ // Registers are duplicate to DAILY_PV_GENERATION / TOTAL_PV_GENERATION
+ // DAILY_OUTPUT_ENERGY(5003, UINT16, ConversionConstants.DIV_BY_TEN, quantityTypeFactory(Units.KILOWATT_HOUR),
+ // TOTAL_OUTPUT_ENERGY(5004, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityTypeFactory(Units.KILOWATT_HOUR),
+
+ INTERNAL_TEMPERATURE(5008, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KELVIN),
+ ConversionConstants.CELSIUS_TO_KELVIN, "overview"),
+ MPPT1_VOLTAGE(5011, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
+ MPPT1_CURRENT(5012, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE), "mppt-information"),
+ MPPT2_VOLTAGE(5013, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
+ MPPT2_CURRENT(5014, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE), "mppt-information"),
+ TOTAL_DC_POWER(5017, UINT32_SWAP, BigDecimal.ONE, quantityFactory(Units.WATT), "overview"),
+ PHASE_A_VOLTAGE(5019, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "overview"),
+ PHASE_B_VOLTAGE(5020, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "overview"),
+ PHASE_C_VOLTAGE(5021, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "overview"),
+
+ REACTIVE_POWER(5033, INT32_SWAP, BigDecimal.ONE, quantityFactory(Units.VAR), "overview"),
+ POWER_FACTOR(5035, INT16, new BigDecimal(BigInteger.ONE, 3), DecimalType::new, "overview"),
+ GRID_FREQUENCY(5036, UINT16, new BigDecimal(BigInteger.ONE, 2), quantityFactory(Units.HERTZ), "overview"),
+
+ /*
+ * Not working
+ * EXPORT_LIMIT_MIN(10, 5622, UINT16, quantityTypeFactory(Units.WATT)),
+ * EXPORT_LIMIT_MAX(10, 5623, UINT16, quantityTypeFactory(Units.WATT)),
+ * BDC_RATED_POWER(100, 5628, UINT16, quantityTypeFactory(Units.WATT)),
+ * MAX_CHARGING_CURRENT(1, 5635, UINT16, quantityTypeFactory(Units.AMPERE)),
+ * MAX_DISCHARGING_CURRENT(1, 5636, UINT16, quantityTypeFactory(Units.AMPERE)),
+ * PV_POWER_TODAY(1, 6100, UINT16, quantityTypeFactory(Units.WATT)),
+ * DAILY_PV_ENERGY_YIELDS(1, 6196, UINT16, quantityTypeFactory(Units.KILOWATT_HOUR)),
+ * MONTHLY_PV_ENERGY_YIELDS(1, 9227, UINT16, quantityTypeFactory(Units.KILOWATT_HOUR)),
+ */
+
+ /**
+ * Registers return invalid values.
+ * SYSTEM_STATE(13000, UINT16, 1, quantityTypeFactory(Units.ONE)),
+ * RUNNING_STATE(13001, UINT16, 1, quantityTypeFactory(Units.ONE)),
+ */
+
+ DAILY_PV_GENERATION(13002, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "overview"),
+ TOTAL_PV_GENERATION(13003, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "overview"),
+ DAILY_EXPORT_ENERGY_FROM_PV(13005, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "grid-information"),
+ TOTAL_EXPORT_ENERGY_FROM_PV(13006, UINT32_SWAP, ConversionConstants.DIV_BY_TEN,
+ quantityFactory(Units.KILOWATT_HOUR), "grid-information"),
+ LOAD_POWER(13008, INT32_SWAP, BigDecimal.ONE, quantityFactory(Units.WATT), "load-information"),
+ EXPORT_POWER(13010, INT32_SWAP, BigDecimal.ONE, quantityFactory(Units.WATT), "grid-information"),
+ DAILY_BATTERY_CHARGE(13012, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ TOTAL_BATTERY_CHARGE(13013, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ /*
+ * Not working
+ * CO2_REDUCTION(13015, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, tech.units.indriya.unit.Units.KILOGRAM),
+ */
+ DAILY_DIRECT_ENERGY_CONSUMPTION(13017, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "load-information"),
+ TOTAL_DIRECT_ENERGY_CONSUMPTION(13018, UINT32_SWAP, ConversionConstants.DIV_BY_TEN,
+ quantityFactory(Units.KILOWATT_HOUR), "load-information"),
+ BATTERY_VOLTAGE(13020, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "battery-information"),
+ BATTERY_CURRENT(13021, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE),
+ "battery-information"),
+ BATTERY_POWER(13022, UINT16, BigDecimal.ONE, quantityFactory(Units.WATT), "battery-information"),
+ BATTERY_LEVEL(13023, UINT16, ConversionConstants.DIV_BY_TEN, PercentType::new, "battery-information"),
+ BATTERY_HEALTHY(13024, UINT16, ConversionConstants.DIV_BY_TEN, PercentType::new, "battery-information"),
+ BATTERY_TEMPERATURE(13025, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KELVIN),
+ ConversionConstants.CELSIUS_TO_KELVIN, "battery-information"),
+ DAILY_BATTERY_DISCHARGE_ENERGY(13026, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ TOTAL_BATTERY_DISCHARGE_ENERGY(13027, UINT32_SWAP, ConversionConstants.DIV_BY_TEN,
+ quantityFactory(Units.KILOWATT_HOUR), "battery-information"),
+ SELF_CONSUMPTION_TODAY(13029, UINT16, ConversionConstants.DIV_BY_TEN, PercentType::new, "load-information"),
+ // Not working
+ // GRID_STATE(13030, UINT16, 1, quantityTypeFactory(Units.ONE, "grid-information"),
+ PHASE_A_CURRENT(13031, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE), "overview"),
+ PHASE_B_CURRENT(13032, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE), "overview"),
+ PHASE_C_CURRENT(13033, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.AMPERE), "overview"),
+ TOTAL_ACTIVE_POWER(13034, INT32_SWAP, BigDecimal.ONE, quantityFactory(Units.WATT), "overview"),
+ DAILY_IMPORT_ENERGY(13036, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "grid-information"),
+ TOTAL_IMPORT_ENERGY(13037, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "grid-information"),
+ BATTERY_CAPACITY(13039, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ DAILY_CHARGE_ENERGY(13040, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ TOTAL_CHARGE_ENERGY(13041, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "battery-information"),
+ // DRM_STATE(13043, UINT16, 1, quantityTypeFactory(Units.ONE, "channelGroup"),
+
+ DAILY_EXPORT_ENERGY(13045, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "grid-information"),
+ TOTAL_EXPORT_ENERGY(13046, UINT32_SWAP, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.KILOWATT_HOUR),
+ "grid-information");
+
+ /*
+ * Status Registers -not known if working so not implemented yet.
+ *
+ *
+ * INVERTER_ALARM(13050, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * GRID_SIDE_FAULT(13052, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * SYSTEM_FAULT_1(13054, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * SYSTEM_FAULT_2(13056, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * DC_SIDE_FAULT(13058, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * PERMANENT_FAULT(13060, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BDC_SIDE_FAULT(13062, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BDC_SIDE_PERMANENT_FAULT(13064, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BATTERY_FAULT(13066, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BATTERY_ALARM(13068, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BMS_ALARM_1(13070, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BMS_PROTECTION(13072, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BMS_FAULT_1(13074, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BMS_FAULT_2(13076, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE),
+ * BMS_ALARM_2(13078, UINT32_SWAP, 1, quantityTypeFactory(Units.ONE);
+ */
+
+ private final BigDecimal multiplier;
+ private final int registerNumber;
+ private final ValueType type;
+
+ private final Function conversion;
+ private final Function stateFactory;
+ private final String channelGroup;
+
+ SungrowInverterRegisters(int registerNumber, ValueType type, BigDecimal multiplier,
+ Function stateFactory, Function conversion,
+ String channelGroup) {
+ this.multiplier = multiplier;
+ this.registerNumber = registerNumber;
+ this.type = type;
+ this.conversion = conversion;
+ this.stateFactory = stateFactory;
+ this.channelGroup = channelGroup;
+ }
+
+ SungrowInverterRegisters(int registerNumber, ValueType type, BigDecimal multiplier,
+ Function stateFactory, String channelGroup) {
+ this.multiplier = multiplier;
+ this.registerNumber = registerNumber;
+ this.type = type;
+ this.conversion = Function.identity();
+ this.stateFactory = stateFactory;
+ this.channelGroup = channelGroup;
+ }
+
+ /**
+ * Creates a Function that creates {@link QuantityType} states with the given {@link Unit}.
+ *
+ * @param unit {@link Unit} to be used for the value.
+ * @return Function for value creation.
+ */
+ private static Function quantityFactory(Unit> unit) {
+ return (BigDecimal value) -> new QuantityType<>(value, unit);
+ }
+
+ /**
+ * Returns the modbus register number.
+ *
+ * @return modbus register number.
+ */
+ public int getRegisterNumber() {
+ return registerNumber;
+ }
+
+ /**
+ * Returns the {@link ValueType} for the channel.
+ *
+ * @return {@link ValueType} for the channel.
+ */
+ public ValueType getType() {
+ return type;
+ }
+
+ /**
+ * Returns the count of registers read to return the value of this register.
+ *
+ * @return register count.
+ */
+ public int getRegisterCount() {
+ return this.type.getBits() / 16;
+ }
+
+ /**
+ * Returns the channel group.
+ *
+ * @return channel group id.
+ */
+ public String getChannelGroup() {
+ return channelGroup;
+ }
+
+ /**
+ * Returns the channel name.
+ *
+ * @return the channel name.
+ */
+ public String getChannelName() {
+ return this.name().toLowerCase().replace('_', '-');
+ }
+
+ /**
+ * Creates the {@link State} for the given register value.
+ *
+ * @param registerValue the value for the channel.
+ * @return {@link State] for the given value.
+ */
+ public State createState(DecimalType registerValue) {
+ final BigDecimal scaledValue = registerValue.toBigDecimal().multiply(this.multiplier);
+
+ final BigDecimal convertedValue = conversion.apply(scaledValue);
+ return this.stateFactory.apply(convertedValue);
+ }
+}
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/config/config.xml
new file mode 100644
index 00000000000..10a87cdb030
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/config/config.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Time between polling the data in ms.
+ 5000
+
+
+
+
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/i18n/sungrow.properties b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/i18n/sungrow.properties
new file mode 100644
index 00000000000..510aeeb41dd
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/i18n/sungrow.properties
@@ -0,0 +1,64 @@
+# thing types
+
+thing-type.modbus.sungrowInverter.label = Sungrow inverter
+thing-type.modbus.sungrowInverter.description = Sungrow inverter connected via Modbus.
+
+# thing types config
+
+thing-type.config.sungrow.inverter.pollInterval.label = Poll Interval
+thing-type.config.sungrow.inverter.pollInterval.description = Time between polling the data in ms.
+
+# channel groups
+channel-group-type.modbus.sg-overview.label = Overview
+channel-group-type.modbus.sg-mppt_information.label = MPPT
+channel-group-type.modbus.sg-battery_information.label = Battery
+channel-group-type.modbus.sg-load_information.label = Load
+channel-group-type.modbus.sg-grid_information.label = Grid
+
+# channel types
+
+channel-type.modbus.sg-battery_capacity.label = Battery Capacity
+channel-type.modbus.sg-battery_current.label = Battery Current
+channel-type.modbus.sg-battery_healthy.label = Battery Health (SOH)
+channel-type.modbus.sg-battery_level.label = Battery Level (SOC)
+channel-type.modbus.sg-battery_power.label = Battery Power
+channel-type.modbus.sg-battery_temperature.label = Battery Temperature
+channel-type.modbus.sg-battery_voltage.label = Battery Voltage
+channel-type.modbus.sg-co2_reduction.label = Co2 Reduction
+channel-type.modbus.sg-daily_battery_charge.label = Daily Battery Charging Energy from PV
+channel-type.modbus.sg-daily_battery_discharge_energy.label = Daily Battery Discharging Energy
+channel-type.modbus.sg-daily_charge_energy.label = Daily Battery Charging Energy
+channel-type.modbus.sg-daily_direct_energy_consumption.label = Daily Load Energy Consumption from PV
+channel-type.modbus.sg-daily_export_energy.label = Daily Feed-in Energy
+channel-type.modbus.sg-daily_export_power_from_pv.label = Daily Feed-in Energy (PV)
+channel-type.modbus.sg-daily_import_energy.label = Daily Purchased Energy
+channel-type.modbus.sg-daily_pv_generation.label = Daily PV Yield
+channel-type.modbus.sg-export_power.label = Total Export Active Power
+channel-type.modbus.sg-grid_frequency.label = Grid Frequency
+channel-type.modbus.sg-internal_temperature.label = Internal Temperature
+channel-type.modbus.sg-load_power.label = Load Power
+channel-type.modbus.sg-mppt1_current.label = MPPT1 Current
+channel-type.modbus.sg-mppt1_voltage.label = MPPT1 Voltage
+channel-type.modbus.sg-mppt2_current.label = MPPT2 Current
+channel-type.modbus.sg-mppt2_voltage.label = MPPT2 Voltage
+channel-type.modbus.sg-phase_a_current.label = Phase A Current
+channel-type.modbus.sg-phase_a_voltage.label = Phase A Voltage
+channel-type.modbus.sg-phase_b_current.label = Phase B Current
+channel-type.modbus.sg-phase_b_voltage.label = Phase B Voltage
+channel-type.modbus.sg-phase_c_current.label = Phase C Current
+channel-type.modbus.sg-phase_c_voltage.label = Phase C Voltage
+channel-type.modbus.sg-power_factor.label = Total Power Factor
+channel-type.modbus.sg-reactive_power.label = Total Reactive Power
+channel-type.modbus.sg-running_state.label = Running Status
+channel-type.modbus.sg-self_consumption_today.label = Daily Self-consumption Rate
+channel-type.modbus.sg-system_state.label = Current Status
+channel-type.modbus.sg-total_active_power.label = Total Active Power
+channel-type.modbus.sg-total_battery_charge.label = Total Battery Charging Energy
+channel-type.modbus.sg-total_battery_discharge_energy.label = Total Battery Discharging Energy
+channel-type.modbus.sg-total_charge_energy.label = Total Battery Charging Energy from PV
+channel-type.modbus.sg-total_dc_power.label = Total DC Power
+channel-type.modbus.sg-total_direct_energy_consumption.label = Total Load Energy Consumption from PV
+channel-type.modbus.sg-total_export_energy.label = Total Feed-in Energy
+channel-type.modbus.sg-total_export_energy_from_pv.label = Total Feed-in Energy (PV)
+channel-type.modbus.sg-total_import_energy.label = Total Purchased Energy
+channel-type.modbus.sg-total_pv_generation.label = Total PV Yield
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 00000000000..127c10bdd68
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+
+
+
+
+ Sungrow inverter connected via Modbus.
+ Inverter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Battery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number:Temperature
+
+ Temperature
+
+ Measurement
+ Temperature
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:Dimensionless
+
+ Energy
+
+
+
+ Number:Frequency
+
+
+ Measurement
+ Frequency
+
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:ElectricPotential
+
+ Energy
+
+ Measurement
+ Voltage
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:Power
+
+ Battery
+
+ Measurement
+ Power
+
+
+
+
+ Number:Dimensionless
+
+ Battery
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Dimensionless
+
+ Battery
+
+
+
+ Number:Temperature
+
+ Battery
+
+ Measurement
+ Temperature
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Dimensionless
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:ElectricCurrent
+
+ Energy
+
+ Measurement
+ Current
+
+
+
+
+ Number:Power
+
+ Energy
+
+ Measurement
+ Power
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
+ Number:Energy
+
+ Energy
+
+ Measurement
+ Energy
+
+
+
+
diff --git a/bundles/org.openhab.binding.modbus.sungrow/src/test/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegistersTest.java b/bundles/org.openhab.binding.modbus.sungrow/src/test/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegistersTest.java
new file mode 100644
index 00000000000..422bcf9954c
--- /dev/null
+++ b/bundles/org.openhab.binding.modbus.sungrow/src/test/java/org/openhab/binding/modbus/sungrow/internal/SungrowInverterRegistersTest.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.modbus.sungrow.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
+import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.types.State;
+
+/**
+ * @author Sönke Küper - Initial contribution
+ */
+@NonNullByDefault
+class SungrowInverterRegistersTest {
+ @Test
+ public void testCreatePercentTypeState() {
+ SungrowInverterRegisters batteryLevelRegister = SungrowInverterRegisters.BATTERY_LEVEL;
+
+ ModbusRegisterArray registers = new ModbusRegisterArray(1000);
+ Optional value = ModbusBitUtilities.extractStateFromRegisters( //
+ registers, //
+ 0, //
+ batteryLevelRegister.getType() //
+ );
+ assertTrue(value.isPresent());
+ DecimalType decimalTypeValue = value.get();
+ // Value is not scaled yet
+ assertEquals(BigDecimal.valueOf(1000), decimalTypeValue.toBigDecimal());
+
+ State state = batteryLevelRegister.createState(decimalTypeValue);
+ assertInstanceOf(PercentType.class, state);
+ assertEquals("100.0", state.toFullString());
+ }
+
+ @Test
+ public void testCreateQuantityTypeState() {
+ SungrowInverterRegisters mpttVoltage = SungrowInverterRegisters.MPPT1_VOLTAGE;
+
+ ModbusRegisterArray registers = new ModbusRegisterArray(1234);
+ Optional value = ModbusBitUtilities.extractStateFromRegisters( //
+ registers, //
+ 0, //
+ mpttVoltage.getType() //
+ );
+ assertTrue(value.isPresent());
+ DecimalType decimalTypeValue = value.get();
+ // Value is not scaled yet
+ assertEquals(BigDecimal.valueOf(1234), decimalTypeValue.toBigDecimal());
+
+ State state = mpttVoltage.createState(decimalTypeValue);
+ assertInstanceOf(QuantityType.class, state);
+ assertEquals("123.4 V", state.toFullString());
+ }
+}
diff --git a/bundles/pom.xml b/bundles/pom.xml
index d6ec5faad6a..5299fc25078 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -249,6 +249,7 @@
org.openhab.binding.modbus.e3dc
org.openhab.binding.modbus.sbc
org.openhab.binding.modbus.studer
+ org.openhab.binding.modbus.sungrow
org.openhab.binding.modbus.sunspec
org.openhab.binding.modbus.stiebeleltron
org.openhab.binding.modbus.helioseasycontrols