[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
34 changed files with 2028 additions and 0 deletions

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>