2 * Copyright (c) 2010-2023 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;
63 private TouchWandSocket touchWandSocket;
64 private boolean isShutDown = false;
65 private CopyOnWriteArraySet<TouchWandUnitStatusUpdateListener> listeners = new CopyOnWriteArraySet<>();
66 private @Nullable ScheduledFuture<?> socketReconnect;
67 private @Nullable URI uri;
69 private ScheduledExecutorService scheduler;
71 public TouchWandWebSockets(String ipAddress, int port, ScheduledExecutorService scheduler) {
72 client = new WebSocketClient();
73 touchWandSocket = new TouchWandSocket();
74 this.controllerAddress = ipAddress;
76 this.scheduler = scheduler;
77 socketReconnect = null;
80 public void connect() {
82 uri = new URI("ws://" + controllerAddress + ":" + String.valueOf(port) + WS_ENDPOINT_TOUCHWAND);
83 } catch (URISyntaxException e) {
84 logger.warn("URI not valid {} message {}", uri, e.getMessage());
88 client.setConnectTimeout(CONNECT_TIMEOUT_MS);
89 ClientUpgradeRequest request = new ClientUpgradeRequest();
90 request.setSubProtocols("relay_protocol");
94 client.connect(touchWandSocket, uri, request);
95 } catch (Exception e) {
96 logger.warn("Could not connect webSocket URI {} message {}", uri, e.getMessage());
101 public void dispose() {
105 ScheduledFuture<?> mySocketReconnect = socketReconnect;
106 if (mySocketReconnect != null) {
107 mySocketReconnect.cancel(true);
109 } catch (Exception e) {
110 logger.warn("Could not stop webSocketClient, message {}", e.getMessage());
114 public void registerListener(TouchWandUnitStatusUpdateListener listener) {
115 if (!listeners.contains(listener)) {
116 logger.debug("Adding TouchWandWebSocket listener {}", listener);
117 listeners.add(listener);
121 public void unregisterListener(TouchWandUnitStatusUpdateListener listener) {
122 logger.debug("Removing TouchWandWebSocket listener {}", listener);
123 listeners.remove(listener);
126 @WebSocket(maxIdleTime = WEBSOCKET_IDLE_TIMEOUT_MS)
127 public class TouchWandSocket {
130 public void onClose(int statusCode, String reason) {
131 logger.debug("Connection closed: {} - {}", statusCode, reason);
133 logger.debug("weSocket Closed - reconnecting");
139 public void onConnect(Session session) {
140 logger.debug("TouchWandWebSockets connected to {}", session.getRemoteAddress().toString());
142 long timestamp = System.currentTimeMillis(); // need unique id
143 String controllerIdStr = String.format("{\"contId\": \"openhab%d\"}", timestamp);
144 session.getRemote().sendString(controllerIdStr);
145 } catch (IOException e) {
146 logger.warn("sendString : {}", e.getMessage());
151 public void onMessage(String msg) {
152 TouchWandUnitData touchWandUnit;
154 JsonObject unitObj = JsonParser.parseString(msg).getAsJsonObject();
155 boolean eventUnitChanged = unitObj.get("type").getAsString().equals("UNIT_CHANGED");
156 if (!eventUnitChanged) {
159 touchWandUnit = TouchWandUnitFromJson.parseResponse(unitObj.get("unit").getAsJsonObject());
160 if (!touchWandUnit.getStatus().equals("ALIVE")) {
161 logger.debug("UNIT_CHANGED unit status not ALIVE : {}", touchWandUnit.getStatus());
163 boolean supportedUnitType = Arrays.asList(SUPPORTED_TOUCHWAND_TYPES).contains(touchWandUnit.getType());
164 if (!supportedUnitType) {
165 logger.debug("UNIT_CHANGED for unsupported unit type {}", touchWandUnit.getType());
168 logger.debug("UNIT_CHANGED: name {} id {} status {}", touchWandUnit.getName(), touchWandUnit.getId(),
169 touchWandUnit.getCurrStatus());
171 for (TouchWandUnitStatusUpdateListener listener : listeners) {
172 listener.onDataReceived(touchWandUnit);
175 } catch (JsonSyntaxException e) {
176 logger.warn("jsonParser.parse {} ", e.getMessage());
181 public void onError(Throwable cause) {
182 logger.warn("WebSocket Error: {}", cause.getMessage());
184 logger.debug("WebSocket onError - reconnecting");
189 private void asyncWeb() {
190 ScheduledFuture<?> mySocketReconnect = socketReconnect;
191 if (mySocketReconnect == null || mySocketReconnect.isDone()) {
192 socketReconnect = scheduler.schedule(TouchWandWebSockets.this::connect,
193 WEBSOCKET_RECONNECT_INTERVAL_SEC, TimeUnit.SECONDS);