added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user