/bundles/org.openhab.binding.tellstick/ @jarlebh
/bundles/org.openhab.binding.tesla/ @kgoderis
/bundles/org.openhab.binding.tibber/ @kjoglum
+/bundles/org.openhab.binding.touchwand /@roieg
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer
/bundles/org.openhab.binding.unifi/ @mgbowman
--- /dev/null
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
--- /dev/null
+# TouchWand Binding
+
+Touchwand Wanderfull™ Hub basic is a plug & play Z-Wave based controller that uses Wi-Fi and Bluetooth to easily connect all smart home components.
+TouchWand products are compatible with most major Z-Wave products, IP controlled devices and KNX devices, providing the ideal solution for building all-inclusive full-featured smart homes.
+[TouchWand.com](http://www.touchwand.com)
+
+
+
+## Supported Things
+
+This binding supports switches, shutters dimmers and wall controllers configured in Touchwand Wanderfull™ Hub Controller.
+
+## Control
+
+1. **switch** - control - ON/OFF
+2. **shutter** - control - UP/DOWN/STOP
+3. **dimmer** - control - ON/OFF/BRIGHTNESS
+4. **wallcontroller** - control - LONG/SHORT
+
+## Discovery
+
+After adding TouchWand Hub the auto discovery will add all switches dimmers and shutters to the inbox.
+
+## Bridge Configuration
+
+**Touchwand Wanderfull™** Hub Controller need to be added manually by IP address. The controller requires **username** and **password**
+
+| Parameter | Description | Units | required |
+|-------------------|-----------------------------------------------------------------------|---------|----------|
+| username | Touchwand hub username | string | yes |
+| password | Touchwand hub password | string | yes |
+| ipAddress | Touchwand hub hotname or IP address | string | yes |
+| port | Management port (default 80) | integer | no |
+| statusrefresh | Unit status refresh interval in seconds | integer | no |
+| addSecondaryUnits | If the controller is primary, add secondary controllers units as well | bool | no |
+
+
+
+## Thing Configuration
+
+No thing configuration is needed
+
+## Full Example
+
+### touchwand.things
+
+Things can be defined manually
+The syntax for touchwand this is
+
+```xtend
+Thing <binding_id>:<type_id>:<thing_id> "Label" @ "Location"
+```
+
+Where <thing_id> is the unit id in touchwand hub.
+
+```
+Bridge touchwand:bridge:1921681116 [ipAddress="192.168.1.116", username="username" , password="password"]{
+Thing switch 408 "Strairs light"
+Thing switch 411 "South Garden light"
+Thing dimmer 415 "Living Room Ceiling dimmer"
+Thing switch 418 "Kitchen light"
+Thing shutter 345 "Living Room North shutter"
+Thing shutter 346 "Living Room South shutter"
+}
+```
+
+### touchwand.items
+
+```
+/* Shutters */
+Rollershutter Rollershutter_345 "Living Room North shutter" {channel="touchwand:shutter:1921681116:345:shutter"}
+Rollershutter Rollershutter_346 "Living Room South shutter" {channel="touchwand:shutter:1921681116:346:shutter"}
+```
+
+```
+/* Switches and Dimmers */
+Switch Switch_408 "Strairs light" {channel="touchwand:switch:1921681116:408:switch"}
+Switch Switch_411 "South Garden light" {channel="touchwand:switch:1921681116:411:switch"}
+Dimmer Switch_415 "Living Room Ceiling dimmer" {channel="touchwand:switch:1921681116:415:switch"}
+Switch Switch_418 "South Garden light" {channel="touchwand:switch:1921681116:418:switch"}
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.touchwand</artifactId>
+ <name>openHAB Add-ons :: Bundles :: TouchWand Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.touchwand-${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-touchwand" description="TouchWand Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.touchwand/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.core.thing.Bridge;
+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.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+
+/**
+ * The {@link TouchWandBaseUnitHandler} is responsible for handling commands and status updates
+ * for TouchWand units. This is an abstract class , units should implement the specific command
+ * handling and status updates.
+ *
+ * @author Roie Geron - Initial contribution
+ *
+ */
+@NonNullByDefault
+public abstract class TouchWandBaseUnitHandler extends BaseThingHandler implements TouchWandUnitUpdateListener {
+
+ protected final Logger logger = LoggerFactory.getLogger(TouchWandBaseUnitHandler.class);
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_SHUTTER, THING_TYPE_SWITCH,
+ THING_TYPE_WALLCONTROLLER, THING_TYPE_DIMMER);
+ protected String unitId = "";
+
+ protected @Nullable TouchWandBridgeHandler bridgeHandler;
+
+ public TouchWandBaseUnitHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ // updateTouchWandUnitState(getUnitState(unitId));
+ } else {
+ touchWandUnitHandleCommand(command);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ TouchWandBridgeHandler myTmpBridgeHandler = bridgeHandler;
+ if (myTmpBridgeHandler != null) {
+ myTmpBridgeHandler.unregisterUpdateListener(this);
+ }
+ }
+
+ @Override
+ public void initialize() {
+ Bridge bridge = getBridge();
+ if (bridge == null || !(bridge.getHandler() instanceof TouchWandBridgeHandler)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+ logger.warn("Trying to initialize {} without a bridge", getThing().getUID());
+ return;
+ }
+
+ bridgeHandler = (TouchWandBridgeHandler) bridge.getHandler();
+
+ unitId = getThing().getProperties().get(HANDLER_PROPERTIES_ID); // TouchWand unit id
+
+ TouchWandBridgeHandler myTmpBridgeHandler = bridgeHandler;
+ if (myTmpBridgeHandler != null) {
+ myTmpBridgeHandler.registerUpdateListener(this);
+ }
+
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.execute(() -> {
+ boolean thingReachable = false;
+ if (myTmpBridgeHandler != null) {
+ String response = myTmpBridgeHandler.touchWandClient.cmdGetUnitById(unitId);
+ thingReachable = !response.isEmpty();
+ if (thingReachable) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unused") // not used at the moment till touchWand state in hub will be fixed
+ private int getUnitState(String unitId) {
+ int status = 0;
+
+ TouchWandBridgeHandler touchWandBridgeHandler = bridgeHandler;
+ if (touchWandBridgeHandler == null) {
+ return status;
+ }
+
+ String response = touchWandBridgeHandler.touchWandClient.cmdGetUnitById(unitId);
+ if (!response.isEmpty()) {
+ return status;
+ }
+
+ JsonParser jsonParser = new JsonParser();
+
+ try {
+ JsonObject unitObj = jsonParser.parse(response).getAsJsonObject();
+ status = unitObj.get("currStatus").getAsInt();
+ if (!this.getThing().getStatusInfo().getStatus().equals(ThingStatus.ONLINE)) {
+ updateStatus(ThingStatus.ONLINE);
+ }
+ } catch (JsonParseException | IllegalStateException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Could not parse cmdGetUnitById:" + e.getMessage());
+ }
+ return status;
+ }
+
+ abstract void touchWandUnitHandleCommand(Command command);
+
+ abstract void updateTouchWandUnitState(TouchWandUnitData unitData);
+
+ @Override
+ public void onItemStatusUpdate(TouchWandUnitData unitData) {
+ if (unitData.getStatus().equals("ALIVE")) {
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ // updateStatus(ThingStatus.OFFLINE); // comment - OFFLINE status is not accurate at the moment
+ }
+ updateTouchWandUnitState(unitData);
+ }
+
+ @Override
+ public String getId() {
+ return unitId;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link TouchWandBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandBindingConstants {
+
+ public static final String BINDING_ID = "touchwand";
+ public static final String DISCOVERY_THREAD_ID = "OH-binding-touchwand-discovery";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
+ public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
+ public static final ThingTypeUID THING_TYPE_SHUTTER = new ThingTypeUID(BINDING_ID, "shutter");
+ public static final ThingTypeUID THING_TYPE_WALLCONTROLLER = new ThingTypeUID(BINDING_ID, "wallcontroller");
+ public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
+ public static final ThingTypeUID THING_TYPE_ALARMSENSOR = new ThingTypeUID(BINDING_ID, "AlarmSensor"); // TBD
+
+ // List of all Channel ids
+ public static final String CHANNEL_SWITCH = "switch";
+ public static final String CHANNEL_SHUTTER = "shutter";
+ public static final String CHANNEL_DIMMER = "brightness";
+ public static final String CHANNEL_ALARM = "alarm";
+ public static final String CHANNEL_WALLCONTROLLER_ACTION = "wallaction";
+ public static final String CHANNEL_BATTERY_LEVEL = "battery_level";
+ public static final String CHANNEL_BATTERY_LOW = "battery_low";
+
+ // List of configuration parameters
+
+ public static final String HOST = "ipAddress";
+ public static final String PORT = "port";
+ public static final String USER = "username";
+ public static final String PASS = "password";
+ public static final String STATUS_REFRESH_TIME = "statusrefresh";
+ public static final String ADD_SECONDARY_UNITS = "addSecondaryUnits";
+
+ // Unit handler properties
+
+ public static final String HANDLER_PROPERTIES_ID = "id";
+ public static final String HANDLER_PROPERTIES_NAME = "name";
+
+ // Connectivity options
+
+ public static final String CONNECTIVITY_KNX = "knx";
+ public static final String CONNECTIVITY_ZWAVE = "zwave";
+
+ // commands
+ public static final String SWITCH_STATUS_ON = "255";
+ public static final String SWITCH_STATUS_OFF = "0";
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
+
+ static {
+ SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_SWITCH);
+ SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_SHUTTER);
+ SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_WALLCONTROLLER);
+ SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_DIMMER);
+ // SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_ALARMSENSOR); // not implemented yet
+ }
+
+ public static final String TYPE_WALLCONTROLLER = "WallController";
+ public static final String TYPE_SWITCH = "Switch";
+ public static final String TYPE_SHUTTER = "shutter";
+ public static final String TYPE_DIMMER = "dimmer";
+ public static final String TYPE_ALARMSENSOR = "AlarmSensor";
+
+ public static final String[] SUPPORTED_TOUCHWAND_TYPES = { TYPE_WALLCONTROLLER, TYPE_SWITCH, TYPE_SHUTTER,
+ TYPE_DIMMER, TYPE_ALARMSENSOR };
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.THING_TYPE_BRIDGE;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.touchwand.internal.config.TouchwandBridgeConfiguration;
+import org.openhab.binding.touchwand.internal.discovery.TouchWandUnitDiscoveryService;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link TouchWandBridgeHandler} is responsible for handling commands, which are
+ * sent to one of the channels TouchWand Wanderfull™ Hub channels .
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandBridgeHandler extends BaseBridgeHandler implements TouchWandUnitStatusUpdateListener {
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
+ private final Logger logger = LoggerFactory.getLogger(TouchWandBridgeHandler.class);
+ private int statusRefreshRateSec;
+ private boolean addSecondaryUnits;
+ private @Nullable TouchWandWebSockets touchWandWebSockets;
+ private Map<String, TouchWandUnitUpdateListener> unitUpdateListeners = new ConcurrentHashMap<>();
+ private volatile boolean isRunning = false;
+
+ public TouchWandRestClient touchWandClient;
+
+ public TouchWandBridgeHandler(Bridge bridge, HttpClient httpClient, BundleContext bundleContext) {
+ super(bridge);
+ touchWandClient = new TouchWandRestClient(httpClient);
+ touchWandWebSockets = null;
+ }
+
+ @Override
+ public synchronized void initialize() {
+ String host;
+ Integer port;
+ TouchwandBridgeConfiguration config;
+
+ updateStatus(ThingStatus.UNKNOWN);
+
+ config = getConfigAs(TouchwandBridgeConfiguration.class);
+
+ host = config.ipAddress;
+ port = config.port;
+ statusRefreshRateSec = config.statusrefresh;
+ addSecondaryUnits = config.addSecondaryUnits;
+
+ isRunning = true;
+
+ scheduler.execute(() -> {
+ boolean thingReachable = false;
+ String password = config.password;
+ String username = config.username;
+ thingReachable = touchWandClient.connect(username, password, host, port.toString());
+ if (thingReachable) {
+ updateStatus(ThingStatus.ONLINE);
+ synchronized (this) {
+ if (isRunning) {
+ TouchWandWebSockets localSockets = touchWandWebSockets = new TouchWandWebSockets(host,
+ scheduler);
+ localSockets.registerListener(this);
+ localSockets.connect();
+ }
+ }
+
+ } else {
+ updateStatus(ThingStatus.OFFLINE);
+ }
+ });
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ }
+
+ public boolean isAddSecondaryControllerUnits() {
+ return addSecondaryUnits;
+ }
+
+ public int getStatusRefreshTime() {
+ return statusRefreshRateSec;
+ }
+
+ @Override
+ public synchronized void dispose() {
+ isRunning = false;
+ TouchWandWebSockets myTouchWandWebSockets = touchWandWebSockets;
+ if (myTouchWandWebSockets != null) {
+ myTouchWandWebSockets.unregisterListener(this);
+ myTouchWandWebSockets.dispose();
+ }
+ }
+
+ public synchronized boolean registerUpdateListener(TouchWandUnitUpdateListener listener) {
+ logger.debug("Adding Status update listener for device {}", listener.getId());
+ unitUpdateListeners.put(listener.getId(), listener);
+ return true;
+ }
+
+ public synchronized boolean unregisterUpdateListener(TouchWandUnitUpdateListener listener) {
+ logger.debug("Remove Status update listener for device {}", listener.getId());
+ unitUpdateListeners.remove(listener.getId());
+ return true;
+ }
+
+ @Override
+ public void onDataReceived(TouchWandUnitData unitData) {
+ if (unitUpdateListeners.containsKey(unitData.getId().toString())) {
+ TouchWandUnitUpdateListener updateListener = unitUpdateListeners.get(unitData.getId().toString());
+ updateListener.onItemStatusUpdate(unitData);
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(TouchWandUnitDiscoveryService.class);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.CHANNEL_DIMMER;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandShutterSwitchUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link TouchWandDimmerHandler} is responsible for handling commands for Dimmer units
+ *
+ * @author Roie Geron - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TouchWandDimmerHandler extends TouchWandBaseUnitHandler {
+
+ public TouchWandDimmerHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void touchWandUnitHandleCommand(Command command) {
+ TouchWandBridgeHandler touchWandBridgeHandler = bridgeHandler;
+ if (touchWandBridgeHandler != null) {
+ if (command instanceof OnOffType) {
+ touchWandBridgeHandler.touchWandClient.cmdSwitchOnOff(unitId, (OnOffType) command);
+ } else {
+ touchWandBridgeHandler.touchWandClient.cmdDimmerPosition(unitId, command.toString());
+ }
+ }
+ }
+
+ @Override
+ void updateTouchWandUnitState(TouchWandUnitData unitData) {
+ if (unitData instanceof TouchWandShutterSwitchUnitData) {
+ int status = ((TouchWandShutterSwitchUnitData) unitData).getCurrStatus();
+ PercentType state = PercentType.ZERO;
+ int convertStatus = status;
+ state = new PercentType(convertStatus);
+ updateState(CHANNEL_DIMMER, state);
+ } else {
+ logger.warn("updateTouchWandUnitState incompatible TouchWandUnitData instance");
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+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;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link TouchWandHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+
+@Component(configurationPid = "binding.touchwand", service = ThingHandlerFactory.class)
+@NonNullByDefault
+public class TouchWandHandlerFactory extends BaseThingHandlerFactory {
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
+ .unmodifiableSet(Stream.concat(TouchWandBridgeHandler.SUPPORTED_THING_TYPES.stream(),
+ TouchWandBaseUnitHandler.SUPPORTED_THING_TYPES.stream()).collect(Collectors.toSet()));
+
+ private @NonNullByDefault({}) HttpClient httpClient;
+
+ @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_BRIDGE.equals(thingTypeUID)) {
+ return new TouchWandBridgeHandler((Bridge) thing, httpClient, bundleContext);
+ } else if (THING_TYPE_SWITCH.equals(thingTypeUID)) {
+ return new TouchWandSwitchHandler(thing);
+ } else if (THING_TYPE_SHUTTER.equals(thingTypeUID)) {
+ return new TouchWandShutterHandler(thing);
+ } else if (THING_TYPE_WALLCONTROLLER.equals(thingTypeUID)) {
+ return new TouchWandWallControllerHandler(thing);
+ } else if (THING_TYPE_DIMMER.equals(thingTypeUID)) {
+ return new TouchWandDimmerHandler(thing);
+ }
+
+ return null;
+ }
+
+ @Reference
+ protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ protected void unsetHttpClientFactory(HttpClientFactory httpClientFactory) {
+ this.httpClient = null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import java.io.UnsupportedEncodingException;
+import java.net.CookieManager;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.openhab.core.library.types.OnOffType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link TouchWandRestClient} is responsible for handling low level commands units TouchWand WonderFull hub
+ * REST API interface
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandRestClient {
+
+ private final Logger logger = LoggerFactory.getLogger(TouchWandRestClient.class);
+
+ static CookieManager cookieManager = new CookieManager();
+
+ private static final HttpMethod METHOD_GET = HttpMethod.GET;
+ private static final HttpMethod METHOD_POST = HttpMethod.POST;
+
+ private static final String CMD_LOGIN = "login";
+ private static final String CMD_LIST_UNITS = "listunits";
+ private static final String CMD_LIST_SCENARIOS = "listsencarios";
+ private static final String CMD_UNIT_ACTION = "action";
+ private static final String CMD_GET_UNIT_BY_ID = "getunitbyid";
+
+ private static final String ACTION_SWITCH_OFF = "{\"id\":%s,\"value\":" + SWITCH_STATUS_OFF + "}";
+ private static final String ACTION_SWITCH_ON = "{\"id\":%s,\"value\":" + SWITCH_STATUS_ON + "}";
+ private static final String ACTION_SHUTTER_DOWN = "{\"id\":%s,\"value\":0,\"type\":\"height\"}";
+ private static final String ACTION_SHUTTER_UP = "{\"id\":%s,\"value\":255,\"type\":\"height\"}";
+ private static final String ACTION_SHUTTER_STOP = "{\"id\":%s,\"value\":0,\"type\":\"stop\"}";
+ private static final String ACTION_SHUTTER_POSITION = "{\"id\":%s,\"value\":%s}";
+ private static final String ACTION_DIMMER_POSITION = "{\"id\":%s,\"value\":%s}";
+
+ private static final String CONTENT_TYPE_APPLICATION_JSON = MimeTypes.Type.APPLICATION_JSON.asString();
+
+ private static final int REQUEST_TIMEOUT_SEC = 10;
+
+ private static final Map<String, String> COMMAND_MAP = new HashMap<String, String>();
+ static {
+ COMMAND_MAP.put(CMD_LOGIN, "/auth/login?");
+ COMMAND_MAP.put(CMD_LIST_UNITS, "/units/listUnits");
+ COMMAND_MAP.put(CMD_LIST_SCENARIOS, "/scenarios/listScenarios");
+ COMMAND_MAP.put(CMD_UNIT_ACTION, "/units/action");
+ COMMAND_MAP.put(CMD_GET_UNIT_BY_ID, "/units/getUnitByID?");
+ }
+
+ private String touchWandIpAddr = "";
+ private String touchWandPort = "";
+ private boolean isConnected = false;
+ private HttpClient httpClient;
+
+ public TouchWandRestClient(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public final boolean connect(String user, String pass, String ipAddr, String port) {
+ touchWandIpAddr = ipAddr;
+ touchWandPort = port;
+ isConnected = cmdLogin(user, pass, ipAddr);
+
+ return isConnected;
+ }
+
+ private final boolean cmdLogin(String user, String pass, String ipAddr) {
+ String encodedUser;
+ String encodedPass;
+ String response = "";
+
+ try {
+ encodedUser = URLEncoder.encode(user, StandardCharsets.UTF_8.toString());
+ encodedPass = URLEncoder.encode(pass, StandardCharsets.UTF_8.toString());
+ String command = buildUrl(CMD_LOGIN) + "user=" + encodedUser + "&" + "psw=" + encodedPass;
+ response = sendCommand(command, METHOD_GET, "");
+ } catch (UnsupportedEncodingException e) {
+ logger.warn("Error url encoding username or password : {}", e.getMessage());
+ }
+
+ return !response.equals("Unauthorized");
+ }
+
+ public String cmdListUnits() {
+ String command = buildUrl(CMD_LIST_UNITS);
+ String response = sendCommand(command, METHOD_GET, "");
+
+ return response;
+ }
+
+ public String cmdGetUnitById(String id) {
+ String command = buildUrl(CMD_GET_UNIT_BY_ID) + "id=" + id;
+ String response = sendCommand(command, METHOD_GET, "");
+
+ return response;
+ }
+
+ public void cmdSwitchOnOff(String id, OnOffType onoff) {
+ String action;
+
+ if (OnOffType.OFF.equals(onoff)) {
+ action = String.format(ACTION_SWITCH_OFF, id);
+ } else {
+ action = String.format(ACTION_SWITCH_ON, id);
+ }
+ cmdUnitAction(action);
+ }
+
+ public void cmdShutterUp(String id) {
+ String action = String.format(ACTION_SHUTTER_UP, id);
+ cmdUnitAction(action);
+ }
+
+ public void cmdShutterDown(String id) {
+ String action = String.format(ACTION_SHUTTER_DOWN, id);
+ cmdUnitAction(action);
+ }
+
+ public void cmdShutterPosition(String id, String position) {
+ String action = String.format(ACTION_SHUTTER_POSITION, id, position);
+ cmdUnitAction(action);
+ }
+
+ public void cmdShutterStop(String id) {
+ String action = String.format(ACTION_SHUTTER_STOP, id);
+ cmdUnitAction(action);
+ }
+
+ public void cmdDimmerPosition(String id, String position) {
+ String action = String.format(ACTION_DIMMER_POSITION, id, position);
+ cmdUnitAction(action);
+ }
+
+ private String cmdUnitAction(String action) {
+ String command = buildUrl(CMD_UNIT_ACTION);
+ String response = sendCommand(command, METHOD_POST, action);
+
+ return response;
+ }
+
+ private String buildUrl(String command) {
+ String url = "http://" + touchWandIpAddr + ":" + touchWandPort + COMMAND_MAP.get(command);
+ return url;
+ }
+
+ private synchronized String sendCommand(String command, HttpMethod method, String content) {
+ ContentResponse response;
+ Request request;
+
+ URL url = null;
+ try {
+ url = new URL(command);
+ } catch (MalformedURLException e) {
+ logger.warn("Error building URL {} : {}", command, e.getMessage());
+ return "";
+ }
+
+ request = httpClient.newRequest(url.toString()).timeout(REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS).method(method);
+ if (method.equals(METHOD_POST) && (!content.isEmpty())) {
+ ContentProvider contentProvider = new StringContentProvider(CONTENT_TYPE_APPLICATION_JSON, content,
+ StandardCharsets.UTF_8);
+ request = request.content(contentProvider);
+ }
+
+ try {
+ response = request.send();
+ return response.getContentAsString();
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ logger.warn("Error opening connecton to {} : {} ", touchWandIpAddr, e.getMessage());
+ }
+ return "";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.CHANNEL_SHUTTER;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandShutterSwitchUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link TouchWandShutterHandler} is responsible for handling commands for Shutter units
+ *
+ * @author Roie Geron - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TouchWandShutterHandler extends TouchWandBaseUnitHandler {
+
+ public TouchWandShutterHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void touchWandUnitHandleCommand(Command command) {
+ TouchWandBridgeHandler touchWandBridgeHandler = bridgeHandler;
+ if (touchWandBridgeHandler != null) {
+ switch (command.toString()) {
+ case "OFF":
+ case "DOWN":
+ touchWandBridgeHandler.touchWandClient.cmdShutterDown(unitId);
+ break;
+ case "ON":
+ case "UP":
+ touchWandBridgeHandler.touchWandClient.cmdShutterUp(unitId);
+ break;
+ case "STOP":
+ touchWandBridgeHandler.touchWandClient.cmdShutterStop(unitId);
+ break;
+ default:
+ touchWandBridgeHandler.touchWandClient.cmdShutterPosition(unitId, command.toString());
+ break;
+ }
+ }
+ }
+
+ @Override
+ void updateTouchWandUnitState(TouchWandUnitData unitData) {
+ if (unitData instanceof TouchWandShutterSwitchUnitData) {
+ int status = ((TouchWandShutterSwitchUnitData) unitData).getCurrStatus();
+ PercentType state = PercentType.ZERO;
+ int convertStatus = 100 - status;
+ state = new PercentType(convertStatus);
+ updateState(CHANNEL_SHUTTER, state);
+ } else {
+ logger.warn("updateTouchWandUnitState incompatible TouchWandUnitData instance");
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandShutterSwitchUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link TouchWandSwitchHandler} is responsible for handling command for Switch unit
+ *
+ *
+ * @author Roie Geron - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TouchWandSwitchHandler extends TouchWandBaseUnitHandler {
+
+ public TouchWandSwitchHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ void updateTouchWandUnitState(TouchWandUnitData unitData) {
+ if (unitData instanceof TouchWandShutterSwitchUnitData) {
+ OnOffType state;
+ int status = ((TouchWandShutterSwitchUnitData) unitData).getCurrStatus();
+ String sStatus = Integer.toString(status);
+
+ if (sStatus.equals(SWITCH_STATUS_OFF)) {
+ state = OnOffType.OFF;
+ } else if ((status >= 1) && (status <= 255)) {
+ state = OnOffType.ON;
+ } else {
+ logger.warn("updateTouchWandUnitState illegal update value {}", status);
+ return;
+ }
+ updateState(CHANNEL_SWITCH, state);
+ } else {
+ logger.warn("updateTouchWandUnitState incompatible TouchWandUnitData instance");
+ }
+ }
+
+ @Override
+ void touchWandUnitHandleCommand(Command command) {
+ if (command instanceof OnOffType) {
+ TouchWandBridgeHandler touchWandBridgeHandler = bridgeHandler;
+ if (touchWandBridgeHandler != null) {
+ touchWandBridgeHandler.touchWandClient.cmdSwitchOnOff(unitId, (OnOffType) command);
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+
+/**
+ * Interface for a listener on the {@link TouchWandWebSocket}.
+ * When it is registered on the socket, it gets called back when {@link TouchWandWebSocket} receives data.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public interface TouchWandUnitStatusUpdateListener {
+
+ void onDataReceived(TouchWandUnitData unitData);
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+
+/**
+ * Listener for Unit updates.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public interface TouchWandUnitUpdateListener {
+
+ void onItemStatusUpdate(TouchWandUnitData unitData);
+
+ String getId();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.CHANNEL_WALLCONTROLLER_ACTION;
+
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitDataWallController;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * The {@link TouchWandWallControllerHandler} is responsible for handling commands and triggers
+ *
+ * for WallController units
+ *
+ * @author Roie Geron - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TouchWandWallControllerHandler extends TouchWandBaseUnitHandler {
+
+ private long timeSinceLastEventMs;
+ private static final int ADJUSTENT_EVENT_FILTER_TIME_MILLISEC = 2000; // 2 seconds
+
+ public TouchWandWallControllerHandler(Thing thing) {
+ super(thing);
+ timeSinceLastEventMs = Instant.now().toEpochMilli();
+ }
+
+ @Override
+ void touchWandUnitHandleCommand(Command command) {
+ }
+
+ @Override
+ void updateTouchWandUnitState(TouchWandUnitData unitData) {
+ int status = ((TouchWandUnitDataWallController) unitData).getCurrStatus();
+ long timeDiff = Instant.now().toEpochMilli() - timeSinceLastEventMs;
+ if ((timeDiff) > ADJUSTENT_EVENT_FILTER_TIME_MILLISEC) {
+ String action = status <= 100 ? "SHORT" : "LONG";
+ triggerChannel(CHANNEL_WALLCONTROLLER_ACTION, action);
+ }
+ timeSinceLastEventMs = Instant.now().toEpochMilli();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.SUPPORTED_TOUCHWAND_TYPES;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArraySet;
+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.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitFromJson;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link TouchWandWebSockets} class implements WebSockets API to TouchWand controller
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandWebSockets {
+
+ private static final int CONNECT_TIMEOUT_SEC = 10;
+ private static final int WEBSOCKET_RECONNECT_INTERVAL_SEC = CONNECT_TIMEOUT_SEC * 2;
+ private static final int WEBSOCKET_IDLE_TIMEOUT_MS = CONNECT_TIMEOUT_SEC * 10 * 1000;
+ private final Logger logger = LoggerFactory.getLogger(TouchWandWebSockets.class);
+ private static final String WS_ENDPOINT_TOUCHWAND = "/async";
+
+ private WebSocketClient client;
+ private String controllerAddress;
+ private TouchWandSocket touchWandSocket;
+ private boolean isShutDown = false;
+ private CopyOnWriteArraySet<TouchWandUnitStatusUpdateListener> listeners = new CopyOnWriteArraySet<>();
+ private @Nullable ScheduledFuture<?> socketReconnect;
+ private @Nullable URI uri;
+
+ private ScheduledExecutorService scheduler;
+
+ public TouchWandWebSockets(String ipAddress, ScheduledExecutorService scheduler) {
+ client = new WebSocketClient();
+ touchWandSocket = new TouchWandSocket();
+ this.controllerAddress = ipAddress;
+ this.scheduler = scheduler;
+ socketReconnect = null;
+ }
+
+ public void connect() {
+ try {
+ uri = new URI("ws://" + controllerAddress + WS_ENDPOINT_TOUCHWAND);
+ } catch (URISyntaxException e) {
+ logger.warn("URI not valid {} message {}", uri, e.getMessage());
+ return;
+ }
+
+ client.setConnectTimeout(CONNECT_TIMEOUT_SEC);
+ ClientUpgradeRequest request = new ClientUpgradeRequest();
+ request.setSubProtocols("relay_protocol");
+
+ try {
+ client.start();
+ client.connect(touchWandSocket, uri, request);
+ } catch (Exception e) {
+ logger.warn("Could not connect webSocket URI {} message {}", uri, e.getMessage());
+ return;
+ }
+ }
+
+ public void dispose() {
+ isShutDown = true;
+ try {
+ client.stop();
+ ScheduledFuture<?> mySocketReconnect = socketReconnect;
+ if (mySocketReconnect != null) {
+ mySocketReconnect.cancel(true);
+ }
+ } catch (Exception e) {
+ logger.warn("Could not stop webSocketClient, message {}", e.getMessage());
+ }
+ }
+
+ public void registerListener(TouchWandUnitStatusUpdateListener listener) {
+ if (!listeners.contains(listener)) {
+ logger.debug("Adding TouchWandWebSocket listener {}", listener);
+ listeners.add(listener);
+ }
+ }
+
+ public void unregisterListener(TouchWandUnitStatusUpdateListener listener) {
+ logger.debug("Removing TouchWandWebSocket listener {}", listener);
+ listeners.remove(listener);
+ }
+
+ @WebSocket(maxIdleTime = WEBSOCKET_IDLE_TIMEOUT_MS)
+ public class TouchWandSocket {
+
+ @OnWebSocketClose
+ public void onClose(int statusCode, String reason) {
+ logger.debug("Connection closed: {} - {}", statusCode, reason);
+ if (!isShutDown) {
+ logger.debug("weSocket Closed - reconnecting");
+ asyncWeb();
+ }
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(Session session) {
+ logger.debug("TouchWandWebSockets connected to {}", session.getRemoteAddress().toString());
+ try {
+ session.getRemote().sendString("{\"myopenhab\": \"myopenhab\"}");
+ } catch (IOException e) {
+ logger.warn("sendString : {}", e.getMessage());
+ }
+ }
+
+ @OnWebSocketMessage
+ public void onMessage(String msg) {
+ TouchWandUnitData touchWandUnit;
+ JsonParser jsonParser = new JsonParser();
+ try {
+ JsonObject unitObj = jsonParser.parse(msg).getAsJsonObject();
+ boolean eventUnitChanged = unitObj.get("type").getAsString().equals("UNIT_CHANGED");
+ if (!eventUnitChanged) {
+ return;
+ }
+ touchWandUnit = TouchWandUnitFromJson.parseResponse(unitObj.get("unit").getAsJsonObject());
+ if (!touchWandUnit.getStatus().equals("ALIVE")) {
+ return;
+ }
+ boolean supportedUnitType = Arrays.asList(SUPPORTED_TOUCHWAND_TYPES).contains(touchWandUnit.getType());
+ if (!supportedUnitType) {
+ logger.debug("UNIT_CHANGED for unsupported unit type {}", touchWandUnit.getType());
+ return;
+ }
+ logger.debug("UNIT_CHANGED: name {} id {} status {}", touchWandUnit.getName(), touchWandUnit.getId(),
+ touchWandUnit.getCurrStatus());
+
+ for (TouchWandUnitStatusUpdateListener listener : listeners) {
+ listener.onDataReceived(touchWandUnit);
+
+ }
+ } catch (JsonSyntaxException e) {
+ logger.warn("jsonParser.parse {} ", e.getMessage());
+ }
+ }
+
+ @OnWebSocketError
+ public void onError(Throwable cause) {
+ logger.warn("WebSocket Error: {}", cause.getMessage());
+ if (!isShutDown) {
+ logger.debug("WebSocket onError - reconnecting");
+ asyncWeb();
+ }
+ }
+
+ private void asyncWeb() {
+ ScheduledFuture<?> mySocketReconnect = socketReconnect;
+ if (mySocketReconnect == null || mySocketReconnect.isDone()) {
+ socketReconnect = scheduler.schedule(TouchWandWebSockets.this::connect,
+ WEBSOCKET_RECONNECT_INTERVAL_SEC, TimeUnit.SECONDS);
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.touchwand.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Configuration class for {@link TouchwandBridgeHandler}.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+
+@NonNullByDefault
+public class TouchwandBridgeConfiguration {
+ public String username = "";
+ public String password = "";
+ public String ipAddress = "";
+ public int port;
+ public int statusrefresh;
+ public boolean addSecondaryUnits;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.discovery;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.THING_TYPE_BRIDGE;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.touchwand.internal.TouchWandBindingConstants;
+import org.openhab.binding.touchwand.internal.TouchWandBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link TouchWandControllerDiscoveryService} Discovery service for Touchwand Controllers.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@Component(service = DiscoveryService.class, configurationPid = "discovery.touchwand")
+@NonNullByDefault
+public class TouchWandControllerDiscoveryService extends AbstractDiscoveryService {
+
+ private static final int SEARCH_TIME_SEC = 2;
+ private static final int TOUCHWAND_BCAST_PORT = 35000;
+ private final Logger logger = LoggerFactory.getLogger(TouchWandControllerDiscoveryService.class);
+
+ private @Nullable Thread socketReceiveThread = null;
+ private DatagramSocket listenSocket;
+
+ public TouchWandControllerDiscoveryService() throws SocketException {
+ super(TouchWandBridgeHandler.SUPPORTED_THING_TYPES, SEARCH_TIME_SEC, true);
+
+ listenSocket = new DatagramSocket(TOUCHWAND_BCAST_PORT);
+ }
+
+ @Override
+ protected void startScan() {
+ DatagramSocket localListenSocket = listenSocket;
+ runReceiveThread(localListenSocket);
+ }
+
+ @Override
+ protected void stopScan() {
+ super.stopScan();
+ }
+
+ @Override
+ public void activate(@Nullable Map<String, @Nullable Object> configProperties) {
+ removeOlderResults(getTimestampOfLastScan());
+ super.activate(configProperties);
+ }
+
+ @Override
+ public void deactivate() {
+ Thread mySocketReceiveThread = socketReceiveThread;
+ if (mySocketReceiveThread != null) {
+ mySocketReceiveThread.interrupt();
+ socketReceiveThread = null;
+ }
+
+ listenSocket.close();
+ super.deactivate();
+ }
+
+ private void addDeviceDiscoveryResult(String label, String ip) {
+ String id = ip.replaceAll("\\.", "");
+ ThingUID thingUID = new ThingUID(THING_TYPE_BRIDGE, id);
+ Map<String, Object> properties = new HashMap<>();
+ properties.put("label", label);
+ properties.put("ipAddress", ip);
+ // @formatter:off
+ logger.debug("Add new Bridge label:{} id {} ",label, id);
+ thingDiscovered(DiscoveryResultBuilder.create(thingUID)
+ .withThingType(THING_TYPE_BRIDGE)
+ .withLabel(label)
+ .withProperties(properties)
+ .withRepresentationProperty("ipAddress")
+ .build()
+ );
+ // @formatter:on
+ }
+
+ protected void runReceiveThread(DatagramSocket socket) {
+ Thread localSocketReceivedThread = socketReceiveThread = new ReceiverThread(socket);
+ localSocketReceivedThread.setName(TouchWandBindingConstants.DISCOVERY_THREAD_ID);
+ localSocketReceivedThread.setDaemon(true);
+ localSocketReceivedThread.start();
+ }
+
+ private class ReceiverThread extends Thread {
+
+ private static final int BUFFER_LENGTH = 256;
+ private DatagramPacket dgram = new DatagramPacket(new byte[BUFFER_LENGTH], BUFFER_LENGTH);
+ private DatagramSocket mySocket;
+
+ public ReceiverThread(DatagramSocket socket) {
+ mySocket = socket;
+ }
+
+ @Override
+ public void run() {
+ receiveData(dgram);
+ }
+
+ private void receiveData(DatagramPacket datagram) {
+ try {
+ while (!isInterrupted()) {
+ mySocket.receive(datagram);
+ InetAddress address = datagram.getAddress();
+ String sentence = new String(dgram.getData(), 0, dgram.getLength(), StandardCharsets.US_ASCII);
+ addDeviceDiscoveryResult(sentence, address.getHostAddress().toString());
+ logger.debug("Received Datagram from {}:{} on Port {} message {}", address.getHostAddress(),
+ dgram.getPort(), mySocket.getLocalPort(), sentence);
+ }
+ } catch (IOException e) {
+ if (!isInterrupted()) {
+ logger.warn("Error while receiving {}", e.getMessage());
+ } else {
+ logger.debug("Receiver thread was interrupted {}", e.getMessage());
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.discovery;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.touchwand.internal.TouchWandBridgeHandler;
+import org.openhab.binding.touchwand.internal.TouchWandUnitStatusUpdateListener;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
+import org.openhab.binding.touchwand.internal.dto.TouchWandUnitFromJson;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link TouchWandUnitDiscoveryService} Discovery service for TouchWand units.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandUnitDiscoveryService extends AbstractDiscoveryService
+ implements DiscoveryService, ThingHandlerService {
+
+ private static final int SEARCH_TIME_SEC = 10;
+ private static final int SCAN_INTERVAL_SEC = 60;
+ private static final int LINK_DISCOVERY_SERVICE_INITIAL_DELAY_SEC = 5;
+ private static final String[] CONNECTIVITY_OPTIONS = { CONNECTIVITY_KNX, CONNECTIVITY_ZWAVE };
+ private @NonNullByDefault({}) TouchWandBridgeHandler touchWandBridgeHandler;
+ private final Logger logger = LoggerFactory.getLogger(TouchWandUnitDiscoveryService.class);
+
+ private @Nullable ScheduledFuture<?> scanningJob;
+ private CopyOnWriteArraySet<TouchWandUnitStatusUpdateListener> listeners = new CopyOnWriteArraySet<>();
+
+ public TouchWandUnitDiscoveryService() {
+ super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME_SEC, true);
+ }
+
+ @Override
+ protected void startScan() {
+ if (touchWandBridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
+ logger.warn("Could not scan units while bridge offline");
+ return;
+ }
+
+ logger.debug("Starting TouchWand discovery on bridge {}", touchWandBridgeHandler.getThing().getUID());
+ String response = touchWandBridgeHandler.touchWandClient.cmdListUnits();
+ if (response.isEmpty()) {
+ return;
+ }
+
+ JsonParser jsonParser = new JsonParser();
+ try {
+ JsonArray jsonArray = jsonParser.parse(response).getAsJsonArray();
+ if (jsonArray.isJsonArray()) {
+ try {
+ for (JsonElement unit : jsonArray) {
+ TouchWandUnitData touchWandUnit;
+ touchWandUnit = TouchWandUnitFromJson.parseResponse(unit.getAsJsonObject());
+ if (touchWandUnit == null) {
+ continue;
+ }
+ if (!touchWandBridgeHandler.isAddSecondaryControllerUnits()) {
+ if (!Arrays.asList(CONNECTIVITY_OPTIONS).contains(touchWandUnit.getConnectivity())) {
+ continue;
+ }
+ }
+ String type = touchWandUnit.getType();
+ if (!Arrays.asList(SUPPORTED_TOUCHWAND_TYPES).contains(type)) {
+ logger.debug("Unit discovery skipping unsupported unit type : {} ", type);
+ continue;
+ }
+ switch (type) {
+ case TYPE_WALLCONTROLLER:
+ addDeviceDiscoveryResult(touchWandUnit, THING_TYPE_WALLCONTROLLER);
+ break;
+ case TYPE_SWITCH:
+ addDeviceDiscoveryResult(touchWandUnit, THING_TYPE_SWITCH);
+ notifyListeners(touchWandUnit);
+ break;
+ case TYPE_DIMMER:
+ addDeviceDiscoveryResult(touchWandUnit, THING_TYPE_DIMMER);
+ notifyListeners(touchWandUnit);
+ break;
+ case TYPE_SHUTTER:
+ addDeviceDiscoveryResult(touchWandUnit, THING_TYPE_SHUTTER);
+ break;
+ default:
+ continue;
+ }
+ }
+ } catch (JsonSyntaxException e) {
+ logger.warn("Could not parse unit {}", e.getMessage());
+ }
+ }
+ } catch (JsonSyntaxException msg) {
+ logger.warn("Could not parse list units response {}", msg.getMessage());
+ }
+ }
+
+ private void notifyListeners(TouchWandUnitData touchWandUnit) {
+ for (TouchWandUnitStatusUpdateListener listener : listeners) {
+ listener.onDataReceived(touchWandUnit);
+ }
+ }
+
+ @Override
+ protected void stopScan() {
+ removeOlderResults(getTimestampOfLastScan());
+ super.stopScan();
+ }
+
+ @Override
+ public void activate() {
+ super.activate(null);
+ removeOlderResults(new Date().getTime(), touchWandBridgeHandler.getThing().getUID());
+ }
+
+ @Override
+ public void deactivate() {
+ removeOlderResults(new Date().getTime(), touchWandBridgeHandler.getThing().getUID());
+ super.deactivate();
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ ScheduledFuture<?> localScanningJob = scanningJob;
+ if (localScanningJob == null || localScanningJob.isCancelled()) {
+ scanningJob = scheduler.scheduleWithFixedDelay(this::startScan, LINK_DISCOVERY_SERVICE_INITIAL_DELAY_SEC,
+ SCAN_INTERVAL_SEC, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ ScheduledFuture<?> myScanningJob = scanningJob;
+ if (myScanningJob != null) {
+ myScanningJob.cancel(true);
+ scanningJob = null;
+ }
+ }
+
+ public void registerListener(TouchWandUnitStatusUpdateListener listener) {
+ if (!listeners.contains(listener)) {
+ logger.debug("Adding TouchWandWebSocket listener {}", listener);
+ listeners.add(listener);
+ }
+ }
+
+ public void unregisterListener(TouchWandUnitStatusUpdateListener listener) {
+ logger.debug("Removing TouchWandWebSocket listener {}", listener);
+ listeners.remove(listener);
+ }
+
+ @Override
+ public int getScanTimeout() {
+ return SEARCH_TIME_SEC;
+ }
+
+ private void addDeviceDiscoveryResult(TouchWandUnitData unit, ThingTypeUID typeUID) {
+ ThingUID bridgeUID = touchWandBridgeHandler.getThing().getUID();
+ ThingUID thingUID = new ThingUID(typeUID, bridgeUID, unit.getId().toString());
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(HANDLER_PROPERTIES_ID, unit.getId().toString());
+ properties.put(HANDLER_PROPERTIES_NAME, unit.getName());
+ // @formatter:off
+ thingDiscovered(DiscoveryResultBuilder.create(thingUID)
+ .withThingType(typeUID)
+ .withLabel(unit.getName())
+ .withBridge(bridgeUID)
+ .withProperties(properties)
+ .withRepresentationProperty(HANDLER_PROPERTIES_ID)
+ .build()
+ );
+ // @formatter:on
+ }
+
+ @Override
+ public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
+ if (handler instanceof TouchWandBridgeHandler) {
+ touchWandBridgeHandler = (TouchWandBridgeHandler) handler;
+ registerListener(touchWandBridgeHandler);
+ }
+ }
+
+ @Override
+ public @NonNull ThingHandler getThingHandler() {
+ return touchWandBridgeHandler;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+/**
+ * The {@link Csc} implements Csc data class.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+public class Csc {
+
+ private int sceneNo;
+ private int ts;
+ private int keyAttr;
+
+ public int getSceneNo() {
+ return sceneNo;
+ }
+
+ public void setSceneNo(int sceneNo) {
+ this.sceneNo = sceneNo;
+ }
+
+ public int getTs() {
+ return ts;
+ }
+
+ public void setTs(int ts) {
+ this.ts = ts;
+ }
+
+ public int getKeyAttr() {
+ return keyAttr;
+ }
+
+ public void setKeyAttr(int keyAttr) {
+ this.keyAttr = keyAttr;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+/**
+ * The {@link CurrStatus} implements CurrStatus data class.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+
+public class CurrStatus {
+
+ private Csc csc;
+
+ public Csc getCsc() {
+ return csc;
+ }
+
+ public void setCsc(Csc csc) {
+ this.csc = csc;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TouchWandShutterSwitchUnitData} implements Shutter and Switch units property.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public class TouchWandShutterSwitchUnitData extends TouchWandUnitData {
+
+ private Integer currStatus = 0;
+
+ @Override
+ public Integer getCurrStatus() {
+ return currStatus;
+ }
+
+ public void setCurrStatus(int currStatus) {
+ this.currStatus = currStatus;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link TouchWandUnitData} implements unit property.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+@NonNullByDefault
+public abstract class TouchWandUnitData {
+
+ private Integer id = 0;
+ private String name = "";
+ private String type = "";
+ private Integer nodeId = 0;
+ private Integer epId = 0;
+ private String icon = "";
+ private String connectivity = "";
+ private String status = "";
+ private boolean isFavorite = false;
+ private Integer errorCode = 0;
+ private boolean hasPowerMeter;
+ private boolean hasBattery;
+ private Object config = "";
+ private Object association = "";
+ private String customOp = "";
+ private boolean isHidden = false;
+ private String createdAt = "";
+ private String updatedAt = "";
+ private Integer roomId = 0;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public int getNodeId() {
+ return nodeId;
+ }
+
+ public void setNodeId(int nodeId) {
+ this.nodeId = nodeId;
+ }
+
+ public int getEpId() {
+ return epId;
+ }
+
+ public void setEpId(int epId) {
+ this.epId = epId;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public String getConnectivity() {
+ return connectivity;
+ }
+
+ public void setConnectivity(String connectivity) {
+ this.connectivity = connectivity;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public boolean getIsFavorite() {
+ return isFavorite;
+ }
+
+ public void setIsFavorite(boolean isFavorite) {
+ this.isFavorite = isFavorite;
+ }
+
+ public Integer getErrorCode() {
+ return errorCode;
+ }
+
+ public void setErrorCode(Integer errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public boolean getHasPowerMeter() {
+ return hasPowerMeter;
+ }
+
+ public void setHasPowerMeter(boolean hasPowerMeter) {
+ this.hasPowerMeter = hasPowerMeter;
+ }
+
+ public boolean getHasBattery() {
+ return hasBattery;
+ }
+
+ public void setHasBattery(boolean hasBattery) {
+ this.hasBattery = hasBattery;
+ }
+
+ public Object getConfig() {
+ return config;
+ }
+
+ public void setConfig(Object config) {
+ this.config = config;
+ }
+
+ public Object getAssociation() {
+ return association;
+ }
+
+ public void setAssociation(Object association) {
+ this.association = association;
+ }
+
+ public String getCustomOp() {
+ return customOp;
+ }
+
+ public void setCustomOp(String customOp) {
+ this.customOp = customOp;
+ }
+
+ public boolean getIsHidden() {
+ return isHidden;
+ }
+
+ public void setIsHidden(boolean isHidden) {
+ this.isHidden = isHidden;
+ }
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(String createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(String updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public Integer getRoomId() {
+ return roomId;
+ }
+
+ public void setRoomId(Integer roomId) {
+ this.roomId = roomId;
+ }
+
+ public abstract Integer getCurrStatus();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+/**
+ * The {@link TouchWandUnitDataWallController} implements WallController unit
+ * property.
+ *
+ * @author Roie Geron - Initial contribution
+ */
+public class TouchWandUnitDataWallController extends TouchWandUnitData {
+
+ private CurrStatus currStatus;
+
+ @Override
+ public Integer getCurrStatus() {
+ if (currStatus != null) {
+ return currStatus.getCsc().getKeyAttr();
+ } else {
+ return 0;
+ }
+ }
+
+ public void setCurrStatus(CurrStatus currStatus) {
+ this.currStatus = currStatus;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.touchwand.internal.dto;
+
+import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.*;
+
+import java.util.Arrays;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * The {@link TouchWandUnitFromJson} parse Json unit data
+ *
+ * @author Roie Geron - Initial contribution
+ */
+public class TouchWandUnitFromJson {
+
+ public TouchWandUnitFromJson() {
+ }
+
+ public static TouchWandUnitData parseResponse(JsonObject jsonUnit) {
+ final Gson gson = new Gson();
+ TouchWandUnitData touchWandUnit;
+ String type = jsonUnit.get("type").getAsString();
+ if (!Arrays.asList(SUPPORTED_TOUCHWAND_TYPES).contains(type)) {
+ return null;
+ }
+
+ if (!jsonUnit.has("currStatus") || (jsonUnit.get("currStatus") == null)) {
+ return null;
+ }
+
+ switch (type) {
+ case TYPE_WALLCONTROLLER:
+ touchWandUnit = gson.fromJson(jsonUnit, TouchWandUnitDataWallController.class);
+ break;
+ case TYPE_SWITCH:
+ touchWandUnit = gson.fromJson(jsonUnit, TouchWandShutterSwitchUnitData.class);
+ break;
+ case TYPE_DIMMER:
+ touchWandUnit = gson.fromJson(jsonUnit, TouchWandShutterSwitchUnitData.class);
+ break;
+ case TYPE_SHUTTER:
+ touchWandUnit = gson.fromJson(jsonUnit, TouchWandShutterSwitchUnitData.class);
+ break;
+ default:
+ return null;
+ }
+
+ return touchWandUnit;
+ }
+
+ public static TouchWandUnitData parseResponse(String JsonUnit) {
+ final JsonParser jsonParser = new JsonParser();
+ JsonObject unitObj = jsonParser.parse(JsonUnit).getAsJsonObject();
+ return parseResponse(unitObj);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="touchwand" 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>TouchWand Binding</name>
+ <description>Connect to TouchWand Wanderfull™ Hub and communicate with units connected to the controller.</description>
+ <author>Roie Geron</author>
+
+</binding:binding>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="touchwand"
+ 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">
+ <bridge-type id="bridge">
+ <label>TouchWand Wanderfull Hub Bridge</label>
+ <description>Multifunctional Gateway - a bridge to units and scenarios</description>
+ <properties>
+ <property name="vendor">TouchWand</property>
+ </properties>
+
+ <config-description>
+ <parameter name="username" type="text">
+ <label>Username</label>
+ <description>Username for TouchWand Wanderfull Hub</description>
+ </parameter>
+ <parameter name="password" type="text" required="true">
+ <context>password</context>
+ <label>Password</label>
+ <description>Password for TouchWand Wanderfull Hub </description>
+ </parameter>
+ <parameter name="ipAddress" type="text" required="true">
+ <context>network-address</context>
+ <label>Network Address</label>
+ <description>Network address of this TouchWand Wanderfull Hub </description>
+ </parameter>
+ <parameter name="port" type="integer" min="80" max="65535" required="true">
+ <label>Port</label>
+ <description>Port of the TouchWand Wanderfull Hub communication channel</description>
+ <default>80</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="statusrefresh" type="integer" unit="s">
+ <default>120</default>
+ <description>Unit status refresh interval (seconds)</description>
+ <label>Refresh</label>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="addSecondaryUnits" type="boolean">
+ <default>false</default>
+ <description>If the controller is primary, add secondary controllers units as well</description>
+ <label>Add Secondary Controllers</label>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </bridge-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="touchwand"
+ 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="dimmer">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"></bridge-type-ref>
+ </supported-bridge-type-refs>
+ <label>TouchWand Dimmer Unit</label>
+ <channels>
+ <channel id="brightness" typeId="system.brightness">
+ <description>light brightness control</description>
+ </channel>
+ </channels>
+ </thing-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="touchwand"
+ 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="shutter">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"></bridge-type-ref>
+ </supported-bridge-type-refs>
+ <label>TouchWand Shutter Unit</label>
+ <channels>
+ <channel id="shutter" typeId="shutter">
+ <description>Shutter control</description>
+ </channel>
+ </channels>
+ </thing-type>
+ <channel-type id="shutter">
+ <item-type>Rollershutter</item-type>
+ <label>Shutter</label>
+ <description>The Shutter channel allows control shutters</description>
+ <category>Blinds</category>
+ </channel-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="touchwand"
+ 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="switch">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"></bridge-type-ref>
+ </supported-bridge-type-refs>
+ <label>TouchWand Switch Unit</label>
+ <channels>
+ <channel id="switch" typeId="switch">
+ <description>Unit on off switch</description>
+ </channel>
+ </channels>
+ </thing-type>
+ <channel-type id="switch">
+ <item-type>Switch</item-type>
+ <label>Switch</label>
+ <description>The switch channel allows to switch the light on and off.
+ </description>
+ <category>Light</category>
+ <tags>
+ <tag>Lighting</tag>
+ </tags>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="touchwand"
+ 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="wallcontroller">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"></bridge-type-ref>
+ </supported-bridge-type-refs>
+ <label>TouchWand WallController Unit</label>
+ <channels>
+ <channel id="wallaction" typeId="wallaction">
+ <description>WallController action</description>
+ </channel>
+ </channels>
+ </thing-type>
+ <channel-type id="wallaction">
+ <item-type>String</item-type>
+ <label>WallController action</label>
+ <event>
+ <options>
+ <option value="LONG">long</option>
+ <option value="SHORT">short</option>
+ </options>
+ </event>
+ </channel-type>
+</thing:thing-descriptions>
<module>org.openhab.binding.tellstick</module>
<module>org.openhab.binding.tesla</module>
<module>org.openhab.binding.tibber</module>
+ <module>org.openhab.binding.touchwand</module>
<module>org.openhab.binding.tplinksmarthome</module>
<module>org.openhab.binding.tradfri</module>
<module>org.openhab.binding.unifi</module>