diff --git a/CODEOWNERS b/CODEOWNERS index 68a951f270d..45b6eaaceed 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -242,6 +242,7 @@ /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 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/NOTICE @@ -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 index 00000000000..0d850c4119b --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/README.md @@ -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 :: "Label" @ "Location" +``` + +Where 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 index 00000000000..a2129d2efa9 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/pom.xml @@ -0,0 +1,15 @@ + + + + 4.0.0 + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.0.0-SNAPSHOT + + + org.openhab.binding.touchwand + openHAB Add-ons :: Bundles :: TouchWand Binding + + 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 index 00000000000..1dad7e81fcb --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.touchwand/${project.version} + + 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 index 00000000000..d1b1de3dcc0 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBaseUnitHandler.java @@ -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 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 index 00000000000..5f16f4b8a3a --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBindingConstants.java @@ -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 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 index 00000000000..cefa670dcce --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandBridgeHandler.java @@ -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 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 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> 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 index 00000000000..cbbdf67c846 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandDimmerHandler.java @@ -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 index 00000000000..9a6f232710b --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandHandlerFactory.java @@ -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 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 index 00000000000..6d43fd0d09c --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandRestClient.java @@ -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 COMMAND_MAP = new HashMap(); + 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 index 00000000000..0e147e90a54 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandShutterHandler.java @@ -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 index 00000000000..63f0366fcff --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandSwitchHandler.java @@ -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 index 00000000000..5fc73218785 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitStatusUpdateListener.java @@ -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 index 00000000000..47305101e36 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandUnitUpdateListener.java @@ -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 index 00000000000..32c3e371b95 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWallControllerHandler.java @@ -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 index 00000000000..e0c59ba3721 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/TouchWandWebSockets.java @@ -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 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 index 00000000000..b21313a006e --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/config/TouchwandBridgeConfiguration.java @@ -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 index 00000000000..f79e3144ee4 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandControllerDiscoveryService.java @@ -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 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 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 index 00000000000..26e4ada02b0 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/discovery/TouchWandUnitDiscoveryService.java @@ -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 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 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 index 00000000000..4d9fb2b95a4 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/Csc.java @@ -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 index 00000000000..5253cf8fd63 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/CurrStatus.java @@ -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 index 00000000000..136e814d790 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandShutterSwitchUnitData.java @@ -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 index 00000000000..5ec2869b341 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitData.java @@ -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 index 00000000000..5f29a88e3c7 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitDataWallController.java @@ -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 index 00000000000..255b9fb2396 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/java/org/openhab/binding/touchwand/internal/dto/TouchWandUnitFromJson.java @@ -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 index 00000000000..0c836430cce --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + TouchWand Binding + Connect to TouchWand Wanderfull™ Hub and communicate with units connected to the controller. + Roie Geron + + 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 index 00000000000..a586c03d737 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,48 @@ + + + + + Multifunctional Gateway - a bridge to units and scenarios + + TouchWand + + + + + + Username for TouchWand Wanderfull Hub + + + password + + Password for TouchWand Wanderfull Hub + + + network-address + + Network address of this TouchWand Wanderfull Hub + + + + Port of the TouchWand Wanderfull Hub communication channel + 80 + true + + + 120 + Unit status refresh interval (seconds) + + true + + + false + If the controller is primary, add secondary controllers units as well + + true + + + + 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 index 00000000000..6b651e2a942 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/dimmer.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + light brightness control + + + + 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 index 00000000000..21bd5cb8c7a --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/shutter.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + Shutter control + + + + + Rollershutter + + The Shutter channel allows control shutters + Blinds + + 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 index 00000000000..c99d235f541 --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/switch.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + Unit on off switch + + + + + Switch + + The switch channel allows to switch the light on and off. + + Light + + Lighting + + + + 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 index 00000000000..345429b242a --- /dev/null +++ b/bundles/org.openhab.binding.touchwand/src/main/resources/OH-INF/thing/wallcontroller.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + WallController action + + + + + String + + + + + + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index e0c7e2eb72f..7d6054735d9 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -274,6 +274,7 @@ org.openhab.binding.tellstick org.openhab.binding.tesla org.openhab.binding.tibber + org.openhab.binding.touchwand org.openhab.binding.tplinksmarthome org.openhab.binding.tradfri org.openhab.binding.unifi