[sungrow] Initial contribution (#15130)

* 0000: Implementation

---------

Signed-off-by: Sönke Küper <soenkekueper@gmx.de>
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
Co-authored-by: Wouter Born <github@maindrain.net>
Co-authored-by: Eric Bodden <eric.bodden@upb.de>
Co-authored-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
Sönke Küper 2024-02-19 14:54:21 +01:00 committed by GitHub
parent c5a3753dd2
commit 31e88692f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1476 additions and 0 deletions

View File

@ -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

View File

@ -1086,6 +1086,11 @@
<artifactId>org.openhab.binding.modbus.studer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.sungrow</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.sunspec</artifactId>

View File

@ -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

View File

@ -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:
<img src="./doc/WiNet-S_Modbus.PNG" alt="WiNet-S Modbus configuration"/>
## 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
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.modbus.sungrow</artifactId>
<name>openHAB Add-ons :: Bundles :: Modbus Sungrow Binding</name>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<BigDecimal, BigDecimal> CELSIUS_TO_KELVIN = (BigDecimal celsius) -> celsius
.add(new BigDecimal(273.15f));
}

View File

@ -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");
}

View File

@ -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<ThingTypeUID> 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;
}
}

View File

@ -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;
}

View File

@ -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<SungrowInverterRegisters> registers;
private final ModbusReadRequestBlueprint blueprint;
public ModbusRequest(Deque<SungrowInverterRegisters> registers, int slaveId) {
this.registers = registers;
this.blueprint = initReadRequest(registers, slaveId);
}
private ModbusReadRequestBlueprint initReadRequest(Deque<SungrowInverterRegisters> 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<ModbusRequest> modbusRequests = new ArrayList<>();
public SungrowInverterHandler(Thing thing) {
super(thing);
}
/**
* Splits the SungrowInverterRegisters into multiple ModbusRequest, to ensure the max request size.
*/
private List<ModbusRequest> buildRequests() {
final List<ModbusRequest> requests = new ArrayList<>();
Deque<SungrowInverterRegisters> 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<ModbusReadRequestBlueprint> 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() //
);
}
}

View File

@ -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<BigDecimal, BigDecimal> conversion;
private final Function<BigDecimal, State> stateFactory;
private final String channelGroup;
SungrowInverterRegisters(int registerNumber, ValueType type, BigDecimal multiplier,
Function<BigDecimal, State> stateFactory, Function<BigDecimal, BigDecimal> 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<BigDecimal, State> 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<BigDecimal, State> 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);
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:sungrow:inverter">
<parameter name="pollInterval" type="integer" required="true" min="100" unit="ms">
<label>Poll Interval</label>
<description>Time between polling the data in ms.</description>
<default>5000</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -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

View File

@ -0,0 +1,507 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="sungrow-inverter">
<supported-bridge-type-refs>
<bridge-type-ref id="serial"/>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Sungrow Inverter</label>
<description>Sungrow inverter connected via Modbus.</description>
<category>Inverter</category>
<channel-groups>
<channel-group id="sg-overview" typeId="sg-overview"/>
<channel-group id="sg-mppt-information" typeId="sg-mppt-information"/>
<channel-group id="sg-battery-information" typeId="sg-battery-information"/>
<channel-group id="sg-grid-information" typeId="sg-grid-information"/>
<channel-group id="sg-load-information" typeId="sg-load-information"/>
</channel-groups>
<config-description-ref uri="thing-type:sungrow:inverter"/>
</thing-type>
<channel-group-type id="sg-overview">
<label>Overview</label>
<channels>
<channel id="sg-total-active-power" typeId="sg-total-active-power"/>
<channel id="sg-total-dc-power" typeId="sg-total-dc-power"/>
<channel id="sg-phase-a-voltage" typeId="sg-phase-a-voltage"/>
<channel id="sg-phase-b-voltage" typeId="sg-phase-b-voltage"/>
<channel id="sg-phase-c-voltage" typeId="sg-phase-c-voltage"/>
<channel id="sg-phase-a-current" typeId="sg-phase-a-current"/>
<channel id="sg-phase-b-current" typeId="sg-phase-b-current"/>
<channel id="sg-phase-c-current" typeId="sg-phase-c-current"/>
<channel id="sg-reactive-power" typeId="sg-reactive-power"/>
<channel id="sg-grid-frequency" typeId="sg-grid-frequency"/>
<channel id="sg-daily-pv-generation" typeId="sg-daily-pv-generation"/>
<channel id="sg-total-pv-generation" typeId="sg-total-pv-generation"/>
<channel id="sg-power-factor" typeId="sg-power-factor"/>
<channel id="sg-internal-temperature" typeId="sg-internal-temperature"/>
</channels>
</channel-group-type>
<channel-group-type id="sg-mppt-information">
<label>MPPT Information</label>
<channels>
<channel id="sg-mppt1-voltage" typeId="sg-mppt1-voltage"/>
<channel id="sg-mppt1-current" typeId="sg-mppt1-current"/>
<channel id="sg-mppt2-voltage" typeId="sg-mppt2-voltage"/>
<channel id="sg-mppt2-current" typeId="sg-mppt2-current"/>
</channels>
</channel-group-type>
<channel-group-type id="sg-battery-information">
<label>Battery Information</label>
<category>Battery</category>
<channels>
<channel id="sg-battery-voltage" typeId="sg-battery-voltage"/>
<channel id="sg-battery-current" typeId="sg-battery-current"/>
<channel id="sg-battery-power" typeId="sg-battery-power"/>
<channel id="sg-battery-level" typeId="sg-battery-level"/>
<channel id="sg-battery-healthy" typeId="sg-battery-healthy"/>
<channel id="sg-battery-temperature" typeId="sg-battery-temperature"/>
<channel id="sg-battery-capacity" typeId="sg-battery-capacity"/>
<channel id="sg-daily-charge-energy" typeId="sg-daily-charge-energy"/>
<channel id="sg-total-charge-energy" typeId="sg-total-charge-energy"/>
<channel id="sg-daily-battery-charge" typeId="sg-daily-battery-charge"/>
<channel id="sg-total-battery-charge" typeId="sg-total-battery-charge"/>
<channel id="sg-daily-battery-discharge-energy" typeId="sg-daily-battery-discharge-energy"/>
<channel id="sg-total-battery-discharge-energy" typeId="sg-total-battery-discharge-energy"/>
</channels>
</channel-group-type>
<channel-group-type id="sg-grid-information">
<label>Grid Information</label>
<channels>
<channel id="sg-daily-export-energy" typeId="sg-daily-export-energy"/>
<channel id="sg-total-export-energy" typeId="sg-total-export-energy"/>
<channel id="sg-daily-import-energy" typeId="sg-daily-import-energy"/>
<channel id="sg-total-import-energy" typeId="sg-total-import-energy"/>
<channel id="sg-daily-export-power-from-pv" typeId="sg-daily-export-power-from-pv"/>
<channel id="sg-total-export-energy-from-pv" typeId="sg-total-export-energy-from-pv"/>
<channel id="sg-export-power" typeId="sg-export-power"/>
</channels>
</channel-group-type>
<channel-group-type id="sg-load-information">
<label>Load Information</label>
<channels>
<channel id="sg-load-power" typeId="sg-load-power"/>
<channel id="sg-daily-direct-energy-consumption" typeId="sg-daily-direct-energy-consumption"/>
<channel id="sg-total-direct-energy-consumption" typeId="sg-total-direct-energy-consumption"/>
<channel id="sg-self-consumption-today" typeId="sg-self-consumption-today"/>
</channels>
</channel-group-type>
<channel-type id="sg-internal-temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Internal Temperature</label>
<category>Temperature</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-mppt1-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>MPPT1 Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-mppt1-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>MPPT1 Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-mppt2-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>MPPT2 Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-mppt2-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>MPPT2 Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-dc-power">
<item-type>Number:Power</item-type>
<label>Total DC Power</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-a-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Phase A Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-b-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Phase B Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-c-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Phase C Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-reactive-power" advanced="true">
<item-type>Number:Power</item-type>
<label>Reactive Power</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-power-factor" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Power Factor</label>
<category>Energy</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-grid-frequency" advanced="true">
<item-type>Number:Frequency</item-type>
<label>Grid Frequency</label>
<tags>
<tag>Measurement</tag>
<tag>Frequency</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-pv-generation">
<item-type>Number:Energy</item-type>
<label>Daily PV Generation</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-pv-generation">
<item-type>Number:Energy</item-type>
<label>Total PV Generation</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-export-power-from-pv">
<item-type>Number:Power</item-type>
<label>Daily Export Power from PV</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-export-energy-from-pv">
<item-type>Number:Energy</item-type>
<label>Total Export Energy from PV</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-load-power">
<item-type>Number:Power</item-type>
<label>Load Power</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-export-power">
<item-type>Number:Power</item-type>
<label>Export Power</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-battery-charge">
<item-type>Number:Energy</item-type>
<label>Daily Battery Charge</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-battery-charge">
<item-type>Number:Energy</item-type>
<label>Total Battery Charge</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-direct-energy-consumption">
<item-type>Number:Energy</item-type>
<label>Daily Direct Energy Consumption</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-direct-energy-consumption">
<item-type>Number:Energy</item-type>
<label>Total Direct Energy Consumption</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Battery Voltage</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Battery Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-power">
<item-type>Number:Power</item-type>
<label>Battery Power</label>
<category>Battery</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-level">
<item-type>Number:Dimensionless</item-type>
<label>Battery Level</label>
<category>Battery</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-healthy">
<item-type>Number:Dimensionless</item-type>
<label>Battery Healthy</label>
<category>Battery</category>
<state pattern="%.2f %%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-temperature">
<item-type>Number:Temperature</item-type>
<label>Battery Temperature</label>
<category>Battery</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-battery-discharge-energy">
<item-type>Number:Energy</item-type>
<label>Daily Battery Discharge Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-battery-discharge-energy">
<item-type>Number:Energy</item-type>
<label>Total Battery Discharge Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-self-consumption-today">
<item-type>Number:Dimensionless</item-type>
<label>Self Consumption Today</label>
<state pattern="%.2f %%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-a-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Phase A Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-b-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Phase B Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-phase-c-current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Phase C Current</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Current</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-active-power">
<item-type>Number:Power</item-type>
<label>Total Active Power</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Power</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-import-energy">
<item-type>Number:Energy</item-type>
<label>Daily Import Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-import-energy">
<item-type>Number:Energy</item-type>
<label>Total Import Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-battery-capacity">
<item-type>Number:Energy</item-type>
<label>Battery Capacity</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-charge-energy">
<item-type>Number:Energy</item-type>
<label>Daily Charge Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-charge-energy">
<item-type>Number:Energy</item-type>
<label>Total Charge Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-daily-export-energy">
<item-type>Number:Energy</item-type>
<label>Daily Export Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="sg-total-export-energy">
<item-type>Number:Energy</item-type>
<label>Total Export Energy</label>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -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<DecimalType> 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<DecimalType> 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());
}
}

View File

@ -249,6 +249,7 @@
<module>org.openhab.binding.modbus.e3dc</module>
<module>org.openhab.binding.modbus.sbc</module>
<module>org.openhab.binding.modbus.studer</module>
<module>org.openhab.binding.modbus.sungrow</module>
<module>org.openhab.binding.modbus.sunspec</module>
<module>org.openhab.binding.modbus.stiebeleltron</module>
<module>org.openhab.binding.modbus.helioseasycontrols</module>