added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.regoheatpump</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,238 @@
# RegoHeatPump Binding
The Rego heat pump binding supports:
* Rego 6xx controllers family and
* Husdata interface.
## The Rego 6xx family
The Rego 6xx controllers family is used in many heat pumps such as IVT/Bosch/Autotherm/Carrier and others.
Rego 6xx unit contain an interface marked as service.
Header of this interface is close to the control unit. This is 5V (TTL) serial interface and is connected by a 9 pin can/d-sub connector. Pinout:
2 - RxD
3 - TxD
4 - +5V
5 - GND
Serial communication is using 19200 bps, 8 bit, no parity, 1 stop bit.
### Thing configuration
Two connection types are supported:
* TCP/IP and
* serial (RS232).
#### TCP/IP connection
A transparent bridge between the serial interface of the heat pump and network (i.e. wifi) is used.
This way no additional wires are required between heat pump and computer, running openHAB.
There are many existing project providing such functionality, i.e. [ser2net](http://ser2net.sourceforge.net/).
For my setup, I used a low budget (~5€) circuit, that is integrated into the heat pump and connects to a wifi using an ESP8266 based module.
Board:
![board](doc/board.png)
The code running on the ESP module can be found [here](https://github.com/crnjan/esp8266-bridge).
There are other projects providing ESP firmware with similar functionality, i.e. [ESP-LINK](https://github.com/jeelabs/esp-link), but did not test with those.
Configuration of the TCP/IP thing:
- address: the hostname/IP address of the transparent bridge on the local network - mandatory,
- tcpPort: the port number to use to connect to the transparent bridge - optional, defaults to 9265,
- refreshInterval: refresh interval in seconds, used to fetch new values from the heat pump - optional, defaults to 60 seconds.
Example thing definition:
```
regoheatpump:ipRego6xx:ivtIP [ address="192.168.2.50", tcpPort="9265" ]
```
#### Serial connection
In order to connect directly to the rego 6xx controller, one needs to adjust the TTL levels coming from the rego unit to levels used by a RS232 serial port, used within computers, using MAX232 or similar.
Parameters:
- portName: the name of the serial port on your computer - mandatory,
- refreshInterval: refresh interval in seconds, used to fetch new values from the heat pump - optional, defaults to 60 seconds.
Example thing definition:
```
regoheatpump:serialRego6xx:ivtSerial [ portName="COM3" ]
```
### Channels
Below is the list of supported channels:
| Channel Type ID | Item Type | Access |
|---------------------------------------|-------------|--------|
| sensorValues#radiatorReturn | Temperature | R |
| sensorValues#outdoor | Temperature | R |
| sensorValues#hotWater | Temperature | R |
| sensors#radiatorForward | Temperature | R |
| sensorValues#indoor | Temperature | R |
| sensorValues#compressor | Temperature | R |
| sensorValues#heatFluidOut | Temperature | R |
| sensorValues#heatFluidIn | Temperature | R |
| sensorValues#coldFluidIn | Temperature | R |
| sensorValues#coldFluidOut | Temperature | R |
| sensorValues#externalHotWater | Temperature | R |
| status#lastErrorTimestamp | DateTime | R |
| status#lastErrorType | String | R |
| frontPanel#powerLamp | Switch | R |
| frontPanel#heatPumpLamp | Switch | R |
| frontPanel#additionalHeatLamp | Switch | R |
| frontPanel#hotWaterLamp | Switch | R |
| frontPanel#alarmLamp | Switch | R |
| controlData#radiatorReturnTarget | Temperature | R |
| controlData#radiatorReturnOn | Temperature | R |
| controlData#radiatorReturnOff | Temperature | R |
| controlData#hotWaterOn | Temperature | R |
| controlData#hotWaterOff | Temperature | R |
| controlData#radiatorForwardTarget | Temperature | R |
| controlData#addHeatPower | Number (%) | R |
| deviceValues#coldFluidPump | Switch | R |
| deviceValues#compressor | Switch | R |
| deviceValues#additionalHeat3kW | Switch | R |
| deviceValues#additionalHeat6kW | Switch | R |
| deviceValues#radiatorPump | Switch | R |
| deviceValues#heatFluidPump | Switch | R |
| deviceValues#switchValue | Switch | R |
| deviceValues#alarm | Switch | R |
| settings#hotWaterTarget | Temperature | RW |
| settings#hotWaterTargetHysteresis | Temperature | RW |
| settings#heatCurve | Number | RW |
| settings#heatCurveFineAdj | Temperature | RW |
| settings#heatCurve2 | Number | RW |
| settings#heatCurve2FineAdj | Temperature | RW |
| settings#indoorTempSetting | Temperature | RW |
| settings#curveInflByInTemp | Number | RW |
| settings#adjCurveAt20 | Temperature | RW |
| settings#adjCurveAt15 | Temperature | RW |
| settings#adjCurveAt10 | Temperature | RW |
| settings#adjCurveAt5 | Temperature | RW |
| settings#adjCurveAt0 | Temperature | RW |
| settings#adjCurveAtMinus5 | Temperature | RW |
| settings#adjCurveAtMinus10 | Temperature | RW |
| settings#adjCurveAtMinus15 | Temperature | RW |
| settings#adjCurveAtMinus20 | Temperature | RW |
| settings#adjCurveAtMinus25 | Temperature | RW |
| settings#adjCurveAtMinus30 | Temperature | RW |
| settings#adjCurveAtMinus35 | Temperature | RW |
| settings#heatCurveCouplingDiff | Temperature | RW |
| settings#summerDisconnection | Temperature | RW |
| operatingTimes#heatPumpInOperationRAD | Hours | R |
| operatingTimes#heatPumpInOperationDHW | Hours | R |
| operatingTimes#addHeatInOperationRAD | Hours | R |
| operatingTimes#addHeatInOperationDHW | Hours | R |
Access: R = read only; RW = read write
**Note - breaking change:** to have all writable channels within the settings group, hotWaterTarget channel was moved from controlData to the setting group.
## The Husdata interface
The [Husdata](https://www.husdata.se/) interface bridges the often complex communication methods with a heat pump controller and provides access through a simple standard interface over RS-232.
Supported heat pump models
| Heat pump models | Technical |
|-----------------------------------|--------------------|
| IVT Greenline / Optima 900 | Rego 600 Serial |
| IVT 490 | Rego 400 Serial |
| IVT Premiumline X, Optima/290-AW | Rego 800, Can bus |
| IVT Greenline HE/HC/HA+Prem HQ/EQ | Rego 1000, Can bus |
| NIBE xx45 | EB100, RS-485 |
| NIBE Fighter series | Styr 2002, RS-485 |
| Thermia Diplomat series | 901510, i2c |
Above list is informational, please consult with the Husdata interface provider for further details.
### Thing configuration
Two connection types are supported:
* TCP/IP and
* serial (RS232).
#### TCP/IP connection
A transparent bridge between the Husdata interface and network (i.e. wifi) is used.
There are many existing project providing such functionality, i.e. [ser2net](http://ser2net.sourceforge.net/).
Configuration of the TCP/IP thing:
- address: the hostname/IP address of the transparent bridge on the local network - mandatory,
- tcpPort: the port number to use to connect to the transparent bridge - optional, defaults to 9265.
Example thing definition:
```
regoheatpump:ipHusdata:ivtIP [ address="192.168.2.50", tcpPort="9265" ]
```
#### Serial connection
One can connect the Husdata interface directly to a computer that runs openHAB.
Parameters:
- portName: the name of the serial port on your computer - mandatory.
Example thing definition:
```
regoheatpump:serialHusdata:ivtSerial [ portName="COM3" ]
```
### Channels
Below is the list of supported channels, all values are read only:
| H1 ID | Name | Channel Type ID | Item Type |
|-------|-----------------------|----------------------------------|------------------|
| 001 | Radiator Return | sensorValues#radiatorReturn | Temperature |
| 002 | Radiator Forward | sensorValues#radiatorForward | Temperature |
| 003 | Heat carrier Return | sensorValues#heatFluidIn | Temperature |
| 004 | Heat carrier Forward | sensorValues#heatFluidOut | Temperature |
| 005 | Brine In / Evaporator | sensorValues#coldFluidIn | Temperature |
| 006 | Brine Out / Condenser | sensorValues#coldFluidOut | Temperature |
| 007 | Outdoor | sensorValues#outdoor | Temperature |
| 008 | Indoor | sensorValues#indoor | Temperature |
| 009 | Hot water 1 / Top | sensorValues#hotWater | Temperature |
| 00A | Hot water 2 / Mid | sensorValues#externalHotWater | Temperature |
| 00B | Hot gas / Compressor | sensorValues#compressor | Temperature |
| 00E | Air intake | sensorValues#airIntake | Temperature |
| 011 | Pool | sensorValues#pool | Temperature |
| 104 | Add heat status | controlData#addHeatPowerPercent | Number - % |
| 104 | Add heat status | controlData#addHeatPowerEnergy | Number - kW |
| 107 | Heating setpoint | controlData#radiatorReturnTarget | Temperature |
| 108 | Compressor speed | controlData#compressorSpeed | Number - % |
| 203 | Room temp setpoint | settings#indoorTempSetting | Temperature |
| 204 | Room sensor influence | settings#curveInflByInTemp | Number |
| 205 | Heat set 1, CurveL | settings#heatCurve | Number |
| 206 | Heat set 2, CurveR | settings#heatCurve2 | Number |
| A01 | Compressor | deviceValues#compressor | Switch |
| A04 | Pump Cold circuit | deviceValues#coldFluidPump | Switch |
| A05 | Pump Heat circuit | deviceValues#heatFluidPump | Switch |
| A06 | Pump Radiator | deviceValues#radiatorPump | Switch |
| A07 | Switch valve 1 | deviceValues#switchValve | Switch |
| A08 | Switch valve 2 | deviceValues#switchValve2 | Switch |
| A09 | Fan | deviceValues#fan | Switch |
| A0A | High Pressostat | deviceValues#highPressostat | Switch |
| A0B | Low Pressostat | deviceValues#lowPressostat | Switch |
| A0C | Heating cable | deviceValues#heatingCable | Switch |
| A0D | Crank case heater | deviceValues#crankCaseHeater | Switch |
| A20 | Alarm | deviceValues#alarm | Switch |
| FF1 | EL-Meter 1 | deviceValues#elMeter1 | Number - pulses |
| FF2 | EL-Meter 2 | deviceValues#elMeter2 | Number - pulses |

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,17 @@
<?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 http://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>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.regoheatpump</artifactId>
<name>openHAB Add-ons :: Bundles :: RegoHeatPump Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.regoheatpump-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-regoheatpump" description="RegoHeatPump Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.regoheatpump/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link RegoHeatPumpBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Boris Krivonog - Initial contribution
*/
@NonNullByDefault
public class RegoHeatPumpBindingConstants {
public static final String BINDING_ID = "regoheatpump";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_IP_REGO6XX = new ThingTypeUID(BINDING_ID, "ipRego6xx");
public static final ThingTypeUID THING_TYPE_SERIAL_REGO6XX = new ThingTypeUID(BINDING_ID, "serialRego6xx");
public static final ThingTypeUID THING_TYPE_IP_HUSDATA = new ThingTypeUID(BINDING_ID, "ipHusdata");
public static final ThingTypeUID THING_TYPE_SERIAL_HUSDATA = new ThingTypeUID(BINDING_ID, "serialHusdata");
// List of all Channel ids
public static final String CHANNEL_GROUP_SENSOR_VALUES = "sensorValues#";
public static final String CHANNEL_GROUP_CONTROL_DATA = "controlData#";
public static final String CHANNEL_GROUP_DEVICE_VALUES = "deviceValues#";
public static final String CHANNEL_GROUP_SETTINGS = "settings#";
public static final String CHANNEL_GROUP_OPERATING_TIMES = "operatingTimes#";
public static final String CHANNEL_LAST_ERROR = "status#lastError";
public static final String CHANNEL_LAST_ERROR_TIMESTAMP = CHANNEL_LAST_ERROR + "Timestamp";
public static final String CHANNEL_LAST_ERROR_TYPE = CHANNEL_LAST_ERROR + "Type";
public static final String CHANNEL_FRONT_PANEL_POWER_LAMP = "frontPanel#powerLamp";
public static final String CHANNEL_FRONT_PANEL_PUMP_LAMP = "frontPanel#heatPumpLamp";
public static final String CHANNEL_FRONT_PANEL_ADDITIONAL_HEAT_LAMP = "frontPanel#additionalHeatLamp";
public static final String CHANNEL_FRONT_PANEL_WATER_HEATER_LAMP = "frontPanel#hotWaterLamp";
public static final String CHANNEL_FRONT_PANEL_ALARM_LAMP = "frontPanel#alarmLamp";
public static final String REFRESH_INTERVAL = "refreshInterval";
// TCP/IP thing
public static final String HOST_PARAMETER = "address";
public static final String TCP_PORT_PARAMETER = "tcpPort";
// Serial thing
public static final String PORT_NAME = "portName";
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal;
import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.regoheatpump.internal.handler.IpHusdataHandler;
import org.openhab.binding.regoheatpump.internal.handler.IpRego6xxHeatPumpHandler;
import org.openhab.binding.regoheatpump.internal.handler.SerialHusdataHandler;
import org.openhab.binding.regoheatpump.internal.handler.SerialRego6xxHeatPumpHandler;
import org.openhab.core.io.transport.serial.SerialPortManager;
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.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link RegoHeatPumpHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Boris Krivonog - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.regoheatpump")
public class RegoHeatPumpHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_IP_REGO6XX, THING_TYPE_SERIAL_REGO6XX, THING_TYPE_IP_HUSDATA, THING_TYPE_SERIAL_HUSDATA)
.collect(Collectors.toSet()));
private final SerialPortManager serialPortManager;
@Activate
public RegoHeatPumpHandlerFactory(final @Reference SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
@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 (thingTypeUID.equals(THING_TYPE_IP_REGO6XX)) {
return new IpRego6xxHeatPumpHandler(thing);
}
if (thingTypeUID.equals(THING_TYPE_SERIAL_REGO6XX)) {
return new SerialRego6xxHeatPumpHandler(thing, serialPortManager);
}
if (thingTypeUID.equals(THING_TYPE_IP_HUSDATA)) {
return new IpHusdataHandler(thing);
}
if (thingTypeUID.equals(THING_TYPE_SERIAL_HUSDATA)) {
return new SerialHusdataHandler(thing, serialPortManager);
}
return null;
}
}

View File

@@ -0,0 +1,284 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HusdataHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
abstract class HusdataHandler extends BaseThingHandler {
private static final Map<Integer, String> MAPPINGS;
private final Logger logger = LoggerFactory.getLogger(HusdataHandler.class);
private RegoConnection connection;
private ScheduledFuture<?> scheduledRefreshFuture;
private BufferedReader bufferedReader;
static {
MAPPINGS = mappings();
}
protected HusdataHandler(Thing thing) {
super(thing);
}
protected abstract RegoConnection createConnection();
@Override
public void initialize() {
bufferedReader = null;
connection = createConnection();
updateStatus(ThingStatus.UNKNOWN);
scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::handleDataFromHusdataInterface, 2, 1,
TimeUnit.SECONDS);
}
@Override
public void dispose() {
super.dispose();
if (scheduledRefreshFuture != null) {
scheduledRefreshFuture.cancel(true);
scheduledRefreshFuture = null;
}
if (connection != null) {
connection.close();
connection = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
private synchronized void handleDataFromHusdataInterface() {
RegoConnection connection = this.connection;
if (connection == null) {
return;
}
while (!Thread.interrupted()) {
try {
if (!connection.isConnected()) {
bufferedReader = null;
connection.connect();
// Request real-time registers.
logger.debug("Requesting read and dump of real-time registers.");
final OutputStream outputStream = connection.outputStream();
outputStream.write(new String("XR\r\n").getBytes());
outputStream.flush();
}
if (bufferedReader == null) {
bufferedReader = new BufferedReader(new InputStreamReader(connection.inputStream()));
}
final String line = bufferedReader.readLine();
if (line == null) {
throw new EOFException();
}
if (line.isEmpty()) {
continue;
}
logger.debug("Got '{}'", line);
processReceivedData(line);
} catch (SocketTimeoutException e) {
// Do nothing. Just happen to allow the thread to check if it has to stop.
break;
} catch (IOException e) {
logger.warn("Processing request failed", e);
bufferedReader = null;
if (!Thread.interrupted()) {
connection.close();
updateStatus(ThingStatus.OFFLINE);
}
break;
} catch (Exception e) {
logger.warn("Error occurred during message waiting", e);
break;
}
}
// If state here is still unknown, than something went wrong so set thing status to OFFLINE.
if (thing.getStatus() == ThingStatus.UNKNOWN) {
updateStatus(ThingStatus.OFFLINE);
}
}
private void processReceivedData(final String line) {
if (line.length() != 10) {
logger.debug("Unexpected length for '{}'", line);
return;
}
if (line.charAt(0) != 'X' || line.charAt(1) != 'R') {
logger.debug("Expecting XRxxxxxxxx but got '{}'", line);
return;
}
int dataType = Integer.parseInt(line.substring(2, 3), 16);
int register = Integer.parseInt(line.substring(3, 6), 16);
int value = (short) (Integer.parseInt(line.substring(6, 8), 16) * 256
+ Integer.parseInt(line.substring(8, 10), 16));
logger.debug("dataType = {}, register = {}, value = {}", dataType, register, value);
updateStatus(ThingStatus.ONLINE);
int channel = ((dataType & 0x0f) << 12) | (register & 0x0fff);
String channelID = MAPPINGS.get(channel);
if (channelID == null) {
logger.debug("Unsupported register {}.", register);
return;
}
if (!isLinked(channelID)) {
logger.debug("Ignoring channel {} since it is not linked.", channelID);
return;
}
switch (dataType) {
case 0x00: // Degrees
updateState(channelID, new QuantityType<>(value / 10.0, SmartHomeUnits.DEGREE_ANGLE));
break;
case 0x02: // Number
updateState(channelID, new DecimalType(value / 10.0));
break;
case 0x03: // Percent
updateState(channelID, new QuantityType<>(value / 10.0, SmartHomeUnits.PERCENT));
break;
case 0x04: // Ampere
updateState(channelID, new QuantityType<>(value / 10.0, SmartHomeUnits.AMPERE));
break;
case 0x05: // kWh
updateState(channelID, new QuantityType<>(value / 10.0, SmartHomeUnits.KILOWATT_HOUR));
break;
case 0x06: // Hours
updateState(channelID, new QuantityType<>(value, SmartHomeUnits.HOUR));
break;
case 0x07: // Minutes
updateState(channelID, new QuantityType<>(value, SmartHomeUnits.MINUTE));
break;
case 0x09: // kw
updateState(channelID, new QuantityType<>(value, MetricPrefix.KILO(SmartHomeUnits.WATT)));
break;
case 0x01: // Switch
case 0x08: // Degree minutes
case 0x0A: // Pulses (For S0 El-meter pulse counter)
updateState(channelID, new DecimalType(value));
break;
default:
logger.debug("Ignoring {} due unsupported data type {}.", channelID, dataType);
break;
}
}
private static Map<Integer, String> mappings() {
final Map<Integer, String> mappings = new HashMap<>();
{
// Sensor values
mappings.put(0x0001, CHANNEL_GROUP_SENSOR_VALUES + "radiatorReturn");
mappings.put(0x0002, CHANNEL_GROUP_SENSOR_VALUES + "radiatorForward");
mappings.put(0x0003, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidIn");
mappings.put(0x0004, CHANNEL_GROUP_SENSOR_VALUES + "heatFluidOut");
mappings.put(0x0005, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidIn");
mappings.put(0x0006, CHANNEL_GROUP_SENSOR_VALUES + "coldFluidOut");
mappings.put(0x0007, CHANNEL_GROUP_SENSOR_VALUES + "outdoor");
mappings.put(0x0008, CHANNEL_GROUP_SENSOR_VALUES + "indoor");
mappings.put(0x0009, CHANNEL_GROUP_SENSOR_VALUES + "hotWater");
mappings.put(0x000A, CHANNEL_GROUP_SENSOR_VALUES + "externalHotWater");
mappings.put(0x000B, CHANNEL_GROUP_SENSOR_VALUES + "compressor");
mappings.put(0x000E, CHANNEL_GROUP_SENSOR_VALUES + "airIntake");
mappings.put(0x0011, CHANNEL_GROUP_SENSOR_VALUES + "pool");
// Control data
mappings.put(0x3104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerPercent"); // %
mappings.put(0x5104, CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerEnergy"); // kWh
mappings.put(0x0107, CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnTarget");
mappings.put(0x2108, CHANNEL_GROUP_CONTROL_DATA + "compressorSpeed");
// Device values
mappings.put(0x1A01, CHANNEL_GROUP_DEVICE_VALUES + "compressor");
mappings.put(0x1A04, CHANNEL_GROUP_DEVICE_VALUES + "coldFluidPump");
mappings.put(0x1A05, CHANNEL_GROUP_DEVICE_VALUES + "heatFluidPump");
mappings.put(0x1A06, CHANNEL_GROUP_DEVICE_VALUES + "radiatorPump");
mappings.put(0x1A07, CHANNEL_GROUP_DEVICE_VALUES + "switchValve");
mappings.put(0x1A08, CHANNEL_GROUP_DEVICE_VALUES + "switchValve2");
mappings.put(0x1A09, CHANNEL_GROUP_DEVICE_VALUES + "fan");
mappings.put(0x1A0A, CHANNEL_GROUP_DEVICE_VALUES + "highPressostat");
mappings.put(0x1A0B, CHANNEL_GROUP_DEVICE_VALUES + "lowPressostat");
mappings.put(0x1A0C, CHANNEL_GROUP_DEVICE_VALUES + "heatingCable");
mappings.put(0x1A0D, CHANNEL_GROUP_DEVICE_VALUES + "crankCaseHeater");
mappings.put(0x1A20, CHANNEL_GROUP_DEVICE_VALUES + "alarm");
mappings.put(0xAFF1, CHANNEL_GROUP_DEVICE_VALUES + "elMeter1");
mappings.put(0xAFF2, CHANNEL_GROUP_DEVICE_VALUES + "elMeter2");
// Settings
mappings.put(0x0203, CHANNEL_GROUP_SETTINGS + "indoorTempSetting");
mappings.put(0x2204, CHANNEL_GROUP_SETTINGS + "curveInflByInTemp");
mappings.put(0x0205, CHANNEL_GROUP_SETTINGS + "heatCurve");
mappings.put(0x0206, CHANNEL_GROUP_SETTINGS + "heatCurve2");
mappings.put(0x0207, CHANNEL_GROUP_SETTINGS + "heatCurveFineAdj");
}
return Collections.unmodifiableMap(mappings);
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants;
import org.openhab.binding.regoheatpump.internal.protocol.IpRegoConnection;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.core.thing.Thing;
/**
* The {@link IpHusdataHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
public class IpHusdataHandler extends HusdataHandler {
public IpHusdataHandler(Thing thing) {
super(thing);
}
@Override
protected RegoConnection createConnection() {
String host = (String) getConfig().get(RegoHeatPumpBindingConstants.HOST_PARAMETER);
Integer port = ((Number) getConfig().get(RegoHeatPumpBindingConstants.TCP_PORT_PARAMETER)).intValue();
return new IpRegoConnection(host, port);
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants;
import org.openhab.binding.regoheatpump.internal.protocol.IpRegoConnection;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.core.thing.Thing;
/**
* The {@link IpRego6xxHeatPumpHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
public class IpRego6xxHeatPumpHandler extends Rego6xxHeatPumpHandler {
public IpRego6xxHeatPumpHandler(Thing thing) {
super(thing);
}
@Override
protected RegoConnection createConnection() {
String host = (String) getConfig().get(RegoHeatPumpBindingConstants.HOST_PARAMETER);
Integer port = ((Number) getConfig().get(RegoHeatPumpBindingConstants.TCP_PORT_PARAMETER)).intValue();
return new IpRegoConnection(host, port);
}
}

View File

@@ -0,0 +1,413 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.measure.Unit;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.binding.regoheatpump.internal.rego6xx.CommandFactory;
import org.openhab.binding.regoheatpump.internal.rego6xx.ErrorLine;
import org.openhab.binding.regoheatpump.internal.rego6xx.Rego6xxProtocolException;
import org.openhab.binding.regoheatpump.internal.rego6xx.RegoRegisterMapper;
import org.openhab.binding.regoheatpump.internal.rego6xx.ResponseParser;
import org.openhab.binding.regoheatpump.internal.rego6xx.ResponseParserFactory;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
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.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Rego6xxHeatPumpHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
abstract class Rego6xxHeatPumpHandler extends BaseThingHandler {
private static final class ChannelDescriptor {
private Date lastUpdate;
private byte[] cachedValue;
public byte[] cachedValueIfNotExpired(int refreshTime) {
if (lastUpdate == null || (lastUpdate.getTime() + refreshTime * 900 < new Date().getTime())) {
return null;
}
return cachedValue;
}
public void setValue(byte[] value) {
lastUpdate = new Date();
cachedValue = value;
}
}
private final Logger logger = LoggerFactory.getLogger(Rego6xxHeatPumpHandler.class);
private final Map<String, ChannelDescriptor> channelDescriptors = new HashMap<>();
private int refreshInterval;
private RegoConnection connection;
private RegoRegisterMapper mapper;
private ScheduledFuture<?> scheduledRefreshFuture;
protected Rego6xxHeatPumpHandler(Thing thing) {
super(thing);
}
protected abstract RegoConnection createConnection();
@Override
public void initialize() {
mapper = RegoRegisterMapper.REGO600;
refreshInterval = ((Number) getConfig().get(REFRESH_INTERVAL)).intValue();
connection = createConnection();
scheduledRefreshFuture = scheduler.scheduleWithFixedDelay(this::refresh, 2, refreshInterval, TimeUnit.SECONDS);
updateStatus(ThingStatus.UNKNOWN);
}
@Override
public void dispose() {
super.dispose();
if (connection != null) {
connection.close();
}
if (scheduledRefreshFuture != null) {
scheduledRefreshFuture.cancel(true);
scheduledRefreshFuture = null;
}
synchronized (channelDescriptors) {
channelDescriptors.clear();
}
connection = null;
mapper = null;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
processChannelReadRequest(channelUID.getId());
} else {
RegoRegisterMapper.Channel channel = mapper.map(channelUID.getId());
if (channel != null) {
logger.debug("Executing command '{}' for channel '{}'", command, channelUID.getId());
processChannelWriteRequest(channel, command);
} else {
logger.debug("Unsupported channel {}", channelUID.getId());
}
}
}
private static double commandToValue(Command command) {
if (command instanceof QuantityType<?>) {
return ((QuantityType<?>) command).doubleValue();
}
if (command instanceof DecimalType) {
return ((DecimalType) command).doubleValue();
}
throw new NumberFormatException("Command '" + command + "' not supported");
}
private void processChannelWriteRequest(RegoRegisterMapper.Channel channel, Command command) {
short value = (short) Math.round(commandToValue(command) / channel.scaleFactor());
byte[] commandPayload = CommandFactory.createWriteToSystemRegisterCommand(channel.address(), value);
executeCommand(null, commandPayload, ResponseParserFactory.WRITE, result -> {
// Ignore result since it is a write command.
});
}
private void processChannelReadRequest(String channelIID) {
switch (channelIID) {
case CHANNEL_LAST_ERROR_TYPE:
readAndUpdateLastErrorType();
break;
case CHANNEL_LAST_ERROR_TIMESTAMP:
readAndUpdateLastErrorTimestamp();
break;
case CHANNEL_FRONT_PANEL_POWER_LAMP:
readAndUpdateFrontPanel(channelIID, (short) 0x0012);
break;
case CHANNEL_FRONT_PANEL_PUMP_LAMP:
readAndUpdateFrontPanel(channelIID, (short) 0x0013);
break;
case CHANNEL_FRONT_PANEL_ADDITIONAL_HEAT_LAMP:
readAndUpdateFrontPanel(channelIID, (short) 0x0014);
break;
case CHANNEL_FRONT_PANEL_WATER_HEATER_LAMP:
readAndUpdateFrontPanel(channelIID, (short) 0x0015);
break;
case CHANNEL_FRONT_PANEL_ALARM_LAMP:
readAndUpdateFrontPanel(channelIID, (short) 0x0016);
break;
default:
readAndUpdateSystemRegister(channelIID);
break;
}
}
private Collection<String> linkedChannels() {
return thing.getChannels().stream().map(Channel::getUID).map(ChannelUID::getId).filter(this::isLinked)
.collect(Collectors.toList());
}
private void refresh() {
for (String channelIID : linkedChannels()) {
if (Thread.interrupted()) {
break;
}
processChannelReadRequest(channelIID);
if (thing.getStatus() != ThingStatus.ONLINE) {
break;
}
}
}
private void readAndUpdateLastErrorType() {
readAndUpdateLastError(CHANNEL_LAST_ERROR_TYPE, e -> new StringType(Byte.toString(e.error())));
}
private void readAndUpdateLastErrorTimestamp() {
readAndUpdateLastError(CHANNEL_LAST_ERROR_TIMESTAMP, e -> new DateTimeType(e.timestamp()));
}
private void readAndUpdateLastError(String channelIID, Function<ErrorLine, State> converter) {
executeCommandAndUpdateState(channelIID, CommandFactory.createReadLastErrorCommand(),
ResponseParserFactory.ERROR_LINE, e -> {
return e == null ? UnDefType.NULL : converter.apply(e);
});
}
private void readAndUpdateFrontPanel(String channelIID, short address) {
byte[] command = CommandFactory.createReadFromFrontPanelCommand(address);
executeCommandAndUpdateState(channelIID, command, ResponseParserFactory.SHORT, DecimalType::new);
}
private void readAndUpdateSystemRegister(String channelIID) {
RegoRegisterMapper.Channel channel = mapper.map(channelIID);
if (channel != null) {
byte[] command = CommandFactory.createReadFromSystemRegisterCommand(channel.address());
executeCommandAndUpdateState(channelIID, command, ResponseParserFactory.SHORT, value -> {
Unit<?> unit = channel.unit();
double result = Math.round(channel.convertValue(value) * channel.scaleFactor() * 10.0) / 10.0;
return unit != null ? new QuantityType<>(result, unit) : new DecimalType(result);
});
} else {
logger.debug("Unsupported channel {}", channelIID);
}
}
private <T> void executeCommandAndUpdateState(String channelIID, byte[] command, ResponseParser<T> parser,
Function<T, State> converter) {
logger.debug("Reading value for channel '{}' ...", channelIID);
executeCommand(channelIID, command, parser, result -> {
logger.debug("Got value for '{}' = {}", channelIID, result);
updateState(channelIID, converter.apply(result));
});
}
private synchronized <T> void executeCommand(String channelIID, byte[] command, ResponseParser<T> parser,
Consumer<T> resultProcessor) {
try {
T result = executeCommandWithRetry(channelIID, command, parser, 5);
resultProcessor.accept(result);
} catch (IOException e) {
logger.warn("Executing command for channel '{}' failed.", channelIID, e);
synchronized (channelDescriptors) {
channelDescriptors.clear();
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (Rego6xxProtocolException e) {
logger.warn("Executing command for channel '{}' failed.", channelIID, e);
updateState(channelIID, UnDefType.UNDEF);
} catch (InterruptedException e) {
logger.debug("Execution interrupted when accessing value for channel '{}'.", channelIID, e);
Thread.currentThread().interrupt();
}
}
private <T> T executeCommandWithRetry(String channelIID, byte[] command, ResponseParser<T> parser, int retry)
throws Rego6xxProtocolException, IOException, InterruptedException {
try {
checkRegoDevice();
return executeCommand(channelIID, command, parser);
} catch (IOException | Rego6xxProtocolException e) {
if (retry > 0) {
logger.debug("Executing command for channel '{}' failed, retry {}.", channelIID, retry, e);
Thread.sleep(200);
return executeCommandWithRetry(channelIID, command, parser, retry - 1);
}
throw e;
}
}
private void checkRegoDevice() throws Rego6xxProtocolException, IOException, InterruptedException {
if (thing.getStatus() != ThingStatus.ONLINE) {
logger.debug("Reading Rego device version...");
Short regoVersion = executeCommand(null, CommandFactory.createReadRegoVersionCommand(),
ResponseParserFactory.SHORT);
if (regoVersion != 600) {
throw new IOException("Invalid rego version received " + regoVersion.toString());
}
updateStatus(ThingStatus.ONLINE);
logger.debug("Connected to Rego version {}.", regoVersion);
}
}
private ChannelDescriptor channelDescriptorForChannel(String channelIID) {
synchronized (channelDescriptors) {
ChannelDescriptor descriptor = channelDescriptors.get(channelIID);
if (descriptor == null) {
descriptor = new ChannelDescriptor();
channelDescriptors.put(channelIID, descriptor);
}
return descriptor;
}
}
private <T> T executeCommand(String channelIID, byte[] command, ResponseParser<T> parser)
throws Rego6xxProtocolException, IOException, InterruptedException {
try {
return executeCommandInternal(channelIID, command, parser);
} catch (IOException e) {
if (connection != null) {
connection.close();
}
throw e;
}
}
private <T> T executeCommandInternal(String channelIID, byte[] command, ResponseParser<T> parser)
throws Rego6xxProtocolException, IOException, InterruptedException {
// CHANNEL_LAST_ERROR_CODE and CHANNEL_LAST_ERROR_TIMESTAMP are read from same
// register. To prevent accessing same register twice when both channels are linked,
// use same name for both so only a single fetch will be triggered.
String mappedChannelIID = (CHANNEL_LAST_ERROR_TYPE.equals(channelIID)
|| CHANNEL_LAST_ERROR_TIMESTAMP.equals(channelIID)) ? CHANNEL_LAST_ERROR : channelIID;
// Use transient channel descriptor for null (not cached) channels.
ChannelDescriptor descriptor = channelIID == null ? new ChannelDescriptor()
: channelDescriptorForChannel(mappedChannelIID);
byte[] cachedValue = descriptor.cachedValueIfNotExpired(refreshInterval);
if (cachedValue != null) {
logger.debug("Cache did not yet expire, using cached value for {}", mappedChannelIID);
return parser.parse(cachedValue);
}
// Send command to device and wait for response.
if (!connection.isConnected()) {
connection.connect();
}
// Give heat pump some time between commands. Feeding commands too quickly
// might cause heat pump not to respond.
Thread.sleep(80);
// Protocol is request driven so there should be no data available before sending
// a command to the heat pump.
InputStream inputStream = connection.inputStream();
int available = inputStream.available();
if (available > 0) {
// Limit to max 64 bytes, fuse.
byte[] buffer = new byte[Math.min(64, available)];
inputStream.read(buffer);
logger.debug("There are {} unexpected bytes available. Skipping {}.", available, buffer);
}
if (logger.isDebugEnabled()) {
logger.debug("Sending {}", HexUtils.bytesToHex(command));
}
// Send command
OutputStream outputStream = connection.outputStream();
outputStream.write(command);
outputStream.flush();
// Read response, wait for max 2 second for data to arrive.
byte[] response = new byte[parser.responseLength()];
long timeout = System.currentTimeMillis() + 2000;
int pos = 0;
do {
int len = inputStream.read(response, pos, response.length - pos);
if (len > 0) {
pos += len;
} else {
// Give some time for response to arrive...
Thread.sleep(50);
}
} while (pos < response.length && timeout > System.currentTimeMillis());
if (pos < response.length) {
logger.debug("Response not received, read {} bytes => {}", pos, response);
throw new IOException("Response not received - got " + Integer.toString(pos) + " bytes of "
+ Integer.toString(response.length));
}
if (logger.isDebugEnabled()) {
logger.debug("Received {}", HexUtils.bytesToHex(response));
}
T result = parser.parse(response);
// If reading/parsing was done successfully, cache response payload.
descriptor.setValue(response);
return result;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.binding.regoheatpump.internal.protocol.SerialRegoConnection;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* The {@link SerialHusdataHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
public class SerialHusdataHandler extends HusdataHandler {
private final SerialPortManager serialPortManager;
private SerialPortIdentifier serialPortIdentifier;
public SerialHusdataHandler(Thing thing, SerialPortManager serialPortManager) {
super(thing);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
String portName = (String) getConfig().get(RegoHeatPumpBindingConstants.PORT_NAME);
serialPortIdentifier = serialPortManager.getIdentifier(portName);
if (serialPortIdentifier == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Serial port does not exist: " + portName);
} else {
super.initialize();
}
}
@Override
protected RegoConnection createConnection() {
return new SerialRegoConnection(serialPortIdentifier, 19200);
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.handler;
import org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants;
import org.openhab.binding.regoheatpump.internal.protocol.RegoConnection;
import org.openhab.binding.regoheatpump.internal.protocol.SerialRegoConnection;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* The {@link SerialRego6xxHeatPumpHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Boris Krivonog - Initial contribution
*/
public class SerialRego6xxHeatPumpHandler extends Rego6xxHeatPumpHandler {
private final SerialPortManager serialPortManager;
private SerialPortIdentifier serialPortIdentifier;
public SerialRego6xxHeatPumpHandler(Thing thing, SerialPortManager serialPortManager) {
super(thing);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
String portName = (String) getConfig().get(RegoHeatPumpBindingConstants.PORT_NAME);
serialPortIdentifier = serialPortManager.getIdentifier(portName);
if (serialPortIdentifier == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Serial port does not exist: " + portName);
} else {
super.initialize();
}
}
@Override
protected RegoConnection createConnection() {
return new SerialRegoConnection(serialPortIdentifier, 19200);
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IpRegoConnection} is responsible for creating TCP/IP connections to clients.
*
* @author Boris Krivonog - Initial contribution
*/
public class IpRegoConnection implements RegoConnection {
/**
* Connection timeout in milliseconds
**/
private static final int CONNECTION_TIMEOUT = 3000;
/**
* Socket read timeout in milliseconds
**/
private static final int SOCKET_READ_TIMEOUT = 2000;
private final Logger logger = LoggerFactory.getLogger(IpRegoConnection.class);
private final String address;
private final int port;
private Socket clientSocket;
public IpRegoConnection(String address, int port) {
this.address = address;
this.port = port;
}
@Override
public void connect() throws IOException {
logger.debug("Connecting to '{}', port = {}.", address, port);
if (clientSocket == null) {
clientSocket = new Socket();
clientSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
clientSocket.setKeepAlive(true);
}
clientSocket.connect(new InetSocketAddress(address, port), CONNECTION_TIMEOUT);
logger.debug("Connected to '{}', port = {}.", address, port);
}
@Override
public boolean isConnected() {
return clientSocket != null && clientSocket.isConnected();
}
@Override
public void close() {
try {
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
// There is really not much we can do here, ignore the error and continue execution.
logger.warn("Closing socket failed", e);
}
clientSocket = null;
}
@Override
public OutputStream outputStream() throws IOException {
return clientSocket.getOutputStream();
}
@Override
public InputStream inputStream() throws IOException {
return clientSocket.getInputStream();
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The {@link RegoConnection} is responsible for creating connections to clients.
*
* @author Boris Krivonog - Initial contribution
*/
public interface RegoConnection {
/**
* Connect to the receiver. Return true if the connection has succeeded or if already connected.
*
**/
public void connect() throws IOException;
/**
* Return true if this manager is connected to the AVR.
*
* @return
*/
public boolean isConnected();
/**
* Closes the connection.
**/
public void close();
/**
* Returns an output stream for this connection.
*/
public OutputStream outputStream() throws IOException;
/**
* Returns an input stream for this connection.
*/
public InputStream inputStream() throws IOException;
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
/**
* The {@link SerialRegoConnection} is responsible for creating serial connections to clients.
*
* @author Boris Krivonog - Initial contribution
*/
public class SerialRegoConnection implements RegoConnection {
private final int baudRate;
private final String portName;
private SerialPort serialPort;
private final SerialPortIdentifier serialPortIdentifier;
public SerialRegoConnection(SerialPortIdentifier serialPortIdentifier, int baudRate) {
this.serialPortIdentifier = serialPortIdentifier;
this.portName = serialPortIdentifier.getName();
this.baudRate = baudRate;
}
@Override
public void connect() throws IOException {
try {
serialPort = serialPortIdentifier.open(SerialRegoConnection.class.getCanonicalName(), 2000);
serialPort.enableReceiveTimeout(100);
serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
} catch (PortInUseException e) {
throw new IOException("Serial port already used: " + portName, e);
} catch (UnsupportedCommOperationException e) {
throw new IOException("Unsupported operation on '" + portName + "': " + e.getMessage(), e);
}
}
@Override
public boolean isConnected() {
return serialPort != null;
}
@Override
public void close() {
if (serialPort != null) {
serialPort.close();
serialPort = null;
}
}
@Override
public OutputStream outputStream() throws IOException {
return serialPort.getOutputStream();
}
@Override
public InputStream inputStream() throws IOException {
return serialPort.getInputStream();
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link AbstractLongResponseParser} is responsible for parsing long form responses.
*
* @author Boris Krivonog - Initial contribution
*/
abstract class AbstractLongResponseParser<T> extends AbstractResponseParser<T> {
@Override
public int responseLength() {
return 42;
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
import org.openhab.core.util.HexUtils;
/**
* The {@link AbstractResponseParser} is responsible for parsing responses coming from
* rego6xx controllers.
*
* @author Boris Krivonog - Initial contribution
*/
abstract class AbstractResponseParser<T> implements ResponseParser<T> {
private static final byte COMPUTER_ADDRESS = (byte) 0x01;
@Override
public abstract int responseLength();
protected abstract T convert(byte[] responseBytes);
@Override
public T parse(byte[] buffer) throws Rego6xxProtocolException {
if (buffer.length != responseLength()) {
throw new Rego6xxProtocolException(
"Expected size does not match: " + buffer.length + " != " + responseLength());
}
if (buffer[0] != COMPUTER_ADDRESS) {
throw new Rego6xxProtocolException("Invalid header " + HexUtils.bytesToHex(buffer));
}
if (responseLength() > 1
&& Checksum.calculate(buffer, 1, responseLength() - 2) != buffer[responseLength() - 1]) {
throw new Rego6xxProtocolException("Invalid crc - " + HexUtils.bytesToHex(buffer));
}
return convert(buffer);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
import java.util.Arrays;
/**
* The {@link Checksum} is responsible for calculating checksum of given data.
*
* @author Boris Krivonog - Initial contribution
*/
class Checksum {
static byte calculate(byte[]... lists) {
return Arrays.stream(lists).reduce((byte) 0, Checksum::calculate, (a, b) -> b);
}
static byte calculate(byte[] buffer, int offset, int count) {
return calculate((byte) 0, buffer, offset, count);
}
private static byte calculate(byte checksum, byte[] buffer) {
return calculate(checksum, buffer, 0, buffer.length);
}
private static byte calculate(byte checksum, byte[] buffer, int offset, int count) {
byte result = checksum;
int end = count + offset;
for (int index = offset; index < end; ++index) {
result = (byte) (result ^ buffer[index]);
}
return result;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link CommandFactory} is responsible for creating different commands that can
* be send to a rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
public class CommandFactory {
private static final byte DEVICE_ADDRESS = (byte) 0x81;
public static byte[] createReadRegoVersionCommand() {
return createReadCommand((byte) 0x7f, (short) 0);
}
public static byte[] createReadFromSystemRegisterCommand(short address) {
return createReadCommand((byte) 0x02, address);
}
public static byte[] createWriteToSystemRegisterCommand(short address, short data) {
return createCommand((byte) 0x03, address, data);
}
public static byte[] createReadFromDisplayCommand(short displayLine) {
return createReadCommand((byte) 0x20, displayLine);
}
public static byte[] createReadLastErrorCommand() {
return createReadCommand((byte) 0x40, (short) 0);
}
public static byte[] createReadFromFrontPanelCommand(short address) {
return createReadCommand((byte) 0x00, address);
}
private static byte[] createReadCommand(byte source, short address) {
return createCommand(source, address, (short) 0);
}
private static byte[] createCommand(byte source, short address, short data) {
byte[] addressBytes = ValueConverter.shortToSevenBitFormat(address);
byte[] dataBytes = ValueConverter.shortToSevenBitFormat(data);
return new byte[] { DEVICE_ADDRESS, source, addressBytes[0], addressBytes[1], addressBytes[2], dataBytes[0],
dataBytes[1], dataBytes[2], Checksum.calculate(addressBytes, dataBytes) };
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* The {@link ErrorLine} is responsible for holding information about a single error line.
*
* @author Boris Krivonog - Initial contribution
*/
public class ErrorLine {
private final byte error;
private final String timestamp;
public ErrorLine(byte error, String timestamp) {
this.error = error;
this.timestamp = timestamp;
}
@Override
public String toString() {
return String.format("%d @ %s", error, timestamp);
}
public byte error() {
return error;
}
public String timestampAsString() {
return timestamp;
}
public ZonedDateTime timestamp() {
int year = Integer.parseInt(timestamp.substring(0, 2)) + 1000;
if (year < 1950) {
year += 1000;
}
int month = Integer.parseInt(timestamp.substring(2, 4));
int day = Integer.parseInt(timestamp.substring(4, 6));
int hour = Integer.parseInt(timestamp.substring(7, 9));
int min = Integer.parseInt(timestamp.substring(10, 12));
int sec = Integer.parseInt(timestamp.substring(13, 15));
return ZonedDateTime.of(year, month, day, hour, min, sec, 0, ZoneId.systemDefault());
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link ErrorLineResponseParser} is responsible for parsing error information (log) entry.
*
* @author Boris Krivonog - Initial contribution
*/
class ErrorLineResponseParser extends AbstractLongResponseParser<ErrorLine> {
@Override
protected ErrorLine convert(byte[] responseBytes) {
// 255 marks no error.
if (responseBytes[1] == (byte) 255) {
return null;
}
return new ErrorLine(ValueConverter.arrayToByte(responseBytes, 1),
ValueConverter.stringFromBytes(responseBytes, 3, 15));
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link Rego6xxProtocolException} is responsible for holding information about an Rego6xx protocol error.
*
* @author Boris Krivonog - Initial contribution
*/
public class Rego6xxProtocolException extends Exception {
private static final long serialVersionUID = 7556083982084149686L;
public Rego6xxProtocolException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,180 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
import static org.openhab.binding.regoheatpump.internal.RegoHeatPumpBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import javax.measure.Unit;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* The {@link RegoRegisterMapper} is responsible for mapping rego 6xx registers into channels.
*
* @author Boris Krivonog - Initial contribution
*/
public class RegoRegisterMapper {
public static final RegoRegisterMapper REGO600;
public static interface Channel {
public short address();
public double scaleFactor();
public Unit<?> unit();
public int convertValue(short value);
}
private static class ChannelFactory {
private static class ChannelImpl implements Channel {
private final short address;
private final double scaleFactor;
private final Unit<?> unit;
private ChannelImpl(short address, double scaleFactor, Unit<?> unit) {
this.address = address;
this.scaleFactor = scaleFactor;
this.unit = unit;
}
@Override
public short address() {
return address;
}
@Override
public double scaleFactor() {
return scaleFactor;
}
@Override
public Unit<?> unit() {
return unit;
}
@Override
public int convertValue(short value) {
return value;
}
}
private ChannelFactory() {
}
static Channel temperature(short address) {
return new ChannelImpl(address, 0.1, SIUnits.CELSIUS);
}
static Channel hours(short address) {
return new ChannelImpl(address, 1, SmartHomeUnits.HOUR) {
@Override
public int convertValue(short value) {
return Short.toUnsignedInt(value);
}
};
}
static Channel percent(short address) {
return new ChannelImpl(address, 0.1, SmartHomeUnits.PERCENT);
}
static Channel unitless(short address, double scaleFactor) {
return new ChannelImpl(address, scaleFactor, null);
}
}
private final Map<String, Channel> mappings;
private RegoRegisterMapper(Map<String, Channel> mappings) {
this.mappings = mappings;
}
public Channel map(String channelIID) {
return mappings.get(channelIID);
}
static {
final Map<String, Channel> mappings = new HashMap<>();
{
// Sensor values
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "radiatorReturn", ChannelFactory.temperature((short) 521));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "outdoor", ChannelFactory.temperature((short) 522));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "hotWater", ChannelFactory.temperature((short) 523));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "radiatorForward", ChannelFactory.temperature((short) 524));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "indoor", ChannelFactory.temperature((short) 525));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "compressor", ChannelFactory.temperature((short) 526));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "heatFluidOut", ChannelFactory.temperature((short) 527));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "heatFluidIn", ChannelFactory.temperature((short) 528));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "coldFluidIn", ChannelFactory.temperature((short) 529));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "coldFluidOut", ChannelFactory.temperature((short) 530));
mappings.put(CHANNEL_GROUP_SENSOR_VALUES + "externalHotWater", ChannelFactory.temperature((short) 531));
// Control data
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnTarget", ChannelFactory.temperature((short) 110));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnOn", ChannelFactory.temperature((short) 111));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "radiatorReturnOff", ChannelFactory.temperature((short) 112));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "hotWaterOn", ChannelFactory.temperature((short) 115));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "hotWaterOff", ChannelFactory.temperature((short) 116));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "radiatorForwardTarget", ChannelFactory.temperature((short) 109));
mappings.put(CHANNEL_GROUP_CONTROL_DATA + "addHeatPowerPercent", ChannelFactory.percent((short) 108));
// Device values
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "coldFluidPump", ChannelFactory.unitless((short) 509, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "compressor", ChannelFactory.unitless((short) 510, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "additionalHeat3kW", ChannelFactory.unitless((short) 511, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "additionalHeat6kW", ChannelFactory.unitless((short) 512, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "radiatorPump", ChannelFactory.unitless((short) 515, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "heatFluidPump", ChannelFactory.unitless((short) 516, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "switchValve", ChannelFactory.unitless((short) 517, 1));
mappings.put(CHANNEL_GROUP_DEVICE_VALUES + "alarm", ChannelFactory.unitless((short) 518, 1));
// Settings
mappings.put(CHANNEL_GROUP_SETTINGS + "heatCurve", ChannelFactory.unitless((short) 0x0000, 0.1));
mappings.put(CHANNEL_GROUP_SETTINGS + "heatCurveFineAdj", ChannelFactory.temperature((short) 0x0001));
mappings.put(CHANNEL_GROUP_SETTINGS + "heatCurveCouplingDiff", ChannelFactory.temperature((short) 0x0002));
mappings.put(CHANNEL_GROUP_SETTINGS + "heatCurve2", ChannelFactory.unitless((short) 0x0003, 0.1));
mappings.put(CHANNEL_GROUP_SETTINGS + "heatCurve2FineAdj", ChannelFactory.temperature((short) 0x0004));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAt20", ChannelFactory.temperature((short) 0x001e));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAt15", ChannelFactory.temperature((short) 0x001c));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAt10", ChannelFactory.temperature((short) 0x001a));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAt5", ChannelFactory.temperature((short) 0x0018));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAt0", ChannelFactory.temperature((short) 0x0016));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus5", ChannelFactory.temperature((short) 0x0014));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus10", ChannelFactory.temperature((short) 0x0012));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus15", ChannelFactory.temperature((short) 0x0010));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus20", ChannelFactory.temperature((short) 0x000e));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus25", ChannelFactory.temperature((short) 0x000c));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus30", ChannelFactory.temperature((short) 0x000a));
mappings.put(CHANNEL_GROUP_SETTINGS + "adjCurveAtMinus35", ChannelFactory.temperature((short) 0x0008));
mappings.put(CHANNEL_GROUP_SETTINGS + "indoorTempSetting", ChannelFactory.temperature((short) 0x0021));
mappings.put(CHANNEL_GROUP_SETTINGS + "curveInflByInTemp", ChannelFactory.unitless((short) 0x0022, 0.1));
mappings.put(CHANNEL_GROUP_SETTINGS + "summerDisconnection", ChannelFactory.temperature((short) 0x0024));
mappings.put(CHANNEL_GROUP_SETTINGS + "hotWaterTarget", ChannelFactory.temperature((short) 0x002b));
mappings.put(CHANNEL_GROUP_SETTINGS + "hotWaterTargetHysteresis",
ChannelFactory.temperature((short) 0x002c));
// Operating times
mappings.put(CHANNEL_GROUP_OPERATING_TIMES + "heatPumpInOperationRAD", ChannelFactory.hours((short) 72));
mappings.put(CHANNEL_GROUP_OPERATING_TIMES + "heatPumpInOperationDHW", ChannelFactory.hours((short) 74));
mappings.put(CHANNEL_GROUP_OPERATING_TIMES + "addHeatInOperationRAD", ChannelFactory.hours((short) 76));
mappings.put(CHANNEL_GROUP_OPERATING_TIMES + "addHeatInOperationDHW", ChannelFactory.hours((short) 78));
}
REGO600 = new RegoRegisterMapper(mappings);
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link ResponseParser} is responsible for parsing arbitrary data coming from a rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
public interface ResponseParser<T> {
public int responseLength();
public T parse(byte[] buffer) throws Rego6xxProtocolException;
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link ResponseParserFactory} is responsible for providing parsers for all known data
* forms coming from the rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
public class ResponseParserFactory {
public static final ResponseParser<Short> SHORT = new ShortResponseParser();
public static final ResponseParser<String> STRING = new StringResponseParser();
public static final ResponseParser<ErrorLine> ERROR_LINE = new ErrorLineResponseParser();
public static final ResponseParser<Void> WRITE = new WriteResponse();
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link ShortResponseParser} is responsible for parsing short form data format
* coming from the rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
class ShortResponseParser extends AbstractResponseParser<Short> {
@Override
public int responseLength() {
return 5;
}
@Override
protected Short convert(byte[] responseBytes) {
return ValueConverter.sevenBitFormatToShort(responseBytes, 1);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link StringResponseParser} is responsible for parsing long (text) form data format
* coming from the rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
class StringResponseParser extends AbstractLongResponseParser<String> {
@Override
protected String convert(byte[] responseBytes) {
return ValueConverter.stringFromBytes(responseBytes, 1, 20);
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link ValueConverter} is responsible for converting various rego 6xx specific data types.
*
* @author Boris Krivonog - Initial contribution
*/
class ValueConverter {
public static byte[] shortToSevenBitFormat(short value) {
byte b1 = (byte) ((value & 0xC000) >> 14);
byte b2 = (byte) ((value & 0x3F80) >> 7);
byte b3 = (byte) (value & 0x007F);
return new byte[] { b1, b2, b3 };
}
public static short sevenBitFormatToShort(byte[] buffer, int offset) {
return (short) (buffer[offset] << 14 | buffer[offset + 1] << 7 | buffer[offset + 2]);
}
public static byte arrayToByte(byte[] buffer, int offset) {
return (byte) (buffer[offset] << 4 | buffer[offset + 1]);
}
public static String stringFromBytes(byte[] buffer, int offset, int charCount) {
StringBuilder builder = new StringBuilder(charCount);
int length = offset + charCount * 2;
for (int i = offset; i < length; i += 2) {
builder.append((char) arrayToByte(buffer, i));
}
return builder.toString();
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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.regoheatpump.internal.rego6xx;
/**
* The {@link WriteResponse} is responsible for parsing write responses
* coming from the rego 6xx unit.
*
* @author Boris Krivonog - Initial contribution
*/
class WriteResponse extends AbstractResponseParser<Void> {
@Override
public int responseLength() {
return 1;
}
@Override
protected Void convert(byte[] responseBytes) {
return null;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="regoheatpump" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>RegoHeatPump Binding</name>
<description>The Rego controller based heat pumps binding.</description>
<author>Boris Krivonog</author>
</binding:binding>

View File

@@ -0,0 +1,817 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="regoheatpump"
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">
<!-- Manage Rego 6xx based Heat Pump over IP -->
<thing-type id="ipRego6xx">
<label>Heat Pump</label>
<description>Manage Rego 6xx based heat pump over TCP/IP</description>
<channel-groups>
<channel-group id="sensorValues" typeId="sensorValues"/>
<channel-group id="controlData" typeId="controlData"/>
<channel-group id="deviceValues" typeId="deviceValues"/>
<channel-group id="settings" typeId="settings"/>
<channel-group id="frontPanel" typeId="frontPanel"/>
<channel-group id="status" typeId="status"/>
<channel-group id="operatingTimes" typeId="operatingTimes"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" required="true">
<context>network-address</context>
<label>Address</label>
<description>The IP address of the Rego to control.</description>
</parameter>
<parameter name="tcpPort" type="integer" max="65535" min="1" required="false">
<default>9265</default>
<label>TCP Port</label>
<description>The TCP port number used to connect to the Rego controller.</description>
</parameter>
<parameter name="refreshInterval" type="integer" max="65535" min="10" required="false">
<default>60</default>
<label>Refresh Interval</label>
<description>Refresh interval in seconds.</description>
</parameter>
</config-description>
</thing-type>
<!-- Manage Rego 6xx based Heat Pump over serial -->
<thing-type id="serialRego6xx">
<label>Heat Pump</label>
<description>Manage Rego 6xx based heat pump over serial port</description>
<channel-groups>
<channel-group id="sensorValues" typeId="sensorValues"/>
<channel-group id="controlData" typeId="controlData"/>
<channel-group id="deviceValues" typeId="deviceValues"/>
<channel-group id="settings" typeId="settings"/>
<channel-group id="frontPanel" typeId="frontPanel"/>
<channel-group id="status" typeId="status"/>
<channel-group id="operatingTimes" typeId="operatingTimes"/>
</channel-groups>
<config-description>
<parameter name="portName" type="text" required="true">
<label>Port</label>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<description>The serial port used to connect to the Rego controller.</description>
</parameter>
<parameter name="refreshInterval" type="integer" max="65535" min="10" required="false">
<default>60</default>
<label>Refresh Interval</label>
<description>Refresh interval in seconds.</description>
</parameter>
</config-description>
</thing-type>
<!-- Manage Husdata interface over IP -->
<thing-type id="ipHusdata">
<label>Husdata Interface</label>
<description>Access heat pump over Husdata interface connected via TCP/IP</description>
<channel-groups>
<channel-group id="sensorValues" typeId="sensorValues"/>
<channel-group id="controlData" typeId="controlData"/>
<channel-group id="deviceValues" typeId="deviceValues"/>
<channel-group id="settings" typeId="settings"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" required="true">
<context>network-address</context>
<label>Address</label>
<description>The IP address of the Rego to control.</description>
</parameter>
<parameter name="tcpPort" type="integer" max="65535" min="1" required="false">
<default>9265</default>
<label>TCP Port</label>
<description>The TCP port number used to connect to the Rego controller.</description>
</parameter>
</config-description>
</thing-type>
<!-- Manage Husdata interface over serial -->
<thing-type id="serialHusdata">
<label>Husdata Interface</label>
<description>Access heat pump over Husdata interface connected via serial port</description>
<channel-groups>
<channel-group id="sensorValues" typeId="sensorValues"/>
<channel-group id="controlData" typeId="controlData"/>
<channel-group id="deviceValues" typeId="deviceValues"/>
<channel-group id="settings" typeId="settings"/>
</channel-groups>
<config-description>
<parameter name="portName" type="text" required="true">
<label>Port</label>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<description>The serial port used to connect to the Husdata interface.</description>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="sensorValues">
<label>Sensor Values</label>
<channels>
<channel id="radiatorReturn" typeId="radiatorReturnTemp"/>
<channel id="outdoor" typeId="outdoorTemp"/>
<channel id="hotWater" typeId="hotWaterTemp"/>
<channel id="radiatorForward" typeId="radiatorForwardTemp"/>
<channel id="indoor" typeId="indoorTemp"/>
<channel id="compressor" typeId="compressorTemp"/>
<channel id="heatFluidOut" typeId="heatFluidOutTemp"/>
<channel id="heatFluidIn" typeId="heatFluidInTemp"/>
<channel id="coldFluidIn" typeId="coldFluidInTemp"/>
<channel id="coldFluidOut" typeId="coldFluidOutTemp"/>
<channel id="externalHotWater" typeId="externalHotWaterTemp"/>
<channel id="airIntake" typeId="airIntakeTemp"/>
<channel id="pool" typeId="poolTemp"/>
</channels>
</channel-group-type>
<channel-group-type id="controlData">
<label>Control Data</label>
<channels>
<channel id="radiatorReturnTarget" typeId="radiatorReturnTarget"/>
<channel id="radiatorReturnOn" typeId="radiatorReturnOn"/>
<channel id="radiatorReturnOff" typeId="radiatorReturnOff"/>
<channel id="hotWaterOn" typeId="hotWaterOn"/>
<channel id="hotWaterOff" typeId="hotWaterOff"/>
<channel id="radiatorForwardTarget" typeId="radiatorForwardTarget"/>
<channel id="addHeatPowerPercent" typeId="addHeatPowerPercent"/>
<channel id="addHeatPowerEnergy" typeId="addHeatPowerEnergy"/>
<channel id="compressorSpeed" typeId="compressorSpeed"/>
</channels>
</channel-group-type>
<channel-group-type id="deviceValues">
<label>Device Values</label>
<channels>
<channel id="radiatorPump" typeId="radiatorPumpState"/>
<channel id="heatFluidPump" typeId="heatFluidPumpState"/>
<channel id="coldFluidPump" typeId="coldFluidPumpState"/>
<channel id="compressor" typeId="compressorState"/>
<channel id="additionalHeat3kW" typeId="additionalHeat3kWState"/>
<channel id="additionalHeat6kW" typeId="additionalHeat6kWState"/>
<channel id="alarm" typeId="alarmState"/>
<channel id="switchValve" typeId="switchValveState"/>
<channel id="switchValve2" typeId="switchValve2State"/>
<channel id="fan" typeId="fanState"/>
<channel id="highPressostat" typeId="highPressostatState"/>
<channel id="lowPressostat" typeId="lowPressostatState"/>
<channel id="heatingCable" typeId="heatingCableState"/>
<channel id="crankCaseHeater" typeId="crankCaseHeaterState"/>
<channel id="elMeter1" typeId="elMeter1"/>
<channel id="elMeter2" typeId="elMeter2"/>
</channels>
</channel-group-type>
<channel-group-type id="settings">
<label>Settings</label>
<channels>
<channel id="hotWaterTarget" typeId="hotWaterTarget"/>
<channel id="hotWaterTargetHysteresis" typeId="hotWaterTargetHysteresis"/>
<channel id="heatCurve" typeId="heatCurve"/>
<channel id="heatCurveFineAdj" typeId="heatCurveFineAdj"/>
<channel id="indoorTempSetting" typeId="indoorTempSetting"/>
<channel id="curveInflByInTemp" typeId="curveInflByInTemp"/>
<channel id="adjCurveAt20" typeId="adjCurveAt20"/>
<channel id="adjCurveAt15" typeId="adjCurveAt15"/>
<channel id="adjCurveAt10" typeId="adjCurveAt10"/>
<channel id="adjCurveAt5" typeId="adjCurveAt5"/>
<channel id="adjCurveAt0" typeId="adjCurveAt0"/>
<channel id="adjCurveAtMinus5" typeId="adjCurveAtMinus5"/>
<channel id="adjCurveAtMinus10" typeId="adjCurveAtMinus10"/>
<channel id="adjCurveAtMinus15" typeId="adjCurveAtMinus15"/>
<channel id="adjCurveAtMinus20" typeId="adjCurveAtMinus20"/>
<channel id="adjCurveAtMinus25" typeId="adjCurveAtMinus25"/>
<channel id="adjCurveAtMinus30" typeId="adjCurveAtMinus30"/>
<channel id="adjCurveAtMinus35" typeId="adjCurveAtMinus35"/>
<channel id="heatCurveCouplingDiff" typeId="heatCurveCouplingDiff"/>
<channel id="heatCurve2" typeId="heatCurve2"/>
<channel id="heatCurve2FineAdj" typeId="heatCurve2FineAdj"/>
<channel id="summerDisconnection" typeId="summerDisconnection"/>
</channels>
</channel-group-type>
<channel-group-type id="status">
<label>Status</label>
<channels>
<channel id="lastErrorTimestamp" typeId="lastErrorTimestamp"/>
<channel id="lastErrorType" typeId="lastErrorType"/>
</channels>
</channel-group-type>
<channel-group-type id="frontPanel">
<label>Front Panel</label>
<channels>
<channel id="powerLamp" typeId="powerLamp"/>
<channel id="heatPumpLamp" typeId="heatPumpLamp"/>
<channel id="additionalHeatLamp" typeId="additionalHeatLamp"/>
<channel id="hotWaterLamp" typeId="hotWaterLamp"/>
<channel id="alarmLamp" typeId="alarmLamp"/>
</channels>
</channel-group-type>
<channel-group-type id="operatingTimes">
<label>Operating Times</label>
<channels>
<channel id="heatPumpInOperationRAD" typeId="heatPumpInOperationRAD"/>
<channel id="heatPumpInOperationDHW" typeId="heatPumpInOperationDHW"/>
<channel id="addHeatInOperationRAD" typeId="addHeatInOperationRAD"/>
<channel id="addHeatInOperationDHW" typeId="addHeatInOperationDHW"/>
</channels>
</channel-group-type>
<channel-type id="radiatorReturnTemp">
<item-type>Number:Temperature</item-type>
<label>Radiator Return</label>
<description>Temperature of the water that returns to the heat pump from the radiators</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="outdoorTemp">
<item-type>Number:Temperature</item-type>
<label>Outdoor</label>
<description>The outdoor temperature. Determines how much heating the heat pump should produce</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="hotWaterTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Hot Water</label>
<description>Temperature in the hot water cylinder</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="radiatorForwardTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Radiator Forward</label>
<description>Temperature on the flow water in the circuit</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="indoorTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Indoor</label>
<description>Present temperature in the room where the sensor is fitted</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="compressorTemp">
<item-type>Number:Temperature</item-type>
<label>Compressor</label>
<description>Compressors working temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatFluidOutTemp">
<item-type>Number:Temperature</item-type>
<label>Heat Fluid Out</label>
<description>Temperature of the radiator water as it leaves the heat pump</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatFluidInTemp">
<item-type>Number:Temperature</item-type>
<label>Heat Fluid In</label>
<description>Temperature of the water that is led into the heat pump</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="coldFluidInTemp">
<item-type>Number:Temperature</item-type>
<label>Cold Fluid In</label>
<description>Temperature of the heat transfer fluid that is led into the heat pump from the bore hole or the ground</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="coldFluidOutTemp">
<item-type>Number:Temperature</item-type>
<label>Cold Fluid Out</label>
<description>Temperature of the heat transfer fluid that is led out of the heat pump to the bore hole or the ground</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="externalHotWaterTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>External Hot Water</label>
<description>Temperature in the external hot water cylinder</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="lastErrorType">
<item-type>String</item-type>
<label>Last Error Type</label>
<description>Information about the alarm type that occured last</description>
<category>Error</category>
<state readOnly="true" pattern="%s">
<options>
<option value="0">Sensor radiator return (GT1)</option>
<option value="1">Outdoor sensor (GT2)</option>
<option value="2">Sensor hot water (GT3)</option>
<option value="3">Mixing valve sensor (GT4)</option>
<option value="4">Room sensor (GT5)</option>
<option value="5">Sensor compressor (GT6)</option>
<option value="6">Sensor heat transfer fluid out (GT8)</option>
<option value="7">Sensor heat transfer fluid in (GT9)</option>
<option value="8">Sensor cold transfer fluid in (GT10)</option>
<option value="9">Sensor cold transfer fluid in (GT11)</option>
<option value="10">Compressor circuit switch</option>
<option value="11">Electrical cassette</option>
<option value="12">HTF C=pump switch (MB2)</option>
<option value="13">Low pressure switch (LP)</option>
<option value="14">High pressure switch (HP)</option>
<option value="15">High return HP (GT9)</option>
<option value="16">HTF out max (GT8)</option>
<option value="17">HTF in under limit (GT10)</option>
<option value="18">HTF out under limit (GT11)</option>
<option value="19">Compressor superheat (GT6)</option>
<option value="20">Three phase incorrect order</option>
<option value="21">Power failure</option>
<option value="22">High delta GT8/GT9</option>
</options>
</state>
</channel-type>
<channel-type id="lastErrorTimestamp">
<item-type>DateTime</item-type>
<label>Last Error Date</label>
<description>Information about when last alarm occurred</description>
<category>Time</category>
<state readOnly="true" pattern="%1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="powerLamp" advanced="true">
<item-type>Switch</item-type>
<label>Power Lamp</label>
<description>Lamp on when the heat pump is on</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatPumpLamp">
<item-type>Switch</item-type>
<label>Heat Pump Lamp</label>
<description>Lamp on when the heat pump (compressor) is operational</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="additionalHeatLamp">
<item-type>Switch</item-type>
<label>Additional Heat Lamp</label>
<description>Lamp on when the heat pump is using additional heat from an electric cassette</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="hotWaterLamp">
<item-type>Switch</item-type>
<label>Hot Water Lamp</label>
<description>Lamp on when the heat pump is heating water in the heater</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmLamp">
<item-type>Switch</item-type>
<label>Alarm Lamp</label>
<description>Lamp indicates that a fault has occurred in the heat pump</description>
<category>Siren</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="coldFluidPumpState" advanced="true">
<item-type>Switch</item-type>
<label>Cold Fluid Pump</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="compressorState" advanced="true">
<item-type>Switch</item-type>
<label>Compressor</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="radiatorPumpState" advanced="true">
<item-type>Switch</item-type>
<label>Radiator Pump</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatFluidPumpState" advanced="true">
<item-type>Switch</item-type>
<label>Heat Fluid Pump</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="additionalHeat3kWState" advanced="true">
<item-type>Switch</item-type>
<label>Additional Heat 3kW</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="additionalHeat6kWState" advanced="true">
<item-type>Switch</item-type>
<label>Additional Heat 6kW</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="switchValveState" advanced="true">
<item-type>Switch</item-type>
<label>Switch Valve</label>
<description>The valve switches between heating the heating water and hot water</description>
<category>Heating</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmState" advanced="true">
<item-type>Switch</item-type>
<label>Alarm</label>
<category>Siren</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="radiatorReturnTarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Radiator Return Target</label>
<description>Calculated desired radiator return temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="radiatorReturnOn" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Minimal Radiator Return</label>
<description>Calculated minimal (start) radiator return temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="radiatorReturnOff" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Maximal Radiator Return</label>
<description>Calculated maximal (stop) radiator return temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="hotWaterTarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Hot Water Target</label>
<description>Desired radiator return temperature</description>
<category>Temperature</category>
<state min="35" max="54" step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="hotWaterTargetHysteresis" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Hot Water Hysteresis</label>
<description> The function measures below and above the value set in hotWaterTarget</description>
<category>Temperature</category>
<state min="2" max="15" step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="hotWaterOn" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Minimal Hot Water</label>
<description>Minimal (start) hot water temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="hotWaterOff" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Maximal Hot Water</label>
<description>Maximal (stop) hot water temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="radiatorForwardTarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Radiator Forward Target</label>
<description>Calculated desired radiator forward temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="addHeatPowerPercent" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Additional Heat Power</label>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="addHeatPowerEnergy" advanced="true">
<item-type>Number:Energy</item-type>
<label>Additional Heat Power</label>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatCurve" advanced="true">
<item-type>Number</item-type>
<label>Heat Curve</label>
<description>Heat curve influences the heat pumps production of heat</description>
<category>Line</category>
<state min="0" max="10" step="0.1" pattern="%.1f"/>
</channel-type>
<channel-type id="heatCurveFineAdj" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Heat Curve Fine Tune</label>
<description>Fine-tuning means that you offset the heat curve in parallel</description>
<category>Temperature</category>
<state min="-10" max="10" step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="indoorTempSetting" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Indoor Target</label>
<description>Desired temperature in the room where the sensor is fitted</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="curveInflByInTemp" advanced="true">
<item-type>Number</item-type>
<label>Room Sensor Influence</label>
<description>Set how much the room sensor should influence the heat curve</description>
<category>Line</category>
<state step="0.1" pattern="%.1f"/>
</channel-type>
<channel-type id="adjCurveAt20" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At 20 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAt15" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At 15 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAt10" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At 10 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAt5" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At 5 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAt0" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At 0 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus5" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -5 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus10" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -10 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus15" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -15 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus20" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -20 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus25" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -25 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus30" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -30 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="adjCurveAtMinus35" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Adjust Heat Curve At -35 °C</label>
<description>Heat curve can be adjusted up or down every fifth outdoor degree. The purpose of breaking the curve is to
be able to influence the heat pump's heat production at extra sensitive outdoor temperatures</description>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatCurveCouplingDiff" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Heat Curve Coupling Diff</label>
<category>Temperature</category>
<state step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="airIntakeTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Air Intake</label>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="poolTemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Pool</label>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="compressorSpeed" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Compressor Speed</label>
<category>Pump</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatCurve2" advanced="true">
<item-type>Number</item-type>
<label>Heat Curve 2</label>
<category>Line</category>
<state min="0" max="10" step="0.1" pattern="%.1f"/>
</channel-type>
<channel-type id="heatCurve2FineAdj" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Heat Curve 2 Fine Tune</label>
<description>Fine-tuning means that you offset the heat curve 2 in parallel</description>
<category>Temperature</category>
<state min="-10" max="10" step="0.1" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="switchValve2State" advanced="true">
<item-type>Switch</item-type>
<label>Switch Valve 2</label>
<category>Heating</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="fanState" advanced="true">
<item-type>Switch</item-type>
<label>Fan</label>
<category>Fan</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="highPressostatState" advanced="true">
<item-type>Switch</item-type>
<label>High Pressostat</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="lowPressostatState" advanced="true">
<item-type>Switch</item-type>
<label>Low Pressostat</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatingCableState" advanced="true">
<item-type>Switch</item-type>
<label>Heating Cable</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="crankCaseHeaterState" advanced="true">
<item-type>Switch</item-type>
<label>Crank Case Heater</label>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="elMeter1" advanced="true">
<item-type>Number</item-type>
<label>Collected Pulses Meter 1</label>
<category>Switch</category>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="elMeter2" advanced="true">
<item-type>Number</item-type>
<label>Collected Pulses Meter 2</label>
<category>Switch</category>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="heatPumpInOperationDHW" advanced="true">
<item-type>Number:Time</item-type>
<label>Heat Pump in Oper. - DHW</label>
<description>Heat pump in operation while heating DHW - number of hours</description>
<category>Time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="heatPumpInOperationRAD" advanced="true">
<item-type>Number:Time</item-type>
<label>Heat Pump in Oper. - Radiators</label>
<description>Heat pump in operation while heating radiators - number of hours</description>
<category>Time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="addHeatInOperationDHW" advanced="true">
<item-type>Number:Time</item-type>
<label>Add. Heat in Oper. - DHW</label>
<description>Additional heat in operation heating DHW - number of hours</description>
<category>Time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="addHeatInOperationRAD" advanced="true">
<item-type>Number:Time</item-type>
<label>Add. Heat in Oper. - Radiators</label>
<description>Additional heat in operation heating radiators - number of hours</description>
<category>Time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="summerDisconnection" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Summer Disconnection</label>
<description>The function means the heat pump only produces hot water when the outdoor temperature rises above the set
value</description>
<category>Temperature</category>
<state min="10" max="30" step="0.1" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>