[ventaair] New VentaAir binding for air humidifiers (#9979)

* [ventaair] New VentaAir binding for air humidifiers

New binding that implements support for air humidifier from Venta Air.

Closes #9922

Signed-off-by: Stefan Triller <github@stefantriller.de>
This commit is contained in:
Stefan Triller 2021-04-10 21:54:28 +02:00 committed by GitHub
parent 95cdc3cb35
commit 08602c04b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2028 additions and 0 deletions

View File

@ -286,6 +286,7 @@
/bundles/org.openhab.binding.velbus/ @cedricboon /bundles/org.openhab.binding.velbus/ @cedricboon
/bundles/org.openhab.binding.velux/ @gs4711 /bundles/org.openhab.binding.velux/ @gs4711
/bundles/org.openhab.binding.venstarthermostat/ @hww3 @digitaldan /bundles/org.openhab.binding.venstarthermostat/ @hww3 @digitaldan
/bundles/org.openhab.binding.ventaair/ @t2000
/bundles/org.openhab.binding.verisure/ @jannegpriv /bundles/org.openhab.binding.verisure/ @jannegpriv
/bundles/org.openhab.binding.vigicrues/ @clinique /bundles/org.openhab.binding.vigicrues/ @clinique
/bundles/org.openhab.binding.vitotronic/ @steand /bundles/org.openhab.binding.vitotronic/ @steand

View File

@ -1411,6 +1411,11 @@
<artifactId>org.openhab.binding.venstarthermostat</artifactId> <artifactId>org.openhab.binding.venstarthermostat</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.ventaair</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.addons.bundles</groupId> <groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.verisure</artifactId> <artifactId>org.openhab.binding.verisure</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,127 @@
# 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
```

View File

@ -0,0 +1,17 @@
<?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>

View File

@ -0,0 +1,9 @@
<?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>

View File

@ -0,0 +1,179 @@
/**
* 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;
}
}

View File

@ -0,0 +1,53 @@
/**
* 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;
}

View File

@ -0,0 +1,32 @@
/**
* 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;
}

View File

@ -0,0 +1,55 @@
/**
* 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;
}
}

View File

@ -0,0 +1,333 @@
/**
* 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);
}
}
}

View File

@ -0,0 +1,172 @@
/**
* 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);
}
}

View File

@ -0,0 +1,22 @@
/**
* 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 {
}

View File

@ -0,0 +1,102 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,29 @@
/**
* 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;
}
}

View File

@ -0,0 +1,28 @@
/**
* 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;
}
}

View File

@ -0,0 +1,33 @@
/**
* 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;
}
}

View File

@ -0,0 +1,51 @@
/**
* 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;
}
}

View File

@ -0,0 +1,86 @@
/**
* 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;
}
}

View File

@ -0,0 +1,128 @@
/**
* 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;
}
}

View File

@ -0,0 +1,58 @@
/**
* 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;
}
}

View File

@ -0,0 +1,35 @@
/**
* 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;
}
}

View File

@ -0,0 +1,9 @@
<?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>

View File

@ -0,0 +1,32 @@
<?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>

View File

@ -0,0 +1,155 @@
<?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>

View File

@ -0,0 +1,67 @@
<?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>

View File

@ -317,6 +317,7 @@
<module>org.openhab.binding.velbus</module> <module>org.openhab.binding.velbus</module>
<module>org.openhab.binding.velux</module> <module>org.openhab.binding.velux</module>
<module>org.openhab.binding.venstarthermostat</module> <module>org.openhab.binding.venstarthermostat</module>
<module>org.openhab.binding.ventaair</module>
<module>org.openhab.binding.verisure</module> <module>org.openhab.binding.verisure</module>
<module>org.openhab.binding.vigicrues</module> <module>org.openhab.binding.vigicrues</module>
<module>org.openhab.binding.vitotronic</module> <module>org.openhab.binding.vitotronic</module>