added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.danfossairunit/.classpath
Normal file
32
bundles/org.openhab.binding.danfossairunit/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.danfossairunit/.project
Normal file
23
bundles/org.openhab.binding.danfossairunit/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.danfossairunit</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>
|
||||
13
bundles/org.openhab.binding.danfossairunit/NOTICE
Normal file
13
bundles/org.openhab.binding.danfossairunit/NOTICE
Normal 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/openhab2-addons
|
||||
82
bundles/org.openhab.binding.danfossairunit/README.md
Normal file
82
bundles/org.openhab.binding.danfossairunit/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# DanfossAirUnit Binding
|
||||
|
||||
This binding supports controlling and monitoring [Danfoss air units](https://www.danfoss.com/en/products/energy-recovery-devices/dhs/heat-recovery-ventilation/air-units/) via Ethernet connection.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding has been tested/reported to work with the Danfoss Air w2 / a2 / a3 devices.
|
||||
|
||||
## Discovery
|
||||
|
||||
Air units in the LAN are automatically discovered via broadcast and added to the Inbox.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
These are the available configuration parameters:
|
||||
|
||||
- `host` Hostname/IP of the air unit (automatically set by discovery service)
|
||||
- `refreshInterval` Time (in seconds) between monitoring requests to the air unit. Smaller values mean more network load, typically set between a few seconds and a minute. Defaults to 10 seconds.
|
||||
- `updateUnchangedValuesEveryMillis` Minimum time between state updates sent to the event bus for a particular channel when the state of the channel didn't change. This should avoid spamming the event bus with unnecessary updates. When set to 0, all channel state are updated every time the air unit requests are sent (see refresh interval). When set to a non zero value, unchanged values are only reported after the configured timespan has passed. Changed values are always sent to the event bus. Defaults to 60.000 (one minute), so updates are sent every minute or if the state of the channel changes.
|
||||
|
||||
## Channels
|
||||
|
||||
| channel | channel group | type | readable only (RO) or writable (RW) | description |
|
||||
|---|---|---|---|---|
|
||||
| current_time | main | DateTime | RO | Current time reported by the air unit. |
|
||||
| mode | main | String | RW | Value to control the operation mode of the air unit. One of DEMAND, PROGRAM, MANUAL and OFF |
|
||||
| manual_fan_speed | main | Dimmer | RW | Value to control the fan speed when in MANUAL mode (10 steps) |
|
||||
| supply_fan_speed | main | Number | RO | Current rotation of the fan supplying air to the rooms (in rpm) |
|
||||
| extract_fan_speed | main | Number | RO | Current rotation of the fan extracting air from the rooms (in rpm) |
|
||||
| supply_fan_step | main | Dimmer | RO | Current 10-step setting of the fan supplying air to the rooms |
|
||||
| extract_fan_step | main | Dimmer | RO | Current 10-step setting of the fan extracting air from the rooms |
|
||||
| boost | main | Switch | RW | Enables fan boost |
|
||||
| night_cooling | main | Switch | RW | Enables night cooling |
|
||||
| room_temp | temps | Number | RO | Temperature of the air in the room of the Air Dial |
|
||||
| room_temp_calculated | temps | Number | RO | Calculated Room Temperature |
|
||||
| outdoor_temp | temps | Number | RO | Temperature of the air outside |
|
||||
| humidity | humidity | Number | RO | Humidity |
|
||||
| bypass | recuperator | Switch | RW | Disables the heat exchange. Useful in summer when room temperature is above target and outside temperature is below target. |
|
||||
| supply_temp | recuperator | Number | RO | Temperature of air which is passed to the rooms |
|
||||
| extract_temp | recuperator | Number | RO | Temperature of the air as extracted from the rooms |
|
||||
| exhaust_temp | recuperator | Number | RO | Temperature of the air when pushed outside |
|
||||
| battery_life | service | Number | RO | Remaining Air Dial Battery Level (percentage) |
|
||||
| filter_life | service | Number | RO | Remaining life of filter until exchange is necessary (percentage) |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
### Things
|
||||
|
||||
Suppose your autodiscovered air unit is identified by the id "danfossairunit:airunit:-1062731769" (see section "Discovery").
|
||||
The channel will then be identified by `<air unit id>:<channel group>#<channel>`
|
||||
|
||||
You can also manually configure your air unit in case you don't want to use autodiscovery
|
||||
(e. g. if you want to have a portable configuration):
|
||||
Create a new file, e. g. `danfoss.things`, in your _things_ configuration folder:
|
||||
```
|
||||
Thing danfossairunit:airunit:myairunit [host="192.168.0.7",
|
||||
refreshInterval=5,
|
||||
updateUnchangedValuesEveryMillis=30000]
|
||||
```
|
||||
|
||||
### Items
|
||||
|
||||
```
|
||||
Dimmer Lueftung_Drehzahl_Manuell "Drehzahl Lüftung %" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:main#manual_fan_speed"}
|
||||
Number Lueftung_Drehzahl_Supply "Drehzahl Lüftung Zuluft (rpm)" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:main#supply_fan_speed"}
|
||||
Number Lueftung_Drehzahl_Extract "Drehzahl Lüftung Abluft (rpm)" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:main#extract_fan_speed"}
|
||||
String Lueftung_Mode "Betriebsart Lüftung" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:main#mode"}
|
||||
Switch Lueftung_Boost "Stoßlüftung" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:main#boost"}
|
||||
Switch Lueftung_Bypass "Lüftung Bypass" (All,Lueftung) {channel = "danfossairunit:airunit:-1062731769:recuperator#bypass"}
|
||||
```
|
||||
|
||||
### Sitemap
|
||||
|
||||
```
|
||||
Slider item=Lueftung_Drehzahl_Manuell
|
||||
Text item=Lueftung_Drehzahl_Supply
|
||||
Text item=Lueftung_Drehzahl_Extract
|
||||
Selection item=Lueftung_Mode mappings=[DEMAND="Bedarfslüftung", OFF="Aus", PROGRAM="Programm", MANUAL="manuell"]
|
||||
Switch item=Lueftung_Boost
|
||||
Switch item=Lueftung_Bypass
|
||||
```
|
||||
17
bundles/org.openhab.binding.danfossairunit/pom.xml
Normal file
17
bundles/org.openhab.binding.danfossairunit/pom.xml
Normal 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.danfossairunit</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: DanfossAirUnit Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.danfossairunit-${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-danfossairunit" description="DanfossAirUnit Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.danfossairunit/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This enum holds the available channels with their properties (name, ...) and read/write accessors to access
|
||||
* the corresponding values on the air unit.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum Channel {
|
||||
|
||||
// Main Channels
|
||||
|
||||
CHANNEL_CURRENT_TIME("current_time", ChannelGroup.MAIN, DanfossAirUnit::getCurrentTime),
|
||||
CHANNEL_MODE("mode", ChannelGroup.MAIN, DanfossAirUnit::getMode, DanfossAirUnit::setMode),
|
||||
CHANNEL_MANUAL_FAN_SPEED("manual_fan_speed", ChannelGroup.MAIN, DanfossAirUnit::getManualFanSpeed,
|
||||
DanfossAirUnit::setManualFanSpeed),
|
||||
CHANNEL_EXTRACT_FAN_SPEED("extract_fan_speed", ChannelGroup.MAIN, DanfossAirUnit::getExtractFanSpeed),
|
||||
CHANNEL_SUPPLY_FAN_SPEED("supply_fan_speed", ChannelGroup.MAIN, DanfossAirUnit::getSupplyFanSpeed),
|
||||
CHANNEL_EXTRACT_FAN_STEP("extract_fan_step", ChannelGroup.MAIN, DanfossAirUnit::getExtractFanStep),
|
||||
CHANNEL_SUPPLY_FAN_STEP("supply_fan_step", ChannelGroup.MAIN, DanfossAirUnit::getSupplyFanStep),
|
||||
|
||||
CHANNEL_BOOST("boost", ChannelGroup.MAIN, DanfossAirUnit::getBoost, DanfossAirUnit::setBoost),
|
||||
CHANNEL_NIGHT_COOLING("night_cooling", ChannelGroup.MAIN, DanfossAirUnit::getNightCooling,
|
||||
DanfossAirUnit::setNightCooling),
|
||||
|
||||
// Main Temperature Channels
|
||||
CHANNEL_ROOM_TEMP("room_temp", ChannelGroup.TEMPS, DanfossAirUnit::getRoomTemperature),
|
||||
CHANNEL_ROOM_TEMP_CALCULATED("room_temp_calculated", ChannelGroup.TEMPS,
|
||||
DanfossAirUnit::getRoomTemperatureCalculated),
|
||||
CHANNEL_OUTDOOR_TEMP("outdoor_temp", ChannelGroup.TEMPS, DanfossAirUnit::getOutdoorTemperature),
|
||||
|
||||
// Humidity Channel
|
||||
CHANNEL_HUMIDITY("humidity", ChannelGroup.HUMIDITY, DanfossAirUnit::getHumidity),
|
||||
|
||||
// recuperator channels
|
||||
CHANNEL_BYPASS("bypass", ChannelGroup.RECUPERATOR, DanfossAirUnit::getBypass, DanfossAirUnit::setBypass),
|
||||
CHANNEL_SUPPLY_TEMP("supply_temp", ChannelGroup.RECUPERATOR, DanfossAirUnit::getSupplyTemperature),
|
||||
CHANNEL_EXTRACT_TEMP("extract_temp", ChannelGroup.RECUPERATOR, DanfossAirUnit::getExtractTemperature),
|
||||
CHANNEL_EXHAUST_TEMP("exhaust_temp", ChannelGroup.RECUPERATOR, DanfossAirUnit::getExhaustTemperature),
|
||||
|
||||
// service channels
|
||||
CHANNEL_BATTERY_LIFE("battery_life", ChannelGroup.SERVICE, DanfossAirUnit::getBatteryLife),
|
||||
CHANNEL_FILTER_LIFE("filter_life", ChannelGroup.SERVICE, DanfossAirUnit::getFilterLife);
|
||||
|
||||
private final String channelName;
|
||||
private final ChannelGroup group;
|
||||
private final DanfossAirUnitReadAccessor readAccessor;
|
||||
@Nullable
|
||||
private final DanfossAirUnitWriteAccessor writeAccessor;
|
||||
|
||||
static Channel getByName(String name) {
|
||||
for (Channel channel : values()) {
|
||||
if (channel.getChannelName().equals(name)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("Unknown channel name: %s", name));
|
||||
}
|
||||
|
||||
Channel(String channelName, ChannelGroup group, DanfossAirUnitReadAccessor readAccessor) {
|
||||
this(channelName, group, readAccessor, null);
|
||||
}
|
||||
|
||||
Channel(String channelName, ChannelGroup group, DanfossAirUnitReadAccessor readAccessor,
|
||||
@Nullable DanfossAirUnitWriteAccessor writeAccessor) {
|
||||
this.channelName = channelName;
|
||||
this.group = group;
|
||||
this.readAccessor = readAccessor;
|
||||
this.writeAccessor = writeAccessor;
|
||||
}
|
||||
|
||||
public String getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
public ChannelGroup getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public DanfossAirUnitReadAccessor getReadAccessor() {
|
||||
return readAccessor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DanfossAirUnitWriteAccessor getWriteAccessor() {
|
||||
return writeAccessor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents a channel group, channels are divided into.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ChannelGroup {
|
||||
MAIN("main"),
|
||||
TEMPS("temps"),
|
||||
HUMIDITY("humidity"),
|
||||
RECUPERATOR("recuperator"),
|
||||
SERVICE("service");
|
||||
|
||||
private final String groupName;
|
||||
|
||||
ChannelGroup(String groupName) {
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Commands} interface holds the commands which can be send to the air unit to read/write values or trigger
|
||||
* actions.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class Commands {
|
||||
|
||||
public static byte[] DISCOVER_SEND = { 0x0c, 0x00, 0x30, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13 };
|
||||
public static byte[] DISCOVER_RECEIVE = { 0x0d, 0x00, 0x07, 0x00, 0x02, 0x02, 0x00 };
|
||||
public static byte[] EMPTY = {};
|
||||
public static byte[] GET_HISTORY = { 0x00, 0x30 };
|
||||
public static byte[] REGISTER_0_READ = { 0x00, 0x04 };
|
||||
public static byte[] REGISTER_1_READ = { 0x01, 0x04 };
|
||||
public static byte[] REGISTER_1_WRITE = { 0x01, 0x06 };
|
||||
public static byte[] REGISTER_2_READ = { 0x02, 0x04 };
|
||||
public static byte[] REGISTER_4_READ = { 0x04, 0x04 };
|
||||
public static byte[] REGISTER_6_READ = { 0x06, 0x04 };
|
||||
public static byte[] MODE = { 0x14, 0x12 };
|
||||
public static byte[] MANUAL_FAN_SPEED_STEP = { 0x15, 0x61 };
|
||||
public static byte[] SUPPLY_FAN_SPEED = { 0x14, 0x50 };
|
||||
public static byte[] EXTRACT_FAN_SPEED = { 0x14, 0x51 };
|
||||
public static byte[] SUPPLY_FAN_STEP = { 0x14, 0x28 };
|
||||
public static byte[] EXTRACT_FAN_STEP = { 0x14, 0x29 };
|
||||
public static byte[] BASE_IN = { 0x14, 0x40 };
|
||||
public static byte[] BASE_OUT = { 0x14, 0x41 };
|
||||
public static byte[] BYPASS = { 0x14, 0x60 };
|
||||
public static byte[] BYPASS_DEACTIVATION = { 0x14, 0x63 };
|
||||
public static byte[] BOOST = { 0x15, 0x30 };
|
||||
public static byte[] NIGHT_COOLING = { 0x15, 0x71 };
|
||||
public static byte[] AUTOMATIC_BYPASS = { 0x17, 0x06 };
|
||||
public static byte[] AUTOMATIC_RUSH_AIRING = { 0x17, 0x02 };
|
||||
public static byte[] HUMIDITY = { 0x14, 0x70 };
|
||||
public static byte[] ROOM_TEMPERATURE = { 0x03, 0x00 };
|
||||
public static byte[] ROOM_TEMPERATURE_CALCULATED = { 0x14, (byte) 0x96 };
|
||||
public static byte[] OUTDOOR_TEMPERATURE = { 0x03, 0x34 };
|
||||
public static byte[] SUPPLY_TEMPERATURE = { 0x14, 0x73 };
|
||||
public static byte[] EXTRACT_TEMPERATURE = { 0x14, 0x74 };
|
||||
public static byte[] EXHAUST_TEMPERATURE = { 0x14, 0x75 };
|
||||
public static byte[] BATTERY_LIFE = { 0x03, 0x0f };
|
||||
public static byte[] FILTER_LIFE = { 0x14, 0x6a };
|
||||
public static byte[] CURRENT_TIME = { 0x15, (byte) 0xe0 };
|
||||
public static byte[] AWAY_TO = { 0x15, 0x20 };
|
||||
public static byte[] AWAY_FROM = { 0x15, 0x21 };
|
||||
public static byte[] UNIT_SERIAL = { 0x00, 0x25 }; // endpoint 4
|
||||
public static byte[] UNIT_NAME = { 0x15, (byte) 0xe5 }; // endpoint 1
|
||||
public static byte[] CCM_SERIAL = { 0x14, 0x6a }; // endpoint 0
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import static org.openhab.binding.danfossairunit.internal.Commands.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
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.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnit} class represents the air unit device and build the commands to be sent by
|
||||
* {@link DanfossAirUnitCommunicationController}
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnit {
|
||||
|
||||
private final DanfossAirUnitCommunicationController communicationController;
|
||||
|
||||
public DanfossAirUnit(InetAddress inetAddr, int port) {
|
||||
this.communicationController = new DanfossAirUnitCommunicationController(inetAddr, port);
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
this.communicationController.disconnect();
|
||||
}
|
||||
|
||||
private boolean getBoolean(byte[] operation, byte[] register) throws IOException {
|
||||
return communicationController.sendRobustRequest(operation, register)[0] != 0;
|
||||
}
|
||||
|
||||
private void setSetting(byte[] register, boolean value) throws IOException {
|
||||
setSetting(register, value ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
private short getWord(byte[] operation, byte[] register) throws IOException {
|
||||
byte[] resultBytes = communicationController.sendRobustRequest(operation, register);
|
||||
return (short) ((resultBytes[0] << 8) | (resultBytes[1] & 0xFF));
|
||||
}
|
||||
|
||||
private byte getByte(byte[] operation, byte[] register) throws IOException {
|
||||
return communicationController.sendRobustRequest(operation, register)[0];
|
||||
}
|
||||
|
||||
private String getString(byte[] operation, byte[] register) throws IOException {
|
||||
// length of the string is stored in the first byte
|
||||
byte[] result = communicationController.sendRobustRequest(operation, register);
|
||||
return new String(result, 1, result[0], StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private void set(byte[] operation, byte[] register, byte value) throws IOException {
|
||||
byte[] valueArray = { value };
|
||||
communicationController.sendRobustRequest(operation, register, valueArray);
|
||||
}
|
||||
|
||||
private void set(byte[] operation, byte[] register, short value) throws IOException {
|
||||
communicationController.sendRobustRequest(operation, register, shortToBytes(value));
|
||||
}
|
||||
|
||||
private byte[] shortToBytes(short s) {
|
||||
return new byte[] { (byte) ((s & 0xFF00) >> 8), (byte) (s & 0x00FF) };
|
||||
}
|
||||
|
||||
private short getShort(byte[] operation, byte[] register) throws IOException {
|
||||
byte[] result = communicationController.sendRobustRequest(operation, register);
|
||||
return (short) ((result[0] << 8) + (result[1] & 0xff));
|
||||
}
|
||||
|
||||
private float getTemperature(byte[] operation, byte[] register)
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
short shortTemp = getShort(operation, register);
|
||||
float temp = ((float) shortTemp) / 100;
|
||||
if (temp <= -274 || temp > 100) {
|
||||
throw new UnexpectedResponseValueException(String.format("Invalid temperature: %s", temp));
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
private ZonedDateTime getTimestamp(byte[] operation, byte[] register)
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
byte[] result = communicationController.sendRobustRequest(operation, register);
|
||||
return asZonedDateTime(result);
|
||||
}
|
||||
|
||||
private ZonedDateTime asZonedDateTime(byte[] data) throws UnexpectedResponseValueException {
|
||||
int second = data[0];
|
||||
int minute = data[1];
|
||||
int hour = data[2] & 0x1f;
|
||||
int day = data[3] & 0x1f;
|
||||
int month = data[4];
|
||||
int year = data[5] + 2000;
|
||||
try {
|
||||
return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault());
|
||||
} catch (DateTimeException e) {
|
||||
String msg = String.format("Ignoring invalid timestamp %s.%s.%s %s:%s:%s", day, month, year, hour, minute,
|
||||
second);
|
||||
throw new UnexpectedResponseValueException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int asUnsignedByte(byte b) {
|
||||
return b & 0xFF;
|
||||
}
|
||||
|
||||
private static float asPercentByte(byte b) {
|
||||
float f = asUnsignedByte(b);
|
||||
return f * 100 / 255;
|
||||
}
|
||||
|
||||
private void setSetting(byte[] register, short value) throws IOException {
|
||||
byte[] valueArray = new byte[2];
|
||||
valueArray[0] = (byte) (value >> 8);
|
||||
valueArray[1] = (byte) value;
|
||||
|
||||
communicationController.sendRobustRequest(REGISTER_1_WRITE, register, valueArray);
|
||||
}
|
||||
|
||||
public String getUnitName() throws IOException {
|
||||
return getString(REGISTER_1_READ, UNIT_NAME);
|
||||
}
|
||||
|
||||
public String getUnitSerialNumber() throws IOException {
|
||||
return String.valueOf(getShort(REGISTER_4_READ, UNIT_SERIAL));
|
||||
}
|
||||
|
||||
public StringType getMode() throws IOException {
|
||||
return new StringType(Mode.values()[getByte(REGISTER_1_READ, MODE)].name());
|
||||
}
|
||||
|
||||
public PercentType getManualFanSpeed() throws IOException {
|
||||
return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, MANUAL_FAN_SPEED_STEP) * 10));
|
||||
}
|
||||
|
||||
public DecimalType getSupplyFanSpeed() throws IOException {
|
||||
return new DecimalType(BigDecimal.valueOf(getWord(REGISTER_4_READ, SUPPLY_FAN_SPEED)));
|
||||
}
|
||||
|
||||
public DecimalType getExtractFanSpeed() throws IOException {
|
||||
return new DecimalType(BigDecimal.valueOf(getWord(REGISTER_4_READ, EXTRACT_FAN_SPEED)));
|
||||
}
|
||||
|
||||
public PercentType getSupplyFanStep() throws IOException {
|
||||
return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, SUPPLY_FAN_STEP)));
|
||||
}
|
||||
|
||||
public PercentType getExtractFanStep() throws IOException {
|
||||
return new PercentType(BigDecimal.valueOf(getByte(REGISTER_4_READ, EXTRACT_FAN_STEP)));
|
||||
}
|
||||
|
||||
public OnOffType getBoost() throws IOException {
|
||||
return getBoolean(REGISTER_1_READ, BOOST) ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
public OnOffType getNightCooling() throws IOException {
|
||||
return getBoolean(REGISTER_1_READ, NIGHT_COOLING) ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
public OnOffType getBypass() throws IOException {
|
||||
return getBoolean(REGISTER_1_READ, BYPASS) ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
public DecimalType getHumidity() throws IOException {
|
||||
BigDecimal value = BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, HUMIDITY)));
|
||||
return new DecimalType(value.setScale(1, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getRoomTemperature() throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_1_READ, ROOM_TEMPERATURE);
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getRoomTemperatureCalculated()
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_0_READ, ROOM_TEMPERATURE_CALCULATED);
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getOutdoorTemperature() throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_1_READ, OUTDOOR_TEMPERATURE);
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getSupplyTemperature() throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_4_READ, SUPPLY_TEMPERATURE);
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getExtractTemperature() throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_4_READ, EXTRACT_TEMPERATURE);
|
||||
}
|
||||
|
||||
public QuantityType<Temperature> getExhaustTemperature() throws IOException, UnexpectedResponseValueException {
|
||||
return getTemperatureAsDecimalType(REGISTER_4_READ, EXHAUST_TEMPERATURE);
|
||||
}
|
||||
|
||||
private QuantityType<Temperature> getTemperatureAsDecimalType(byte[] operation, byte[] register)
|
||||
throws IOException, UnexpectedResponseValueException {
|
||||
BigDecimal value = BigDecimal.valueOf(getTemperature(operation, register));
|
||||
return new QuantityType<>(value.setScale(1, RoundingMode.HALF_UP), SIUnits.CELSIUS);
|
||||
}
|
||||
|
||||
public DecimalType getBatteryLife() throws IOException {
|
||||
return new DecimalType(BigDecimal.valueOf(asUnsignedByte(getByte(REGISTER_1_READ, BATTERY_LIFE))));
|
||||
}
|
||||
|
||||
public DecimalType getFilterLife() throws IOException {
|
||||
return new DecimalType(BigDecimal.valueOf(asPercentByte(getByte(REGISTER_1_READ, FILTER_LIFE))));
|
||||
}
|
||||
|
||||
public DateTimeType getCurrentTime() throws IOException, UnexpectedResponseValueException {
|
||||
ZonedDateTime timestamp = getTimestamp(REGISTER_1_READ, CURRENT_TIME);
|
||||
return new DateTimeType(timestamp);
|
||||
}
|
||||
|
||||
public PercentType setManualFanSpeed(Command cmd) throws IOException {
|
||||
return setPercentTypeRegister(cmd, MANUAL_FAN_SPEED_STEP);
|
||||
}
|
||||
|
||||
private PercentType setPercentTypeRegister(Command cmd, byte[] register) throws IOException {
|
||||
if (cmd instanceof PercentType) {
|
||||
byte value = (byte) ((((PercentType) cmd).intValue() + 5) / 10);
|
||||
set(REGISTER_1_WRITE, register, value);
|
||||
}
|
||||
return new PercentType(BigDecimal.valueOf(getByte(REGISTER_1_READ, register) * 10));
|
||||
}
|
||||
|
||||
private OnOffType setOnOffTypeRegister(Command cmd, byte[] register) throws IOException {
|
||||
if (cmd instanceof OnOffType) {
|
||||
set(REGISTER_1_WRITE, register, OnOffType.ON.equals(cmd) ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
return getBoolean(REGISTER_1_READ, register) ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
private StringType setStringTypeRegister(Command cmd, byte[] register) throws IOException {
|
||||
if (cmd instanceof StringType) {
|
||||
byte value = (byte) (Mode.valueOf(cmd.toString()).ordinal());
|
||||
set(REGISTER_1_WRITE, register, value);
|
||||
}
|
||||
|
||||
return new StringType(Mode.values()[getByte(REGISTER_1_READ, register)].name());
|
||||
}
|
||||
|
||||
public StringType setMode(Command cmd) throws IOException {
|
||||
return setStringTypeRegister(cmd, MODE);
|
||||
}
|
||||
|
||||
public OnOffType setBoost(Command cmd) throws IOException {
|
||||
return setOnOffTypeRegister(cmd, BOOST);
|
||||
}
|
||||
|
||||
public OnOffType setNightCooling(Command cmd) throws IOException {
|
||||
return setOnOffTypeRegister(cmd, NIGHT_COOLING);
|
||||
}
|
||||
|
||||
public OnOffType setBypass(Command cmd) throws IOException {
|
||||
return setOnOffTypeRegister(cmd, BYPASS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitBindingConstants {
|
||||
|
||||
public static String BINDING_ID = "danfossairunit";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample");
|
||||
|
||||
// List of all Channel ids
|
||||
public static String CHANNEL_1 = "channel1";
|
||||
|
||||
// The only thing type UIDs
|
||||
public static ThingTypeUID THING_TYPE_AIRUNIT = new ThingTypeUID(BINDING_ID, "airunit");
|
||||
|
||||
// The thing type as a set
|
||||
public static Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AIRUNIT);
|
||||
|
||||
// Properties
|
||||
public static String PROPERTY_UNIT_NAME = "Unit Name";
|
||||
public static String PROPERTY_SERIAL = "Serial Number";
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import static org.openhab.binding.danfossairunit.internal.Commands.EMPTY;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitCommunicationController} class does the actual network communication with the air unit.
|
||||
*
|
||||
* @author Robert Bach - initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitCommunicationController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitCommunicationController.class);
|
||||
|
||||
private final InetAddress inetAddr;
|
||||
private final int port;
|
||||
private boolean connected = false;
|
||||
private @Nullable Socket socket;
|
||||
private @Nullable OutputStream oStream;
|
||||
private @Nullable InputStream iStream;
|
||||
|
||||
public DanfossAirUnitCommunicationController(InetAddress inetAddr, int port) {
|
||||
this.inetAddr = inetAddr;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public synchronized void connect() throws IOException {
|
||||
if (connected) {
|
||||
return;
|
||||
}
|
||||
socket = new Socket(inetAddr, port);
|
||||
oStream = socket.getOutputStream();
|
||||
iStream = socket.getInputStream();
|
||||
connected = true;
|
||||
}
|
||||
|
||||
public synchronized void disconnect() {
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Connection to air unit could not be closed gracefully. {}", ioe.getMessage());
|
||||
} finally {
|
||||
socket = null;
|
||||
iStream = null;
|
||||
oStream = null;
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
|
||||
public byte[] sendRobustRequest(byte[] operation, byte[] register) throws IOException {
|
||||
return sendRobustRequest(operation, register, EMPTY);
|
||||
}
|
||||
|
||||
public synchronized byte[] sendRobustRequest(byte[] operation, byte[] register, byte[] value) throws IOException {
|
||||
connect();
|
||||
byte[] request = new byte[4 + value.length];
|
||||
System.arraycopy(operation, 0, request, 0, 2);
|
||||
System.arraycopy(register, 0, request, 2, 2);
|
||||
System.arraycopy(value, 0, request, 4, value.length);
|
||||
try {
|
||||
return sendRequestInternal(request);
|
||||
} catch (IOException ioe) {
|
||||
// retry once if there was connection problem
|
||||
disconnect();
|
||||
connect();
|
||||
return sendRequestInternal(request);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized byte[] sendRequestInternal(byte[] request) throws IOException {
|
||||
|
||||
if (oStream == null) {
|
||||
throw new IOException(
|
||||
String.format("Output stream is null while sending request: %s", Arrays.toString(request)));
|
||||
}
|
||||
oStream.write(request);
|
||||
oStream.flush();
|
||||
|
||||
byte[] result = new byte[63];
|
||||
if (iStream == null) {
|
||||
throw new IOException(
|
||||
String.format("Input stream is null while sending request: %s", Arrays.toString(request)));
|
||||
}
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
iStream.read(result, 0, 63);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitConfiguration} class contains configuration parameters for the air unit.
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitConfiguration {
|
||||
|
||||
public @Nullable String host;
|
||||
|
||||
public int refreshInterval = 10;
|
||||
|
||||
public long updateUnchangedValuesEveryMillis = 60000;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import static org.openhab.binding.danfossairunit.internal.DanfossAirUnitBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
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.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitHandler.class);
|
||||
private @NonNullByDefault({}) DanfossAirUnitConfiguration config;
|
||||
private @Nullable ValueCache valueCache;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable DanfossAirUnit hrv;
|
||||
|
||||
public DanfossAirUnitHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateAllChannels();
|
||||
} else {
|
||||
try {
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
if (danfossAirUnit != null) {
|
||||
Channel channel = Channel.getByName(channelUID.getIdWithoutGroup());
|
||||
DanfossAirUnitWriteAccessor writeAccessor = channel.getWriteAccessor();
|
||||
if (writeAccessor != null) {
|
||||
updateState(channelUID, writeAccessor.access(danfossAirUnit, command));
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
|
||||
"Air unit connection not initialized.");
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Ignoring unknown channel id: {}", channelUID.getIdWithoutGroup(), e);
|
||||
} catch (IOException ioe) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, ioe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
config = getConfigAs(DanfossAirUnitConfiguration.class);
|
||||
valueCache = new ValueCache(config.updateUnchangedValuesEveryMillis);
|
||||
try {
|
||||
hrv = new DanfossAirUnit(InetAddress.getByName(config.host), 30046);
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
thing.setProperty(PROPERTY_UNIT_NAME, danfossAirUnit.getUnitName());
|
||||
thing.setProperty(PROPERTY_SERIAL, danfossAirUnit.getUnitSerialNumber());
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::updateAllChannels, 5, config.refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (UnknownHostException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Unknown host: " + config.host);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAllChannels() {
|
||||
DanfossAirUnit danfossAirUnit = hrv;
|
||||
if (danfossAirUnit != null) {
|
||||
logger.debug("Updating DanfossHRV data '{}'", getThing().getUID());
|
||||
|
||||
for (Channel channel : Channel.values()) {
|
||||
if (Thread.interrupted()) {
|
||||
logger.debug("Polling thread interrupted...");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(),
|
||||
channel.getReadAccessor().access(danfossAirUnit));
|
||||
} catch (UnexpectedResponseValueException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
logger.debug(
|
||||
"Cannot update channel {}: an unexpected or invalid response has been received from the air unit: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
} catch (IOException e) {
|
||||
updateState(channel.getGroup().getGroupName(), channel.getChannelName(), UnDefType.UNDEF);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||
logger.debug("Cannot update channel {}: an error occurred retrieving the value: {}",
|
||||
channel.getChannelName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing Danfoss HRV handler '{}'", getThing().getUID());
|
||||
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
|
||||
if (hrv != null) {
|
||||
hrv.cleanUp();
|
||||
hrv = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState(String groupId, String channelId, State state) {
|
||||
if (valueCache.updateValue(channelId, state)) {
|
||||
updateState(new ChannelUID(thing.getUID(), groupId, channelId), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import static org.openhab.binding.danfossairunit.internal.DanfossAirUnitBindingConstants.THING_TYPE_AIRUNIT;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.danfossairunit", service = ThingHandlerFactory.class)
|
||||
public class DanfossAirUnitHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AIRUNIT);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_AIRUNIT)) {
|
||||
return new DanfossAirUnitHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitReadAccessor} encapsulates access to an air unit value to be read.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
public interface DanfossAirUnitReadAccessor {
|
||||
State access(DanfossAirUnit hrv) throws IOException, UnexpectedResponseValueException;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link DanfossAirUnitWriteAccessor} encapsulates access to an air unit value to be written.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
public interface DanfossAirUnitWriteAccessor {
|
||||
State access(DanfossAirUnit hrv, Command command) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Mode} enum represents an air unit operation mode.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum Mode {
|
||||
DEMAND,
|
||||
PROGRAM,
|
||||
MANUAL,
|
||||
OFF
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* An exception representing an unexpected value received from the air unit.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnexpectedResponseValueException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5727747058755880978L;
|
||||
|
||||
public UnexpectedResponseValueException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnexpectedResponseValueException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ValueCache} is responsible for holding the last value of the channels for a
|
||||
* certain amount of time {@link ValueCache#durationMs} to prevent unnecessary event bus updates if the value didn't
|
||||
* change.
|
||||
*
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValueCache {
|
||||
|
||||
private Map<String, StateWithTimestamp> stateByValue = new HashMap<>();
|
||||
|
||||
private final long durationMs;
|
||||
|
||||
public ValueCache(long durationMs) {
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or inserts the given value into the value cache. Returns true if there was no value in the cache
|
||||
* for the given channelId or if the value has updated to a different value or if the value is older than
|
||||
* the cache duration
|
||||
*/
|
||||
public boolean updateValue(String channelId, State newState) {
|
||||
long currentTimeMs = Calendar.getInstance().getTimeInMillis();
|
||||
StateWithTimestamp oldState = stateByValue.get(channelId);
|
||||
boolean writeToCache;
|
||||
if (oldState == null) {
|
||||
writeToCache = true;
|
||||
} else {
|
||||
writeToCache = !oldState.state.equals(newState) || oldState.timestamp < (currentTimeMs - durationMs);
|
||||
}
|
||||
if (writeToCache) {
|
||||
stateByValue.put(channelId, new StateWithTimestamp(newState, currentTimeMs));
|
||||
}
|
||||
return writeToCache;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private static class StateWithTimestamp {
|
||||
State state;
|
||||
long timestamp;
|
||||
|
||||
public StateWithTimestamp(State state, long timestamp) {
|
||||
this.state = state;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.danfossairunit.internal.DanfossAirUnitBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The Discovery service implementation to scan for available air units in the network via broadcast.
|
||||
*
|
||||
* @author Ralf Duckstein - Initial contribution
|
||||
* @author Robert Bach - heavy refactorings
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, immediate = true)
|
||||
@NonNullByDefault
|
||||
public class DanfossAirUnitDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final int BROADCAST_PORT = 30045;
|
||||
private static final byte[] DISCOVER_SEND = { 0x0c, 0x00, 0x30, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13 };
|
||||
private static final byte[] DISCOVER_RECEIVE = { 0x0d, 0x00, 0x07, 0x00, 0x02, 0x02, 0x00 };
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DanfossAirUnitDiscoveryService.class);
|
||||
|
||||
public DanfossAirUnitDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, 15, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
logger.debug("Start Danfoss Air CCM background discovery");
|
||||
scheduler.execute(this::discover);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
logger.debug("Start Danfoss Air CCM scan");
|
||||
discover();
|
||||
}
|
||||
|
||||
private synchronized void discover() {
|
||||
logger.debug("Try to discover all Danfoss Air CCM devices");
|
||||
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
if (interfaceAddress.getBroadcast() == null) {
|
||||
continue;
|
||||
}
|
||||
logger.debug("Sending broadcast on interface {} to discover Danfoss Air CCM device...",
|
||||
interfaceAddress.getAddress());
|
||||
sendBroadcastToDiscoverThing(socket, interfaceAddress.getBroadcast());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.debug("No Danfoss Air CCM device found. Diagnostic: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendBroadcastToDiscoverThing(DatagramSocket socket, InetAddress broadcastAddress) throws IOException {
|
||||
socket.setBroadcast(true);
|
||||
socket.setSoTimeout(500);
|
||||
// send discover
|
||||
byte[] sendBuffer = DISCOVER_SEND;
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, broadcastAddress, BROADCAST_PORT);
|
||||
socket.send(sendPacket);
|
||||
logger.debug("Discover message sent");
|
||||
|
||||
// wait for responses
|
||||
while (true) {
|
||||
byte[] receiveBuffer = new byte[7];
|
||||
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
|
||||
try {
|
||||
socket.receive(receivePacket);
|
||||
} catch (SocketTimeoutException e) {
|
||||
break; // leave the endless loop
|
||||
}
|
||||
|
||||
byte[] data = receivePacket.getData();
|
||||
if (Arrays.equals(data, DISCOVER_RECEIVE)) {
|
||||
logger.debug("Discover received correct response");
|
||||
|
||||
String host = receivePacket.getAddress().getHostName();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("host", host);
|
||||
|
||||
logger.debug("Adding a new Danfoss Air Unit CCM '{}' to inbox", host);
|
||||
|
||||
ThingUID uid = new ThingUID(THING_TYPE_AIRUNIT, String.valueOf(receivePacket.getAddress().hashCode()));
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withRepresentationProperty("host")
|
||||
.withProperties(properties).withLabel("Danfoss HRV").build();
|
||||
thingDiscovered(result);
|
||||
|
||||
logger.debug("Thing discovered '{}'", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="danfossairunit" 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>DanfossAirUnit Binding</name>
|
||||
<description>This is the binding for DanfossAirUnit.</description>
|
||||
<author>Ralf Duckstein, Robert Bach</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,199 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="danfossairunit"
|
||||
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">
|
||||
|
||||
<!--HRV -->
|
||||
<thing-type id="airunit">
|
||||
<label>Danfoss Air Unit</label>
|
||||
<description>The Danfoss Air Unit Heat Exchanger, CCM and Air Dial</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="main" typeId="main"/>
|
||||
<channel-group id="temps" typeId="temps"/>
|
||||
<channel-group id="humidity" typeId="humidity"/>
|
||||
<channel-group id="recuperator" typeId="recuperator"/>
|
||||
<channel-group id="service" typeId="service"/>
|
||||
</channel-groups>
|
||||
<properties>
|
||||
<property name="Unit Name">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
</properties>
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host</label>
|
||||
<context>network-address</context>
|
||||
<description>Host name or IP address of the Danfoss Air CCM</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" required="false" unit="s">
|
||||
<default>10</default>
|
||||
<label>Refresh Interval</label>
|
||||
<unitLabel>Seconds</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="updateUnchangedValuesEveryMillis" type="integer" min="0" unit="ms">
|
||||
<label>Interval for Updating Unchanged Values</label>
|
||||
<default>60000</default>
|
||||
<unitLabel>ms</unitLabel>
|
||||
<description>Interval to update unchanged values (to the event bus) in milliseconds. A value of 0 means that every
|
||||
value (received via polling from the air unit) is updated to the event bus, unchanged or not.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<!--Cannel Group Definitions -->
|
||||
<channel-group-type id="main">
|
||||
<label>Mode and Fan Speeds</label>
|
||||
<channels>
|
||||
<channel id="current_time" typeId="currentTime"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="manual_fan_speed" typeId="manualFanSpeed"/>
|
||||
<channel id="supply_fan_speed" typeId="supplyFanSpeed"/>
|
||||
<channel id="extract_fan_speed" typeId="extractFanSpeed"/>
|
||||
<channel id="supply_fan_step" typeId="supplyFanStep"/>
|
||||
<channel id="extract_fan_step" typeId="extractFanStep"/>
|
||||
<channel id="boost" typeId="switch">
|
||||
<label>Boost</label>
|
||||
<description>Enables fan boost</description>
|
||||
</channel>
|
||||
<channel id="night_cooling" typeId="switch">
|
||||
<label>Night Cooling</label>
|
||||
<description>Enables night cooling</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="temps">
|
||||
<label>Temperatures</label>
|
||||
<category>Temperature</category>
|
||||
<channels>
|
||||
<channel id="room_temp" typeId="temperature">
|
||||
<label>Room Temperature</label>
|
||||
<description>Temperature of the air in the room of the Air Dial</description>
|
||||
</channel>
|
||||
<channel id="room_temp_calculated" typeId="temperature">
|
||||
<label>Calculated Room Temperature</label>
|
||||
<description>Calculated Room Temperature</description>
|
||||
</channel>
|
||||
<channel id="outdoor_temp" typeId="temperature">
|
||||
<label>Outdoor Temperature</label>
|
||||
<description>Temperature of the air outside</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="humidity">
|
||||
<label>Humidity</label>
|
||||
<channels>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="recuperator" advanced="true">
|
||||
<label>Recuperator</label>
|
||||
<description>Heat exchaning device in the Air Unit</description>
|
||||
<channels>
|
||||
<channel id="bypass" typeId="switch">
|
||||
<label>Bypass</label>
|
||||
<description>Disables the heat exchange. Useful in summer when room temperature is above target and outside
|
||||
temperature is below target.</description>
|
||||
</channel>
|
||||
<channel id="supply_temp" typeId="temperature">
|
||||
<label>Supply Air Temperature</label>
|
||||
<description>Temperature of air which is passed to the rooms</description>
|
||||
</channel>
|
||||
<channel id="extract_temp" typeId="temperature">
|
||||
<label>Extract Air Temperature</label>
|
||||
<description>Temperature of the air as extracted from the rooms</description>
|
||||
</channel>
|
||||
<channel id="exhaust_temp" typeId="temperature">
|
||||
<label>Exhaust Air Temperature</label>
|
||||
<description>Temperature of the air when pushed outside</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="service" advanced="true">
|
||||
<label>Service</label>
|
||||
<channels>
|
||||
<channel id="battery_life" typeId="percentage">
|
||||
<label>Battery Life</label>
|
||||
<description>Remaining Air Dial Battery Level</description>
|
||||
</channel>
|
||||
<channel id="filter_life" typeId="percentage">
|
||||
<label>Remaining Filter Life</label>
|
||||
<description>Remaining life of filter until exchange is necessary</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!--Channel Definitions -->
|
||||
<channel-type id="currentTime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Current Time</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="mode">
|
||||
<item-type>String</item-type>
|
||||
<label>Mode</label>
|
||||
<description>Off, Demand, Manual, Program</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="DEMAND">Demand</option>
|
||||
<option value="PROGRAM">Program</option>
|
||||
<option value="MANUAL">Manual</option>
|
||||
<option value="OFF">Off</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="manualFanSpeed">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Manual Fan Speed</label>
|
||||
<state step="10" min="0" max="100"/>
|
||||
</channel-type>
|
||||
<channel-type id="supplyFanSpeed">
|
||||
<item-type>Number</item-type>
|
||||
<label>Supply Fan Speed</label>
|
||||
<state pattern="%.0f rpm" readOnly="true" min="0"/>
|
||||
</channel-type>
|
||||
<channel-type id="extractFanSpeed">
|
||||
<item-type>Number</item-type>
|
||||
<label>Extract Fan Speed</label>
|
||||
<state pattern="%.0f rpm" readOnly="true" min="0"/>
|
||||
</channel-type>
|
||||
<channel-type id="supplyFanStep">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Supply Fan Step</label>
|
||||
<state step="10" min="0" max="100" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="extractFanStep">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Extract Fan Step</label>
|
||||
<state step="10" min="0" max="100" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="percentage">
|
||||
<item-type>Number</item-type>
|
||||
<label>Percentage</label>
|
||||
<description>Read only percentage</description>
|
||||
<state pattern="%.0f %%" readOnly="true" min="0" max="100"/>
|
||||
</channel-type>
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number</item-type>
|
||||
<label>Humidity</label>
|
||||
<category>Humidity</category>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %%" readOnly="true" min="0" max="100">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Something that can be turned on or off</label>
|
||||
</channel-type>
|
||||
<channel-type id="temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Current temperature</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.danfossairunit.internal;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* @author Robert Bach - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValueCacheTest {
|
||||
|
||||
@Test
|
||||
public void updateValueNotInCache() {
|
||||
ValueCache valueCache = new ValueCache(10000);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateValueInCacheUnchangedWithinCacheDuration() {
|
||||
ValueCache valueCache = new ValueCache(10000);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
assertFalse(valueCache.updateValue("channel", OnOffType.ON));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateValueInCacheChangedWithinCacheDuration() {
|
||||
ValueCache valueCache = new ValueCache(10000);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.OFF));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateValueInCacheUnchangedButCacheDurationExpired() throws InterruptedException {
|
||||
ValueCache valueCache = new ValueCache(1);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
Thread.sleep(2);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateValueMultipleCacheUpdatesButNotReportedAsToUpdate() throws InterruptedException {
|
||||
ValueCache valueCache = new ValueCache(60);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
Thread.sleep(30);
|
||||
assertFalse(valueCache.updateValue("channel", OnOffType.ON));
|
||||
Thread.sleep(35);
|
||||
assertTrue(valueCache.updateValue("channel", OnOffType.ON));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user