/bundles/org.openhab.binding.velbus/ @cedricboon
/bundles/org.openhab.binding.velux/ @gs4711
/bundles/org.openhab.binding.venstarthermostat/ @hww3 @digitaldan
+/bundles/org.openhab.binding.ventaair/ @t2000
/bundles/org.openhab.binding.verisure/ @jannegpriv
/bundles/org.openhab.binding.vigicrues/ @clinique
/bundles/org.openhab.binding.vitotronic/ @steand
<artifactId>org.openhab.binding.venstarthermostat</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.ventaair</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.verisure</artifactId>
--- /dev/null
+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
--- /dev/null
+# VentaAir Binding
+
+This binding is for air humidifiers from Venta Air.
+Thankfully the vendor allows for communicating within the local network without needing any internet access or accounts.
+This is even stated in the official manual.
+Hence this binding communicates locally with the humidifier and is able to read out the current measurements and settings, as well as changing the settings.
+
+## Supported Things
+
+It currently supports the LW60-T device (`ThingType`: "lw60t") as well as a `ThingType` ("generic") for other models.
+For now the generic `ThingType` only adds the "boost" channel, but in the status reply from the device there is more which could be added in the future by someone who owns a different device.
+
+## Discovery
+
+This binding supports an automatic discovery for humidifiers that are connected to the local network and which are in the same broadcast domain.
+To do so, the binding listens to UDP port 48000 for data and creates `DiscoveryResult`s based on the received data from the device.
+This comes in handy for getting the MAC address for the device for example.
+Once the `DiscoveryResult` is added as a `Thing`, a connection to the device will be created and it will beep, showing a confirmation screen that the device "openHAB" would like to get access.
+After confirming this request, the user can link its items to receive data or control the device.
+
+## Thing Configuration
+
+There are three mandatory configuration parameters for a thing: `ipAddress`, `macAddress` and `deviceType`.
+
+| parameter | required | description |
+|----------|------------|-------------------------------------------|
+| ipAddress | Y | The IP Address or hostname of the device. |
+| macAddress | Y | The MAC address of the device. |
+| deviceType | Y | Defines the type of device. It is an integer value and its best to use the automatic discovery to obtain it from the device. |
+| pollingTime | N | The time interval in seconds in which the data should be polled from the device, default is 10 seconds. |
+| hash | N | It is a negative integer value and it is used by the device to identify a connection to a client, like the App from the vendor for example. (*) |
+
+(*) I do not know whether there are devices which are restricted to only one client, so I added this parameter to allow the user to set the same value as his App on the phone (can be obtained via sniffing the network).
+However, the LW60-T allows for multiple connections to different clients, identified by different `hash` values at the same time without issues.
+By default the binding uses "-42", so a new ID that is not known to the device and hence it asks for confirmation, see the Discovery section.
+
+Example Thing configuration:
+
+```
+Thing ventaair:lw60t:humidifier [ ipAddress="192.168.42.69", macAddress="f8:f0:05:a6:4e:03", deviceType=4, pollingTime=10, hash=-42]
+```
+
+## Channels
+
+These are the channels that are currently supported:
+
+
+| channel | type (RO=read-only) | description |
+|----------|--------|------------------------------|
+| power | Switch | This is the power on/off channel |
+| fanSpeed | Number | This is the channel to control the steps (in range 0-5 where 0 means "off") for the speed of the fan |
+| targetHumidity | Number | This channel sets the target humidity (in percent) that should be tried to reach by the device (allowed values: 30-70) |
+| timer | Number | This channel sets the power off timer to the set value in hours, i.e. 3 = turn off in 3 hours from now (allowed values: 0-9 where 0 means "off") |
+| sleepMode | Switch | This channel controls the sleep mode of the device (dims the display and slows down the fan) |
+| childLock | Switch | This is the control channel for the child lock |
+| automatic | Switch | This is the control channel to start the automatic operation mode of the device |
+| cleanMode | Switch (RO) | This is the channel that indicates if the device is in the cleaning mode |
+| temperature | Number:Temperature (RO) | This channel provides the current measured temperature in Celsius or Fahrenheit as configured on the device |
+| humidity | Number:Dimensionless (RO) | This channel provides the humidity measured by the device in percent |
+| waterLevel | Number (RO) | This channel indicates the water level of the tank where 1 is equal to the yellow "refill tank" warning on the device/App |
+| fanRPM | Number (RO) | This channel provides the speed of the ventilation fan |
+| timerTimePassed | Number:Time (RO) | If a timer has been set, this channel provides the minutes since when the timer was started |
+| operationTime | Number:Time (RO) | This channel provides the operation time of the device in hours |
+| discReplaceTime | Number:Time (RO) | This channel provides the time in how many hours the cleaning disc should be replaced |
+| cleaningTime | Number:Time (RO) | This channel provides the time in how many hours the device should be cleaned |
+| boost | Switch | This is the control channel for the boost mode (on some devices that supports it) |
+
+## Full Example
+
+Things:
+
+```
+Thing ventaair:lw60t:humidifier [ ipAddress="192.168.42.69", macAddress="f8:f0:05:a6:4e:03", deviceType=4, pollingTime=10, hash=-42]
+```
+
+Items:
+
+```
+Group gHumidifier "Air Humidifier" <humidity>
+
+Switch Humidifier_Power "Power: [%s]" (gHumidifier) { channel="ventaair:lw60t:humidifier:power" }
+Number Humidifier_FanSpeed "FanSpeed: [%s]" (gHumidifier) { channel="ventaair:lw60t:humidifier:fanSpeed" }
+Number Humidifier_TargetHum "Target Humidity: [%s]" (gHumidifier) { channel="ventaair:lw60t:humidifier:targetHumidity" }
+Number Humidifier_Timer "Timer: [%s]" (gHumidifier) { channel="ventaair:lw60t:humidifier:timer" }
+
+Switch Humidifier_SleepMode "SleepMode:" (gHumidifier) { channel="ventaair:lw60t:humidifier:sleepMode" }
+Switch Humidifier_ChildLock "ChildLock:" (gHumidifier) { channel="ventaair:lw60t:humidifier:childLock" }
+Switch Humidifier_Automatic "Automatic:" (gHumidifier) { channel="ventaair:lw60t:humidifier:automatic" }
+
+Switch Humidifier_CleaningMode "Cleaning mode:" (gHumidifier) { channel="ventaair:lw60t:humidifier:cleanMode" }
+
+Number:Temperature Humidifier_Temperature "Temp: [%.1f %unit%]" (gHumidifier) { channel="ventaair:lw60t:humidifier:temperature" }
+Number:Temperature Humidifier_temperatureF "Temp: [%.1f °F]" (gHumidifier) { channel="ventaair:lw60t:humidifier:temperature" }
+Number Humidifier_Humidity "Humidity: [%.1f %%]" (gHumidifier) { channel="ventaair:lw60t:humidifier:humidity" }
+
+Number Humidifier_WaterLevel "WaterLevel: [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:waterLevel" }
+Number Humidifier_FanRPM "Fan RPM: [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:fanRPM" }
+
+Number Humidifier_TimerTime "Timer time: [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:timerTimePassed" }
+Number Humidifier_OpTime "Operation Time: [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:operationTime" }
+Number Humidifier_ReplaceTime "Disc replace in (h): [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:discReplaceTime" }
+Number Humidifier_CleaningTime "Cleaning in (h): [%d]" (gHumidifier) { channel="ventaair:lw60t:humidifier:cleaningTime" }
+
+//for generic devices:
+Switch boost "Boost:" { channel="ventaair:generic:humidifier:boost" }
+```
+
+Sitemap:
+
+```
+Text item=Humidifier_Humidity
+Text item=Humidifier_Temperature
+Switch item=Humidifier_Power
+Switch item=Humidifier_SleepMode
+Switch item=Humidifier_FanSpeed icon="fan" mappings=[0="0", 1="1", 2="2", 3="3", 4="4", 5="5"]
+Switch item=Humidifier_TargetHum mappings=[30="30", 35="35", 40="40", 45="45", 50="50", 55="55", 60="60", 65="65", 70="70"]
+Switch item=Humidifier_Timer mappings=[0="0", 1="1", 3="3", 5="5", 7="7", 9="9"]
+Text item=Humidifier_WaterLevel
+Text item=Humidifier_FanRPM
+Text item=Humidifier_OpTime
+Text item=Humidifier_ReplaceTime
+Text item=Humidifier_CleaningTime
+Text item=Humidifier_TimerTime
+Switch item=Humidifier_CleaningModeActive
+Switch item=Humidifier_ChildLock
+Switch item=Humidifier_Automatic
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ 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.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.ventaair</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: VentaAir Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.ventaair-${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-ventaair" description="VentaAir Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ventaair/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.concurrent.ScheduledExecutorService;
+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.binding.ventaair.internal.VentaThingHandler.StateUpdatedCallback;
+import org.openhab.binding.ventaair.internal.message.action.Action;
+import org.openhab.binding.ventaair.internal.message.dto.CommandMessage;
+import org.openhab.binding.ventaair.internal.message.dto.DeviceInfoMessage;
+import org.openhab.binding.ventaair.internal.message.dto.Header;
+import org.openhab.binding.ventaair.internal.message.dto.Message;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.util.HexUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link Communicator} is responsible for sending/receiving commands to/from the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class Communicator {
+ private static final Duration COMMUNICATION_TIMEOUT = Duration.ofSeconds(5);
+
+ private final Logger logger = LoggerFactory.getLogger(Communicator.class);
+
+ private @Nullable String ipAddress;
+ private Header header;
+ private int pollingTimeInSeconds;
+ private StateUpdatedCallback callback;
+
+ private Gson gson = new Gson();
+
+ private @Nullable ScheduledFuture<?> pollingJob;
+
+ public Communicator(@Nullable String ipAddress, Header header, @Nullable BigDecimal pollingTime,
+ StateUpdatedCallback callback) {
+ this.ipAddress = ipAddress;
+ this.header = header;
+ if (pollingTime != null) {
+ this.pollingTimeInSeconds = pollingTime.intValue();
+ } else {
+ this.pollingTimeInSeconds = 60;
+ }
+ this.callback = callback;
+ }
+
+ /**
+ * Sends a request message to the device, reads the reply and informs the listener about the current device data
+ */
+ public void pollDataFromDevice() {
+ String messageJson = gson.toJson(new Message(header));
+
+ try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
+ socket.setSoTimeout((int) COMMUNICATION_TIMEOUT.toMillis());
+ InputStream input = socket.getInputStream();
+ OutputStream output = socket.getOutputStream();
+
+ byte[] dataToSend = buildMessageBytes(messageJson, "GET", "Complete");
+ // we write these lines to the log in order to help users with new/other venta devices, so they only need to
+ // enable debug logging
+ logger.debug("Sending request data message (String):\n{}", new String(dataToSend));
+ logger.debug("Sending request data message (bytes): [{}]", HexUtils.bytesToHex(dataToSend, ", "));
+ output.write(dataToSend);
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ String reply = "";
+ while ((reply = br.readLine()) != null) {
+ if (reply.startsWith("{")) {
+ // remove padding byte(s) after JSON data
+ String data = String.valueOf(reply.toCharArray(), 0, reply.length() - 1);
+ // we write this line to the log in order to help users with new/other venta devices, so they only
+ // need to enable debug logging
+ logger.debug("Got Data from device: {}", data);
+
+ DeviceInfoMessage deviceInfoMessage = gson.fromJson(data, DeviceInfoMessage.class);
+ if (deviceInfoMessage != null) {
+ callback.stateUpdated(deviceInfoMessage);
+ }
+ }
+ }
+ br.close();
+ socket.close();
+ } catch (IOException e) {
+ callback.communicationProblem();
+ }
+ }
+
+ private byte[] buildMessageBytes(String message, String method, String endpoint) throws IOException {
+ ByteArrayOutputStream getInfoOutputStream = new ByteArrayOutputStream();
+ getInfoOutputStream
+ .write(createMessageHeader(method, endpoint, message.length()).getBytes(StandardCharsets.UTF_8));
+ getInfoOutputStream.write(message.getBytes(StandardCharsets.UTF_8));
+ getInfoOutputStream.write(new byte[] { 0x1c, 0x00 });
+ return getInfoOutputStream.toByteArray();
+ }
+
+ private String createMessageHeader(String method, String endPoint, int contentLength) {
+ return method + " /" + endPoint + "\n" + "Content-Length: " + contentLength + "\n" + "\n";
+ }
+
+ /**
+ * Sends and {@link Action} to the device to set for example the FanSpeed or TargetHumidity
+ *
+ * @param action - The action to be send to the device
+ */
+ public void sendActionToDevice(Action action) throws IOException {
+ CommandMessage message = new CommandMessage(action, header);
+
+ String messageJson = gson.toJson(message);
+
+ try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
+ OutputStream output = socket.getOutputStream();
+
+ byte[] dataToSend = buildMessageBytes(messageJson, "POST", "Action");
+
+ // we write these lines to the log in order to help users with new/other venta devices, so they only need to
+ // enable debug logging
+ logger.debug("sending: {}", new String(dataToSend));
+ logger.debug("sendingArray: {}", Arrays.toString(dataToSend));
+
+ output.write(dataToSend);
+ socket.close();
+ }
+ }
+
+ /**
+ * Starts the polling job to fetch the current device data
+ *
+ * @param scheduler - The scheduler of the {@link ThingHandler}
+ */
+ public void startPollDataFromDevice(ScheduledExecutorService scheduler) {
+ stopPollDataFromDevice();
+ pollingJob = scheduler.scheduleWithFixedDelay(this::pollDataFromDevice, 2, pollingTimeInSeconds,
+ TimeUnit.SECONDS);
+ }
+
+ /**
+ * Stops the polling for device data
+ */
+ public void stopPollDataFromDevice() {
+ ScheduledFuture<?> localPollingJob = pollingJob;
+ if (localPollingJob != null && !localPollingJob.isCancelled()) {
+ localPollingJob.cancel(true);
+ }
+ logger.debug("Setting polling job to null");
+ pollingJob = null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link VentaAirBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Stefan Triller - Initial contribution
+ */
+@NonNullByDefault
+public class VentaAirBindingConstants {
+
+ private static final String BINDING_ID = "ventaair";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_LW60T = new ThingTypeUID(BINDING_ID, "lw60t");
+ public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, "generic");
+
+ // List of all Channel ids
+ public static final String CHANNEL_POWER = "power";
+ public static final String CHANNEL_FAN_SPEED = "fanSpeed";
+ public static final String CHANNEL_TARGET_HUMIDITY = "targetHumidity";
+ public static final String CHANNEL_TIMER = "timer";
+ public static final String CHANNEL_SLEEP_MODE = "sleepMode";
+ public static final String CHANNEL_BOOST = "boost";
+ public static final String CHANNEL_CHILD_LOCK = "childLock";
+ public static final String CHANNEL_AUTOMATIC = "automatic";
+ public static final String CHANNEL_TEMPERATURE = "temperature";
+ public static final String CHANNEL_HUMIDITY = "humidity";
+ public static final String CHANNEL_WATERLEVEL = "waterLevel";
+ public static final String CHANNEL_FAN_RPM = "fanRPM";
+ public static final String CHANNEL_CLEAN_MODE = "cleanMode";
+ public static final String CHANNEL_OPERATION_TIME = "operationTime";
+ public static final String CHANNEL_DISC_REPLACE_TIME = "discReplaceTime";
+ public static final String CHANNEL_CLEANING_TIME = "cleaningTime";
+ public static final String CHANNEL_TIMER_TIME_PASSED = "timerTimePassed";
+
+ public static final int PORT = 48000;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link VentaAirDeviceConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Stefan Triller - Initial contribution
+ */
+@NonNullByDefault
+public class VentaAirDeviceConfiguration {
+ public String ipAddress = "";
+ public String macAddress = "";
+ public BigDecimal deviceType = BigDecimal.ZERO;
+ // we all know that 42 is the answer to everything, so let's pick this one ;)
+ public BigDecimal hash = new BigDecimal("-42");
+ public BigDecimal pollingTime = BigDecimal.TEN;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal;
+
+import static org.openhab.binding.ventaair.internal.VentaAirBindingConstants.*;
+
+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 VentaAirHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Stefan Triller - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.ventaair", service = ThingHandlerFactory.class)
+public class VentaAirHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LW60T, THING_TYPE_GENERIC);
+
+ @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_LW60T.equals(thingTypeUID) || THING_TYPE_GENERIC.equals(thingTypeUID)) {
+ return new VentaThingHandler(thing);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.measure.Unit;
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Temperature;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ventaair.internal.message.action.Action;
+import org.openhab.binding.ventaair.internal.message.action.AllActions;
+import org.openhab.binding.ventaair.internal.message.action.AutomaticAction;
+import org.openhab.binding.ventaair.internal.message.action.BoostAction;
+import org.openhab.binding.ventaair.internal.message.action.ChildLockAction;
+import org.openhab.binding.ventaair.internal.message.action.FanAction;
+import org.openhab.binding.ventaair.internal.message.action.HumidityAction;
+import org.openhab.binding.ventaair.internal.message.action.PowerAction;
+import org.openhab.binding.ventaair.internal.message.action.SleepModeAction;
+import org.openhab.binding.ventaair.internal.message.action.TimerAction;
+import org.openhab.binding.ventaair.internal.message.dto.DeviceInfoMessage;
+import org.openhab.binding.ventaair.internal.message.dto.Header;
+import org.openhab.binding.ventaair.internal.message.dto.Info;
+import org.openhab.binding.ventaair.internal.message.dto.Measurements;
+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.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+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 VentaThingHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Stefan Triller - Initial contribution
+ */
+@NonNullByDefault
+public class VentaThingHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(VentaThingHandler.class);
+
+ private VentaAirDeviceConfiguration config = new VentaAirDeviceConfiguration();
+
+ private @Nullable Communicator communicator;
+
+ private Map<String, State> channelValueCache = new HashMap<>();
+
+ public VentaThingHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("Handle command={} for channel={} with channelID={}", command, channelUID, channelUID.getId());
+ if (command instanceof RefreshType) {
+ refreshChannelFromCache(channelUID);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case VentaAirBindingConstants.CHANNEL_POWER:
+ if (command instanceof OnOffType) {
+ dispatchActionToDevice(new PowerAction(command == OnOffType.ON));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_FAN_SPEED:
+ if (command instanceof DecimalType) {
+ int fanStage = ((DecimalType) command).intValue();
+ dispatchActionToDevice(new FanAction(fanStage));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY:
+ if (command instanceof DecimalType) {
+ int targetHumidity = ((DecimalType) command).intValue();
+ dispatchActionToDevice(new HumidityAction(targetHumidity));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_TIMER:
+ if (command instanceof DecimalType) {
+ int timer = ((DecimalType) command).intValue();
+ dispatchActionToDevice(new TimerAction(timer));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_SLEEP_MODE:
+ if (command instanceof OnOffType) {
+ dispatchActionToDevice(new SleepModeAction(command == OnOffType.ON));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_BOOST:
+ if (command instanceof OnOffType) {
+ dispatchActionToDevice(new BoostAction(command == OnOffType.ON));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_CHILD_LOCK:
+ if (command instanceof OnOffType) {
+ dispatchActionToDevice(new ChildLockAction(command == OnOffType.ON));
+ }
+ break;
+ case VentaAirBindingConstants.CHANNEL_AUTOMATIC:
+ if (command instanceof OnOffType) {
+ dispatchActionToDevice(new AutomaticAction(command == OnOffType.ON));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(VentaAirDeviceConfiguration.class);
+
+ updateStatus(ThingStatus.UNKNOWN);
+
+ String configErrorMessage;
+ if ((configErrorMessage = validateConfig()) != null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configErrorMessage);
+ return;
+ }
+
+ Header header = new Header(config.macAddress, config.deviceType.intValue(), config.hash.toString(), "openHAB");
+
+ communicator = new Communicator(config.ipAddress, header, config.pollingTime, new StateUpdatedCallback());
+ communicator.startPollDataFromDevice(scheduler);
+ }
+
+ private @Nullable String validateConfig() {
+ if (config.ipAddress.isEmpty()) {
+ return "IP address not set";
+ }
+ if (config.macAddress.isEmpty()) {
+ return "Mac Address not set, use discovery to find the correct one";
+ }
+ if (config.deviceType == BigDecimal.ZERO) {
+ return "Device Type not set, use discovery to find the correct one";
+ }
+ if (config.pollingTime.compareTo(BigDecimal.ZERO) <= 0) {
+ return "Polling time has to be larger than 0 seconds";
+ }
+
+ return null;
+ }
+
+ private void dispatchActionToDevice(Action action) {
+ Communicator localCommunicator = communicator;
+ if (localCommunicator != null) {
+ logger.debug("Dispatching Action={} to the device", action.getClass());
+ try {
+ localCommunicator.sendActionToDevice(action);
+ } catch (IOException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ return;
+ }
+ localCommunicator.pollDataFromDevice();
+ } else {
+ logger.error("Should send action={} to device but communicator is not available.", action.getClass());
+ }
+ }
+
+ private void refreshChannelFromCache(ChannelUID channelUID) {
+ State cachedState = channelValueCache.get(channelUID.getId());
+ if (cachedState != null) {
+ updateState(channelUID, cachedState);
+ }
+ }
+
+ private void updateProperties(Info info) {
+ Thing thing = getThing();
+ thing.setProperty("SWDisplay", info.getSwDisplay());
+ thing.setProperty("SWPower", info.getSwPower());
+ thing.setProperty("SWTouch", info.getSwTouch());
+ thing.setProperty("SWWIFI", info.getSwWIFI());
+ }
+
+ @Override
+ public void dispose() {
+ Communicator localCommunicator = communicator;
+ if (localCommunicator != null) {
+ localCommunicator.stopPollDataFromDevice();
+ }
+ communicator = null;
+ }
+
+ class StateUpdatedCallback {
+ /**
+ * Method to pass the data received from the device to the handler
+ *
+ * @param message - message containing the parsed data from the device
+ */
+ public void stateUpdated(DeviceInfoMessage message) {
+ if (messageIsEmpty(message)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+ "Please allow openHAB to access your device");
+ return;
+ }
+
+ AllActions actions = message.getCurrentActions();
+
+ Unit<Temperature> temperatureUnit = SIUnits.CELSIUS;
+
+ if (actions != null) {
+ OnOffType powerState = OnOffType.from(actions.isPower());
+ updateState(VentaAirBindingConstants.CHANNEL_POWER, powerState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_POWER, powerState);
+
+ DecimalType fanspeedState = new DecimalType(actions.getFanSpeed());
+ updateState(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_SPEED, fanspeedState);
+
+ DecimalType targetHumState = new DecimalType(actions.getTargetHum());
+ updateState(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_TARGET_HUMIDITY, targetHumState);
+
+ DecimalType timerState = new DecimalType(actions.getTimer());
+ updateState(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER, timerState);
+
+ OnOffType sleepModeState = OnOffType.from(actions.isSleepMode());
+ updateState(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_SLEEP_MODE, sleepModeState);
+
+ OnOffType boostState = OnOffType.from(actions.isBoost());
+ updateState(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_BOOST, boostState);
+
+ OnOffType childLockState = OnOffType.from(actions.isChildLock());
+ updateState(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_CHILD_LOCK, childLockState);
+
+ OnOffType automaticState = OnOffType.from(actions.isAutomatic());
+ updateState(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_AUTOMATIC, automaticState);
+
+ temperatureUnit = actions.getTempUnit() == 0 ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
+ }
+
+ Measurements measurements = message.getMeasurements();
+
+ if (measurements != null) {
+ QuantityType<Temperature> temperatureState = new QuantityType<>(measurements.getTemperature(),
+ temperatureUnit);
+ updateState(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_TEMPERATURE, temperatureState);
+
+ QuantityType<Dimensionless> humidityState = new QuantityType<>(measurements.getHumidity(),
+ Units.PERCENT);
+ updateState(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_HUMIDITY, humidityState);
+
+ DecimalType waterLevelState = new DecimalType(measurements.getWaterLevel());
+ updateState(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_WATERLEVEL, waterLevelState);
+
+ DecimalType fanRPMstate = new DecimalType(measurements.getFanRpm());
+ updateState(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_FAN_RPM, fanRPMstate);
+ }
+
+ Info info = message.getInfo();
+ if (info != null) {
+ int opHours = info.getOperationT() * 5 / 60;
+ int discReplaceHours = info.getDiscIonT() * 5 / 60;
+ int cleaningHours = info.getCleaningT() * 5 / 60;
+
+ QuantityType<Time> opHoursState = new QuantityType<Time>(opHours, Units.HOUR);
+ updateState(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_OPERATION_TIME, opHoursState);
+
+ QuantityType<Time> discReplaceHoursState = new QuantityType<Time>(2200 - discReplaceHours, Units.HOUR);
+ updateState(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_DISC_REPLACE_TIME, discReplaceHoursState);
+
+ QuantityType<Time> cleaningHoursState = new QuantityType<Time>(4400 - cleaningHours, Units.HOUR);
+ updateState(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEANING_TIME, cleaningHoursState);
+
+ OnOffType cleanModeState = info.isCleanMode() ? OnOffType.ON : OnOffType.OFF;
+ updateState(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_CLEAN_MODE, cleanModeState);
+
+ QuantityType<Time> timerTimePassedState = new QuantityType<Time>(info.getTimerT(), Units.MINUTE);
+ updateState(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
+ channelValueCache.put(VentaAirBindingConstants.CHANNEL_TIMER_TIME_PASSED, timerTimePassedState);
+
+ updateProperties(info);
+ }
+
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ private boolean messageIsEmpty(DeviceInfoMessage message) {
+ if (message.getCurrentActions() == null && message.getInfo() == null && message.getMeasurements() == null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method to inform the handler about a communication issue
+ */
+ public void communicationProblem() {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.discovery;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+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.binding.ventaair.internal.VentaAirBindingConstants;
+import org.openhab.binding.ventaair.internal.message.dto.Header;
+import org.openhab.binding.ventaair.internal.message.dto.Message;
+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.openhab.core.util.HexUtils;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * Discovers Venta Air humidifier and cleaner devices by listening for UDP messages
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component(service = DiscoveryService.class, configurationPid = "discovery.ventaair")
+public class VentaDeviceDiscovery extends AbstractDiscoveryService {
+ private static final String REPRESENTATION_PROPERTY = "macAddress";
+ private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
+ .singleton(VentaAirBindingConstants.THING_TYPE_LW60T);
+ // defined as int, because AbstractDiscoveryService wants and int and not long as provided by Duration.getSeconds()
+ private static final int MANUAL_DISCOVERY_TIME = 30;
+ private static final Duration TIME_BETWEEN_SCANS = Duration.ofSeconds(30);
+
+ private final Logger logger = LoggerFactory.getLogger(VentaDeviceDiscovery.class);
+
+ private @Nullable ScheduledFuture<?> scanJob = null;
+
+ public VentaDeviceDiscovery() {
+ super(SUPPORTED_THING_TYPES_UIDS, MANUAL_DISCOVERY_TIME, true);
+ }
+
+ @Override
+ protected void startScan() {
+ findDevices();
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ super.startBackgroundDiscovery();
+
+ ScheduledFuture<?> localScanJob = scanJob;
+ if (localScanJob != null) {
+ localScanJob.cancel(true);
+ }
+
+ scanJob = scheduler.scheduleWithFixedDelay(this::findDevices, 5, TIME_BETWEEN_SCANS.getSeconds(),
+ TimeUnit.SECONDS);
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ super.stopBackgroundDiscovery();
+
+ ScheduledFuture<?> localScanJob = scanJob;
+ if (localScanJob != null) {
+ localScanJob.cancel(true);
+ }
+ scanJob = null;
+ }
+
+ private void findDevices() {
+ byte[] buf = new byte[512];
+ try (DatagramSocket socket = new DatagramSocket(VentaAirBindingConstants.PORT)) {
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ socket.receive(packet);
+
+ Message m = parseDiscoveryPaket(packet.getData());
+ if (m == null) {
+ logger.debug("Received broken discovery packet data={}", HexUtils.bytesToHex(packet.getData(), ", "));
+ return;
+ }
+
+ logger.debug("Found device with: IP={} Mac={} and device type={}", m.getHeader().getIpAdress(),
+ m.getHeader().getMacAdress(), m.getHeader().getDeviceType());
+
+ ThingTypeUID thingTypeUID;
+ switch (m.getHeader().getDeviceType()) {
+ case 4:
+ thingTypeUID = VentaAirBindingConstants.THING_TYPE_LW60T;
+ break;
+ default:
+ thingTypeUID = VentaAirBindingConstants.THING_TYPE_GENERIC;
+ break;
+ }
+ createDiscoveryResult(thingTypeUID, m.getHeader());
+ } catch (SocketException e) {
+ logger.warn("Could not open port {} to scan for Venta devices in the network.",
+ VentaAirBindingConstants.PORT);
+ } catch (IOException e) {
+ // swallow, since we already log the broken packet above
+ }
+ }
+
+ private @Nullable Message parseDiscoveryPaket(byte[] packet) {
+ Gson gson = new Gson();
+ Message msg = null;
+
+ String packetAsString = new String(packet, StandardCharsets.UTF_8);
+
+ String[] lines = packetAsString.split("\n");
+ if (lines.length >= 3) {
+ String input = lines[2];
+ int end = input.lastIndexOf("}"); // strip padding bytes added by the device
+ if (end > 0) {
+ String rawJSONstring = input.substring(0, end + 1);
+ try {
+ msg = gson.fromJson(rawJSONstring, Message.class);
+ } catch (JsonSyntaxException e) {
+ logger.debug("Received invalid JSON data={}", rawJSONstring, e);
+ }
+ }
+ }
+ return msg;
+ }
+
+ private void createDiscoveryResult(ThingTypeUID thingTypeUID, Header messageHeader) {
+ String ipAddress = messageHeader.getIpAdress();
+ String macAddress = messageHeader.getMacAdress();
+ int deviceType = messageHeader.getDeviceType();
+
+ ThingUID uid = new ThingUID(thingTypeUID, ipAddress.replace(".", "_"));
+ HashMap<String, Object> properties = new HashMap<>();
+ properties.put("ipAddress", ipAddress);
+ properties.put(REPRESENTATION_PROPERTY, macAddress);
+ properties.put("deviceType", deviceType);
+
+ String typeLabel = thingTypeUID.getId().toUpperCase();
+
+ DiscoveryResult result = DiscoveryResultBuilder.create(uid).withRepresentationProperty(REPRESENTATION_PROPERTY)
+ .withProperties(properties).withLabel(typeLabel + " (IP=" + ipAddress + ")").build();
+
+ this.thingDiscovered(result);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Marker interface for Actions that can be send to the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public interface Action {
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Actions send by the device, containing information about the current device settings
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class AllActions implements Action {
+ private boolean Power = true;
+ private int FanSpeed;
+ private int TargetHum = 65;
+ private int Timer;
+ private boolean Boost;
+ private boolean SleepMode;
+ private boolean ChildLock;
+ private boolean Automatic;
+ private int SysLanguage; // 3?
+ private int CleanLanguage; // 0?
+ private int TempUnit; // 0=Celsius, 1=Fahrenheit?
+ private int DisplayLeft;
+ private int DisplayRight;
+ private int Reset;
+ private int ConINet;
+ private boolean DelUser; // default false
+
+ public boolean isPower() {
+ return Power;
+ }
+
+ public int getFanSpeed() {
+ return FanSpeed;
+ }
+
+ public int getTargetHum() {
+ return TargetHum;
+ }
+
+ public int getTimer() {
+ return Timer;
+ }
+
+ public boolean isBoost() {
+ return Boost;
+ }
+
+ public boolean isSleepMode() {
+ return SleepMode;
+ }
+
+ public boolean isChildLock() {
+ return ChildLock;
+ }
+
+ public boolean isAutomatic() {
+ return Automatic;
+ }
+
+ public int getSysLanguage() {
+ return SysLanguage;
+ }
+
+ public int getCleanLanguage() {
+ return CleanLanguage;
+ }
+
+ public int getTempUnit() {
+ return TempUnit;
+ }
+
+ public int getDisplayLeft() {
+ return DisplayLeft;
+ }
+
+ public int getDisplayRight() {
+ return DisplayRight;
+ }
+
+ public int getReset() {
+ return Reset;
+ }
+
+ public int getConINet() {
+ return ConINet;
+ }
+
+ public boolean isDelUser() {
+ return DelUser;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to enable the automatic mode of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class AutomaticAction implements Action {
+ @SuppressWarnings("unused")
+ private boolean Automatic;
+
+ public AutomaticAction(boolean automatic) {
+ this.Automatic = automatic;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to enable the boost mode of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class BoostAction implements Action {
+ @SuppressWarnings("unused")
+ private boolean Boost;
+
+ public BoostAction(boolean boost) {
+ this.Boost = boost;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to enable the child lock mode of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class ChildLockAction implements Action {
+ @SuppressWarnings("unused")
+ private boolean ChildLock;
+
+ public ChildLockAction(boolean childLockOn) {
+ this.ChildLock = childLockOn;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to control the fan speed of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class FanAction implements Action {
+ @SuppressWarnings("unused")
+ private int FanSpeed;
+
+ public FanAction(int fanSpeed) {
+ this.FanSpeed = fanSpeed;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to set the target humidity of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class HumidityAction implements Action {
+ @SuppressWarnings("unused")
+ private int TargetHum;
+
+ public HumidityAction(int targetHumidity) {
+ this.TargetHum = targetHumidity;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to power on/off the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class PowerAction implements Action {
+ @SuppressWarnings("unused")
+ private boolean Power;
+
+ public PowerAction(boolean on) {
+ this.Power = on;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to enable the sleep mode of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class SleepModeAction implements Action {
+ @SuppressWarnings("unused")
+ private boolean SleepMode;
+
+ public SleepModeAction(boolean sleepModeOn) {
+ this.SleepMode = sleepModeOn;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to change the temperature unit of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class TemperatureUnitAction implements Action {
+ @SuppressWarnings("unused")
+ // 0=Celsius, 1=Fahrenheit
+ private int TempUnit;
+
+ public TemperatureUnitAction(int temperatureUnit) {
+ this.TempUnit = temperatureUnit;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.action;
+
+/**
+ * Action to set an off timer on the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class TimerAction implements Action {
+ @SuppressWarnings("unused")
+ private int Timer;
+
+ public TimerAction(int timer) {
+ this.Timer = timer;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import org.openhab.binding.ventaair.internal.message.action.Action;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Message containing a command to be send to the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class CommandMessage extends Message {
+ @SerializedName(value = "Action")
+ private Action action;
+
+ public CommandMessage(Action action, Header header) {
+ super(header);
+ this.action = action;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import org.openhab.binding.ventaair.internal.message.action.AllActions;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Message send by the device, containing information about its current state
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class DeviceInfoMessage extends Message {
+
+ public DeviceInfoMessage(Header header) {
+ super(header);
+ }
+
+ @SerializedName(value = "Action")
+ private AllActions currentActions;
+
+ @SerializedName(value = "Info")
+ private Info info;
+
+ @SerializedName(value = "Measure")
+ private Measurements measurements;
+
+ public AllActions getCurrentActions() {
+ return currentActions;
+ }
+
+ public Info getInfo() {
+ return info;
+ }
+
+ public Measurements getMeasurements() {
+ return measurements;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Header which is part of a message to/from a device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class Header {
+
+ @SerializedName(value = "MacAdress")
+ private String macAdress;
+
+ @SerializedName(value = "IpAdress")
+ private String ipAdress;
+
+ @SerializedName(value = "DeviceType")
+ private int deviceType;
+
+ @SerializedName(value = "Hash")
+ private String hash;
+
+ @SerializedName(value = "DeviceName")
+ private String deviceName;
+
+ public Header(String mac, int devType, String hash, String devName) {
+ this.macAdress = mac;
+ this.deviceType = devType;
+ this.hash = hash;
+ this.deviceName = devName;
+ }
+
+ public String getMacAdress() {
+ return macAdress;
+ }
+
+ public void setMacAdress(String macAdress) {
+ this.macAdress = macAdress;
+ }
+
+ public String getIpAdress() {
+ return ipAdress;
+ }
+
+ public void setIpAdress(String ipAdress) {
+ this.ipAdress = ipAdress;
+ }
+
+ public int getDeviceType() {
+ return deviceType;
+ }
+
+ public void setDeviceType(int deviceType) {
+ this.deviceType = deviceType;
+ }
+
+ public String getHash() {
+ return hash;
+ }
+
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+ public String getDeviceName() {
+ return deviceName;
+ }
+
+ public void setDeviceName(String deviceName) {
+ this.deviceName = deviceName;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Part of the {@link DeviceInfoMessage} containing details about the device state
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class Info {
+ @SerializedName(value = "SWDisplay")
+ private String swDisplay;
+
+ @SerializedName(value = "SWPower")
+ private String swPower;
+
+ @SerializedName(value = "SWTouch")
+ private String swTouch;
+
+ @SerializedName(value = "SWWIFI")
+ private String swWIFI;
+
+ @SerializedName(value = "CleanMode")
+ private boolean cleanMode; // default false?
+
+ @SerializedName(value = "RelState")
+ private boolean[] relState; // [true,true,false,false]
+
+ @SerializedName(value = "TimerT")
+ private int timerT;
+
+ @SerializedName(value = "OperationT")
+ private int operationT;
+
+ @SerializedName(value = "DiscIonT")
+ private int discIonT;
+
+ @SerializedName(value = "CleaningT")
+ private int cleaningT;
+
+ @SerializedName(value = "FilterT")
+ private int filterT;
+
+ @SerializedName(value = "UVCOnT")
+ private int uvCOnT;
+
+ @SerializedName(value = "UVCOffT")
+ private int uvCOffT;
+
+ @SerializedName(value = "CleaningR")
+ private int cleaningR;
+
+ @SerializedName(value = "Warnings")
+ private int warnings;
+
+ public String getSwDisplay() {
+ return swDisplay;
+ }
+
+ public String getSwPower() {
+ return swPower;
+ }
+
+ public String getSwTouch() {
+ return swTouch;
+ }
+
+ public String getSwWIFI() {
+ return swWIFI;
+ }
+
+ public boolean isCleanMode() {
+ return cleanMode;
+ }
+
+ public boolean[] getRelState() {
+ return relState;
+ }
+
+ public int getTimerT() {
+ return timerT;
+ }
+
+ public int getOperationT() {
+ return operationT;
+ }
+
+ public int getDiscIonT() {
+ return discIonT;
+ }
+
+ public int getCleaningT() {
+ return cleaningT;
+ }
+
+ public int getFilterT() {
+ return filterT;
+ }
+
+ public int getUvCOnT() {
+ return uvCOnT;
+ }
+
+ public int getUvCOffT() {
+ return uvCOffT;
+ }
+
+ public int getCleaningR() {
+ return cleaningR;
+ }
+
+ public int getWarnings() {
+ return warnings;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Part of the {@link DeviceInfoMessage} containing the measurements of the device
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class Measurements {
+ @SerializedName(value = "Temperature")
+ private double temperature;
+
+ @SerializedName(value = "Humidity")
+ private double humidity;
+
+ @SerializedName(value = "Dust")
+ private int dust;
+
+ @SerializedName(value = "WaterLevel")
+ private int waterLevel;
+
+ @SerializedName(value = "FanRpm")
+ private int fanRpm;
+
+ public double getTemperature() {
+ return temperature;
+ }
+
+ public double getHumidity() {
+ return humidity;
+ }
+
+ public int getDust() {
+ return dust;
+ }
+
+ public int getWaterLevel() {
+ return waterLevel;
+ }
+
+ public int getFanRpm() {
+ return fanRpm;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.ventaair.internal.message.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Base class for messages
+ *
+ * @author Stefan Triller - Initial contribution
+ *
+ */
+public class Message {
+
+ @SerializedName(value = "Header")
+ protected Header header;
+
+ public Message(Header header) {
+ this.header = header;
+ }
+
+ public Header getHeader() {
+ return header;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="ventaair" 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>VentaAir Binding</name>
+ <description>This is the binding for Venta Air - Air cleaning and humidifying devices</description>
+
+</binding:binding>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+ <config-description uri="thing-type:ventaair:humidifier">
+ <parameter name="ipAddress" type="text" required="true">
+ <label>IP Address</label>
+ <context>network-address</context>
+ <description>IP Address or hostname of the device</description>
+ </parameter>
+ <parameter name="macAddress" type="text" required="true">
+ <label>MAC Address</label>
+ <description>MAC Address of the device</description>
+ </parameter>
+ <parameter name="deviceType" type="integer" required="true">
+ <label>Device Type</label>
+ <description>Type of the device as integer</description>
+ </parameter>
+ <parameter name="pollingTime" type="integer" required="false" unit="s" min="1" max="86400">
+ <label>Polling Interval</label>
+ <default>10</default>
+ <description>Time in seconds between fetching data from the device</description>
+ </parameter>
+ <parameter name="hash" type="integer" required="false" max="-1">
+ <label>Hash</label>
+ <description>Optional negative number that relates to a connection (like from the VentaApp) to the device</description>
+ </parameter>
+ </config-description>
+
+</config-description:config-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="ventaair"
+ 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">
+
+ <channel-type id="fanSpeed">
+ <item-type>Number</item-type>
+ <label>Fan Speed</label>
+ <description>Speed of the ventilation fan (0-5)</description>
+ <state readOnly="false">
+ <options>
+ <option value="0">Off</option>
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="targetHumidity">
+ <item-type>Number</item-type>
+ <label>Target Humidity</label>
+ <description>Target Humidity (30-70)</description>
+ <category>Humidity</category>
+ <state readOnly="false">
+ <options>
+ <option value="30">30 %</option>
+ <option value="35">35 %</option>
+ <option value="40">40 %</option>
+ <option value="45">45 %</option>
+ <option value="50">50 %</option>
+ <option value="55">55 %</option>
+ <option value="60">60 %</option>
+ <option value="65">65 %</option>
+ <option value="70">70 %</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="timer">
+ <item-type>Number</item-type>
+ <label>Timer</label>
+ <description>Timer (0,1,3,5,7,9h)</description>
+ <state readOnly="false">
+ <options>
+ <option value="0">Off</option>
+ <option value="1">1 hour</option>
+ <option value="3">3 hours</option>
+ <option value="5">5 hours</option>
+ <option value="7">7 hours</option>
+ <option value="9">9 hours</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="timerTimePassed">
+ <item-type>Number:Time</item-type>
+ <label>Timer Time Passed</label>
+ <description>Time that has passed since set by the Timer</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="sleepMode">
+ <item-type>Switch</item-type>
+ <label>Sleep Mode</label>
+ <description>Sleep Mode</description>
+ </channel-type>
+
+ <channel-type id="boost">
+ <item-type>Switch</item-type>
+ <label>Boost</label>
+ <description>Boost</description>
+ </channel-type>
+
+ <channel-type id="childLock">
+ <item-type>Switch</item-type>
+ <label>Child Lock</label>
+ <description>Child Lock</description>
+ </channel-type>
+
+ <channel-type id="automatic">
+ <item-type>Switch</item-type>
+ <label>Automatic</label>
+ <description>Automatic</description>
+ </channel-type>
+
+ <channel-type id="temperature">
+ <item-type>Number:Temperature</item-type>
+ <label>Temperature</label>
+ <description>Current Temperature</description>
+ <category>Temperature</category>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="humidity">
+ <item-type>Number:Dimenionsless</item-type>
+ <label>Humidity</label>
+ <description>Current Humidity</description>
+ <category>Humidity</category>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="waterLevel">
+ <item-type>Number</item-type>
+ <label>Water Level</label>
+ <description>Water Level</description>
+ <state readOnly="true">
+ <options>
+ <option value="0">Critical</option>
+ <option value="1">Refill tank</option>
+ <option value="2">OK</option>
+ <option value="3">Full</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="fanRPM">
+ <item-type>Number</item-type>
+ <label>Fan RPM</label>
+ <description>Fan RPM</description>
+ <state pattern="%d RPM" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="cleanMode">
+ <item-type>Switch</item-type>
+ <label>Cleaning Mode</label>
+ <description>Device is in cleaning mode (ON)</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="operationTime">
+ <item-type>Number:Time</item-type>
+ <label>Operation Time</label>
+ <description>Operation Time since the device was first started (in hours)</description>
+ <state pattern="%d" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="discReplaceTime">
+ <item-type>Number:Time</item-type>
+ <label>Hygiene Disc Replacement</label>
+ <description>Time until the Hygiene Disc should be replaced (in hours)</description>
+ <state pattern="%d" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="cleaningTime">
+ <item-type>Number:Time</item-type>
+ <label>Cleaning Time</label>
+ <description>Time until next cleaning (in hours)</description>
+ <state pattern="%d" readOnly="true"/>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="ventaair"
+ 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="lw60t">
+ <label>LW60-T VentaAir Humidifier</label>
+ <description>Thing for Venta Air LW60-T Humidifiers</description>
+
+ <channels>
+ <channel id="power" typeId="system.power"/>
+ <channel id="fanSpeed" typeId="fanSpeed"/>
+ <channel id="targetHumidity" typeId="targetHumidity"/>
+ <channel id="timer" typeId="timer"/>
+ <channel id="sleepMode" typeId="sleepMode"/>
+ <channel id="childLock" typeId="childLock"/>
+ <channel id="automatic" typeId="automatic"/>
+ <channel id="temperature" typeId="temperature"/>
+ <channel id="humidity" typeId="humidity"/>
+ <channel id="waterLevel" typeId="waterLevel"/>
+ <channel id="fanRPM" typeId="fanRPM"/>
+ <channel id="cleanMode" typeId="cleanMode"/>
+ <channel id="timerTimePassed" typeId="timerTimePassed"/>
+ <channel id="operationTime" typeId="operationTime"/>
+ <channel id="discReplaceTime" typeId="discReplaceTime"/>
+ <channel id="cleaningTime" typeId="cleaningTime"/>
+ </channels>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:ventaair:humidifier"/>
+
+ </thing-type>
+
+ <!-- Generic type has boost channel and maybe in the future other channels -->
+ <thing-type id="generic">
+ <label>Generic Humidifier/Cleaner</label>
+ <description>Thing for Venta Air Humidifiers/Cleaners</description>
+
+ <channels>
+ <channel id="power" typeId="system.power"/>
+ <channel id="fanSpeed" typeId="fanSpeed"/>
+ <channel id="targetHumidity" typeId="targetHumidity"/>
+ <channel id="timer" typeId="timer"/>
+ <channel id="sleepMode" typeId="sleepMode"/>
+ <channel id="boost" typeId="boost"/>
+ <channel id="childLock" typeId="childLock"/>
+ <channel id="automatic" typeId="automatic"/>
+ <channel id="temperature" typeId="temperature"/>
+ <channel id="humidity" typeId="humidity"/>
+ <channel id="waterLevel" typeId="waterLevel"/>
+ <channel id="fanRPM" typeId="fanRPM"/>
+ <channel id="cleanMode" typeId="cleanMode"/>
+ <channel id="timerTimePassed" typeId="timerTimePassed"/>
+ <channel id="operationTime" typeId="operationTime"/>
+ <channel id="discReplaceTime" typeId="discReplaceTime"/>
+ <channel id="cleaningTime" typeId="cleaningTime"/>
+ </channels>
+
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:ventaair:humidifier"/>
+
+ </thing-type>
+
+</thing:thing-descriptions>
<module>org.openhab.binding.velbus</module>
<module>org.openhab.binding.velux</module>
<module>org.openhab.binding.venstarthermostat</module>
+ <module>org.openhab.binding.ventaair</module>
<module>org.openhab.binding.verisure</module>
<module>org.openhab.binding.vigicrues</module>
<module>org.openhab.binding.vitotronic</module>