added migrated 2.x add-ons

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

View File

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

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.valloxmv</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,89 @@
# ValloxMV Binding
This binding is designed to connect to the web interface of Vallox MV series of ventilation unit.
It has been tested so far only with Vallox 350 MV and 510 MV.
## Supported Things
There is one thing (valloxmv) supporting the connection via the web interface of the Vallox MV. There is NO support of former modbus connected devices.
## Discovery
This binding does not support any discovery, IP address has to be provided.
## Thing Configuration
The Thing needs the information at which IP the web interface could be reached and how often the values should be updated.
Minimum update interval is limited to 15 sec in order to avoid polling again before results have been evaluated.
| Config | Description | Type | Default |
| :-------------------- |:------------------------------------------------------|:-----:|:-------:|
| ip | IP address of web interface |string | n/a |
| updateinterval | Interval in seconds in which the interface is polled |int | 60 |
## Channels
Overview of provided channels
| Channel ID | Vallox Name | Description | Read/Write | Values |
| :------------------------- | :--------------------------- |:-----------------------------------|:-:|:----------------------:|
| onoff | A_CYC_MODE | On off switch |rw| On/Off |
| state | _several_ | Current state of ventilation unit |rw| 1=FIREPLACE, 2=AWAY, 3=ATHOME, 4=BOOST |
| fanspeed | A_CYC_FAN_SPEED | Fan speed |r | 0 - 100 (%) |
| fanspeedextract | A_CYC_EXTR_FAN_SPEED | Fan speed of extracting fan |r | 1/min |
| fanspeedsupply | A_CYC_SUPP_FAN_SPEED | Fan speed of supplying fan |r | 1/min |
| tempinside | A_CYC_TEMP_EXTRACT_AIR | Extracted air temp |r | Number (°C) |
| tempoutside | A_CYC_TEMP_OUTDOOR_AIR | Outside air temp |r | Number (°C) |
| tempexhaust | A_CYC_TEMP_EXHAUST_AIR | Exhausted air temp |r | Number (°C) |
| tempincomingbeforeheating | A_CYC_TEMP_SUPPLY_CELL_AIR | Incoming air temp (pre heating) |r | Number (°C) |
| tempincoming | A_CYC_TEMP_SUPPLY_AIR | Incoming air temp |r | Number (°C) |
| humidity | A_CYC_RH_VALUE | Extracted air humidity |r | 0 - 100 (%) |
| cellstate | A_CYC_CELL_STATE | Current cell state |r | 0=heat recovery, 1=cool recovery, 2=bypass, 3=defrosting |
| uptimeyears | A_CYC_TOTAL_UP_TIME_YEARS | Total uptime years |r | Y |
| uptimehours | A_CYC_TOTAL_UP_TIME_HOURS | Total uptime hours |r | h |
| uptimehourscurrent | A_CYC_CURRENT_UP_TIME_HOURS | Current uptime in hours |r | h |
| filterchangeddate | A\_CYC\_FILTER\_CHANGED\_DAY/MONTH/YEAR | Last filter change |r | date |
| remainingfilterdays | A_CYC_CURRENT_UP_TIME_HOURS | Days until filter change |r | d |
| extrfanbalancebase | A_CYC_EXTR_FAN_BALANCE_BASE | Extract fan base speed |rw| 0 - 100 (%) |
| suppfanbalancebase | A_CYC_SUPP_FAN_BALANCE_BASE | Supply fan base speed |rw| 0 - 100 (%) |
| homespeedsetting | A_CYC_HOME_SPEED_SETTING | Home fan speed |rw| 0 - 100 (%) |
| awayspeedsetting | A_CYC_AWAY_SPEED_SETTING | Away fan speed |rw| 0 - 100 (%) |
| boostspeedsetting | A_CYC_BOOST_SPEED_SETTING | Boost fan speed |rw| 0 - 100 (%) |
| homeairtemptarget | A_CYC_HOME_AIR_TEMP_TARGET | Target temperature in home state |rw| Number (°C) |
| awayairtemptarget | A_CYC_AWAY_AIR_TEMP_TARGET | Target temperature in away state |rw| Number (°C) |
| boostairtemptarget | A_CYC_BOOST_AIR_TEMP_TARGET | Target temperature in boost state |rw| Number (°C) |
| boosttime | A_CYC_BOOST_TIME | Timer value in boost profile |rw| 1 - 65535 (min) |
| boosttimerenabled | A_CYC_BOOST_TIMER_ENABLED | Timer enabled setting in boost profile |rw| On/Off |
| fireplaceextrfan | A_CYC_FIREPLACE_EXTR_FAN | Fireplace profile extract fan speed |rw| 0 - 100 (%) |
| fireplacesuppfan | A_CYC_FIREPLACE_SUPP_FAN | Fireplace profile supply fan speed |rw| 0 - 100 (%) |
| fireplacetime | A_CYC_FIREPLACE_TIME | Timer value in fireplace profile |rw| 1 - 65535 (min) |
| fireplacetimerenabled | A_CYC_FIREPLACE_TIMER_ENABLED | Timer enabled setting in fireplace profile |rw| On/Off |
| extraairtemptarget | A_CYC_EXTRA_AIR_TEMP_TARGET | Target temperature in extra profile |rw| Number (°C) |
| extraextrfan | A_CYC_EXTRA_EXTR_FAN | Extra profile extract fan speed |rw| 0 - 100 (%) |
| extrasuppfan | A_CYC_EXTRA_EXTR_FAN | Extra profile supply fan speed |rw| 0 - 100 (%) |
| extratime | A_CYC_EXTRA_TIME | Timer value in extra profile |rw| 1 - 65535 (min) |
| extratimerenabled | A_CYC_EXTRA_TIMER_ENABLED | Timer enabled setting in extra profile |rw| On/Off |
| weeklytimerenabled | A_CYC_WEEKLY_TIMER_ENABLED | Weekly timer enabled setting |rw| On/Off |
## Example
### Things file ###
```
Thing valloxmv:valloxmv:lueftung [ip="192.168.1.3", updateinterval=60]
```
### Items file ###
```
Number State "Current state: [%d]" {channel="valloxmv:valloxmv:lueftung:state"}
Number FanSpeed "Fanspeed [%d %%]" {channel="valloxmv:valloxmv:lueftung:fanspeed"}
Number Temp_TempInside "Temp inside [%.1f °C]" <temperature> {channel="valloxmv:valloxmv:lueftung:tempinside"}
Number Temp_TempOutside "Temp outside [%.1f °C]" <temperature> {channel="valloxmv:valloxmv:lueftung:tempoutside"}
Number Temp_TempExhaust "Temp outgoing [%.1f °C]" <temperature> {channel="valloxmv:valloxmv:lueftung:tempexhaust"}
Number Temp_TempIncoming "Temp incoming [%.1f °C]" <temperature> {channel="valloxmv:valloxmv:lueftung:tempincoming"}
Number Humidity "Humidity [%d %%]" {channel="valloxmv:valloxmv:lueftung:humidity"}
```

File diff suppressed because it is too large Load Diff

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

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.valloxmv-${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-valloxmv" description="ValloxMV Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.valloxmv/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,268 @@
/**
* 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.valloxmv.internal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ValloxMVBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Björn Brings - Initial contribution
*/
@NonNullByDefault
public class ValloxMVBindingConstants {
private static final String BINDING_ID = "valloxmv";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_VALLOXMV = new ThingTypeUID(BINDING_ID, "valloxmv");
// 4 states of ventilation unit (Fireplace = 1, Away = 2, At home = 3, Boost = 4)
public static final int STATE_FIREPLACE = 1;
public static final int STATE_AWAY = 2;
public static final int STATE_ATHOME = 3;
public static final int STATE_BOOST = 4;
// List of all Channel ids
/**
* Ventilation unit powered on
*/
public static final String CHANNEL_ONOFF = "onoff";
/**
* Current state ventilation unit (Fireplace = 1, Away = 2, At home = 3, Boost = 4)
*/
public static final String CHANNEL_STATE = "state";
/**
* Current fan speed (0 - 100)
*/
public static final String CHANNEL_FAN_SPEED = "fanspeed";
/**
* Current fan speed of extracting fan (1/min)
*/
public static final String CHANNEL_FAN_SPEED_EXTRACT = "fanspeedextract";
/**
* Current fan speed of supplying fan (1/min)
*/
public static final String CHANNEL_FAN_SPEED_SUPPLY = "fanspeedsupply";
/**
* Current temperature inside the building
*/
public static final String CHANNEL_TEMPERATURE_INSIDE = "tempinside";
/**
* Current temperature outside the building
*/
public static final String CHANNEL_TEMPERATURE_OUTSIDE = "tempoutside";
/**
* Current temperature of the air flow exhausting the building.
*/
public static final String CHANNEL_TEMPERATURE_EXHAUST = "tempexhaust";
/**
* Current temperature of the air flow incoming to the building before heating (if optional heating module included
* in ventilation unit).
*/
public static final String CHANNEL_TEMPERATURE_INCOMING_BEFORE_HEATING = "tempincomingbeforeheating";
/**
* Current temperature of the air flow incoming to the building.
*/
public static final String CHANNEL_TEMPERATURE_INCOMING = "tempincoming";
/**
* Current humidity of the air flow exhausting the building.
*/
public static final String CHANNEL_HUMIDITY = "humidity";
/**
* Current cell state (0=heat recovery, 1=cool recovery, 2=bypass, 3=defrosting).
*/
public static final String CHANNEL_CELLSTATE = "cellstate";
/**
* Total uptime in years (+ uptime in hours = total uptime).
*/
public static final String CHANNEL_UPTIME_YEARS = "uptimeyears";
/**
* Total uptime in hours (+ uptime in years = total uptime).
*/
public static final String CHANNEL_UPTIME_HOURS = "uptimehours";
/**
* Current uptime in hours.
*/
public static final String CHANNEL_UPTIME_HOURS_CURRENT = "uptimehourscurrent";
/**
* Date filter was changed last time.
*/
public static final String CHANNEL_FILTER_CHANGED_DATE = "filterchangeddate";
/**
* Days until filter has to be changed.
*/
public static final String CHANNEL_REMAINING_FILTER_DAYS = "remainingfilterdays";
/**
* Extract fan base speed in % (0-100).
*/
public static final String CHANNEL_EXTR_FAN_BALANCE_BASE = "extrfanbalancebase";
/**
* Supply fan base speed in % (0-100).
*/
public static final String CHANNEL_SUPP_FAN_BALANCE_BASE = "suppfanbalancebase";
/**
* Home fan speed in % (0-100).
*/
public static final String CHANNEL_HOME_SPEED_SETTING = "homespeedsetting";
/**
* Away fan speed in % (0-100).
*/
public static final String CHANNEL_AWAY_SPEED_SETTING = "awayspeedsetting";
/**
* Boost fan speed in % (0-100).
*/
public static final String CHANNEL_BOOST_SPEED_SETTING = "boostspeedsetting";
/**
* Target temperature in home state.
*/
public static final String CHANNEL_HOME_AIR_TEMP_TARGET = "homeairtemptarget";
/**
* Target temperature in away state.
*/
public static final String CHANNEL_AWAY_AIR_TEMP_TARGET = "awayairtemptarget";
/**
* Target temperature in boost state.
*/
public static final String CHANNEL_BOOST_AIR_TEMP_TARGET = "boostairtemptarget";
/**
* Timer value setting in minutes of boost profile (1-65535).
*/
public static final String CHANNEL_BOOST_TIME = "boosttime";
/**
* Timer enabled setting in boost profile (Enabled = 1, Disabled = 0).
*/
public static final String CHANNEL_BOOST_TIMER_ENABLED = "boosttimerenabled";
/**
* Fireplace profile extract fan speed setting in % (0-100).
*/
public static final String CHANNEL_FIREPLACE_EXTR_FAN = "fireplaceextrfan";
/**
* Fireplace profile supply fan speed setting in % (0-100).
*/
public static final String CHANNEL_FIREPLACE_SUPP_FAN = "fireplacesuppfan";
/**
* Timer value setting in minutes of fireplace profile (1-65535).
*/
public static final String CHANNEL_FIREPLACE_TIME = "fireplacetime";
/**
* Timer enabled setting in fireplace profile (Enabled = 1, Disabled = 0).
*/
public static final String CHANNEL_FIREPLACE_TIMER_ENABLED = "fireplacetimerenabled";
/**
* Programmable profile enabled
* Not sure if this is needed at all, Vallox modbus document does not list this.
*/
// public static final String CHANNEL_EXTRA_ENABLED = "extraenabled";
/**
* Target temperature in programmable profile.
*/
public static final String CHANNEL_EXTRA_AIR_TEMP_TARGET = "extraairtemptarget";
/**
* Programmable profile extract fan speed setting in % (0-100).
*/
public static final String CHANNEL_EXTRA_EXTR_FAN = "extraextrfan";
/**
* Programmable profile supply fan speed setting in % (0-100).
*/
public static final String CHANNEL_EXTRA_SUPP_FAN = "extrasuppfan";
/**
* Timer value setting in minutes of programmable profile (1-65535).
*/
public static final String CHANNEL_EXTRA_TIME = "extratime";
/**
* Timer enabled setting in programmable profile (Enabled = 1, Disabled = 0).
*/
public static final String CHANNEL_EXTRA_TIMER_ENABLED = "extratimerenabled";
/**
* Weekly Timer enabled setting (Enabled = 1, Disabled = 0).
*/
public static final String CHANNEL_WEEKLY_TIMER_ENABLED = "weeklytimerenabled";
/**
* Set of writable channels that are Switches
*/
public static final Set<String> WRITABLE_CHANNELS_SWITCHES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(CHANNEL_ONOFF, CHANNEL_BOOST_TIMER_ENABLED,
CHANNEL_FIREPLACE_TIMER_ENABLED, CHANNEL_EXTRA_TIMER_ENABLED, CHANNEL_WEEKLY_TIMER_ENABLED)));
/**
*
* Set of writable channels that are dimensionless
*/
public static final Set<String> WRITABLE_CHANNELS_DIMENSIONLESS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(CHANNEL_EXTR_FAN_BALANCE_BASE, CHANNEL_SUPP_FAN_BALANCE_BASE,
CHANNEL_HOME_SPEED_SETTING, CHANNEL_AWAY_SPEED_SETTING, CHANNEL_BOOST_SPEED_SETTING,
CHANNEL_BOOST_TIME, CHANNEL_BOOST_TIMER_ENABLED, CHANNEL_FIREPLACE_EXTR_FAN,
CHANNEL_FIREPLACE_SUPP_FAN, CHANNEL_FIREPLACE_TIME, CHANNEL_FIREPLACE_TIMER_ENABLED,
CHANNEL_EXTRA_EXTR_FAN, CHANNEL_EXTRA_SUPP_FAN, CHANNEL_EXTRA_TIME, CHANNEL_EXTRA_TIMER_ENABLED,
CHANNEL_WEEKLY_TIMER_ENABLED)));
/**
* Set of writable channels that are temperatures
*/
public static final Set<String> WRITABLE_CHANNELS_TEMPERATURE = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(CHANNEL_HOME_AIR_TEMP_TARGET, CHANNEL_AWAY_AIR_TEMP_TARGET,
CHANNEL_BOOST_AIR_TEMP_TARGET, CHANNEL_EXTRA_AIR_TEMP_TARGET)));
// Thing configuration
/**
* Name of the configuration parameters
*/
public static final String CONFIG_UPDATE_INTERVAL = "updateinterval";
public static final String CONFIG_IP = "ip";
}

View File

@@ -0,0 +1,40 @@
/**
* 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.valloxmv.internal;
/**
* The {@link ValloxMVConfig} class holds the configuration properties of the thing.
*
* @author Björn Brings - Initial contribution
*/
public class ValloxMVConfig {
private String ip;
private int updateinterval;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getUpdateinterval() {
return updateinterval;
}
public void setUpdateinterval(int updateinterval) {
this.updateinterval = updateinterval;
}
}

View File

@@ -0,0 +1,208 @@
/**
* 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.valloxmv.internal;
import java.math.BigDecimal;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.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 ValloxMVHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Björn Brings - Initial contribution
*/
@NonNullByDefault
public class ValloxMVHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(ValloxMVHandler.class);
private @Nullable ScheduledFuture<?> readDataJob;
private @Nullable ValloxMVWebSocket valloxSocket;
private @Nullable WebSocketClient webSocketClient;
/**
* Refresh interval in seconds.
*/
private int readDataInterval;
/**
* IP of vallox ventilation unit web interface.
*/
public ValloxMVHandler(Thing thing, @Nullable WebSocketClient webSocketClient) {
super(thing);
this.webSocketClient = webSocketClient;
}
@SuppressWarnings({ "null", "unchecked" })
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (valloxSocket == null) {
return;
}
if (command instanceof RefreshType) {
// We don't have Vallox device values in memory - we cannot update channel with value
// We can re-schedule readDataJob in case next read is more than 5 seconds away
if (readDataJob != null) {
if ((!readDataJob.isDone()) && (readDataJob.getDelay(TimeUnit.MILLISECONDS) > 5000)) {
// Next read is more than 5 seconds away, re-schedule
// Cancel read data job
cancelReadDataJob();
// Schedule read data job with 2 seconds initial delay
scheduleReadDataJob(2);
}
}
} else {
String strUpdateValue = "";
if (ValloxMVBindingConstants.CHANNEL_STATE.equals(channelUID.getId())) {
try {
int cmd = Integer.parseInt(command.toString());
if ((cmd == ValloxMVBindingConstants.STATE_FIREPLACE)
|| (cmd == ValloxMVBindingConstants.STATE_ATHOME)
|| (cmd == ValloxMVBindingConstants.STATE_AWAY)
|| (cmd == ValloxMVBindingConstants.STATE_BOOST)) {
// logger.debug("Changing state to: {}", command);
strUpdateValue = command.toString();
}
} catch (NumberFormatException nfe) {
// Other commands like refresh
return;
}
} else if (ValloxMVBindingConstants.WRITABLE_CHANNELS_SWITCHES.contains(channelUID.getId())) {
if (ValloxMVBindingConstants.CHANNEL_ONOFF.equals(channelUID.getId())) {
// Vallox MV MODE: Normal mode = 0, Switch off = 5
strUpdateValue = (OnOffType.ON.equals(command)) ? "0" : "5";
} else {
// Switches with ON = 1, OFF = 0
strUpdateValue = (OnOffType.ON.equals(command)) ? "1" : "0";
}
} else if (ValloxMVBindingConstants.WRITABLE_CHANNELS_DIMENSIONLESS.contains(channelUID.getId())) {
if (command instanceof QuantityType) {
QuantityType<Dimensionless> quantity = (QuantityType<Dimensionless>) command;
strUpdateValue = Integer.toString(quantity.intValue());
}
} else if (ValloxMVBindingConstants.WRITABLE_CHANNELS_TEMPERATURE.contains(channelUID.getId())) {
if (command instanceof QuantityType) {
// Convert temperature to centiKelvin (= (Celsius * 100) + 27315 )
QuantityType<Temperature> quantity = ((QuantityType<Temperature>) command)
.toUnit(SmartHomeUnits.KELVIN);
if (quantity == null) {
return;
}
int centiKelvin = quantity.multiply(new BigDecimal(100)).intValue();
strUpdateValue = Integer.toString(centiKelvin);
}
} else {
// Not writable channel
return;
}
if (strUpdateValue != "") {
if (readDataJob != null) {
// Re-schedule readDataJob to read device values after data write
// Avoid re-scheduling job several times in case of subsequent data writes
long timeToRead = readDataJob.getDelay(TimeUnit.MILLISECONDS);
if ((!readDataJob.isDone()) && ((timeToRead < 2000) || (timeToRead > 5000))) {
// Next read is not within the next 2 to 5 seconds, cancel read data job
cancelReadDataJob();
// Schedule read data job with 5 seconds initial delay
scheduleReadDataJob(5);
}
}
// Send command and process response
valloxSocket.request(channelUID, strUpdateValue);
}
}
}
@Override
public void initialize() {
logger.debug("Initializing thing {}", getThing().getUID());
updateStatus(ThingStatus.UNKNOWN);
String ip = getConfigAs(ValloxMVConfig.class).getIp();
valloxSocket = new ValloxMVWebSocket(webSocketClient, ValloxMVHandler.this, ip);
logger.debug("Vallox MV IP address : {}", ip);
// Schedule read data job with 2 seconds initial delay
scheduleReadDataJob(2);
logger.debug("Thing {} initialized", getThing().getUID());
}
private void scheduleReadDataJob(int initialDelay) {
if (initialDelay < 0)
initialDelay = 0;
readDataInterval = getConfigAs(ValloxMVConfig.class).getUpdateinterval();
if (readDataInterval < 15)
readDataInterval = 60;
logger.debug("Data table request interval {} seconds, Request in {} seconds", readDataInterval, initialDelay);
readDataJob = scheduler.scheduleWithFixedDelay(() -> {
// Read all device values
valloxSocket.request(null, null);
}, initialDelay, readDataInterval, TimeUnit.SECONDS);
}
private void cancelReadDataJob() {
if (readDataJob != null) {
if (!readDataJob.isDone()) {
readDataJob.cancel(true);
logger.debug("Scheduled data table requests cancelled");
}
}
}
@Override
public void dispose() {
logger.debug("Disposing thing {}", getThing().getUID());
cancelReadDataJob();
logger.debug("Thing {} disposed", getThing().getUID());
}
@Override
protected void updateState(String strChannelName, State dt) {
super.updateState(strChannelName, dt);
}
@Override
protected void updateStatus(ThingStatus ts, ThingStatusDetail statusDetail) {
super.updateStatus(ts, statusDetail);
}
@Override
protected void updateStatus(ThingStatus ts) {
super.updateStatus(ts);
}
}

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.valloxmv.internal;
import static org.openhab.binding.valloxmv.internal.ValloxMVBindingConstants.THING_TYPE_VALLOXMV;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.core.io.net.http.WebSocketFactory;
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 ValloxMVHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Björn Brings - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.valloxmv")
@NonNullByDefault()
public class ValloxMVHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_VALLOXMV);
private final WebSocketClient webSocketClient;
@Activate
public ValloxMVHandlerFactory(@Reference final WebSocketFactory webSocketFactory) {
this.webSocketClient = webSocketFactory.getCommonWebSocketClient();
}
@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_VALLOXMV.equals(thingTypeUID)) {
return new ValloxMVHandler(thing, webSocketClient);
}
return null;
}
}

View File

@@ -0,0 +1,590 @@
/**
* 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.valloxmv.internal;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.core.library.types.DateTimeType;
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.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ValloxMVWebSocket} is responsible for socket communication with the vallox ventilation unit
*
* @author Björn Brings - Initial contribution
*/
public class ValloxMVWebSocket {
private final ValloxMVHandler voHandler;
private final WebSocketClient client;
private final URI destUri;
private ValloxMVWebSocketListener socket;
private int iBoostTime;
private OnOffType ooBoostTimerEnabled;
private int iFireplaceTime;
private OnOffType ooFireplaceTimerEnabled;
private final Logger logger = LoggerFactory.getLogger(ValloxMVWebSocket.class);
public ValloxMVWebSocket(WebSocketClient webSocketClient, ValloxMVHandler voHandler, String ip) {
this.voHandler = voHandler;
this.client = webSocketClient;
URI tempUri;
try {
tempUri = new URI("ws://" + ip + ":80");
} catch (URISyntaxException e) {
tempUri = null;
connectionError(e);
}
destUri = tempUri;
}
public void request(ChannelUID channelUID, String updateState) {
Future<?> sessionFuture = null;
try {
socket = new ValloxMVWebSocketListener(channelUID, updateState);
ClientUpgradeRequest request = new ClientUpgradeRequest();
logger.debug("Connecting to: {}", destUri);
sessionFuture = client.connect(socket, destUri, request);
socket.awaitClose(2, TimeUnit.SECONDS);
} catch (InterruptedException | IOException e) {
connectionError(e);
} catch (Exception e) {
logger.debug("Unexpected error");
connectionError(e);
} finally {
if (sessionFuture != null && !sessionFuture.isDone()) {
sessionFuture.cancel(true);
}
}
}
public void connectionError(Exception e) {
logger.debug("Error connecting vallox unit.", e);
voHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
}
@WebSocket
public class ValloxMVWebSocketListener {
private final CountDownLatch closeLatch = new CountDownLatch(1);
private final Logger logger = LoggerFactory.getLogger(ValloxMVWebSocketListener.class);
private final String updateState;
private ChannelUID channelUID;
private int iValloxCmd;
public ValloxMVWebSocketListener(ChannelUID channelUID, String updateState) {
this.updateState = updateState;
this.channelUID = channelUID;
}
@OnWebSocketConnect
public void onConnect(Session session) {
try {
logger.debug("Connected to: {}", session.getRemoteAddress().getAddress());
ByteBuffer buf = generateRequest();
session.getRemote().sendBytes(buf);
} catch (IOException e) {
connectionError(e);
}
}
/**
* Method to generate ByteBuffer request to be sent to vallox online websocket
* to request or set data
*
* @param mode 246 for data request, 249 for setting data
* @param hmParameters HashMap for setting data with register as key and value as value
* @return ByteBuffer to be sent to websocket
*/
public ByteBuffer generateCustomRequest(Integer mode, Map<Integer, Integer> hmParameters) {
// If we just want data the format is different so its just hardcoded here
if (mode == 246) {
// requestData (Length 3, Command to get data 246, 0, checksum [sum of everything before])
return ByteBuffer.wrap(new byte[] { 3, 0, (byte) 246, 0, 0, 0, (byte) 249, 0 });
}
int numberParameters = (hmParameters.size() * 2) + 2; // Parameters (key + value) + Mode + Checksum
int checksum = numberParameters;
// Allocate for bytebuffer incl. Length
ByteBuffer bb = ByteBuffer.allocate((numberParameters + 1) * 2)
.put(convertIntegerIntoByteBuffer(numberParameters));
// Put Mode, HashMap and checksum to ByteArray
bb.put(convertIntegerIntoByteBuffer(mode));
checksum += mode;
for (Map.Entry<Integer, Integer> i : hmParameters.entrySet()) {
bb.put(convertIntegerIntoByteBuffer(i.getKey()));
bb.put(convertIntegerIntoByteBuffer(i.getValue()));
checksum += i.getKey() + i.getValue();
}
// We have to make sure that checksum is within the range
// Checksum may hold larger value than 65535 from the above loop
// We will never reach integer max value in the loop so it is ok to do this after the loop
// This is not needed if we make sure that conversion to bytes will take care of it
// bitwise AND inside convertIntegerIntoByteBuffer() takes care of this
// checksum = checksum & 0xffff;
bb.put(convertIntegerIntoByteBuffer(checksum));
bb.position(0);
return bb;
}
// Convert Integer to ByteBuffer in Little-endian
public ByteBuffer convertIntegerIntoByteBuffer(Integer i) {
// Use bitwise operators to extract two rightmost bytes from the integer
byte b1 = (byte) (i & 0xff); // Rightmost byte
byte b2 = (byte) ((i >> 8) & 0xff); // Second rightmost byte
return ByteBuffer.wrap(new byte[] { b1, b2 });
}
public ByteBuffer generateRequest() {
if ((updateState == null) || (channelUID == null)) {
// requestData (Length 3, Command to get data 246, empty set, checksum [sum of everything before])
iValloxCmd = 246;
return generateCustomRequest(246, new HashMap<>());
}
String strChannelUIDid = channelUID.getId();
int iUpdateState = Integer.parseInt(updateState);
Map<Integer, Integer> request = new HashMap<>();
switch (strChannelUIDid) {
case ValloxMVBindingConstants.CHANNEL_STATE:
switch (iUpdateState) {
case ValloxMVBindingConstants.STATE_FIREPLACE:
// Fireplace (Length 6, Command to set data 249, CYC_BOOST_TIMER (4612) = 0,
// CYC_FIREPLACE_TIMER (4613) = value from CYC_FIREPLACE_TIME, checksum)
// CYC_FIREPLACE_TIME is read during READ_TABLES and stored into outer class variable
if (iFireplaceTime < 1) {
// use 15 minutes in case not initialized (should never happen)
iFireplaceTime = 15;
}
if (OnOffType.ON.equals(ooFireplaceTimerEnabled)) {
logger.debug("Changing to Fireplace profile, timer {} minutes", iFireplaceTime);
} else {
logger.debug("Changing to Fireplace profile, timer not enabled");
}
request.put(4612, 0);
request.put(4613, iFireplaceTime);
break;
case ValloxMVBindingConstants.STATE_ATHOME:
// At Home (Length 8, Command to set data 249, CYC_STATE (4609) = 0,
// CYC_BOOST_TIMER (4612) = 0, CYC_FIREPLACE_TIMER (4613) = 0, checksum)
logger.debug("Changing to At Home profile");
request.put(4609, 0);
request.put(4612, 0);
request.put(4613, 0);
break;
case ValloxMVBindingConstants.STATE_AWAY:
// Away (Length 8, Command to set data 249, CYC_STATE (4609) = 1,
// CYC_BOOST_TIMER (4612) = 0, CYC_FIREPLACE_TIMER (4613) = 0, checksum)
logger.debug("Changing to Away profile");
request.put(4609, 1);
request.put(4612, 0);
request.put(4613, 0);
break;
case ValloxMVBindingConstants.STATE_BOOST:
// Boost (Length 6, Command to set data 249,
// CYC_BOOST_TIMER (4612) = value from CYC_BOOST_TIME,
// CYC_FIREPLACE_TIMER (4613) = 0, checksum)
// CYC_BOOST_TIME is read during READ_TABLES and stored into outer class variable
if (iBoostTime < 1) {
// use 30 minutes in case not initialized (should never happen)
iBoostTime = 30;
}
if (OnOffType.ON.equals(ooBoostTimerEnabled)) {
logger.debug("Changing to Boost profile, timer {} minutes", iBoostTime);
} else {
logger.debug("Changing to Boost profile, timer not enabled");
}
request.put(4612, iBoostTime);
request.put(4613, 0);
break;
default:
// This should never happen. Let's get back to basic profile.
// Clearing boost and fireplace timers.
logger.debug("Incorrect profile requested, changing back to basic profile");
request.put(4612, 0);
request.put(4613, 0);
}
break;
case ValloxMVBindingConstants.CHANNEL_ONOFF:
request.put(4610, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTR_FAN_BALANCE_BASE:
request.put(20485, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_SUPP_FAN_BALANCE_BASE:
request.put(20486, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_HOME_SPEED_SETTING:
request.put(20507, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_AWAY_SPEED_SETTING:
request.put(20501, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_BOOST_SPEED_SETTING:
request.put(20513, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_HOME_AIR_TEMP_TARGET:
request.put(20508, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_AWAY_AIR_TEMP_TARGET:
request.put(20502, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_BOOST_AIR_TEMP_TARGET:
request.put(20514, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_BOOST_TIME:
iBoostTime = iUpdateState;
request.put(20544, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_BOOST_TIMER_ENABLED:
ooBoostTimerEnabled = OnOffType.from(Integer.toString(iUpdateState));
request.put(21766, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_FIREPLACE_EXTR_FAN:
request.put(20487, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_FIREPLACE_SUPP_FAN:
request.put(20488, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIME:
iFireplaceTime = iUpdateState;
request.put(20545, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIMER_ENABLED:
ooFireplaceTimerEnabled = OnOffType.from(Integer.toString(iUpdateState));
request.put(21767, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTRA_AIR_TEMP_TARGET:
request.put(20493, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTRA_EXTR_FAN:
request.put(20494, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTRA_SUPP_FAN:
request.put(20495, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTRA_TIME:
request.put(20496, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_EXTRA_TIMER_ENABLED:
request.put(21772, iUpdateState);
break;
case ValloxMVBindingConstants.CHANNEL_WEEKLY_TIMER_ENABLED:
request.put(4615, iUpdateState);
break;
default:
return null;
}
iValloxCmd = 249;
return generateCustomRequest(249, request);
}
@OnWebSocketMessage
public void onMessage(String message) {
logger.debug("Message from Server: {}", message);
}
@OnWebSocketError
public void onError(Throwable cause) {
voHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
logger.debug("Connection failed: {}", cause.getMessage());
}
@OnWebSocketMessage
public void onBinary(InputStream in) {
logger.debug("Got binary message");
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = in.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] bytes = buffer.toByteArray();
if ((bytes.length > 5) && (bytes.length % 2 == 0)) {
logger.debug("Response length: {} bytes", bytes.length);
} else {
logger.debug("Response corrupted, length: {} bytes", bytes.length);
return;
}
int iDataLength = bytes.length / 2;
// Verify responses to requests
if ((iValloxCmd == 249) || (iValloxCmd == 250)) {
// COMMAND_WRITE_DATA (249) or COMMAND_READ_DATA (250)
int iChecksum = 0;
int[] arriData = new int[iDataLength];
for (int i = 0; i < iDataLength; i++) {
arriData[i] = getNumberLE(bytes, (i * 2));
}
for (int i = 0; i < (iDataLength - 1); i++) {
iChecksum += arriData[i];
}
iChecksum &= 0xffff;
if ((arriData[0] != (iDataLength - 1)) || (arriData[iDataLength - 1] != iChecksum)) {
// Data length or Checksum do not match
logger.debug("Response corrupted, Data length or Checksum do not match");
return;
}
// COMMAND_WRITE_DATA (249)
if (iValloxCmd == 249) {
String strChannelUIDid = channelUID.getId();
if (arriData[1] == 245) {
// ACK
logger.debug("Channel {} successfully updated to {}", strChannelUIDid, updateState);
} else {
logger.debug("Channel {} could not be updated", strChannelUIDid);
}
return;
}
// COMMAND_READ_DATA (250)
/* Read data command is not implemented, response check is ready for future implementation
// @formatter:off
if ((((iDataLength - 1) % 2) != 0) || (iDataLength < 5)) {
logger.debug("Response corrupted, data length is wrong");
return;
}
// number of data pairs (address, value) = (iDataLength - 3) / 2
// First data pair (address, value) is in positions 2 and 3
// (address, value) pairs could be put into array or hashmap
// @formatter:on
*/
logger.debug("Vallox command {} not implemented", iValloxCmd);
return;
} else if (iValloxCmd == 246) {
// COMMAND_READ_TABLES (246)
if (iDataLength > 704) {
logger.debug("Data table response with {} values, updating to channels", iDataLength);
} else {
logger.debug("Response corrupted, data table response not complete");
return;
}
} else {
logger.debug("Vallox command {} not implemented", iValloxCmd);
return;
}
// COMMAND_READ_TABLES (246)
// Read values from received tables
int iFanspeed = getNumberBE(bytes, 128);
int iFanspeedExtract = getNumberBE(bytes, 144);
int iFanspeedSupply = getNumberBE(bytes, 146);
BigDecimal bdTempInside = getTemperature(bytes, 130);
BigDecimal bdTempExhaust = getTemperature(bytes, 132);
BigDecimal bdTempOutside = getTemperature(bytes, 134);
BigDecimal bdTempIncomingBeforeHeating = getTemperature(bytes, 136);
BigDecimal bdTempIncoming = getTemperature(bytes, 138);
int iHumidity = getNumberBE(bytes, 166);
int iStateOrig = getNumberBE(bytes, 214);
int iBoostTimer = getNumberBE(bytes, 220);
int iFireplaceTimer = getNumberBE(bytes, 222);
int iCellstate = getNumberBE(bytes, 228);
int iUptimeYears = getNumberBE(bytes, 230);
int iUptimeHours = getNumberBE(bytes, 232);
int iUptimeHoursCurrent = getNumberBE(bytes, 234);
int iRemainingTimeForFilter = getNumberBE(bytes, 236);
int iFilterChangedDateDay = getNumberBE(bytes, 496);
int iFilterChangedDateMonth = getNumberBE(bytes, 498);
int iFilterChangedDateYear = getNumberBE(bytes, 500);
Calendar cFilterChangedDate = Calendar.getInstance();
cFilterChangedDate.set(iFilterChangedDateYear + 2000,
iFilterChangedDateMonth - 1 /* Month is 0-based */, iFilterChangedDateDay, 0, 0, 0);
int iExtrFanBalanceBase = getNumberBE(bytes, 374);
int iSuppFanBalanceBase = getNumberBE(bytes, 376);
int iHomeSpeedSetting = getNumberBE(bytes, 418);
int iAwaySpeedSetting = getNumberBE(bytes, 406);
int iBoostSpeedSetting = getNumberBE(bytes, 430);
BigDecimal bdHomeAirTempTarget = getTemperature(bytes, 420);
BigDecimal bdAwayAirTempTarget = getTemperature(bytes, 408);
BigDecimal bdBoostAirTempTarget = getTemperature(bytes, 432);
// Using outer class variable for boost time and timer enabled
iBoostTime = getNumberBE(bytes, 492);
ooBoostTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 528)));
int iFireplaceExtrFan = getNumberBE(bytes, 378);
int iFireplaceSuppFan = getNumberBE(bytes, 380);
// Using outer class variable for fireplace time and timer enabled
iFireplaceTime = getNumberBE(bytes, 494);
ooFireplaceTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 530)));
BigDecimal bdExtraAirTempTarget = getTemperature(bytes, 390);
int iExtraExtrFan = getNumberBE(bytes, 392);
int iExtraSuppFan = getNumberBE(bytes, 394);
int iExtraTime = getNumberBE(bytes, 396);
OnOffType ooExtraTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 540)));
OnOffType ooWeeklyTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 226)));
BigDecimal bdState;
if (iFireplaceTimer > 0) {
bdState = new BigDecimal(ValloxMVBindingConstants.STATE_FIREPLACE);
} else if (iBoostTimer > 0) {
bdState = new BigDecimal(ValloxMVBindingConstants.STATE_BOOST);
} else if (iStateOrig == 1) {
bdState = new BigDecimal(ValloxMVBindingConstants.STATE_AWAY);
} else {
bdState = new BigDecimal(ValloxMVBindingConstants.STATE_ATHOME);
}
OnOffType ooOnOff = OnOffType.from(bytes[217] != 5);
// Update channels with read values
updateChannel(ValloxMVBindingConstants.CHANNEL_ONOFF, ooOnOff);
updateChannel(ValloxMVBindingConstants.CHANNEL_STATE, new DecimalType(bdState));
updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED,
new QuantityType<>(iFanspeed, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED_EXTRACT, new DecimalType(iFanspeedExtract));
updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED_SUPPLY, new DecimalType(iFanspeedSupply));
updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INSIDE,
new QuantityType<>(bdTempInside, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_OUTSIDE,
new QuantityType<>(bdTempOutside, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_EXHAUST,
new QuantityType<>(bdTempExhaust, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INCOMING_BEFORE_HEATING,
new QuantityType<>(bdTempIncomingBeforeHeating, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INCOMING,
new QuantityType<>(bdTempIncoming, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_HUMIDITY,
new QuantityType<>(iHumidity, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_CELLSTATE, new DecimalType(iCellstate));
updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_YEARS, new DecimalType(iUptimeYears));
updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_HOURS, new DecimalType(iUptimeHours));
updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_HOURS_CURRENT,
new DecimalType(iUptimeHoursCurrent));
updateChannel(ValloxMVBindingConstants.CHANNEL_FILTER_CHANGED_DATE, new DateTimeType(
ZonedDateTime.ofInstant(cFilterChangedDate.toInstant(), TimeZone.getDefault().toZoneId())));
updateChannel(ValloxMVBindingConstants.CHANNEL_REMAINING_FILTER_DAYS,
new QuantityType<>(iRemainingTimeForFilter, SmartHomeUnits.DAY));
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTR_FAN_BALANCE_BASE,
new QuantityType<>(iExtrFanBalanceBase, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_SUPP_FAN_BALANCE_BASE,
new QuantityType<>(iSuppFanBalanceBase, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_HOME_SPEED_SETTING,
new QuantityType<>(iHomeSpeedSetting, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_AWAY_SPEED_SETTING,
new QuantityType<>(iAwaySpeedSetting, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_SPEED_SETTING,
new QuantityType<>(iBoostSpeedSetting, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_HOME_AIR_TEMP_TARGET,
new QuantityType<>(bdHomeAirTempTarget, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_AWAY_AIR_TEMP_TARGET,
new QuantityType<>(bdAwayAirTempTarget, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_AIR_TEMP_TARGET,
new QuantityType<>(bdBoostAirTempTarget, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_TIME, new DecimalType(iBoostTime));
updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_TIMER_ENABLED, ooBoostTimerEnabled);
updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_EXTR_FAN,
new QuantityType<>(iFireplaceExtrFan, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_SUPP_FAN,
new QuantityType<>(iFireplaceSuppFan, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIME, new DecimalType(iFireplaceTime));
updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIMER_ENABLED, ooFireplaceTimerEnabled);
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_AIR_TEMP_TARGET,
new QuantityType<>(bdExtraAirTempTarget, SIUnits.CELSIUS));
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_EXTR_FAN,
new QuantityType<>(iExtraExtrFan, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_SUPP_FAN,
new QuantityType<>(iExtraSuppFan, SmartHomeUnits.PERCENT));
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_TIME, new DecimalType(iExtraTime));
updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_TIMER_ENABLED, ooExtraTimerEnabled);
updateChannel(ValloxMVBindingConstants.CHANNEL_WEEKLY_TIMER_ENABLED, ooWeeklyTimerEnabled);
voHandler.updateStatus(ThingStatus.ONLINE);
logger.debug("Data updated successfully");
} catch (IOException e) {
connectionError(e);
}
}
private void updateChannel(String strChannelName, State state) {
voHandler.updateState(strChannelName, state);
}
private int getNumberBE(byte[] bytes, int pos) {
return ((bytes[pos] & 0xff) << 8) | (bytes[pos + 1] & 0xff);
}
private int getNumberLE(byte[] bytes, int pos) {
return (bytes[pos] & 0xff) | ((bytes[pos + 1] & 0xff) << 8);
}
@SuppressWarnings("null")
private BigDecimal getTemperature(byte[] bytes, int pos) {
// Fetch 2 byte number out of bytearray representing the temperature in centiKelvin
BigDecimal bdTemperatureCentiKelvin = new BigDecimal(getNumberBE(bytes, pos));
// Return number converted to degree celsius (= (centiKelvin - 27315) / 100 )
return (new QuantityType<>(bdTemperatureCentiKelvin, MetricPrefix.CENTI(SmartHomeUnits.KELVIN))
.toUnit(SIUnits.CELSIUS)).toBigDecimal();
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
logger.debug("WebSocket Closed. Code: {}; Reason: {}", statusCode, reason);
this.closeLatch.countDown();
}
public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException {
return this.closeLatch.await(duration, unit);
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="valloxmv" 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>ValloxMV Binding</name>
<description>Binding for online interface of Vallox ventilation unit</description>
<author>Björn Brings</author>
</binding:binding>

View File

@@ -0,0 +1,71 @@
# binding
binding.valloxmv.name = Vallox Lüftungsanlagen
binding.valloxmv.description = Binding für das Online-Interface von Vallox Lüftungsanlagen
# thing types
thing-type.valloxmv.valloxmv.label = Vallox Lüftungsanlage
thing-type.valloxmv.valloxmv.description = Anbindung an das Online-Interface einer Vallox Lüftungsanlage
thing-type.config.valloxmv.valloxmv.ip.label = IP
thing-type.config.valloxmv.valloxmv.ip.description = IP Adresse oder DNS-Name des Online-Interface der Lüftungsanlage
thing-type.config.valloxmv.valloxmv.updateinterval.label = Update Intervall
thing-type.config.valloxmv.valloxmv.updateinterval.description = Intervall in dem die Daten aktualisiert werden in Sekunden (Standard: 60s)
# channel types
channel-type.valloxmv.onoff.label = An/Aus
channel-type.valloxmv.onoff.description = An/Aus-Schalter der Lüftungsanlage
channel-type.valloxmv.state.label = Profil
channel-type.valloxmv.state.description = Aktuell aktives Profil der Lüftungsanlage
channel-type.valloxmv.state.state.option.1 = Kaminfunktion
channel-type.valloxmv.state.state.option.2 = Abwesend
channel-type.valloxmv.state.state.option.3 = Anwesend
channel-type.valloxmv.state.state.option.4 = Stoßlüftung
channel-type.valloxmv.fanspeed.label = Ventilatorleistung
channel-type.valloxmv.fanspeed.description = Leistung in % (0-100)
channel-type.valloxmv.fanspeedextract.label = Ventilatorleistung Abluft
channel-type.valloxmv.fanspeedextract.description = Leistung Abluft in (1/min)
channel-type.valloxmv.fanspeedsupply.label = Ventilatorleistung Zuluft
channel-type.valloxmv.fanspeedsupply.description = Leistung Zuluft in (1/min)
channel-type.valloxmv.tempinside.label = Temperatur innen
channel-type.valloxmv.tempinside.description = Aktuelle Temperatur im Gebäude
channel-type.valloxmv.tempoutside.label = Temperatur außen
channel-type.valloxmv.tempoutside.description = Aktuelle Temperatur außerhalb des Gebäudes
channel-type.valloxmv.tempexhaust.label = Temperatur Abluft
channel-type.valloxmv.tempexhaust.description = Aktuelle Temperatur der ausgestoßenen Luft
channel-type.valloxmv.tempincomingbeforeheating.label = Temperatur Zuluft vor Heizung
channel-type.valloxmv.tempincomingbeforeheating.description = Aktuelle Temperatur der Zuluft vor Erwärmung durch Heizung (optionales Zubehör)
channel-type.valloxmv.tempincoming.label = Temperatur Zuluft
channel-type.valloxmv.tempincoming.description = Aktuelle Temperatur der Zuluft
channel-type.valloxmv.humidity.label = Luftfeuchtigkeit
channel-type.valloxmv.humidity.description = Aktuelle Luftfeuchtigkeit der Abluft
channel-type.valloxmv.cellstate.label = Status Wärmetauscher
channel-type.valloxmv.cellstate.description = Status Wärmetauscher
channel-type.valloxmv.cellstate.state.option.0 = Wärmerückgewinnung
channel-type.valloxmv.cellstate.state.option.1 = Kälterückgewinnung
channel-type.valloxmv.cellstate.state.option.2 = Deaktiviert (bypass)
channel-type.valloxmv.cellstate.state.option.3 = Enteisung
channel-type.valloxmv.uptimeyears.label = Zeit in Betrieb (Jahre)
channel-type.valloxmv.uptimeyears.description = Betriebszeit in Jahren (+ Betriebszeit in Stunden = Gesamtbetriebszeit)
channel-type.valloxmv.uptimehours.label = Zeit in Betrieb (Stunden)
channel-type.valloxmv.uptimehours.description = Betriebszeit in Stunden (+ Betriebszeit in Jahren = Gesamtbetriebszeit)
channel-type.valloxmv.uptimehourscurrent.label = Betriebszeit seit letztem Stromausfall (Stunden)
channel-type.valloxmv.uptimehourscurrent.description = Betriebszeit seit letztem Stromausfall in Stunden
channel-type.valloxmv.filterchangeddate.label = Letzter Filterwechsel
channel-type.valloxmv.filterchangeddate.description = Datum des letzten Filterwechsels
channel-type.valloxmv.remainingfilterdays.label = Tage bis Filtertausch
channel-type.valloxmv.remainingfilterdays.description = Anzahl an Tagen bis zum nächsten Filtertausch
channel-type.valloxmv.extrfanbalancebase.label = Abluft Basis Leistung
channel-type.valloxmv.extrfanbalancebase.description = Abluft Basis Leistung in % (0-100)
channel-type.valloxmv.suppfanbalancebase.label = Zuluft Basis Leistung
channel-type.valloxmv.suppfanbalancebase.description = Zuluft Basis Leistung in % (0-100)
channel-type.valloxmv.homespeedsetting.label = Ventilatorleistung Anwesend
channel-type.valloxmv.homespeedsetting.description = Ventilatorleistung im Profil Anwesend in % (0-100)
channel-type.valloxmv.awayspeedsetting.label = Ventilatorleistung Abwesend
channel-type.valloxmv.awayspeedsetting.description = Ventilatorleistung im Profil Abwesend in % (0-100)
channel-type.valloxmv.boostspeedsetting.label = Ventilatorleistung Stoßlüftung
channel-type.valloxmv.boostspeedsetting.description = Ventilatorleistung im Profil Stoßlüftung in % (0-100)
channel-type.valloxmv.homeairtemptarget.label = Zieltemperatur Anwesend
channel-type.valloxmv.homeairtemptarget.description = Zieltemperatur im Profil Anwesend
channel-type.valloxmv.awayairtemptarget.label = Zieltemperatur Abwesend
channel-type.valloxmv.awayairtemptarget.description = Zieltemperatur im Profil Abwesend
channel-type.valloxmv.boostairtemptarget.label = Zieltemperatur Stoßlüftung
channel-type.valloxmv.boostairtemptarget.description = Zieltemperatur im Profil Stoßlüftung

View File

@@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="valloxmv"
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">
<thing-type id="valloxmv">
<label>Vallox Ventilation Unit</label>
<description>Interface to online interface of Vallox ventilation unit</description>
<channels>
<channel id="onoff" typeId="onoff"/>
<channel id="state" typeId="state"/>
<channel id="fanspeed" typeId="fanspeed"/>
<channel id="fanspeedextract" typeId="fanspeedextract"/>
<channel id="fanspeedsupply" typeId="fanspeedsupply"/>
<channel id="tempinside" typeId="tempinside"/>
<channel id="tempoutside" typeId="tempoutside"/>
<channel id="tempexhaust" typeId="tempexhaust"/>
<channel id="tempincomingbeforeheating" typeId="tempincomingbeforeheating"/>
<channel id="tempincoming" typeId="tempincoming"/>
<channel id="humidity" typeId="humidity"/>
<channel id="cellstate" typeId="cellstate"/>
<channel id="uptimeyears" typeId="uptimeyears"/>
<channel id="uptimehours" typeId="uptimehours"/>
<channel id="uptimehourscurrent" typeId="uptimehourscurrent"/>
<channel id="filterchangeddate" typeId="filterchangeddate"/>
<channel id="remainingfilterdays" typeId="remainingfilterdays"/>
<channel id="extrfanbalancebase" typeId="extrfanbalancebase"/>
<channel id="suppfanbalancebase" typeId="suppfanbalancebase"/>
<channel id="homespeedsetting" typeId="homespeedsetting"/>
<channel id="awayspeedsetting" typeId="awayspeedsetting"/>
<channel id="boostspeedsetting" typeId="boostspeedsetting"/>
<channel id="homeairtemptarget" typeId="homeairtemptarget"/>
<channel id="awayairtemptarget" typeId="awayairtemptarget"/>
<channel id="boostairtemptarget" typeId="boostairtemptarget"/>
<channel id="boosttime" typeId="boosttime"/>
<channel id="boosttimerenabled" typeId="boosttimerenabled"/>
<channel id="fireplaceextrfan" typeId="fireplaceextrfan"/>
<channel id="fireplacesuppfan" typeId="fireplacesuppfan"/>
<channel id="fireplacetime" typeId="fireplacetime"/>
<channel id="fireplacetimerenabled" typeId="fireplacetimerenabled"/>
<channel id="extraairtemptarget" typeId="extraairtemptarget"/>
<channel id="extraextrfan" typeId="extraextrfan"/>
<channel id="extrasuppfan" typeId="extrasuppfan"/>
<channel id="extratime" typeId="extratime"/>
<channel id="extratimerenabled" typeId="extratimerenabled"/>
<channel id="weeklytimerenabled" typeId="weeklytimerenabled"/>
</channels>
<config-description>
<parameter name="ip" type="text" required="true">
<label>IP Address</label>
<context>network-address</context>
<description>IP address or host name of online interface of ventilation unit</description>
</parameter>
<parameter name="updateinterval" type="integer" required="false">
<label>Update Interval</label>
<description>Data update interval in seconds (default: 60s)</description>
<required>false</required>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="onoff">
<item-type>Switch</item-type>
<label>On</label>
<description>Power switch for ventilation unit</description>
</channel-type>
<channel-type id="state">
<item-type>Number</item-type>
<label>State</label>
<description>Current state of ventilation unit</description>
<state readOnly="false">
<options>
<option value="1">Fireplace</option>
<option value="2">Away</option>
<option value="3">At home</option>
<option value="4">Boost</option>
</options>
</state>
</channel-type>
<channel-type id="fanspeed">
<item-type>Number:Dimensionless</item-type>
<label>Fan Speed</label>
<description>Fan speed in % (0-100)</description>
<state readOnly="true" min="0" max="1" pattern="%d %unit%"/>
</channel-type>
<channel-type id="fanspeedextract" advanced="true">
<item-type>Number</item-type>
<label>Fan Speed Extracting</label>
<description>Fan speed of extracting fan (1/min)</description>
<state pattern="%d 1/min" readOnly="true"></state>
</channel-type>
<channel-type id="fanspeedsupply" advanced="true">
<item-type>Number</item-type>
<label>Fan Speed Supplying</label>
<description>Fan speed of supplying fan (1/min)</description>
<state pattern="%d 1/min" readOnly="true"></state>
</channel-type>
<channel-type id="tempinside">
<item-type>Number:Temperature</item-type>
<label>Temperature Inside</label>
<description>Current temperature inside the building.</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tempoutside">
<item-type>Number:Temperature</item-type>
<label>Temperature Outside</label>
<description>Current temperature outside the building.</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tempexhaust">
<item-type>Number:Temperature</item-type>
<label>Temperature Exhaust</label>
<description>Current temperature of the air flow exhausting the building.</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tempincomingbeforeheating" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Temperature Incoming Before Heating</label>
<description>
Current temperature of the air flow incoming to the building before heating (if optional heating module
included in ventilation unit).
</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="tempincoming">
<item-type>Number:Temperature</item-type>
<label>Temperature Incoming</label>
<description>Current temperature of the air flow incoming to the building.</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>Current humidity of the air flow exhausting the building.</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="cellstate">
<item-type>Number</item-type>
<label>Cell State</label>
<description>Current cell state</description>
<state readOnly="true">
<options>
<option value="0">Heat recovery</option>
<option value="1">Cool recovery</option>
<option value="2">Bypass</option>
<option value="3">Defrosting</option>
</options>
</state>
</channel-type>
<channel-type id="uptimeyears">
<item-type>Number</item-type>
<label>Total Uptime Years</label>
<description>Total uptime in years (+ uptime in hours = total uptime)</description>
<state pattern="%d Y" readOnly="true"/>
</channel-type>
<channel-type id="uptimehours">
<item-type>Number</item-type>
<label>Total Uptime Hours</label>
<description>Total uptime in hours (+ uptime in years = total uptime)</description>
<state pattern="%d h" readOnly="true"/>
</channel-type>
<channel-type id="uptimehourscurrent">
<item-type>Number</item-type>
<label>Current Uptime Hours</label>
<description>Current uptime in hours</description>
<state pattern="%d h" readOnly="true"/>
</channel-type>
<channel-type id="filterchangeddate" advanced="true">
<item-type>DateTime</item-type>
<label>Last Filter Change</label>
<description>Date filter was changed last time</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="remainingfilterdays" advanced="true">
<item-type>Number:Time</item-type>
<label>Next Filter Change</label>
<description>Days until filter has to be changed</description>
<state pattern="%d d" readOnly="true"/>
</channel-type>
<channel-type id="extrfanbalancebase" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Extract Fan Base Speed</label>
<description>Extract fan base speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="suppfanbalancebase" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Supply Fan Base Speed</label>
<description>Supply fan base speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="homespeedsetting" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Home Fan Speed</label>
<description>Home fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="awayspeedsetting" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Away Fan Speed</label>
<description>Away fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="boostspeedsetting" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Boost Fan Speed</label>
<description>Boost fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="homeairtemptarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Home Target Temperature</label>
<description>Target temperature in home state</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="awayairtemptarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Away Target Temperature</label>
<description>Target temperature in away state</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="boostairtemptarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Boost Target Temperature</label>
<description>Target temperature in boost state</description>
<category>Temperature</category>
<state pattern="%.2f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="boosttime" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Boost Timer</label>
<description>Boost profile timer value in minutes</description>
<state readOnly="false" min="1" max="65535" pattern="%d min"/>
</channel-type>
<channel-type id="boosttimerenabled" advanced="true">
<item-type>Switch</item-type>
<label>Boost Timer Enabled</label>
<description>Timer enabled setting in boost profile</description>
</channel-type>
<channel-type id="fireplaceextrfan" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Fireplace Extract Fan Speed</label>
<description>Fireplace profile extract fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="fireplacesuppfan" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Fireplace Supply Fan Speed</label>
<description>Fireplace profile supply fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="fireplacetime" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Fireplace Timer</label>
<description>Fireplace profile timer value in minutes</description>
<state readOnly="false" min="1" max="65535" pattern="%d min"/>
</channel-type>
<channel-type id="fireplacetimerenabled" advanced="true">
<item-type>Switch</item-type>
<label>Fireplace Timer Enabled</label>
<description>Timer enabled setting in fireplace profile</description>
</channel-type>
<channel-type id="extraairtemptarget" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Extra Target Temperature</label>
<description>Target temperature in extra profile</description>
<category>Temperature</category>
<state readOnly="false" min="5" max="25" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="extraextrfan" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Extra Extract Fan Speed</label>
<description>Extra profile extract fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="extrasuppfan" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Extra Supply Fan Speed</label>
<description>Extra profile supply fan speed in % (0-100)</description>
<state readOnly="false" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="extratime" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Extra Timer</label>
<description>Extra profile timer value in minutes</description>
<state readOnly="false" min="1" max="65535" pattern="%d min"/>
</channel-type>
<channel-type id="extratimerenabled" advanced="true">
<item-type>Switch</item-type>
<label>Extra Timer Enabled</label>
<description>Timer enabled setting in extra profile</description>
</channel-type>
<channel-type id="weeklytimerenabled" advanced="true">
<item-type>Switch</item-type>
<label>Weekly Timer Enabled</label>
<description>Weekly timer enabled setting</description>
</channel-type>
</thing:thing-descriptions>