[touchwand] Touchwand Binding initial contribution - migration to OH3 (#8754)

* initial migration to OH 3

Signed-off-by: Roie Geron <roie.geron@gmail.com>
This commit is contained in:
Roie Geron
2020-10-18 03:19:40 +03:00
committed by GitHub
parent de6390501b
commit 7cd5510a72
33 changed files with 2308 additions and 0 deletions

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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 };
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 "";
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>