[kermi] Initial contribution (#16329)

* [kermi] Initial working state

Signed-off-by: Kai Neuhaus <code@kaineuhaus.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
KaiNative 2024-11-23 16:07:38 +01:00 committed by Ciprian Pascu
parent 5c38c8fea6
commit 48429361b4
36 changed files with 2157 additions and 0 deletions

View File

@ -1151,6 +1151,11 @@
<artifactId>org.openhab.binding.modbus.helioseasycontrols</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.kermi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.sbc</artifactId>

View File

@ -0,0 +1,235 @@
# Kermi Heat pump
Integrates the X-Center Device (X-Center Pro) of Kermi Heat pump.
Kermi X-Center & other attached devices (in progress) are integrated into the Modbus Binding.
This binding was tested and developed with Kermi x-change dynamic pro heat pump (build 2023).
Hint: This binding _may_ also work with devices from "Bösch" in Austria, which is a sub-brand of Kermi, they are nearly identically.
## Prerequisite
Requirement is contacting Kermi Support to activate Modbus-TCP which can be connected directly by network.
Older devices (non-Pro ?) were connected by Modbus-RCP - maybe you can try to connect them here using a Modbus-TCP modulator (e.x. from waveshare or similar).
## Supported Things
First you need a "Modbus TCP-Bridge" which establishes the basic connection towards your X-Center device.
| Name | Thing Type ID | Description |
|--------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------|
| Kermi Heat Pump X-Center | kermi-xcenter | Provides (electric) Power values, Flow-information, PV-States, Temperatures and general Information of your Kermi Heat Pump |
## Discovery
This binding does not support autodiscovery.
## Device IDs
| Device | Device ID | Comment |
|---------------------|-----------|-------------------------------------------------|
| X-Center | 40 | on cascade-circuits: slave1: 41, slave2: 42, ...|
## Thing Configuration
The needed Bridge can be found in the **Modbus Binding** and have to be added manually without Discovery.
1. Create _Modbus TCP Slave (Bridge)_ with matching Settings of your Kermi Device:
- **IP Address** - IP Address or hostname of your heatpump
- **Port** - Port of modbus on your heatpump (normally 502)
- **Device ID** - ID on modbus, 40 is default for Kermi (see [Device IDs](#device-ids))
1. Create _Kermi Heat Pump X-Center_ and attach it to the previous installed _Modbus TCP Slave (Bridge)_.
Configuration requires an appropriate Data Refresh Interval with more than 2000 Milliseconds, default is 5000.
If it's too fast, you may experience errors in openHAB or your X-Center!
Reboot if X-Center stops responding on network access.
You can enable "PV Modulation" if you want to read the values (default: disabled)
Details on Configurations explained below.
### Modbus TCP Slave
| Parameter | Type | Description |
|-----------|---------|---------------------------------------------------------------|
| host | text | IP Address or reachable hostname of your device |
| port | integer | TCP Port of your Kermi device Modbus Settings. Default is 502 |
| deviceId | integer | Modbus ID of your Kermi device Modbus Settings. Default is 40 |
### Kermi Heat Pump X-Center
Select as Bridge your previously created Modbus TCP Slave.
| Parameter | Type | Description |
|-----------|---------|----------------------------------------------------------------|
| refresh | integer | Refresh Rate of X-Center values in Milliseconds (default:5000) |
| pvEnabled | boolean | Read PV-Modulation (default:false) |
### Kermi StorageSystemModule (support planned in future releases)
Select as Bridge a separate (second) Modbus TCP Slave.
| Parameter | Type | Description |
|-----------|---------|--------------------------------------------------------------------|
| host | text | IP Address or reachable hostname of your device (same as X-Center) |
| port | integer | TCP Port of your Kermi device Modbus Settings. Default is 502 |
| deviceId | integer | Modbus ID of your Kermi device Modbus Settings. Default is 50 |
### Channel-Groups
The X-Center / StorageSystemModule device offers quite an amount of channels.
They are grouped into 7 channel-groups:
- State
- EnergySource
- Charging Circuit
- Power & Efficiency
- Workhours
- Alarm
- PV Modulation
### Channels
#### Status
| Channel Id | Channel | Type | Description |
|-----------------|-----------------|--------|----------------------------------------------------------------------|
| global-state-id | Global State ID | Number | State of heat pump as Number, displayed as readable State-Text in UI |
Possible states:
- Standby
- Alarm
- DrinkingWater
- Heating
- Defrost
- Preparing
- Blocked
- EVU Blocktime
- Unavailable
#### Energy-Source
| Channel Id | Channel Label | Channnel Id | Type | Description |
|----------------------------|----------------------|-------------|---------------------|-------------------------------------------------|
| exit-temperature | Exit temperature | | Number<Temperature> | in °C - Air temperature exiting heat pump |
| incoming-temperature | Incoming temperature | | Number<Temperature> | in °C - Air temperature incoming into heat pump |
| temperature-sensor-outside | Temperature Outside | | Number<Temperature> | in °C - Outside Air Temperature (if connected) |
#### Charging Circuit
| Channel Id | Channel Label | Type | Description |
|--------------------|--------------------|----------------------------|-----------------------------------------------------------------------------------|
| flow-temperature | Flow temperature | Number<Temperature> | in °C - Water temperature **from** heat pump to drinking water or heating circuit |
| return-temperature | Return temperature | Number<Temperature> | in °C - Water temperature returning back **to** heat pump |
| flow-speed | Flow speed | Number<VolumetricFlowRate> | in l/min - Flow speed of the water |
#### Power and efficiency
| Channel Id | Channel Label | Type | Description |
|--------------------------------|---------------------------------------|---------------|--------------------------------------------------|
| cop | Current COP | Number | current cop overall (Coefficient Of Performance) |
| cop-heating | Current COP heating | Number | cop for heating |
| cop-drinkingwater | Current COP drinking water | Number | cop for drinking water |
| cop-cooling | Current COP cooling | Number | cop for cooling |
| | | | |
| power | Current power | Number<Power> | in W (Watt) - Power overall |
| power-heating | Current power heating | Number<Power> | in W (Watt) - Power for heating |
| power-drinkingwater | Current power drinking water | Number<Power> | in W (Watt) - Power for drinking water |
| power-cooling | Current power cooling | Number<Power> | in W (Watt) - Power for cooling |
| | | | |
| electrical-power | Current electric power | Number<Power> | in W (Watt) - electric Power overall |
| electrical-power-heating | Current electric power heating | Number<Power> | in W (Watt) - electric Power for heating |
| electrical-power-drinkingwater | Current electric power drinking water | Number<Power> | in W (Watt) - electric Power for drinking water |
| electrical-power-cooling | Current electric power cooling | Number<Power> | in W (Watt) - electric Power for cooling |
#### Workhours
| Channel Id | Channel Label | Type | Description |
|--------------------------------|--------------------------------|--------------|--------------------------------------------------------|
| workhours-fan | Fan workhours | Number<Time> | in h (hour) - worked hours of the fan |
| workhours-storage-loading-pump | Storage Loading Pump workhours | Number<Time> | in h (hour) - worked hours of the storage loading pump |
| workhours-compressor | Compressor workhours | Number<Time> | in h (hour) - worked hours of the compressor |
#### Alarm
| Channel Id | Channel Label | Type | Description |
|-------------|---------------|--------|---------------------------------|
| alarm-state | Alarm state | Switch | On / true if an alarm is raised |
#### PV Modulation
| Channel Id | Channel Label | Type | Description |
|-------------------------------------|-----------------------------------|---------------------|-------------------------------------------------------------------|
| pv-state | PV Modulation Active | Switch | On / true if PV Modulation is currently active |
| pv-power | PV Power | Numbery<Power> | in W (Watt) - Power of PV Modulation |
| pv-target-temperature-heating | Target temperature heating | Number<Temperature> | in °C - target Temperature in PV Mode of heating (storage) |
| pv-target-temperature-drinkingwater | Target temperature drinking water | Number<Temperature> | in °C - target Temperature in PV Mode of drinking water (storage) |
## Full Example
Attention: Configuration by file is not recommended. You can configure everything in the main UI.
### `kermi.things` Example
```java
Bridge modbus:tcp:device "Kermi X-Center Modbus TCP" [ host="xcenter", port=502, id=40 ] {
Bridge kermi-xcenter heatpump "Kermi X-Center Heat Pump" [ refresh=5000, pvEnabled=false ]
}
```
### Items
```java
Number XCenter_Global_State_Id "X-Center Global State ID" (kermi) { channel="modbus:tcp:device:heatpump:state#global-state-id" }
Number:Temperature Heatpump_FlowTemperature "Flow Temperature" (kermi,persist) { channel="modbus:tcp:device:heatpump:charging-circuit#flow-temperature" }
Number:Temperature Heatpump_ReturnTemperature "Return Temperature" (kermi,persist) { channel="modbus:tcp:device:heatpump:charging-circuit#return-temperature" }
Number:VolumetricFlowRate Heatpump_FlowSpeed "Flow Speed" (kermi,persist) { channel="modbus:tcp:device:heatpump:charging-circuit#flow-speed" }
Number:Temperature Heatpump_ExitTemperature "Exit Temperature" (kermi,persist) { channel="modbus:tcp:device:heatpump:energy-source#exit-temperature" }
Number:Temperature Heatpump_Incomingtemperature "Incoming temperature" (kermi,persist) { channel="modbus:tcp:device:heatpump:energy-source#incoming-temperature" }
Number:Temperature Heatpump_TemperatureOutside "Temperature Outside" (kermi,persist) { channel="modbus:tcp:device:heatpump:energy-source#temperature-sensor-outside" }
Number Heatpump_CurrentCOP "Current COP" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#cop" }
Number Heatpump_CurrentCOPHeating "Current COP Heating" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#cop-heating" }
Number Heatpump_CurrentCOPdrinkingwater "Current COP drinking water" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#cop-drinkingwater" }
Number Heatpump_CurrentCOPCooling "Current COP Cooling" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#cop-cooling" }
Number:Power Heatpump_CurrentPower "Current Power" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#power" }
Number:Power Heatpump_Currentpowerheating "Current power heating" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#power-heating" }
Number:Power Heatpump_CurrentPowerDrinkingWater "Current Power Drinking Water" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#power-drinkingwater" }
Number:Power Heatpump_Currentpowercooling "Current power cooling" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#power-cooling" }
Number:Power Heatpump_CurrentElectricPower "Current Electric Power" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#electric-power" }
Number:Power Heatpump_CurrentElectricPowerHeating "Current Electric Power Heating" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#electric-power-heating" }
Number:Power Heatpump_Currentelectricpowerdrinkingwater "Current electric power drinking water" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#electric-power-drinkingwater" }
Number:Power Heatpump_CurrentElectricPowerCooling "Current Electric Power Cooling" (kermi) { channel="modbus:tcp:device:heatpump:power-channel#electric-power-cooling" }
Switch Heatpump_PVModulationActive "PV Modulation Active" (kermi) { channel="modbus:tcp:device:heatpump:pv-modulation#pv-state" }
Number:Power Heatpump_PVPower "PV Power" (kermi) { channel="modbus:tcp:device:heatpump:pv-modulation#pv-power" }
Number:Temperature Heatpump_PVTempHeating "PV Temp Heating" (kermi) { channel="modbus:tcp:device:heatpump:pv-modulation#pv-target-temperature-heating" }
Number:Temperature Heatpump_PVTempDrinkingwater "PV Temp Drinkingwater" (kermi) { channel="modbus:tcp:device:heatpump:pv-modulation#pv-target-temperature-drinkingwater" }
Number:Time Heatpump_FanWorkhours "Fan Workhours" (kermi) { channel="modbus:tcp:device:heatpump:workhours#workhours-fan" }
Number:Time Heatpump_StorageLoadingPumpWorkhours "StorageLoadingPump Workhours" (kermi) { channel="modbus:tcp:device:heatpump:workhours#workhours-storage-loading-pump" }
Number:Time Heatpump_CompressorWorkhours "Compressor Workhours" (kermi) { channel="modbus:tcp:device:heatpump:workhours#workhours-compressor" }
```
## Persistence
You can / should persist some items you want to track, maybe you track your power consumption with another device (PV-System or 'smart' electricity meter), so you can compare these values.
Suggestion / Optional:
As these (power & temperature) values are long-running ones, maybe you should invest a little amount of time using influxDB as persistence (additionally to your existing default persistence in openHAB).
InfluxDB is a good storage-solution for long terms and uses very small space for its data. Please read the documentation for better understanding how it works.
### Visualization
As many other users I like and use the Grafana approach (in combination with influxdb).
See here for more information [InfluxDB & Grafana Tutorial](https://community.openhab.org/t/influxdb-grafana-persistence-and-graphing/13761)
### Credits
Credits goes to Bernd Weymann (Author of E3DC-Modbus-Binding).
I used its basic structure / code and handling of Modbus Messages for this binding. Thanks.

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.modbus.kermi</artifactId>
<name>openHAB Add-ons :: Bundles :: Kermi Modbus 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,86 @@
/**
* 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.kermi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.ModbusBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link KermiBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
public class KermiBindingConstants {
public static final String STATE_AND_ALARM_READ_ERRORS = "Status And Alarm Modbus Read Errors";
public static final String STATE_READ_ERROR = "Information Modbus Read Error";
public static final String DATA_READ_ERROR = "Data Modbus Read Error";
static final String PV_READ_ERROR = "PV Modbus Read Error";
public static final String ALARM_GROUP = "xcenter-alarm";
public static final String STATE_GROUP = "xcenter-state";
public static final String ENERGY_SOURCE_GROUP = "xcenter-energy-source";
public static final String CHARGING_CIRCUIT_GROUP = "xcenter-charging-circuit";
public static final String POWER_GROUP = "xcenter-power";
public static final String WORKHOURS_GROUP = "xcenter-workhours";
public static final String PV_GROUP = "xcenter-pv-modulation";
private static final String BINDING_ID = ModbusBindingConstants.BINDING_ID;
// Supported Thing Types
public static final ThingTypeUID THING_TYPE_KERMI_XCENTER = new ThingTypeUID(BINDING_ID, "kermi-xcenter");
// Channels for State
public static final String GLOBAL_STATE_ID_CHANNEL = "global-state-id";
// Alarm State
public static final String ALARM_STATE_CHANNEL = "alarm-state";
// Energy Source
public static final String FLOW_TEMPERATURE_CHANNEL = "flow-temperature";
public static final String RETURN_TEMPERATURE_CHANNEL = "return-temperature";
public static final String FLOW_SPEED_CHANNEL = "flow-speed";
// Charging Circuit
public static final String EXIT_TEMPERATURE_CHANNEL = "exit-temperature";
public static final String INCOMING_TEMPERATURE_CHANNEL = "incoming-temperature";
public static final String TEMPERATURE_SENSOR_OUTSIDE_CHANNEL = "temperature-sensor-outside";
// Power
public static final String COP_CHANNEL = "cop";
public static final String COP_HEATING_CHANNEL = "cop-heating";
public static final String COP_DRINKINGWATER_CHANNEL = "cop-drinkingwater";
public static final String COP_COOLING_CHANNEL = "cop-cooling";
public static final String POWER_CHANNEL = "power";
public static final String POWER_HEATING_CHANNEL = "power-heating";
public static final String POWER_DRINKINGWATER_CHANNEL = "power-drinkingwater";
public static final String POWER_COOLING_CHANNEL = "power-cooling";
public static final String ELECTRIC_POWER_CHANNEL = "electric-power";
public static final String ELECTRIC_POWER_HEATING_CHANNEL = "electric-power-heating";
public static final String ELECTRIC_POWER_DRINKINGWATER_CHANNEL = "electric-power-drinkingwater";
public static final String ELECTRIC_POWER_COOLING_CHANNEL = "electric-power-cooling";
// Work hours
public static final String WORKHOURS_FAN_CHANNEL = "workhours-fan";
public static final String WORKHOURS_STORAGE_LOADING_PUMP_CHANNEL = "workhours-storage-loading-pump";
public static final String WORKHOURS_COMPRESSOR_CHANNEL = "workhours-compressor";
// PV
public static final String PV_STATE_CHANNEL = "pv-state";
public static final String PV_POWER_CHANNEL = "pv-power";
public static final String PV_TARGET_TEMPERATURE_HEATING_CHANNEL = "pv-target-temperature-heating";
public static final String PV_TARGET_TEMPERATURE_DRINKINGWATER_CHANNEL = "pv-target-temperature-drinkingwater";
}

View File

@ -0,0 +1,27 @@
/**
* 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.kermi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link KermiConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
public class KermiConfiguration {
public int refresh = 5000;
public boolean pvEnabled = false;
}

View File

@ -0,0 +1,47 @@
/**
* 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.kermi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.kermi.internal.handler.KermiXcenterThingHandler;
import org.openhab.core.thing.Bridge;
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 KermiHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.kermi", service = ThingHandlerFactory.class)
public class KermiHandlerFactory extends BaseThingHandlerFactory {
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return thingTypeUID.equals(KermiBindingConstants.THING_TYPE_KERMI_XCENTER);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (KermiBindingConstants.THING_TYPE_KERMI_XCENTER.equals(thingTypeUID)) {
return new KermiXcenterThingHandler((Bridge) thing);
} // else here
return null;
}
}

View File

@ -0,0 +1,34 @@
/**
* 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.kermi.internal.dto;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.ALARM_REG_SIZE;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
import org.openhab.core.library.types.OnOffType;
/**
* The {@link AlarmDTO} Data object for Kermi Xcenter
*
* @author Kai Neuhaus - Initial contribution
*/
public class AlarmDTO implements Data {
public OnOffType alarmIsActive;
public AlarmDTO(byte[] bArray) {
int status = ModbusBitUtilities.extractBit(bArray, ALARM_REG_SIZE);
alarmIsActive = OnOffType.from(status != 0);
}
}

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.kermi.internal.dto;
import static org.openhab.core.library.unit.Units.LITRE_PER_MINUTE;
import javax.measure.quantity.Temperature;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ValueBuffer;
import org.openhab.core.library.dimension.VolumetricFlowRate;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
/**
* The {@link ChargingCircuitDTO} Data object for Kermi Xcenter
*
* @author Kai Neuhaus - Initial contribution
*/
public class ChargingCircuitDTO implements Data {
public QuantityType<Temperature> flowTemperature;
public QuantityType<Temperature> returnFlowTemperature;
public QuantityType<VolumetricFlowRate> flowSpeed;
public ChargingCircuitDTO(byte[] bArray) {
ValueBuffer wrap = ValueBuffer.wrap(bArray);
flowTemperature = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
returnFlowTemperature = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
flowSpeed = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), LITRE_PER_MINUTE);
}
}

View File

@ -0,0 +1,83 @@
/**
* 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.kermi.internal.dto;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
import org.openhab.core.io.transport.modbus.ValueBuffer;
/**
* The {@link DataConverter} Helper class to convert bytes from modbus into desired data format
*
* @author Bernd Weymann - Initial contribution
* @author Kai Neuhaus - used for Modbus.Kermi Binding
*/
@NonNullByDefault
public class DataConverter {
/**
* Retrieves a double value from the first two bytes of the provided {@link ValueBuffer} using the given correction
* factor.
*
* @param wrap The {@link ValueBuffer} from which to extract the bytes.
* @param factor The correction factor to apply to the extracted value.
* @return The calculated double value.
*/
public static double getUDoubleValue(ValueBuffer wrap, double factor) {
return round(wrap.getUInt16() * factor, 2);
}
/**
* Retrieves a signed double value from the first two bytes of the provided {@link ValueBuffer} using the given
* correction factor.
*
* @param wrap The {@link ValueBuffer} from which to extract the bytes. This {@link ValueBuffer} should contain at
* least two bytes.
* @param factor The correction factor to apply to the extracted value. This factor is used to adjust the calculated
* value.
* @return The calculated signed double value. The value is obtained by extracting the first two bytes from the
* {@link ValueBuffer},
* converting them to a signed 16-bit integer, multiplying it by the correction factor, and rounding the
* result to two decimal places.
*/
public static double getSDoubleValue(ValueBuffer wrap, double factor) {
return round(wrap.getSInt16() * factor, 2);
}
public static String getString(byte[] bArray) {
return ModbusBitUtilities.extractStringFromBytes(bArray, 0, bArray.length, StandardCharsets.US_ASCII).trim();
}
public static int toInt(BitSet bitSet) {
int intValue = 0;
for (int bit = 0; bit < bitSet.length(); bit++) {
if (bitSet.get(bit)) {
intValue |= (1 << bit);
}
}
return intValue;
}
public static double round(double value, int places) {
if (places < 0) {
throw new IllegalArgumentException();
}
long factor = (long) Math.pow(10, places);
long tmp = Math.round(value * factor);
return (double) tmp / factor;
}
}

View File

@ -0,0 +1,39 @@
/**
* 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.kermi.internal.dto;
import javax.measure.quantity.Temperature;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ValueBuffer;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
/**
* The {@link EnergySourceDTO} Data object for Kermi Xcenter
*
* @author Kai Neuhaus - Initial contribution
*/
public class EnergySourceDTO implements Data {
public QuantityType<Temperature> exitTemperature;
public QuantityType<Temperature> incomingTemperature;
public QuantityType<Temperature> outsideTemperature;
public EnergySourceDTO(byte[] bArray) {
ValueBuffer wrap = ValueBuffer.wrap(bArray);
exitTemperature = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
incomingTemperature = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
outsideTemperature = QuantityType.valueOf(DataConverter.getSDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
}
}

View File

@ -0,0 +1,64 @@
/**
* 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.kermi.internal.dto;
import javax.measure.quantity.Power;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ValueBuffer;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
/**
* The {@link PowerDTO} Data object for Kermi Xcenter State Block
*
* @author Kai Neuhaus - Initial contribution
*/
public class PowerDTO implements Data {
public DecimalType cop;
public DecimalType copHeating;
public DecimalType copDrinkingwater;
public DecimalType copCooling;
public QuantityType<Power> power;
public QuantityType<Power> powerHeating;
public QuantityType<Power> powerDrinkingwater;
public QuantityType<Power> powerCooling;
public QuantityType<Power> electricPower;
public QuantityType<Power> electricPowerHeating;
public QuantityType<Power> electricPowerDrinkingwater;
public QuantityType<Power> electricPowerCooling;
public PowerDTO(byte[] bArray) {
ValueBuffer wrap = ValueBuffer.wrap(bArray);
cop = new DecimalType(DataConverter.getUDoubleValue(wrap, 0.1));
copHeating = new DecimalType(DataConverter.getUDoubleValue(wrap, 0.1));
copDrinkingwater = new DecimalType(DataConverter.getUDoubleValue(wrap, 0.1));
copCooling = new DecimalType(DataConverter.getUDoubleValue(wrap, 0.1));
power = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
powerHeating = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
powerDrinkingwater = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
powerCooling = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
electricPower = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
electricPowerHeating = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
electricPowerDrinkingwater = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
electricPowerCooling = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 100), Units.WATT);
}
}

View File

@ -0,0 +1,52 @@
/**
* 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.kermi.internal.dto;
import javax.measure.quantity.Power;
import javax.measure.quantity.Temperature;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
import org.openhab.core.io.transport.modbus.ValueBuffer;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
/**
* The {@link PvDTO} Data object for Kermi Xcenter
*
* @author Kai Neuhaus - Initial contribution
*/
public class PvDTO implements Data {
public OnOffType pvModulationActive;
public QuantityType<Power> pvModulationPower;
public QuantityType<Temperature> pvTargetTemperatureHeating;
public QuantityType<Temperature> pvTargetTemperatureDrinkingwater;
public PvDTO(byte[] bArray) {
int modActive = ModbusBitUtilities.extractBit(bArray, 0);
pvModulationActive = modActive == 0 ? OnOffType.OFF : OnOffType.ON;
ValueBuffer wrap = ValueBuffer.wrap(bArray);
// skip first bit -> modActive-Value
wrap.position(2);
pvModulationPower = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1), Units.WATT);
pvTargetTemperatureHeating = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1), SIUnits.CELSIUS);
pvTargetTemperatureDrinkingwater = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1),
SIUnits.CELSIUS);
}
}

View File

@ -0,0 +1,32 @@
/**
* 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.kermi.internal.dto;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
import org.openhab.core.library.types.DecimalType;
/**
* The {@link StateDTO} Data object for Kermi Xcenter
*
* @author Kai Neuhaus - Initial contribution
*/
public class StateDTO implements Data {
public DecimalType globalStateId;
public StateDTO(byte[] bArray) {
int status = ModbusBitUtilities.extractBit(bArray, 0);
globalStateId = new DecimalType(status);
}
}

View File

@ -0,0 +1,41 @@
/**
* 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.kermi.internal.dto;
import javax.measure.quantity.Time;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.core.io.transport.modbus.ValueBuffer;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
/**
* The {@link WorkHoursDTO} Data object for Kermi Xcenter State Block
*
* @author Kai Neuhaus - Initial contribution
*/
public class WorkHoursDTO implements Data {
public QuantityType<Time> workHoursFan;
public QuantityType<Time> workHoursStorageLoadingPump;
public QuantityType<Time> workHoursCompressor;
public WorkHoursDTO(byte[] bArray) {
ValueBuffer wrap = ValueBuffer.wrap(bArray);
workHoursFan = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1), Units.HOUR);
workHoursStorageLoadingPump = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1), Units.HOUR);
workHoursCompressor = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.1), Units.HOUR);
}
}

View File

@ -0,0 +1,613 @@
/**
* 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.kermi.internal.handler;
import static org.openhab.binding.modbus.kermi.internal.KermiBindingConstants.*;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.kermi.internal.KermiBindingConstants;
import org.openhab.binding.modbus.kermi.internal.KermiConfiguration;
import org.openhab.binding.modbus.kermi.internal.dto.AlarmDTO;
import org.openhab.binding.modbus.kermi.internal.dto.ChargingCircuitDTO;
import org.openhab.binding.modbus.kermi.internal.dto.EnergySourceDTO;
import org.openhab.binding.modbus.kermi.internal.dto.PowerDTO;
import org.openhab.binding.modbus.kermi.internal.dto.PvDTO;
import org.openhab.binding.modbus.kermi.internal.dto.StateDTO;
import org.openhab.binding.modbus.kermi.internal.dto.WorkHoursDTO;
import org.openhab.binding.modbus.kermi.internal.modbus.Data;
import org.openhab.binding.modbus.kermi.internal.modbus.Parser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.thing.Bridge;
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.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KermiXcenterThingHandler} Basic modbus connection to the Kermi device(s)
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
public class KermiXcenterThingHandler extends BaseBridgeHandler {
public enum ReadStatus {
NOT_RECEIVED,
READ_SUCCESS,
READ_FAILED
}
// Energy source
private ChannelUID exitTemperatureChannel;
private ChannelUID incomingTemperatureChannel;
private ChannelUID outsideTemperatureChannel;
// Charging circuit
private ChannelUID flowTemperatureChannel;
private ChannelUID returnFlowTemperatureChannel;
private ChannelUID flowSpeedChannel;
// Power
private ChannelUID copChannel;
private ChannelUID copHeatingChannel;
private ChannelUID copDrinkingWaterChannel;
private ChannelUID copCoolingChannel;
private ChannelUID powerChannel;
private ChannelUID powerHeatingChannel;
private ChannelUID powerDrinkingWaterChannel;
private ChannelUID powerCoolingChannel;
private ChannelUID electricPowerChannel;
private ChannelUID electricPowerHeatingChannel;
private ChannelUID electricPowerDrinkingWaterChannel;
private ChannelUID electricPowerCoolingChannel;
// Global State
// private ChannelUID globalStateChannel;
private ChannelUID globalStateIdChannel;
// Alarm State
private ChannelUID alarmStateChannel;
// Work hours
private ChannelUID workHoursFanChannel;
private ChannelUID workHoursStorageLoadingPumpChannel;
private ChannelUID workHoursCompressorChannel;
// PV Modulation
private ChannelUID pvStateChannel;
private ChannelUID pvPowerChannel;
private ChannelUID pvTargetTemperatureHeatingChannel; // READ-WRITE
private ChannelUID pvTargetTemperatureDrinkingWaterChannel; // READ-WRITE
private final Logger logger = LoggerFactory.getLogger(KermiXcenterThingHandler.class);
private final Parser alarmParser = new Parser(Data.DataType.ALARM_STATE);
private ReadStatus alarmRead = ReadStatus.NOT_RECEIVED;
private final Parser chargingCircuitParser = new Parser(Data.DataType.CHARGING_CIRCUIT);
private ReadStatus chargingCircuitRead = ReadStatus.NOT_RECEIVED;
private final Parser energySourceParser = new Parser(Data.DataType.ENERGY_SOURCE);
private ReadStatus energySourceRead = ReadStatus.NOT_RECEIVED;
private final Parser powerParser = new Parser(Data.DataType.POWER);
private ReadStatus powerRead = ReadStatus.NOT_RECEIVED;
private final Parser pvParser = new Parser(Data.DataType.PV);
private ReadStatus pvRead = ReadStatus.NOT_RECEIVED;
private final Parser stateParser = new Parser(Data.DataType.STATE);
private ReadStatus stateRead = ReadStatus.NOT_RECEIVED;
private final Parser workHoursParser = new Parser(Data.DataType.WORK_HOURS);
private ReadStatus workHoursRead = ReadStatus.NOT_RECEIVED;
private @Nullable PollTask alarmPoller;
private @Nullable PollTask chargingCircuitPoller;
private @Nullable PollTask energySourcePoller;
private @Nullable PollTask powerPoller;
private @Nullable PollTask pvPoller;
private @Nullable PollTask statePoller;
private @Nullable PollTask workHourPoller;
private List<@Nullable PollTask> pollTasks = new ArrayList<>();
private @Nullable KermiConfiguration config;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
private int slaveId;
public KermiXcenterThingHandler(Bridge thing) {
super(thing);
// STATE
globalStateIdChannel = channelUID(thing, KermiBindingConstants.STATE_GROUP, GLOBAL_STATE_ID_CHANNEL);
// ALARM
alarmStateChannel = channelUID(thing, KermiBindingConstants.ALARM_GROUP, ALARM_STATE_CHANNEL);
// Energy source
exitTemperatureChannel = channelUID(thing, KermiBindingConstants.ENERGY_SOURCE_GROUP, EXIT_TEMPERATURE_CHANNEL);
incomingTemperatureChannel = channelUID(thing, KermiBindingConstants.ENERGY_SOURCE_GROUP,
INCOMING_TEMPERATURE_CHANNEL);
outsideTemperatureChannel = channelUID(thing, KermiBindingConstants.ENERGY_SOURCE_GROUP,
TEMPERATURE_SENSOR_OUTSIDE_CHANNEL);
// Loading circuit
flowTemperatureChannel = channelUID(thing, KermiBindingConstants.CHARGING_CIRCUIT_GROUP,
FLOW_TEMPERATURE_CHANNEL);
returnFlowTemperatureChannel = channelUID(thing, KermiBindingConstants.CHARGING_CIRCUIT_GROUP,
RETURN_TEMPERATURE_CHANNEL);
flowSpeedChannel = channelUID(thing, KermiBindingConstants.CHARGING_CIRCUIT_GROUP, FLOW_SPEED_CHANNEL);
// Power
copChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, COP_CHANNEL);
copHeatingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, COP_HEATING_CHANNEL);
copDrinkingWaterChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, COP_DRINKINGWATER_CHANNEL);
copCoolingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, COP_COOLING_CHANNEL);
powerChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, POWER_CHANNEL);
powerHeatingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, POWER_HEATING_CHANNEL);
powerDrinkingWaterChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, POWER_DRINKINGWATER_CHANNEL);
powerCoolingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, POWER_COOLING_CHANNEL);
electricPowerChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP, ELECTRIC_POWER_CHANNEL);
electricPowerHeatingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP,
ELECTRIC_POWER_HEATING_CHANNEL);
electricPowerDrinkingWaterChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP,
ELECTRIC_POWER_DRINKINGWATER_CHANNEL);
electricPowerCoolingChannel = channelUID(thing, KermiBindingConstants.POWER_GROUP,
ELECTRIC_POWER_COOLING_CHANNEL);
// Work hours
workHoursFanChannel = channelUID(thing, KermiBindingConstants.WORKHOURS_GROUP, WORKHOURS_FAN_CHANNEL);
workHoursStorageLoadingPumpChannel = channelUID(thing, KermiBindingConstants.WORKHOURS_GROUP,
WORKHOURS_STORAGE_LOADING_PUMP_CHANNEL);
workHoursCompressorChannel = channelUID(thing, KermiBindingConstants.WORKHOURS_GROUP,
WORKHOURS_COMPRESSOR_CHANNEL);
// PV Modulation
pvStateChannel = channelUID(thing, KermiBindingConstants.PV_GROUP, PV_STATE_CHANNEL);
pvPowerChannel = channelUID(thing, KermiBindingConstants.PV_GROUP, PV_POWER_CHANNEL);
pvTargetTemperatureHeatingChannel = channelUID(thing, KermiBindingConstants.PV_GROUP,
PV_TARGET_TEMPERATURE_HEATING_CHANNEL); // READ-WRITE
pvTargetTemperatureDrinkingWaterChannel = channelUID(thing, KermiBindingConstants.PV_GROUP,
PV_TARGET_TEMPERATURE_DRINKINGWATER_CHANNEL); // READ-WRITE
}
public @Nullable ModbusCommunicationInterface getComms() {
return comms;
}
public int getSlaveId() {
return slaveId;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// no control of Kermi device possible yet
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> {
KermiConfiguration localConfig = getConfigAs(KermiConfiguration.class);
config = localConfig;
if (config == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Kermi Configuration missing");
return;
}
ModbusCommunicationInterface localComms = connectEndpoint();
if (localComms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Connection failure on initialize...");
return;
}
/**
* Before asking: i tried to create 1 "big poller" and separate the data from the received bytes.
* But when polling "registers" which are not declared (from Kermi), the answer was corrupted or
* invalid.
* e.x. polling 100 to 150, expecting 50 registers the device returns 18 only, strange behaviour.
* Maybe polling will be improved in future - to have not that "huge" amount of pollerTasks.
*/
// very slow requests
ModbusReadRequestBlueprint workHoursRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, WORK_HOURS_REG_START, WORK_HOURS_REG_SIZE, 3);
workHourPoller = localComms.registerRegularPoll(workHoursRequest, SLOW_POLL_REFRESH_TIME_MS,
WARMUP_TIME_LONG, this::handleWorkHoursResult, this::handleWorkHoursFailure);
pollTasks.add(workHourPoller);
// register low speed state & alarm poller
ModbusReadRequestBlueprint alarmRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, ALARM_REG_START, ALARM_REG_SIZE, 3);
alarmPoller = localComms.registerRegularPoll(alarmRequest, STATE_POLL_REFRESH_TIME_MS, WARMUP_TIME_LONG,
this::handleAlarmResult, this::handleAlarmFailure);
pollTasks.add(alarmPoller);
ModbusReadRequestBlueprint stateRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, STATE_REG_START, STATE_REG_SIZE, 3);
statePoller = localComms.registerRegularPoll(stateRequest, STATE_POLL_REFRESH_TIME_MS, WARMUP_TIME_LONG,
this::handleStateResult, this::handleStateFailure);
pollTasks.add(statePoller);
// default polling speed
ModbusReadRequestBlueprint chargingcircuitRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, CHARGING_CIRCUIT_REG_START,
CHARGING_CIRCUIT_REG_SIZE, 3);
chargingCircuitPoller = localComms.registerRegularPoll(chargingcircuitRequest, localConfig.refresh,
WARMUP_TIME_SHORT, this::handleChargingCircuitResult, this::handleChargingCircuitFailure);
pollTasks.add(chargingCircuitPoller);
ModbusReadRequestBlueprint energySourceRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, ENERGY_SOURCE_REG_START, ENERGY_SOURCE_REG_SIZE, 3);
energySourcePoller = localComms.registerRegularPoll(energySourceRequest, localConfig.refresh,
WARMUP_TIME_SHORT, this::handleEnergySourceResult, this::handleEnergySourceFailure);
pollTasks.add(energySourcePoller);
ModbusReadRequestBlueprint powerRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, POWER_REG_START, POWER_REG_SIZE, 3);
powerPoller = localComms.registerRegularPoll(powerRequest, localConfig.refresh, WARMUP_TIME_SHORT,
this::handlePowerResult, this::handlePowerFailure);
pollTasks.add(powerPoller);
if (localConfig.pvEnabled) {
ModbusReadRequestBlueprint pvRequest = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, PV_MODULATION_REG_START, PV_MODULATION_REG_SIZE,
3);
pvPoller = localComms.registerRegularPoll(pvRequest, localConfig.refresh, WARMUP_TIME_SHORT,
this::handlePvResult, this::handlePvFailure);
pollTasks.add(pvPoller);
}
});
}
/**
* Get a reference to the modbus endpoint
*/
private @Nullable ModbusCommunicationInterface connectEndpoint() {
if (comms != null) {
return comms;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("SlaveEndpointThingHandler is null, Thing & Bridge are offline");
return null;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Slave Endpoint not initialized");
logger.debug("Slave Endpoint not initialized, Thing is offline");
return null;
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface is null, Thing & Bridge are offline");
return null;
} else {
return comms;
}
}
/**
* Get the endpoint handler from the bridge this handler is connected to
* Checks that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
if (bridge.getHandler() instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
logger.debug("Bridge handler null or unexpected type: {}", bridge.getHandler());
return null;
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param group String of channel group
* @param id String the channel id in that group
* @return the globally unique channel uid
*/
private ChannelUID channelUID(Thing t, String group, String id) {
return new ChannelUID(t.getUID(), group, id);
}
/******** Start ResultHandler *****************/
private void handleAlarmResult(AsyncModbusReadResult result) {
if (alarmRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
alarmRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
alarmParser.handle(result);
Optional<Data> dtoOpt = alarmParser.parse(Data.DataType.ALARM_STATE);
if (dtoOpt.isPresent()) {
AlarmDTO alarmDTO = (AlarmDTO) dtoOpt.get();
updateState(alarmStateChannel, alarmDTO.alarmIsActive);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.ALARM_STATE, alarmParser.toString());
}
}
private void handleAlarmFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (alarmRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
alarmRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
private void handleChargingCircuitResult(AsyncModbusReadResult result) {
if (chargingCircuitRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
chargingCircuitRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
chargingCircuitParser.handle(result);
Optional<Data> dtoOpt = chargingCircuitParser.parse(Data.DataType.CHARGING_CIRCUIT);
if (dtoOpt.isPresent()) {
ChargingCircuitDTO chargingCircuitDTO = (ChargingCircuitDTO) dtoOpt.get();
updateState(flowTemperatureChannel, chargingCircuitDTO.flowTemperature);
updateState(returnFlowTemperatureChannel, chargingCircuitDTO.returnFlowTemperature);
updateState(flowSpeedChannel, chargingCircuitDTO.flowSpeed);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.CHARGING_CIRCUIT,
chargingCircuitParser.toString());
}
}
private void handleChargingCircuitFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (chargingCircuitRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
chargingCircuitRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
private void handleEnergySourceResult(AsyncModbusReadResult result) {
if (energySourceRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
energySourceRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
energySourceParser.handle(result);
Optional<Data> dtoOpt = energySourceParser.parse(Data.DataType.ENERGY_SOURCE);
if (dtoOpt.isPresent()) {
EnergySourceDTO energySourceDTO = (EnergySourceDTO) dtoOpt.get();
updateState(exitTemperatureChannel, energySourceDTO.exitTemperature);
updateState(incomingTemperatureChannel, energySourceDTO.incomingTemperature);
updateState(outsideTemperatureChannel, energySourceDTO.outsideTemperature);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.ENERGY_SOURCE,
energySourceParser.toString());
}
}
private void handleEnergySourceFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (energySourceRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
energySourceRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
private void handlePowerResult(AsyncModbusReadResult result) {
if (powerRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
powerRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
powerParser.handle(result);
Optional<Data> dtoOpt = powerParser.parse(Data.DataType.POWER);
if (dtoOpt.isPresent()) {
PowerDTO powerDTO = (PowerDTO) dtoOpt.get();
updateState(copChannel, powerDTO.cop);
updateState(copHeatingChannel, powerDTO.copHeating);
updateState(copDrinkingWaterChannel, powerDTO.copDrinkingwater);
updateState(copCoolingChannel, powerDTO.copCooling);
updateState(powerChannel, powerDTO.power);
updateState(powerHeatingChannel, powerDTO.powerHeating);
updateState(powerDrinkingWaterChannel, powerDTO.powerDrinkingwater);
updateState(powerCoolingChannel, powerDTO.powerCooling);
updateState(electricPowerChannel, powerDTO.electricPower);
updateState(electricPowerHeatingChannel, powerDTO.electricPowerHeating);
updateState(electricPowerDrinkingWaterChannel, powerDTO.electricPowerDrinkingwater);
updateState(electricPowerCoolingChannel, powerDTO.electricPowerCooling);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.POWER, powerParser.toString());
}
}
private void handlePowerFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (powerRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
powerRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
private void handlePvResult(AsyncModbusReadResult result) {
if (pvRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
pvRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
pvParser.handle(result);
Optional<Data> dtoOpt = pvParser.parse(Data.DataType.PV);
if (dtoOpt.isPresent()) {
PvDTO pvDTO = (PvDTO) dtoOpt.get();
updateState(pvStateChannel, pvDTO.pvModulationActive);
updateState(pvPowerChannel, pvDTO.pvModulationPower);
updateState(pvTargetTemperatureHeatingChannel, pvDTO.pvTargetTemperatureHeating);
updateState(pvTargetTemperatureDrinkingWaterChannel, pvDTO.pvTargetTemperatureDrinkingwater);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.PV, pvParser.toString());
}
}
private void handlePvFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (pvRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
pvRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
void handleStateResult(AsyncModbusReadResult result) {
if (stateRead != ReadStatus.READ_SUCCESS) {
// update status only if bit switches
stateRead = ReadStatus.READ_SUCCESS;
updateStatus();
}
stateParser.handle(result);
Optional<Data> dtoOpt = stateParser.parse(Data.DataType.STATE);
if (dtoOpt.isPresent()) {
StateDTO dto = (StateDTO) dtoOpt.get();
// updateState(globalStateChannel, dto.globalState);
updateState(globalStateIdChannel, dto.globalStateId);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.STATE, stateParser.toString());
}
}
void handleStateFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (stateRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
stateRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
void handleWorkHoursResult(AsyncModbusReadResult result) {
workHoursParser.handle(result);
Optional<Data> dtoOpt = workHoursParser.parse(Data.DataType.WORK_HOURS);
if (dtoOpt.isPresent()) {
WorkHoursDTO dto = (WorkHoursDTO) dtoOpt.get();
updateState(workHoursFanChannel, dto.workHoursFan);
updateState(workHoursStorageLoadingPumpChannel, dto.workHoursStorageLoadingPump);
updateState(workHoursCompressorChannel, dto.workHoursCompressor);
} else {
logger.debug("Unable to get {} from provider {}", Data.DataType.WORK_HOURS, workHoursParser.toString());
}
}
private void handleWorkHoursFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
if (workHoursRead != ReadStatus.READ_FAILED) {
// update status only if bit switches
workHoursRead = ReadStatus.READ_FAILED;
logger.debug("Cause of failure: {}", result.getCause().getMessage());
updateStatus();
}
}
@Override
public void dispose() {
ModbusCommunicationInterface localComms = comms;
if (localComms != null) {
for (PollTask p : pollTasks) {
PollTask localPoller = p;
if (localPoller != null) {
localComms.unregisterRegularPoll(localPoller);
}
}
}
// Comms will be close()'d by endpoint thing handler
comms = null;
}
private void updateStatus() {
logger.debug("Status update: State {} Data {} WorkHours {} PV {} ", stateRead, powerRead, workHoursRead,
pvRead);
if (stateRead != ReadStatus.NOT_RECEIVED) { // && dataRead != ReadStatus.NOT_RECEIVED
if (stateRead == ReadStatus.READ_SUCCESS) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
KermiBindingConstants.STATE_READ_ERROR);
}
if (stateRead == alarmRead) {
// both reads are ok or else both failed
if (stateRead == ReadStatus.READ_SUCCESS) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
KermiBindingConstants.STATE_AND_ALARM_READ_ERRORS);
}
} else {
// either info or data read failed - update status with details
if (stateRead == ReadStatus.READ_FAILED) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
KermiBindingConstants.STATE_READ_ERROR);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
KermiBindingConstants.DATA_READ_ERROR);
}
}
}
}
}

View File

@ -0,0 +1,34 @@
/**
* 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.kermi.internal.modbus;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Data} Marker Interface for delivered Modbus Data
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
public interface Data {
enum DataType {
STATE,
ALARM_STATE,
ENERGY_SOURCE,
CHARGING_CIRCUIT,
POWER,
WORK_HOURS,
PV,
DATA // marks all types besides INFO/STATE
}
}

View File

@ -0,0 +1,66 @@
/**
* 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.kermi.internal.modbus;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link KermiModbusConstans} Variables for register handling.
* The numbers are taken from Kermi ExcelChart for Modbus
* (Registers start from 0 (not 1!) so from the documented registers subtract 1)
*
* @author Kai Neuhaus - Initial contribution
*/
@NonNullByDefault
public class KermiModbusConstans {
public static final int WARMUP_TIME_LONG = 1500;
public static final int WARMUP_TIME_SHORT = 500;
// "String" registers at the beginning shall be read with very low frequency - 1 hour
public static final int SLOW_POLL_REFRESH_TIME_MS = 60 * 60 * 1000;
// poll every 30 seconds
public static final int STATE_POLL_REFRESH_TIME_MS = 30 * 1000;
// Constants where a certain Block starts and block size. Note: General offset is -1 so INFO_REG from E3DC Modbus
// Spec starts at 1 but it's Register 0!
public static final int XCENTER_DATA_REG_START = 1;
public static final int XCENTER_DATA_REG_SIZE = 111;
public static final int ENERGY_SOURCE_REG_START = 1;
public static final int ENERGY_SOURCE_REG_SIZE = 3;
public static final int CHARGING_CIRCUIT_REG_START = 50;
public static final int CHARGING_CIRCUIT_REG_SIZE = 3;
public static final int POWER_REG_START = 100;
public static final int POWER_REG_SIZE = 12;
/** INFO BLOCK **/
public static final int WORK_HOURS_REG_START = 150;
public static final int WORK_HOURS_REG_SIZE = 3;
public static final int STATE_REG_START = 200;
public static final int STATE_REG_SIZE = 1;
public static final int ALARM_REG_START = 250;
public static final int ALARM_REG_SIZE = 1;
/** END INFO BLOCK **/
/** PV BLOCK **/
public static final int PV_MODULATION_REG_START = 300;
public static final int PV_MODULATION_REG_SIZE = 4;
public static final int DATA_REGISTER_LENGTH = 111;
}

View File

@ -0,0 +1,144 @@
/**
* 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.kermi.internal.modbus;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.ALARM_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.CHARGING_CIRCUIT_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.DATA_REGISTER_LENGTH;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.ENERGY_SOURCE_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.POWER_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.PV_MODULATION_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.STATE_REG_SIZE;
import static org.openhab.binding.modbus.kermi.internal.modbus.KermiModbusConstans.WORK_HOURS_REG_SIZE;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.kermi.internal.dto.AlarmDTO;
import org.openhab.binding.modbus.kermi.internal.dto.ChargingCircuitDTO;
import org.openhab.binding.modbus.kermi.internal.dto.EnergySourceDTO;
import org.openhab.binding.modbus.kermi.internal.dto.PowerDTO;
import org.openhab.binding.modbus.kermi.internal.dto.PvDTO;
import org.openhab.binding.modbus.kermi.internal.dto.StateDTO;
import org.openhab.binding.modbus.kermi.internal.dto.WorkHoursDTO;
import org.openhab.binding.modbus.kermi.internal.modbus.Data.DataType;
import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Parser} class receives callbacks from modbus poller
*
* @author Bernd Weymann - Initial contribution
* @author Kai Neuhaus - adapted for Kermi
*/
@NonNullByDefault
public class Parser {
private static final int MEASURE_COUNT = 100;
private final Logger logger = LoggerFactory.getLogger(Parser.class);
private DataType callbackType;
private byte[] bArray;
private int size;
private int counter = 0;
private long maxDuration = Long.MIN_VALUE;
private long minDuration = Long.MAX_VALUE;
private long avgDuration = 0;
public Parser(DataType type) {
callbackType = type;
if (type.equals(DataType.STATE)) {
size = (STATE_REG_SIZE) * 2;
bArray = new byte[size];
} else if (type.equals(DataType.ENERGY_SOURCE)) {
size = ENERGY_SOURCE_REG_SIZE * 2;
bArray = new byte[size];
} else if (type.equals(DataType.CHARGING_CIRCUIT)) {
size = CHARGING_CIRCUIT_REG_SIZE * 2;
bArray = new byte[size];
} else if (type.equals(DataType.POWER)) {
size = POWER_REG_SIZE * 2;
bArray = new byte[size];
} else if (type.equals(DataType.ALARM_STATE)) {
size = ALARM_REG_SIZE * 2;
bArray = new byte[size];
} else if (type.equals(DataType.PV)) {
size = PV_MODULATION_REG_SIZE * 2;
bArray = new byte[size];
} else if (type.equals(DataType.WORK_HOURS)) {
size = WORK_HOURS_REG_SIZE * 2;
bArray = new byte[size];
} else {
size = DATA_REGISTER_LENGTH * 2;
bArray = new byte[size];
}
}
public void handle(AsyncModbusReadResult result) {
long startTime = System.currentTimeMillis();
Optional<ModbusRegisterArray> opt = result.getRegisters();
if (opt.isPresent()) {
setArray(opt.get().getBytes());
long duration = System.currentTimeMillis() - startTime;
avgDuration += duration;
minDuration = Math.min(minDuration, duration);
maxDuration = Math.max(maxDuration, duration);
counter++;
if (counter % MEASURE_COUNT == 0) {
logger.debug("Min {} Max {} Avg {}", minDuration, maxDuration, avgDuration / MEASURE_COUNT);
avgDuration = 0;
minDuration = Long.MAX_VALUE;
maxDuration = Long.MIN_VALUE;
}
} else {
logger.warn("Modbus read result doesn't return expected registers");
}
}
public synchronized void setArray(byte[] b) {
if (b.length != size) {
logger.warn("Wrong byte size received. Should be {} but is {}. Data maybe corrupted!", size, b.length);
}
bArray = b.clone();
}
public Optional<Data> parse(DataType type) {
synchronized (bArray) {
if (type.equals(DataType.STATE) && callbackType.equals(DataType.STATE)) {
return Optional.of(new StateDTO(bArray));
} else if (type.equals(DataType.ENERGY_SOURCE) && callbackType.equals(DataType.ENERGY_SOURCE)) {
return Optional.of(new EnergySourceDTO(bArray));
} else if (type.equals(DataType.CHARGING_CIRCUIT) && callbackType.equals(DataType.CHARGING_CIRCUIT)) {
return Optional.of(new ChargingCircuitDTO(bArray));
} else if (type.equals(DataType.POWER) && callbackType.equals(DataType.POWER)) {
return Optional.of(new PowerDTO(bArray));
} else if (type.equals(DataType.PV) && callbackType.equals(DataType.PV)) {
return Optional.of(new PvDTO(bArray));
} else if (type.equals(DataType.ALARM_STATE) && callbackType.equals(DataType.ALARM_STATE)) {
return Optional.of(new AlarmDTO(bArray));
} else if (type.equals(DataType.WORK_HOURS) && callbackType.equals(DataType.WORK_HOURS)) {
return Optional.of(new WorkHoursDTO(bArray));
}
}
logger.warn("Wrong Block requested. Request is {} but type is {}", type, callbackType);
return Optional.empty();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName()).append(":").append(callbackType);
return sb.toString();
}
}

View File

@ -0,0 +1,99 @@
# thing types
thing-type.modbus.kermi-xcenter.label = Kermi X-Center Heat Pump
thing-type.modbus.kermi-xcenter.description = Provide Energy values, String Details and general Information of your Kermi Heat Pump
# thing types config
thing-type.config.modbus.kermi-xcenter.refresh.label = Refresh Interval
thing-type.config.modbus.kermi-xcenter.refresh.description = Refresh Rate of values in milliseconds
thing-type.config.modbus.kermi-xcenter.pvEnabled.label = Read PV-Modulation States
thing-type.config.modbus.kermi-xcenter.pvEnabled.description = Enabled reading of PV-Modulation (disable to skip reading)
# channel group types
channel-group-type.modbus.xcenter-alarm-values.label = Alarm Values
channel-group-type.modbus.xcenter-alarm-values.description = Alarm State of your Kermi Heatpump X-Center
channel-group-type.modbus.xcenter-charging-circuit-values.label = Charging Circuit
channel-group-type.modbus.xcenter-charging-circuit-values.description = Information about the charging circuit
channel-group-type.modbus.xcenter-energy-source-values.label = Energy-Source
channel-group-type.modbus.xcenter-energy-source-values.description = Information about the energy-source
channel-group-type.modbus.xcenter-power-values.label = Power and efficency
channel-group-type.modbus.xcenter-power-values.description = Information about power and efficiency
channel-group-type.modbus.xcenter-pv-modulation-values.label = PV Modulation
channel-group-type.modbus.xcenter-pv-modulation-values.description = PV Modulation Information and Settings for Kermi Heatpump X-Center
channel-group-type.modbus.xcenter-state-values.label = Status
channel-group-type.modbus.xcenter-state-values.description = Basic Information and State of your Kermi Heatpump X-Center
channel-group-type.modbus.xcenter-workhours-values.label = Workhours
channel-group-type.modbus.xcenter-workhours-values.description = Basic Information of workhours
# channel types
channel-type.modbus.alarm-state-channel.label = Alarm State
channel-type.modbus.alarm-state-channel.description = Current alarm-state of your heat pump
channel-type.modbus.flow-temperature-channel.label = Flow temperature
channel-type.modbus.flow-temperature-channel.description = B16 - Vorlauftemperatur WP
channel-type.modbus.return-temperature-channel.label = Return temperature
channel-type.modbus.return-temperature-channel.description = B17 - Rücklauftemperatur WP
channel-type.modbus.flow-speed-channel.label = Flow speed
channel-type.modbus.flow-speed-channel.description = P13 - Durchfluss WP
channel-type.modbus.exit-temperature-channel.label = Exit temperature
channel-type.modbus.exit-temperature-channel.description = B14 - Energiequelle Austrittstemperatur
channel-type.modbus.incoming-temperature-channel.label = Incoming temperature
channel-type.modbus.incoming-temperature-channel.description = B15 - Energiequelle Eintrittstemperatur
channel-type.modbus.temperature-sensor-outside-channel.label = Temperature Outside
channel-type.modbus.temperature-sensor-outside-channel.description = BOT - Außentemperaturfühler
channel-type.modbus.cop-channel.label = Current COP
channel-type.modbus.cop-channel.description = Current COP of your installation
channel-type.modbus.cop-heating-channel.label = Current COP heating
channel-type.modbus.cop-heating-channel.description =
channel-type.modbus.cop-drinkingwater-channel.label = Current COP drinking water
channel-type.modbus.cop-drinkingwater-channel.description =
channel-type.modbus.cop-cooling-channel.label = Current COP cooling
channel-type.modbus.cop-cooling-channel.description =
channel-type.modbus.power-channel.label = Current power
channel-type.modbus.power-channel.description =
channel-type.modbus.power-heating-channel.label = Current power heating
channel-type.modbus.power-heating-channel.description =
channel-type.modbus.power-drinkingwater-channel.label = Current power drinking water
channel-type.modbus.power-drinkingwater-channel.description =
channel-type.modbus.power-cooling-channel.label = Current power cooling
channel-type.modbus.power-cooling-channel.description =
channel-type.modbus.electric-power-channel.label = Current electric power
channel-type.modbus.electric-power-channel.description =
channel-type.modbus.electric-power-heating-channel.label = Current electric power heating
channel-type.modbus.electric-power-heating-channel.description =
channel-type.modbus.electric-power-drinkingwater-channel.label = Current electric power drinking water
channel-type.modbus.electric-power-drinkingwater-channel.description =
channel-type.modbus.electric-power-cooling-channel.label = Current electric power cooling
channel-type.modbus.electric-power-cooling-channel.description =
channel-type.modbus.pv-state-channel.label = PV Modulation Active
channel-type.modbus.pv-state-channel.description = State of PV-Modulation
channel-type.modbus.pv-power-channel.label = PV Power
channel-type.modbus.pv-power-channel.description =
channel-type.modbus.pv-target-temperature-heating-channel.label = PV Temp Heating
channel-type.modbus.pv-target-temperature-heating-channel.description =
channel-type.modbus.pv-target-temperature-drinkingwater-channel.label = PV Temp Drinkingwater
channel-type.modbus.pv-target-temperature-drinkingwater-channel.description =
channel-type.modbus.global-state-id-channel.label = Global State
channel-type.modbus.global-state-id-channel.description = Current mode of your heat pump
channel-type.modbus.global-alarm-channel.label = Global Alarm State
channel-type.modbus.global-alarm-channel.description = If there's a global alarm
channel-type.modbus.workhours-fan-channel.label = Fan Workhours
channel-type.modbus.workhours-fan-channel.description = Work hours of the fan
channel-type.modbus.workhours-storage-loading-pump-channel.label = StorageLoadingPump Workhours
channel-type.modbus.workhours-storage-loading-pump-channel.description = Work hours of the storage loading pump
channel-type.modbus.workhours-compressor-channel.label = Compressor Workhours
channel-type.modbus.workhours-compressor-channel.description = Work hours of the compressor

View File

@ -0,0 +1,35 @@
<?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">
<bridge-type id="kermi-xcenter">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Kermi Heat Pump X-Center</label>
<description>Provide Energy values, String Details and general Information of your Kermi Heat Pump</description>
<channel-groups>
<channel-group id="xcenter-state" typeId="xcenter-state-values"/>
<channel-group id="xcenter-energy-source" typeId="xcenter-energy-source-values"/>
<channel-group id="xcenter-charging-circuit" typeId="xcenter-charging-circuit-values"/>
<channel-group id="xcenter-power" typeId="xcenter-power-values"/>
<channel-group id="xcenter-workhours" typeId="xcenter-workhours-values"/>
<channel-group id="xcenter-alarm" typeId="xcenter-alarm-values"/>
<channel-group id="xcenter-pv-modulation" typeId="xcenter-pv-modulation-values"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="1000" unit="ms">
<label>Refresh Interval</label>
<description>Refresh Rate of X-Center values in Milliseconds</description>
<default>5000</default>
<advanced>true</advanced>
</parameter>
<parameter name="pvEnabled" type="boolean">
<label>Read PV-Modulation States</label>
<description>Enabled reading of PV-Modulation (disable to skip reading)</description>
<default>false</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,13 @@
<?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">
<channel-group-type id="xcenter-alarm-values">
<label>Alarm</label>
<description>Alarm State of your Kermi Heatpump X-Center</description>
<channels>
<channel id="alarm-state" typeId="alarm-state-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,11 @@
<?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">
<channel-type id="alarm-state-channel">
<item-type>Contact</item-type>
<label>Alarm State</label>
<description>Current alarm-state of your heat pump</description>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,15 @@
<?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">
<channel-group-type id="xcenter-charging-circuit-values">
<label>Charging Circuit</label>
<description>Information about the charging circuit</description>
<channels>
<channel id="flow-temperature" typeId="flow-temperature-channel"/>
<channel id="return-temperature" typeId="return-temperature-channel"/>
<channel id="flow-speed" typeId="flow-speed-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,24 @@
<?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">
<channel-type id="flow-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Flow Temperature</label>
<description>B16 - Flow Temperature</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="return-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Return Temperature</label>
<description>B17 - Return Temperature</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="flow-speed-channel">
<item-type>Number:VolumetricFlowRate</item-type>
<label>Flow Speed</label>
<description>P13 - Flow speed</description>
<state pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,15 @@
<?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">
<channel-group-type id="xcenter-energy-source-values">
<label>Energy-Source</label>
<description>Information about the energy-source</description>
<channels>
<channel id="exit-temperature" typeId="exit-temperature-channel"/>
<channel id="incoming-temperature" typeId="incoming-temperature-channel"/>
<channel id="temperature-sensor-outside" typeId="temperature-sensor-outside-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,24 @@
<?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">
<channel-type id="exit-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Exit Temperature</label>
<description>B14 - Exit Temperature Energy Source</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="incoming-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Incoming temperature</label>
<description>B15 - Incoming Temperature Energy Source</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperature-sensor-outside-channel">
<item-type>Number:Temperature</item-type>
<label>Temperature Outside</label>
<description>BOT - Sensor Outside Temperature</description>
<state pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,24 @@
<?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">
<channel-group-type id="xcenter-power-values">
<label>Power and Efficiency</label>
<description>Information about power and efficiency</description>
<channels>
<channel id="cop" typeId="cop-channel"/>
<channel id="cop-heating" typeId="cop-heating-channel"/>
<channel id="cop-drinkingwater" typeId="cop-drinkingwater-channel"/>
<channel id="cop-cooling" typeId="cop-cooling-channel"/>
<channel id="power" typeId="power-channel"/>
<channel id="power-heating" typeId="power-heating-channel"/>
<channel id="power-drinkingwater" typeId="power-drinkingwater-channel"/>
<channel id="power-cooling" typeId="power-cooling-channel"/>
<channel id="electric-power" typeId="electric-power-channel"/>
<channel id="electric-power-heating" typeId="electric-power-heating-channel"/>
<channel id="electric-power-drinkingwater" typeId="electric-power-drinkingwater-channel"/>
<channel id="electric-power-cooling" typeId="electric-power-cooling-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,68 @@
<?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">
<channel-type id="cop-channel">
<item-type>Number</item-type>
<label>Current COP</label>
<state pattern="%d"/>
</channel-type>
<channel-type id="cop-heating-channel">
<item-type>Number</item-type>
<label>Current COP Heating</label>
<state pattern="%d"/>
</channel-type>
<channel-type id="cop-drinkingwater-channel">
<item-type>Number</item-type>
<label>Current COP drinking water</label>
<state pattern="%d"/>
</channel-type>
<channel-type id="cop-cooling-channel">
<item-type>Number</item-type>
<label>Current COP Cooling</label>
<state pattern="%d"/>
</channel-type>
<channel-type id="power-channel">
<item-type>Number:Power</item-type>
<label>Current Power</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="power-heating-channel">
<item-type>Number:Power</item-type>
<label>Current power heating</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="power-drinkingwater-channel">
<item-type>Number:Power</item-type>
<label>Current Power Drinking Water</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="power-cooling-channel">
<item-type>Number:Power</item-type>
<label>Current power cooling</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="electric-power-channel">
<item-type>Number:Power</item-type>
<label>Current Electric Power</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="electric-power-heating-channel">
<item-type>Number:Power</item-type>
<label>Current Electric Power Heating</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="electric-power-drinkingwater-channel">
<item-type>Number:Power</item-type>
<label>Current electric power drinking water</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="electric-power-cooling-channel">
<item-type>Number:Power</item-type>
<label>Current Electric Power Cooling</label>
<state pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,18 @@
<?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">
<channel-group-type id="xcenter-pv-modulation-values">
<label>PV Modulation</label>
<description>PV Modulation Information and Settings for Kermi Heatpump X-Center</description>
<channels>
<channel id="pv-state" typeId="pv-state-channel"/>
<channel id="pv-power" typeId="pv-power-channel"/>
<!-- R/W -->
<channel id="pv-target-temperature-heating" typeId="pv-target-temperature-heating-channel"/>
<!-- R/W -->
<channel id="pv-target-temperature-drinkingwater" typeId="pv-target-temperature-drinkingwater-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,26 @@
<?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">
<channel-type id="pv-state-channel">
<item-type>Switch</item-type>
<label>PV Modulation Active</label>
<description>State of PV-Modulation</description>
</channel-type>
<channel-type id="pv-power-channel">
<item-type>Number:Power</item-type>
<label>PV Power</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="pv-target-temperature-heating-channel">
<item-type>Number:Temperature</item-type>
<label>PV Temp Heating</label>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="pv-target-temperature-drinkingwater-channel">
<item-type>Number:Temperature</item-type>
<label>PV Temp Drinkingwater</label>
<state pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,13 @@
<?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">
<channel-group-type id="xcenter-state-values">
<label>Status</label>
<description>Basic Information and State of your Kermi Heatpump X-Center</description>
<channels>
<channel id="global-state-id" typeId="global-state-id-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,29 @@
<?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">
<channel-type id="global-state-id-channel">
<item-type>Number</item-type>
<label>Global State</label>
<description>Current state of your heat pump</description>
<state readOnly="true" pattern="%d">
<options>
<option value="0">Standby</option>
<option value="1">Alarm</option>
<option value="2">DrinkingWater</option>
<option value="4">Heating</option>
<option value="5">Defrost</option>
<option value="6">Preparing</option>
<option value="7">Blocked</option>
<option value="8">EVU Blocktime</option>
<option value="9">Unavailable</option>
</options>
</state>
</channel-type>
<channel-type id="global-alarm-channel">
<item-type>Contact</item-type>
<label>Global Alarm State</label>
<description>If there's a global alarm</description>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,15 @@
<?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">
<channel-group-type id="xcenter-workhours-values">
<label>Workhours</label>
<description>Basic Information of workhours</description>
<channels>
<channel id="workhours-fan" typeId="workhours-fan-channel"/>
<channel id="workhours-storage-loading-pump" typeId="workhours-storage-loading-pump-channel"/>
<channel id="workhours-compressor" typeId="workhours-compressor-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,24 @@
<?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">
<channel-type id="workhours-fan-channel">
<item-type unitHint="h">Number:Time</item-type>
<label>Fan Workhours</label>
<description>Work hours of the fan</description>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="workhours-storage-loading-pump-channel">
<item-type unitHint="h">Number:Time</item-type>
<label>StorageLoadingPump Workhours</label>
<description>Work hours of the storage loading pump</description>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="workhours-compressor-channel">
<item-type unitHint="h">Number:Time</item-type>
<label>Compressor Workhours</label>
<description>Work hours of the compressor</description>
<state pattern="%d %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,34 @@
/**
* 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.kermi.internal.dto;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/**
* Test for {@link StateDTO} class
*
* @author Kai Neuhaus - Initial contribution
*/
public class StateDTOTest {
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
}

View File

@ -265,6 +265,7 @@
<module>org.openhab.binding.minecraft</module>
<module>org.openhab.binding.modbus</module>
<module>org.openhab.binding.modbus.e3dc</module>
<module>org.openhab.binding.modbus.kermi</module>
<module>org.openhab.binding.modbus.sbc</module>
<module>org.openhab.binding.modbus.studer</module>
<module>org.openhab.binding.modbus.sungrow</module>