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="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" 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 exported="true" 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.heliosventilation</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,111 @@
# HeliosVentilation Binding
This is the binding for Helios Ventilation Systems KWL EC 200/300/500 Pro.
It requires a connection to the RS485 bus used by the original remote controls KWL-FB (9417) and does not use the Modbus/TCP interface of the newer EasyControl devices.
For electrical connection it is recommended to use an USB-RS485 interface, but any RS485 interface that shows up as a serial port will do.
Setup the device as described in https://www.openhab.org/docs/administration/serial.html.
The binding will use the remote control address 15 for communication, so make sure that this is not assigned to a physically present remote control.
## Supported Things
There is only one thing type supported by this binding: a Helios Ventilation System KWL EC 200/300/500 Pro from Helios.
The binding was developed and test on a KWL EC 200 Pro device.
## Binding Configuration
The binding requires access to the serial device connecting to the RS485 bus as described in https://www.openhab.org/docs/administration/serial.html.
Otherwise only thing configuration is needed.
## Thing Configuration
The binding supports only one thing and requires the configuration of the serial port (typically /dev/ttyUSB0 on Linux and COM3 on Windows) and optionally the polling time which is the cycle time after which the binding tries to reconnect to the bus and requests data updates.
## Channels
Supported operation channels:
| channel | type | description |
|--------------------|----------------------|-----------------------------------------------|
| outsideTemp | Number:Temperature | Temperature sensor in the outside air flow |
| outgoingTemp | Number:Temperature | Temperature sensor in the outgoing air flow |
| extractTemp | Number:Temperature | Temperature sensor in the extract air flow |
| supplyTemp | Number:Temperature | Temperature sensor in the supply air flow |
| setTemp | Number:Temperature | Set temperature for supply (not always used) |
| fanspeed | Number | Level of the fanspeed (1-8) |
| powerState | Switch | Main power switch |
| co2State | Switch | Switch for CO2 regulation |
| rhState | Switch | Switch for humidity regulation |
| winterMode | Switch | Switch to set winter mode |
Supported configuration channels:
| channel | type | description |
|--------------------|----------------------|-----------------------------------------------|
| bypassTemp | Number:Temperature | Temperature to disable the bypass function |
| supplyStopTemp | Number:Temperature | Temperature to stop supply fan for defrosting |
| preheatTemp | Number:Temperature | Temperature to enable the preheater |
| minFanspeed | Number | Minimal level of the fanspeed (1-8) |
| maxFanspeed | Number | Maximal level of the fanspeed (1-8) |
| rhLimit | Number:Dimensionless | Limit for relative humidity sensor |
| hysteresis | Number:Temperature | Hysteresis on defroster temperature |
| DCFanExtract | Number:Dimensionless | Speed reduction for the extract fan |
| DCFanSupply | Number:Dimensionless | Speed reduction for the supply fan |
| maintenanceInterval| Number:Dimensionless | Maintenance interval in months |
| adjustInveral | Number:Dimensionless | Adjust interval in minutes for air quality |
| RHLevelAuto | Switch | Automatic base humidity determination |
| switchType | Switch | External Switch type (Boost or Fireplace) |
| radiatorType | Switch | Use water (ON) or electric (OFF) radiator |
| cascade | Switch | System is cascaded |
Note: the configuration channels are not intended to be written regularly.
## Full Example
Things:
```
heliosventilation:ventilation:MyKWL [ serialPort="/dev/ttyUSB0" ]
```
Items:
```
Switch KWLOnOff { channel="heliosventilation:ventilation:MyKWL:powerState" }
Switch KWLWinter { channel="heliosventilation:ventilation:MyKWL:winterMode" }
Group VentilationTemp "Measured Temperatures in Ventilation System"
Number:Temperature Outside_Temperature "Outside Temperature [%.1f °C]" <temperature> (VentilationTemp) { channel="heliosventilation:ventilation:MyKWL:outsideTemp" }
Number:Temperature Outgoing_Temperature "Outgoing Temperature [%.1f °C]" <temperature> (VentilationTemp) { channel="heliosventilation:ventilation:MyKWL:outgoingTemp" }
Number:Temperature Extract_Temperature "Extract Temperature [%.1f °C]" <temperature> (VentilationTemp) { channel="heliosventilation:ventilation:MyKWL:extractTemp" }
Number:Temperature Supply_Temperature "Supply Temperature [%.1f °C]" <temperature> (VentilationTemp) { channel="heliosventilation:ventilation:MyKWL:supplyTemp" }
Number Fan_Speed "Fan Speed" <fan> { channel="heliosventilation:ventilation:MyKWL:fanspeed" }
Number Min_Fan_Speed "Min Fan Speed" <fan> { channel="heliosventilation:ventilation:MyKWL:minFanspeed" }
Number Max_Fan_Speed "Max Fan Speed" <fan> { channel="heliosventilation:ventilation:MyKWL:maxFanspeed" }
```
Sitemap:
```
sitemap helios_kwl label="Helios Ventilation" {
Frame label="Temperatures" {
Text item=Outside_Temperature
Text item=Outgoing_Temperature
Text item=Extract_Temperature
Text item=Supply_Temperature
}
Frame label="Control" {
Switch item=KWLOnOff
Switch item=KWLWinter
Slider item=Fan_Speed icon="fan" minValue=1 maxValue=8 step=1
}
Frame label="Configuration" {
Slider item=Min_Fan_Speed
Setpoint item=Max_Fan_Speed icon="fan"
}
}
```

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.heliosventilation</artifactId>
<name>openHAB Add-ons :: Bundles :: HeliosVentilation Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.heliosventilation-${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-heliosventilation" description="HeliosVentilation 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.heliosventilation/${project.version}</bundle>
</feature>
</features>

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.heliosventilation.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HeliosPropertiesFormatException} class defines an exception to describe parsing format errors
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class HeliosPropertiesFormatException extends Exception {
private static final long serialVersionUID = 8051109351111509577L;
private final String channelName;
private final String fullSpec;
private final String reason;
public HeliosPropertiesFormatException(String reason, String channelName, String fullSpec) {
this.channelName = channelName;
this.fullSpec = fullSpec;
this.reason = reason;
}
public String getChannelName() {
return channelName;
}
public String getFullSpec() {
return fullSpec;
}
@Override
public String getMessage() {
return "Cannot parse '" + fullSpec + "' for datapoint '" + channelName + "': " + reason;
}
}

View File

@@ -0,0 +1,89 @@
/**
* 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.heliosventilation.internal;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HeliosVentilationBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class HeliosVentilationBindingConstants {
public static final String BINDING_ID = "heliosventilation";
public static final String DATAPOINT_FILE = "datapoints.properties";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HELIOS_VENTILATION = new ThingTypeUID(BINDING_ID, "ventilation");
public static final Map<Byte, HeliosVentilationDataPoint> DATAPOINTS;
private static final Logger LOGGER;
static {
/* logger is used by readChannelProperties() so we need to initialize logger first. */
LOGGER = LoggerFactory.getLogger(HeliosVentilationBindingConstants.class);
DATAPOINTS = readChannelProperties();
}
// List of all Channel ids
// Channel ids are only in datapoints.properties and thing-types.xml
/**
* parse datapoints from properties
*
*/
private static Map<Byte, HeliosVentilationDataPoint> readChannelProperties() {
HashMap<Byte, HeliosVentilationDataPoint> result = new HashMap<Byte, HeliosVentilationDataPoint>();
URL resource = Thread.currentThread().getContextClassLoader().getResource(DATAPOINT_FILE);
Properties properties = new Properties();
try {
properties.load(resource.openStream());
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
String channel = (String) keys.nextElement();
HeliosVentilationDataPoint dp;
try {
dp = new HeliosVentilationDataPoint(channel, properties.getProperty(channel));
if (result.containsKey(dp.address())) {
result.get(dp.address()).append(dp);
} else {
result.put(dp.address(), dp);
}
} catch (HeliosPropertiesFormatException e) {
LOGGER.warn("could not read resource file {}, binding will probably fail: {}", DATAPOINT_FILE,
e.getMessage());
}
}
} catch (IOException e) {
LOGGER.warn("could not read resource file {}, binding will probably fail: {}", DATAPOINT_FILE,
e.getMessage());
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.heliosventilation.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HeliosVentilationConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class HeliosVentilationConfiguration {
/**
* Port name for a serial connection to RS485 bus. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or
* /dev/ttyUSB0 for Linux.
*/
public String serialPort = "";
/**
* The Panel Poll Period. Default is 60 sec. = 1 minute;
*/
public int pollPeriod = 60;
}

View File

@@ -0,0 +1,334 @@
/**
* 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.heliosventilation.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@next HeliosVentilationDataPoint} is a description of a datapoint in the Helios ventilation system.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class HeliosVentilationDataPoint {
public enum DataType {
TEMPERATURE,
HYSTERESIS,
FANSPEED,
SWITCH,
BYTE_PERCENT,
PERCENT,
NUMBER
}
/**
* mapping from temperature byte values to °C
*/
private static final int[] TEMP_MAP = { -74, -70, -66, -62, -59, -56, -54, -52, -50, -48, -47, -46, -44, -43, -42,
-41, -40, -39, -38, -37, -36, -35, -34, -33, -33, -32, -31, -30, -30, -29, -28, -28, -27, -27, -26, -25,
-25, -24, -24, -23, -23, -22, -22, -21, -21, -20, -20, -19, -19, -19, -18, -18, -17, -17, -16, -16, -16,
-15, -15, -14, -14, -14, -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7,
-7, -6, -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 1, 1, 1, 2, 2, 2,
3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13,
13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 21, 21, 21, 22, 22, 22,
23, 23, 24, 24, 24, 25, 25, 26, 26, 27, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35,
35, 36, 36, 37, 37, 38, 38, 39, 40, 40, 41, 41, 42, 43, 43, 44, 45, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53,
53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 65, 66, 68, 69, 71, 73, 75, 77, 79, 81, 82, 86, 90, 93, 97, 100,
100, 100, 100, 100, 100, 100, 100, 100 };
/**
* mapping from human readable fanspeed to raw value
*/
private static final int[] FANSPEED_MAP = { 0, 1, 3, 7, 15, 31, 63, 127, 255 };
private static final int BYTE_PERCENT_OFFSET = 52;
private String name;
private boolean writable;
private DataType datatype;
private byte address;
private int bitStart;
private int bitLength;
private @Nullable HeliosVentilationDataPoint next;
/**
* parse fullSpec in the properties format to declare a datapoint
*
* @param name the name of the datapoint
* @param fullSpec datapoint specification, see format in datapoints.properties
* @throws HeliosPropertiesFormatException in case fullSpec is not parsable
*/
public HeliosVentilationDataPoint(String name, String fullSpec) throws HeliosPropertiesFormatException {
String specWithoutComment;
if (fullSpec.contains("#")) {
specWithoutComment = fullSpec.substring(0, fullSpec.indexOf("#"));
} else {
specWithoutComment = fullSpec;
}
String[] tokens = specWithoutComment.split(",");
this.name = name;
if (tokens.length != 3) {
throw new HeliosPropertiesFormatException("invalid length", name, fullSpec);
}
try {
String addr = tokens[0];
String[] addrTokens;
if (addr.contains(":")) {
addrTokens = addr.split(":");
} else {
addrTokens = new String[] { addr };
}
bitLength = 8;
bitStart = 0;
this.address = (byte) (int) Integer.decode(addrTokens[0]);
if (addrTokens.length > 1) {
bitStart = (byte) (int) Integer.decode(addrTokens[1]);
bitLength = 1;
}
if (addrTokens.length > 2) {
bitLength = (byte) (int) Integer.decode(addrTokens[2]) - bitStart + 1;
}
if (addrTokens.length > 3) {
throw new HeliosPropertiesFormatException(
"invalid address spec: too many separators in bit specification", name, fullSpec);
}
} catch (NumberFormatException e) {
throw new HeliosPropertiesFormatException("invalid address spec", name, fullSpec);
}
this.writable = Boolean.parseBoolean(tokens[1]);
try {
this.datatype = DataType.valueOf(tokens[2].replaceAll("\\s+", ""));
} catch (IllegalArgumentException e) {
throw new HeliosPropertiesFormatException("invalid type spec", name, fullSpec);
}
}
public HeliosVentilationDataPoint(String name, byte address, boolean writable, DataType datatype) {
this.datatype = datatype;
this.writable = writable;
this.name = name;
this.address = address;
}
public boolean isWritable() {
return writable;
}
@Override
public String toString() {
return name;
}
/**
*
* @return the name of the variable, which is also the channel name
*/
public String getName() {
return name;
}
/**
*
* @return address of the variable
*/
public byte address() {
return address;
}
/**
* @return the bit mask of the data point. 0xFF in case the full byte is used.
*/
public byte bitMask() {
byte mask = (byte) 0xff;
if (datatype == DataType.NUMBER || datatype == DataType.SWITCH) {
mask = (byte) (((1 << bitLength) - 1) << bitStart);
}
return mask;
}
/**
* interpret the given byte b and return the value as State.
*
* @param b
* @return state representation of byte value b in current datatype
*/
public State asState(byte b) {
int val = b & 0xff;
switch (datatype) {
case TEMPERATURE:
return new QuantityType<>(TEMP_MAP[val], SIUnits.CELSIUS);
case BYTE_PERCENT:
return new QuantityType<>((int) ((val - BYTE_PERCENT_OFFSET) * 100.0 / (255 - BYTE_PERCENT_OFFSET)),
SmartHomeUnits.PERCENT);
case SWITCH:
if (bitLength != 1) {
return UnDefType.UNDEF;
} else if ((b & (1 << bitStart)) != 0) {
return OnOffType.ON;
} else {
return OnOffType.OFF;
}
case NUMBER:
int value = (b & bitMask()) >> bitStart;
return new DecimalType(value);
case PERCENT:
return new QuantityType<>(val, SmartHomeUnits.PERCENT);
case FANSPEED:
int i = 1;
while (i < FANSPEED_MAP.length && FANSPEED_MAP[i] < val) {
i++;
}
return new DecimalType(i);
case HYSTERESIS:
return new QuantityType<>(val / 3, SIUnits.CELSIUS);
default:
return UnDefType.UNDEF;
}
}
/**
* interpret the given byte b and return the value as string.
*
* @param b
* @return sting representation of byte value b in current datatype
*/
public String asString(byte b) {
State ste = asState(b);
String str = ste.toString();
if (ste instanceof UnDefType) {
return String.format("<unknown type> %02X ", b);
} else {
return str;
}
}
/**
* generate byte data to transmit
*
* @param val is the state of a channel
* @return byte value with RS485 representation. Bit level values are returned in the correct location, but other
* bits/datapoints in the same address are zero.
*/
public byte getTransmitDataFor(State val) {
byte result = 0;
DecimalType value = val.as(DecimalType.class);
if (value == null) {
/*
* if value is not convertible to a numeric type we cannot do anything reasonable with it, let's use the
* initial value for it
*/
} else {
QuantityType<?> quantvalue;
switch (datatype) {
case TEMPERATURE:
quantvalue = ((QuantityType<?>) val);
quantvalue = quantvalue.toUnit(SIUnits.CELSIUS);
if (quantvalue != null) {
value = quantvalue.as(DecimalType.class);
if (value != null) {
int temp = (int) Math.round(value.doubleValue());
int i = 0;
while (i < TEMP_MAP.length && TEMP_MAP[i] < temp) {
i++;
}
result = (byte) i;
}
}
break;
case FANSPEED:
int i = value.intValue();
if (i < 0) {
i = 0;
} else if (i > 8) {
i = 8;
}
result = (byte) FANSPEED_MAP[i];
break;
case BYTE_PERCENT:
result = (byte) ((value.doubleValue() / 100.0) * (255 - BYTE_PERCENT_OFFSET) + BYTE_PERCENT_OFFSET);
break;
case PERCENT:
double d = (Math.round(value.doubleValue()));
if (d < 0.0) {
d = 0.0;
} else if (d > 100.0) {
d = 100.0;
}
result = (byte) d;
break;
case HYSTERESIS:
quantvalue = ((QuantityType<?>) val).toUnit(SIUnits.CELSIUS);
if (quantvalue != null) {
result = (byte) (quantvalue.intValue() * 3);
}
break;
case SWITCH:
case NUMBER:
// those are the types supporting bit level specification
// output only the relevant bits
result = (byte) (value.intValue() << bitStart);
break;
}
}
return result;
}
/**
* Get further datapoint linked to the same address.
*
* @return sister datapoint
*/
public @Nullable HeliosVentilationDataPoint next() {
return next;
}
/**
* Add a next to a datapoint on the same address.
* Caller has to ensure that identical datapoints are not added several times.
*
* @param next is the sister datapoint
*/
public void append(HeliosVentilationDataPoint next) {
HeliosVentilationDataPoint existing = this.next;
if (this == next) {
// this datapoint is already there, so we do nothing and return
return;
} else if (existing != null) {
existing.append(next);
} else {
this.next = next;
}
}
/**
* @return true if writing to this datapoint requires a read-modify-write on the address
*/
public boolean requiresReadModifyWrite() {
/*
* the address either has multiple datapoints linked to it or is a bit-level point
* this means we need to do read-modify-write on udpate and therefore we store the data in memory
*/
return (bitMask() != (byte) 0xFF || next != null);
}
}

View File

@@ -0,0 +1,455 @@
/**
* 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.heliosventilation.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortEvent;
import org.openhab.core.io.transport.serial.SerialPortEventListener;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HeliosVentilationHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class HeliosVentilationHandler extends BaseThingHandler implements SerialPortEventListener {
private static final int BUSMEMBER_MAINBOARD = 0x11;
private static final int BUSMEMBER_SLAVEBOARDS = 0x10;
private static final byte BUSMEMBER_CONTROLBOARDS = (byte) 0x20;
private static final int BUSMEMBER_REC_MASK = 0xF0; // interpreting frames delivered to BUSMEMBER_ME &
// BUSMEMBER_REC_MASK
private static final int BUSMEMBER_ME = 0x2F; // used as sender when communicating with the helios system
private static final int POLL_OFFLINE_THRESHOLD = 3;
/** Logger Instance */
private final Logger logger = LoggerFactory.getLogger(HeliosVentilationHandler.class);
/**
* store received data for read-modify-write operations on bitlevel
*/
private final Map<Byte, Byte> memory = new HashMap<Byte, Byte>();
private final SerialPortManager serialPortManager;
/**
* init to default to avoid NPE in case handleCommand() is called before initialize()
*/
private HeliosVentilationConfiguration config = new HeliosVentilationConfiguration();
private @Nullable SerialPort serialPort;
private @Nullable InputStream inputStream;
private @Nullable OutputStream outputStream;
private @Nullable ScheduledFuture<?> pollingTask;
private int pollCounter;
public HeliosVentilationHandler(Thing thing, final SerialPortManager serialPortManager) {
super(thing);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
config = getConfigAs(HeliosVentilationConfiguration.class);
logger.debug("Serial Port: {}, 9600 baud, PollPeriod: {}", config.serialPort, config.pollPeriod);
if (config.serialPort.length() < 1) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
return;
} else {
SerialPortIdentifier portId = serialPortManager.getIdentifier(config.serialPort);
if (portId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Port " + config.serialPort + " is not known!");
serialPort = null;
} else {
updateStatus(ThingStatus.UNKNOWN);
if (this.config.pollPeriod > 0) {
startPolling();
}
}
}
scheduler.execute(this::connect);
}
private synchronized void connect() {
logger.debug("HeliosVentilation: connecting...");
// parse ports and if the port is found, initialize the reader
SerialPortIdentifier portId = serialPortManager.getIdentifier(config.serialPort);
if (portId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Port " + config.serialPort + " is not known!");
serialPort = null;
disconnect();
} else if (!isConnected()) {
// initialize serial port
try {
SerialPort serial = portId.open(getThing().getUID().toString(), 2000);
serial.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serial.addEventListener(this);
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// ignore the exception on close
inputStream = null;
}
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
// ignore the exception on close
outputStream = null;
}
inputStream = serial.getInputStream();
outputStream = serial.getOutputStream();
// activate the DATA_AVAILABLE notifier
serial.notifyOnDataAvailable(true);
serialPort = serial;
updateStatus(ThingStatus.UNKNOWN);
} catch (final IOException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error!");
} catch (PortInUseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use!");
} catch (TooManyListenersException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Cannot attach listener to port!");
} catch (UnsupportedCommOperationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Serial port does not support the RS485 parameters of the Helios remote protocol.");
}
}
}
@Override
public void dispose() {
stopPolling();
disconnect();
super.dispose();
}
/**
* Start the polling task.
*/
public synchronized void startPolling() {
final ScheduledFuture<?> task = pollingTask;
if (task != null && task.isCancelled()) {
task.cancel(true);
}
if (config.pollPeriod > 0) {
pollingTask = scheduler.scheduleWithFixedDelay(this::polling, 10, config.pollPeriod, TimeUnit.SECONDS);
} else {
pollingTask = null;
}
}
/**
* Stop the polling task.
*/
public synchronized void stopPolling() {
final ScheduledFuture<?> task = pollingTask;
if (task != null && !task.isCancelled()) {
task.cancel(true);
pollingTask = null;
}
}
/**
* Method for polling the RS485 Helios RemoteContol bus
*/
public synchronized void polling() {
if (logger.isTraceEnabled()) {
logger.trace("HeliosVentilation Polling data for '{}'", getThing().getUID());
}
pollCounter++;
if (pollCounter > POLL_OFFLINE_THRESHOLD) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE, "No data received!");
logger.info("No data received for '{}' disconnecting now...", getThing().getUID());
disconnect();
}
if (!isConnected()) {
connect(); // let's try to reconnect if the connection failed or was never established before
}
HeliosVentilationBindingConstants.DATAPOINTS.values().forEach((v) -> {
if (isLinked(v.getName())) {
poll(v);
}
});
}
private void disconnect() {
if (thing.getStatus() != ThingStatus.REMOVING) {
updateStatus(ThingStatus.OFFLINE);
}
synchronized (this) {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// ignore the exception on close
inputStream = null;
}
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
// ignore the exception on close
outputStream = null;
}
SerialPort serial = serialPort;
if (serial != null) {
serial.close();
}
serialPort = null;
}
}
private void poll(HeliosVentilationDataPoint v) {
byte[] txFrame = { 0x01, BUSMEMBER_ME, BUSMEMBER_MAINBOARD, 0x00, v.address(), 0x00 };
txFrame[5] = (byte) checksum(txFrame);
tx(txFrame);
}
/*
* transmit a frame
*/
private void tx(byte[] txFrame) {
try {
OutputStream out = outputStream;
if (out != null) {
if (logger.isTraceEnabled()) {
logger.trace("HeliosVentilation: Write to serial port: {}",
String.format("%02x %02x %02x %02x", txFrame[1], txFrame[2], txFrame[3], txFrame[4]));
}
out.write(txFrame);
out.flush();
// after each frame we have to wait.
// 30 ms is taken from what we roughly see the original remote control is doing
Thread.sleep(30);
}
} catch (IOException e) {
// in case we cannot write the connection is somehow broken, let's officially disconnect
disconnect();
connect();
} catch (InterruptedException e) {
// ignore if we got interrupted
}
}
/**
* Check connection status
*
* @return true if currently connected
*/
private boolean isConnected() {
return serialPort != null && inputStream != null && outputStream != null;
}
@Override
public synchronized void serialEvent(SerialPortEvent event) {
switch (event.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
// we get here if data has been received
try {
// Wait roughly a frame length to ensure that the complete frame is already buffered. This improves
// the robustness for RS485/USB converters which sometimes duplicate bytes otherwise.
Thread.sleep(8);
} catch (InterruptedException e) {
// ignore interruption
}
byte[] frame = { 0, 0, 0, 0, 0, 0 };
InputStream in = inputStream;
if (in != null) {
try {
do {
int cnt = 0;
// read data from serial device
while (cnt < 6 && in.available() > 0) {
final int bytes = in.read(frame, cnt, 1);
if (cnt > 0 || frame[0] == 0x01) {
// only proceed if the first byte was 0x01
cnt += bytes;
}
}
int sum = checksum(frame);
if (sum == (frame[5] & 0xff)) {
if (logger.isTraceEnabled()) {
logger.trace("HeliosVentilation: Read from serial port: {}", String
.format("%02x %02x %02x %02x", frame[1], frame[2], frame[3], frame[4]));
}
interpretFrame(frame);
} else {
if (logger.isTraceEnabled()) {
logger.trace(
"HeliosVentilation: Read frame with not matching checksum from serial port: {}",
String.format("%02x %02x %02x %02x %02x %02x (expected %02x)", frame[0],
frame[1], frame[2], frame[3], frame[4], frame[5], sum));
}
}
} while (in.available() > 0);
} catch (IOException e1) {
logger.debug("Error reading from serial port: {}", e1.getMessage(), e1);
}
}
break;
default:
break;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
scheduler.execute(this::polling);
} else if (command instanceof DecimalType || command instanceof QuantityType || command instanceof OnOffType) {
scheduler.execute(() -> update(channelUID, command));
}
}
/**
* Update the variable corresponding to given channel/command
*
* @param channelUID UID of the channel to update
* @param command data element to write
*
*/
public void update(ChannelUID channelUID, Command command) {
HeliosVentilationBindingConstants.DATAPOINTS.values().forEach((outer) -> {
HeliosVentilationDataPoint v = outer;
do {
if (channelUID.getThingUID().equals(thing.getUID()) && v.getName().equals(channelUID.getId())) {
if (v.isWritable()) {
byte[] txFrame = { 0x01, BUSMEMBER_ME, BUSMEMBER_CONTROLBOARDS, v.address(), 0x00, 0x00 };
txFrame[4] = v.getTransmitDataFor((State) command);
if (v.requiresReadModifyWrite()) {
txFrame[4] |= memory.get(v.address()) & ~v.bitMask();
memory.put(v.address(), txFrame[4]);
}
txFrame[5] = (byte) checksum(txFrame);
tx(txFrame);
txFrame[2] = BUSMEMBER_SLAVEBOARDS;
txFrame[5] = (byte) checksum(txFrame);
tx(txFrame);
txFrame[2] = BUSMEMBER_MAINBOARD;
txFrame[5] = (byte) checksum(txFrame);
tx(txFrame);
}
}
v = v.next();
} while (v != null);
});
}
/**
* calculate checksum of a frame
*
* @param frame filled with 5 bytes
* @return checksum of the first 5 bytes of frame
*/
private int checksum(byte[] frame) {
int sum = 0;
for (int a = 0; a < 5; a++) {
sum += frame[a] & 0xff;
}
sum %= 256;
return sum;
}
/**
* interpret a frame, which is already validated to be in correct format with valid checksum
*
* @param frame 6 bytes long data with 0x01, sender, receiver, address, value, checksum
*/
private void interpretFrame(byte[] frame) {
if ((frame[2] & BUSMEMBER_REC_MASK) == (BUSMEMBER_ME & BUSMEMBER_REC_MASK)) {
// something to read for us
byte var = frame[3];
byte val = frame[4];
if (HeliosVentilationBindingConstants.DATAPOINTS.containsKey(var)) {
HeliosVentilationDataPoint datapoint = HeliosVentilationBindingConstants.DATAPOINTS.get(var);
if (datapoint.requiresReadModifyWrite()) {
memory.put(var, val);
}
do {
if (logger.isTraceEnabled()) {
String t = datapoint.asString(val);
logger.trace("Received {} = {}", datapoint, t);
}
updateStatus(ThingStatus.ONLINE);
pollCounter = 0;
updateState(datapoint.getName(), datapoint.asState(val));
datapoint = datapoint.next();
} while (datapoint != null);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Received unkown data @{} = {}", String.format("%02X ", var),
String.format("%02X ", val));
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.heliosventilation.internal;
import static org.openhab.binding.heliosventilation.internal.HeliosVentilationBindingConstants.THING_TYPE_HELIOS_VENTILATION;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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 HeliosVentilationHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.heliosventilation", service = ThingHandlerFactory.class)
public class HeliosVentilationHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.singleton(THING_TYPE_HELIOS_VENTILATION);
private final SerialPortManager serialPortManager;
@Activate
public HeliosVentilationHandlerFactory(@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 (THING_TYPE_HELIOS_VENTILATION.equals(thingTypeUID)) {
return new HeliosVentilationHandler(thing, serialPortManager);
}
return null;
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="heliosventilation" 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>HeliosVentilation Binding</name>
<description>This is the binding for Helios Ventilation Systems KWL EC 200/300/500 Pro. It requires a connection to the
RS485 bus used by the original remote controls KWL-FB (9417).</description>
<author>Raphael Mack</author>
</binding:binding>

View File

@@ -0,0 +1,85 @@
# binding
binding.heliosventilation.name = Helios KWL Binding
binding.heliosventilation.description = Dies ist das Binding für Helios KWL Systeme KWL EC 200/300/500 Pro. Es benötigt eine Verbindung zum RS485 Bus für die Fernbedienung KWL-FB (9417).
# thing types
thing-type.heliosventilation.ventilation.label = KWL
thing-type.heliosventilation.ventilation.description = Lüftungsgerät zur kontrollierten Wohnraumlüftung.
# thing type config description
thing-type.config.heliosventilation.ventilation.serialPort.label = Serielle Schnittstelle
thing-type.config.heliosventilation.ventilation.serialPort.description = Die Betreibssystembezeichnung des Gerätes für die serielle Schnittstelle. Gültige Werte sind z. B. COM1 unter Windows und /dev/ttyS0 oder /dev/ttyUSB0 unter GNU/Linux.
thing-type.config.heliosventilation.ventilation.pollPeriod.label = Poll-Zyklus
thing-type.config.heliosventilation.ventilation.pollPeriod.description = Der Poll-Zyklus in Sekunden, 0 für keine wiederkehrende Aktualisierung.
# channel types
channel-type.heliosventilation.outside_temperature.label = Außenlufttemperatur
channel-type.heliosventilation.outside_temperature.description = Temperatur gemessen im Außenluftstrom.
channel-type.heliosventilation.outgoing_temperature.label = Fortlufttemperatur
channel-type.heliosventilation.outgoing_temperature.description = Temperatur gemessen im Fortluftstrom (das Haus verlassend).
channel-type.heliosventilation.extract_temperature.label = Ablufttemperatur
channel-type.heliosventilation.extract_temperature.description = Temperatur gemessen im Abluftstrom (Raumluft).
channel-type.heliosventilation.supply_temperature.label = Zulufttemperatur
channel-type.heliosventilation.supply_temperature.description = Temperatur gemessen im Zuluftstrom (in den Raum einströhmend).
channel-type.heliosventilation.bypass_temperature.label = WRG Bypasstemperatur
channel-type.heliosventilation.bypass_temperature.description = Temperaturwert der von der Außenlufttemperatur überschritten werden muss um im Sommerbetrieb die Wärmetauscherumgehung zu aktivieren.
channel-type.heliosventilation.supply_stop_temperature.label = Frostschutztemperatur
channel-type.heliosventilation.supply_stop_temperature.description = Abschalttemperatur des Zuluftventilators zur Entfrostung des Wärmetauschers.
channel-type.heliosventilation.preheat_temperature.label = Vorheizregister
channel-type.heliosventilation.preheat_temperature.description = Solltemperatur der Vorheizung für die Entfrosterfunktion des Wärmetauschers.
channel-type.heliosventilation.supply_stop_temperature.label = Frostschutztemperatur
channel-type.heliosventilation.supply_stop_temperature.description = Abschalttemperatur des Zuluftventilators zur Entfrostung des Wärmetauschers.
channel-type.heliosventilation.preheat_temperature.label = Vorheizregister
channel-type.heliosventilation.preheat_temperature.description = Solltemperatur der Vorheizung für die Entforsterfunktion des Wärmetauschers.
channel-type.heliosventilation.fanspeed.label = Ventilator-Drehzahlstufe
channel-type.heliosventilation.min_fanspeed.label = Grundlüftungsstufe
channel-type.heliosventilation.max_fanspeed.label = Maximale Drehzahlstufe
channel-type.heliosventilation.rh_limit.label = Feuchtegrenzwert
channel-type.heliosventilation.hysteresis.label = Entfrosterhysterese
channel-type.heliosventilation.hysteresis.description = Hysterese der Entfrosterfunktion
channel-type.heliosventilation.set_temperature.label = Vorgabetemperatur
channel-type.heliosventilation.set_temperature.description = Vorgabe für die Raumtemperatur. Wird nicht von allen Helios KWLs verwendet.
channel-type.heliosventilation.dc_fan_extract.label = Ablufventilator
channel-type.heliosventilation.dc_fan_extract.description = Drehzahlreduktion des Abluftventilators.
channel-type.heliosventilation.dc_fan_supply.label = Zulufventilator
channel-type.heliosventilation.dc_fan_supply.description = Drehzahlreduktion des Zuluftventilators.
channel-type.heliosventilation.maintenance_interval.label = Wartungsinterval
channel-type.heliosventilation.radiator_type.label = Wasser-Nachheizregister
channel-type.heliosventilation.radiator_type.description = An für wasserbetriebenes, aus für elektrisches Nachheizregister.
channel-type.heliosventilation.switch_type.label = Stoßlüftungstaste
channel-type.heliosventilation.switch_type.description = An: Externer Taster wird als Stoßlüftungstaste verwendet. Aus: Taste ist in Funktion "Kamintaste".
channel-type.heliosventilation.cascade_mode.label = Kaskadensteuerung
channel-type.heliosventilation.rh_level_auto.label = Automatische Basisfeuchtebestimmung
channel-type.heliosventilation.power_state.label = Hauptschalter
channel-type.heliosventilation.power_state.description = Schaltet die KWL an/aus.
channel-type.heliosventilation.co2_state.label = CO2-Regelung
channel-type.heliosventilation.co2_state.description = Schaltet die CO2-Sensor-basierte Regelung an/aus.
channel-type.heliosventilation.rh_state.label = Feuchteregelung
channel-type.heliosventilation.rh_state.description = Schaltet die Feuchte-basierte Regelung an/aus.
channel-type.heliosventilation.winter_state.label = Winterbetrieb
channel-type.heliosventilation.winter_state.description = Schaltet die KWL in Winterbetrieb um die Bypass-Funktion zu deaktivieren. Falls Winterbetrieb aus ist, wird zur Kühlung der Wärmetauscher umgangen, falls die Außenluft kälter als die Abluft ist.
channel-type.heliosventilation.adjust_interval.label = Regelintervall

View File

@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="heliosventilation"
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">
<!-- Ventilation Thing Type -->
<thing-type id="ventilation">
<label>HeliosVentilation (KWL)</label>
<description>A domestic ventilation system (KWL) from Helios.</description>
<channels>
<channel id="outsideTemp" typeId="outside_temperature"/>
<channel id="outgoingTemp" typeId="outgoing_temperature"/>
<channel id="extractTemp" typeId="extract_temperature"/>
<channel id="supplyTemp" typeId="supply_temperature"/>
<channel id="setTemp" typeId="set_temperature"/>
<channel id="bypassTemp" typeId="bypass_temperature"/>
<channel id="supplyStopTemp" typeId="supply_stop_temperature"/>
<channel id="preheatTemp" typeId="preheat_temperature"/>
<channel id="minFanspeed" typeId="min_fanspeed"/>
<channel id="maxFanspeed" typeId="max_fanspeed"/>
<channel id="fanspeed" typeId="fanspeed"/>
<channel id="rhLimit" typeId="rh_limit"/>
<channel id="hysteresis" typeId="hysteresis"/>
<channel id="DCFanExtract" typeId="dc_fan_extract"/>
<channel id="DCFanSupply" typeId="dc_fan_supply"/>
<channel id="maintenanceInterval" typeId="maintenance_interval"/>
<channel id="radiatorType" typeId="radiator_type"/>
<channel id="switchType" typeId="switch_type"/>
<channel id="cascade" typeId="cascade_mode"/>
<channel id="RHLevelAuto" typeId="rh_level_auto"/>
<channel id="powerState" typeId="power_state"/>
<channel id="co2State" typeId="co2_state"/>
<channel id="rhState" typeId="rh_state"/>
<channel id="winterMode" typeId="winter_state"/>
<channel id="adjustInveral" typeId="adjust_interval"/>
</channels>
<config-description>
<parameter name="serialPort" type="text" required="true">
<context>serial-port</context>
<label>RS485 Interface Serial Port</label>
<description>The serial port name for the RS485 interfaces. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or
/dev/ttyUSB0 for Linux.</description>
</parameter>
<parameter name="pollPeriod" type="integer" min="0" unit="s">
<label>Poll Period</label>
<description>The poll period in seconds use 0 for no polling.</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="outside_temperature" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Outside Temperature</label>
<description>Temperature measured in the outdoor air flow.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="extract_temperature" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Extract Temperature</label>
<description>Temperature measured in the extract (indoor, room temperature) air flow.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="supply_temperature" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Supply Temperature</label>
<description>Temperature measured in the supply (incoming) air flow.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="outgoing_temperature" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Outgoing Temperature</label>
<description>Temperature measured in the outgoing air flow.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="set_temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Set temperature for the supply air. Not used in all ventilation systems.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="false"/>
</channel-type>
<channel-type id="fanspeed" advanced="false">
<item-type>Number</item-type>
<label>Fanspeed</label>
<category>HVAC</category>
<state min="1" max="8" pattern="%d" readOnly="false"/>
</channel-type>
<channel-type id="min_fanspeed" advanced="true">
<item-type>Number</item-type>
<label>Minimal Fanspeed</label>
<category>HVAC</category>
<state min="1" max="8" pattern="%d" readOnly="false"/>
</channel-type>
<channel-type id="max_fanspeed" advanced="true">
<item-type>Number</item-type>
<label>Maximum Fanspeed</label>
<category>HVAC</category>
<state min="1" max="8" pattern="%d" readOnly="false"/>
</channel-type>
<channel-type id="bypass_temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Cell Bypass Temperature</label>
<description>Bypass temperature to disable the bypass function if outside temperature is below this threshold even if
ventilation system is in summer mode.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="false"/>
</channel-type>
<channel-type id="supply_stop_temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Supply Stop Temperature</label>
<description>Stop the supply fan if outside temperature is below this threshold.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="false"/>
</channel-type>
<channel-type id="preheat_temperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Preheat Temperature</label>
<description>Set temperature for preheater.</description>
<category>Temperature</category>
<state pattern="%d %unit%" readOnly="false"/>
</channel-type>
<channel-type id="rh_limit" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>RH Limit</label>
<description>Limit for relative humidity sensor.</description>
<category>Humidity</category>
<state pattern="%f %unit%" min="0" max="100" readOnly="false"/>
</channel-type>
<channel-type id="dc_fan_supply" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Supply Fan</label>
<description>Speed of the supply air fan (incoming air).</description>
<category>HVAC</category>
<state pattern="%f %unit%" min="0" max="100" readOnly="false"/>
</channel-type>
<channel-type id="dc_fan_extract" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Extract Fan</label>
<description>Speed of the extract air fan (outgoing air).</description>
<category>HVAC</category>
<state pattern="%f %unit%" min="0" max="100" readOnly="false"/>
</channel-type>
<channel-type id="hysteresis" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Hysteresis</label>
<description>Hysteresis on defroster temperature.</description>
<state pattern="%d %unit%" min="1" max="10" readOnly="false"/>
</channel-type>
<channel-type id="power_state" advanced="false">
<item-type>Switch</item-type>
<label>Power</label>
<description>State of the ventilation system.</description>
</channel-type>
<channel-type id="co2_state" advanced="false">
<item-type>Switch</item-type>
<label>CO2 Control</label>
<description>Control the ventilation system by CO2 sensor.</description>
</channel-type>
<channel-type id="rh_state" advanced="false">
<item-type>Switch</item-type>
<label>Humidity Control</label>
<description>Control the ventilation system by humidity sensor.</description>
</channel-type>
<channel-type id="winter_state" advanced="false">
<item-type>Switch</item-type>
<label>Winter Mode</label>
<description>Ventilation system is in winter mode and will not use bypass for cooling. If OFF, the bypass function
will be used for cooling if the outside temperature is above the Cell Bypass Temperature.</description>
</channel-type>
<channel-type id="rh_level_auto" advanced="true">
<item-type>Switch</item-type>
<label>Auto Humidity level</label>
</channel-type>
<channel-type id="radiator_type" advanced="true">
<item-type>Switch</item-type>
<label>Water radiator</label>
<description>Ventilation system with water radiator (ON) or electric radiator (OFF).</description>
</channel-type>
<channel-type id="switch_type" advanced="true">
<item-type>Switch</item-type>
<label>Boost switch</label>
<description>External switch is used for boost (ON) or fireplace (OFF).</description>
</channel-type>
<channel-type id="cascade_mode" advanced="true">
<item-type>Switch</item-type>
<label>Cascaded ventilation system</label>
</channel-type>
<channel-type id="adjust_interval" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Adjust Interval</label>
<category>HVAC</category>
<state pattern="%f %unit%" min="1" max="15" readOnly="false"/>
</channel-type>
<channel-type id="maintenance_interval" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Maintenance Interval</label>
<category>HVAC</category>
<state pattern="%f %unit%" min="1" max="15" readOnly="false"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,48 @@
#
# datapoints.properties - This file defines the datapoints of the Helios ventilation system
#
# Format: <name> = <address(:bitspec)>,writable,type
#
# bitspec is
# - a single digit in range 0-7 or
# - start:end (where start is the number of the LSB and end the number of the MSB of the field)
#
# type is one of
# - TEMPERATURE
# - FANSPEED
# - PERCENT
# - BYTE_PERCENT
# - SWITCH
# - NUMBER
# - HYSTERESIS
#
# on change of this file, ensure that the thing-types.xml is consistent
fanspeed = 0x29,true,FANSPEED
outsideTemp = 0x32,false,TEMPERATURE
outgoingTemp = 0x33,false,TEMPERATURE
extractTemp = 0x34,false,TEMPERATURE
supplyTemp = 0x35,false,TEMPERATURE
DCFanSupply = 0xB0,true,PERCENT
DCFanExtract = 0xB1,true,PERCENT
hysteresis = 0xB2,true,HYSTERESIS
setTemp = 0xA4,true,TEMPERATURE
maxFanspeed = 0xA5,true,FANSPEED
maintenanceInterval = 0xA6:0:3,true,NUMBER
preheatTemp = 0xA7,true,TEMPERATURE
supplyStopTemp = 0xA8,true,TEMPERATURE
minFanspeed = 0xA9,true,FANSPEED
rhLimit = 0xAE,true,BYTE_PERCENT
bypassTemp = 0xAF,true,TEMPERATURE
adjustInveral = 0xAA:0:3,true,NUMBER
RHLevelAuto = 0xAA:4,true,SWITCH
switchType = 0xAA:5,true,SWITCH # ON = boost, OFF = fireplace
radiatorType = 0xAA:6,true,SWITCH # ON = water, OFF = electric
cascade = 0xAA:7,true,SWITCH
powerState = 0xA3:0,true,SWITCH
co2State = 0xA3:1,true,SWITCH
rhState = 0xA3:2,true,SWITCH
winterMode = 0xA3:3,true,SWITCH # ON = bypass disabled