2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.touchwand.internal;
15 import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.SUPPORTED_TOUCHWAND_TYPES;
17 import java.io.IOException;
19 import java.net.URISyntaxException;
20 import java.util.Arrays;
21 import java.util.concurrent.CopyOnWriteArraySet;
22 import java.util.concurrent.ScheduledExecutorService;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.websocket.api.Session;
29 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
30 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
31 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
32 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
33 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
34 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
35 import org.eclipse.jetty.websocket.client.WebSocketClient;
36 import org.openhab.binding.touchwand.internal.dto.TouchWandUnitData;
37 import org.openhab.binding.touchwand.internal.dto.TouchWandUnitFromJson;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.gson.JsonObject;
42 import com.google.gson.JsonParser;
43 import com.google.gson.JsonSyntaxException;
46 * The {@link TouchWandWebSockets} class implements WebSockets API to TouchWand controller
48 * @author Roie Geron - Initial contribution
51 public class TouchWandWebSockets {
53 private static final int CONNECT_TIMEOUT_SEC = 15;
54 private static final int CONNECT_TIMEOUT_MS = CONNECT_TIMEOUT_SEC * 1000;
55 private static final int WEBSOCKET_RECONNECT_INTERVAL_SEC = CONNECT_TIMEOUT_SEC * 2;
56 private static final int WEBSOCKET_IDLE_TIMEOUT_MS = CONNECT_TIMEOUT_SEC * 10 * 1000;
57 private final Logger logger = LoggerFactory.getLogger(TouchWandWebSockets.class);
58 private static final String WS_ENDPOINT_TOUCHWAND = "/async";
60 private WebSocketClient client;
61 private String controllerAddress;
62 private TouchWandSocket touchWandSocket;
63 private boolean isShutDown = false;
64 private CopyOnWriteArraySet<TouchWandUnitStatusUpdateListener> listeners = new CopyOnWriteArraySet<>();
65 private @Nullable ScheduledFuture<?> socketReconnect;
66 private @Nullable URI uri;
68 private ScheduledExecutorService scheduler;
70 public TouchWandWebSockets(String ipAddress, ScheduledExecutorService scheduler) {
71 client = new WebSocketClient();
72 touchWandSocket = new TouchWandSocket();
73 this.controllerAddress = ipAddress;
74 this.scheduler = scheduler;
75 socketReconnect = null;
78 public void connect() {
80 uri = new URI("ws://" + controllerAddress + WS_ENDPOINT_TOUCHWAND);
81 } catch (URISyntaxException e) {
82 logger.warn("URI not valid {} message {}", uri, e.getMessage());
86 client.setConnectTimeout(CONNECT_TIMEOUT_MS);
87 ClientUpgradeRequest request = new ClientUpgradeRequest();
88 request.setSubProtocols("relay_protocol");
92 client.connect(touchWandSocket, uri, request);
93 } catch (Exception e) {
94 logger.warn("Could not connect webSocket URI {} message {}", uri, e.getMessage());
99 public void dispose() {
103 ScheduledFuture<?> mySocketReconnect = socketReconnect;
104 if (mySocketReconnect != null) {
105 mySocketReconnect.cancel(true);
107 } catch (Exception e) {
108 logger.warn("Could not stop webSocketClient, message {}", e.getMessage());
112 public void registerListener(TouchWandUnitStatusUpdateListener listener) {
113 if (!listeners.contains(listener)) {
114 logger.debug("Adding TouchWandWebSocket listener {}", listener);
115 listeners.add(listener);
119 public void unregisterListener(TouchWandUnitStatusUpdateListener listener) {
120 logger.debug("Removing TouchWandWebSocket listener {}", listener);
121 listeners.remove(listener);
124 @WebSocket(maxIdleTime = WEBSOCKET_IDLE_TIMEOUT_MS)
125 public class TouchWandSocket {
128 public void onClose(int statusCode, String reason) {
129 logger.debug("Connection closed: {} - {}", statusCode, reason);
131 logger.debug("weSocket Closed - reconnecting");
137 public void onConnect(Session session) {
138 logger.debug("TouchWandWebSockets connected to {}", session.getRemoteAddress().toString());
140 long timestamp = System.currentTimeMillis(); // need unique id
141 String controllerIdStr = String.format("{\"contId\": \"openhab%d\"}", timestamp);
142 session.getRemote().sendString(controllerIdStr);
143 } catch (IOException e) {
144 logger.warn("sendString : {}", e.getMessage());
149 public void onMessage(String msg) {
150 TouchWandUnitData touchWandUnit;
152 JsonObject unitObj = JsonParser.parseString(msg).getAsJsonObject();
153 boolean eventUnitChanged = unitObj.get("type").getAsString().equals("UNIT_CHANGED");
154 if (!eventUnitChanged) {
157 touchWandUnit = TouchWandUnitFromJson.parseResponse(unitObj.get("unit").getAsJsonObject());
158 if (!touchWandUnit.getStatus().equals("ALIVE")) {
159 logger.debug("UNIT_CHANGED unit status not ALIVE : {}", touchWandUnit.getStatus());
161 boolean supportedUnitType = Arrays.asList(SUPPORTED_TOUCHWAND_TYPES).contains(touchWandUnit.getType());
162 if (!supportedUnitType) {
163 logger.debug("UNIT_CHANGED for unsupported unit type {}", touchWandUnit.getType());
166 logger.debug("UNIT_CHANGED: name {} id {} status {}", touchWandUnit.getName(), touchWandUnit.getId(),
167 touchWandUnit.getCurrStatus());
169 for (TouchWandUnitStatusUpdateListener listener : listeners) {
170 listener.onDataReceived(touchWandUnit);
173 } catch (JsonSyntaxException e) {
174 logger.warn("jsonParser.parse {} ", e.getMessage());
179 public void onError(Throwable cause) {
180 logger.warn("WebSocket Error: {}", cause.getMessage());
182 logger.debug("WebSocket onError - reconnecting");
187 private void asyncWeb() {
188 ScheduledFuture<?> mySocketReconnect = socketReconnect;
189 if (mySocketReconnect == null || mySocketReconnect.isDone()) {
190 socketReconnect = scheduler.schedule(TouchWandWebSockets.this::connect,
191 WEBSOCKET_RECONNECT_INTERVAL_SEC, TimeUnit.SECONDS);