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: +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 + } +} +``` 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