]> git.basschouten.com Git - openhab-addons.git/commitdiff
[touchwand] Touchwand Binding initial contribution - migration to OH3 (#8754)
authorRoie Geron <roie.geron@gmail.com>
Sun, 18 Oct 2020 00:19:40 +0000 (03:19 +0300)
committerGitHub <noreply@github.com>
Sun, 18 Oct 2020 00:19:40 +0000 (17:19 -0700)
* initial migration to OH 3

Signed-off-by: Roie Geron <roie.geron@gmail.com>
33 files changed:
CODEOWNERS
bundles/org.openhab.binding.touchwand/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/README.md [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBaseUnitHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBridgeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandDimmerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandRestClient.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandShutterHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandSwitchHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitStatusUpdateListener.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitUpdateListener.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWallControllerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWebSockets.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/config/TouchwandBridgeConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandControllerDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandUnitDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/Csc.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/CurrStatus.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandShutterSwitchUnitData.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitData.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitDataWallController.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitFromJson.java [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/bridge.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/dimmer.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/shutter.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/switch.xml [new file with mode: 0644]
bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/wallcontroller.xml [new file with mode: 0644]
bundles/pom.xml

index 68a951f270df806802896dc8da29d5e2b0079238..45b6eaaceeda9841e825715eb631c0d150d49d46 100644 (file)
 /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
diff --git a/bundles/org.openhab.binding.touchwand/NOTICE b/bundles/org.openhab.binding.touchwand/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.touchwand/README.md b/bundles/org.openhab.binding.touchwand/README.md
new file mode 100644 (file)
index 0000000..0d850c4
--- /dev/null
@@ -0,0 +1,81 @@
+# 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)
+
+![Touchwand Wanderfull™ Hub](http://www.touchwand.com/wp-content/uploads/2017/12/hub-toch-1.png)
+
+## 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"}
+```
diff --git a/bundles/org.openhab.binding.touchwand/pom.xml b/bundles/org.openhab.binding.touchwand/pom.xml
new file mode 100644 (file)
index 0000000..a2129d2
--- /dev/null
@@ -0,0 +1,15 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/feature/feature.xml b/bundles/org.openhab.binding.touchwand/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..1dad7e8
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBaseUnitHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBaseUnitHandler.java
new file mode 100644 (file)
index 0000000..d1b1de3
--- /dev/null
@@ -0,0 +1,157 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBindingConstants.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBindingConstants.java
new file mode 100644 (file)
index 0000000..5f16f4b
--- /dev/null
@@ -0,0 +1,91 @@
+/**
+ * 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 };
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBridgeHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBridgeHandler.java
new file mode 100644 (file)
index 0000000..cefa670
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * 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);
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandDimmerHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandDimmerHandler.java
new file mode 100644 (file)
index 0000000..cbbdf67
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * 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");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandHandlerFactory.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandHandlerFactory.java
new file mode 100644 (file)
index 0000000..9a6f232
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandRestClient.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandRestClient.java
new file mode 100644 (file)
index 0000000..6d43fd0
--- /dev/null
@@ -0,0 +1,208 @@
+/**
+ * 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 "";
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandShutterHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandShutterHandler.java
new file mode 100644 (file)
index 0000000..0e147e9
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * 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");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandSwitchHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandSwitchHandler.java
new file mode 100644 (file)
index 0000000..63f0366
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * 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);
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitStatusUpdateListener.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitStatusUpdateListener.java
new file mode 100644 (file)
index 0000000..5fc7321
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.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);
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitUpdateListener.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitUpdateListener.java
new file mode 100644 (file)
index 0000000..4730510
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.openhab.binding.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();
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWallControllerHandler.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWallControllerHandler.java
new file mode 100644 (file)
index 0000000..32c3e37
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * 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();
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWebSockets.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWebSockets.java
new file mode 100644 (file)
index 0000000..e0c59ba
--- /dev/null
@@ -0,0 +1,193 @@
+/**
+ * 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);
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/config/TouchwandBridgeConfiguration.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/config/TouchwandBridgeConfiguration.java
new file mode 100644 (file)
index 0000000..b21313a
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * 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;
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandControllerDiscoveryService.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandControllerDiscoveryService.java
new file mode 100644 (file)
index 0000000..f79e314
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * 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());
+                }
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandUnitDiscoveryService.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandUnitDiscoveryService.java
new file mode 100644 (file)
index 0000000..26e4ada
--- /dev/null
@@ -0,0 +1,223 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/Csc.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/Csc.java
new file mode 100644 (file)
index 0000000..4d9fb2b
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/CurrStatus.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/CurrStatus.java
new file mode 100644 (file)
index 0000000..5253cf8
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandShutterSwitchUnitData.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandShutterSwitchUnitData.java
new file mode 100644 (file)
index 0000000..136e814
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitData.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitData.java
new file mode 100644 (file)
index 0000000..5ec2869
--- /dev/null
@@ -0,0 +1,199 @@
+/**
+ * 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();
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitDataWallController.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitDataWallController.java
new file mode 100644 (file)
index 0000000..5f29a88
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitFromJson.java b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitFromJson.java
new file mode 100644 (file)
index 0000000..255b9fb
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * 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);
+    }
+}
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..0c83643
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/bridge.xml
new file mode 100644 (file)
index 0000000..a586c03
--- /dev/null
@@ -0,0 +1,48 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/dimmer.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/dimmer.xml
new file mode 100644 (file)
index 0000000..6b651e2
--- /dev/null
@@ -0,0 +1,18 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/shutter.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/shutter.xml
new file mode 100644 (file)
index 0000000..21bd5cb
--- /dev/null
@@ -0,0 +1,24 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/switch.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/switch.xml
new file mode 100644 (file)
index 0000000..c99d235
--- /dev/null
@@ -0,0 +1,29 @@
+<?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>
diff --git a/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/wallcontroller.xml b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/wallcontroller.xml
new file mode 100644 (file)
index 0000000..345429b
--- /dev/null
@@ -0,0 +1,28 @@
+<?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>
index e0c7e2eb72f84c62aa218ae77e1302e5701c8394..7d6054735d904e66f04bfe1ebfc309a4ff4dfa1f 100644 (file)
     <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>