]> git.basschouten.com Git - openhab-addons.git/blob
9d2c99ba36c4ef3a201672d259d6ab152faae8d5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.touchwand.internal;
14
15 import static org.openhab.binding.touchwand.internal.TouchWandBindingConstants.SUPPORTED_TOUCHWAND_TYPES;
16
17 import java.io.IOException;
18 import java.net.URI;
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;
25
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;
40
41 import com.google.gson.JsonObject;
42 import com.google.gson.JsonParser;
43 import com.google.gson.JsonSyntaxException;
44
45 /**
46  * The {@link TouchWandWebSockets} class implements WebSockets API to TouchWand controller
47  *
48  * @author Roie Geron - Initial contribution
49  */
50 @NonNullByDefault
51 public class TouchWandWebSockets {
52
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";
59
60     private WebSocketClient client;
61     private String controllerAddress;
62     private int port;
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;
68
69     private ScheduledExecutorService scheduler;
70
71     public TouchWandWebSockets(String ipAddress, int port, ScheduledExecutorService scheduler) {
72         client = new WebSocketClient();
73         touchWandSocket = new TouchWandSocket();
74         this.controllerAddress = ipAddress;
75         this.port = port;
76         this.scheduler = scheduler;
77         socketReconnect = null;
78     }
79
80     public void connect() {
81         try {
82             uri = new URI("ws://" + controllerAddress + ":" + port + WS_ENDPOINT_TOUCHWAND);
83         } catch (URISyntaxException e) {
84             logger.warn("URI not valid {} message {}", uri, e.getMessage());
85             return;
86         }
87
88         client.setConnectTimeout(CONNECT_TIMEOUT_MS);
89         ClientUpgradeRequest request = new ClientUpgradeRequest();
90         request.setSubProtocols("relay_protocol");
91
92         try {
93             client.start();
94             client.connect(touchWandSocket, uri, request);
95         } catch (Exception e) {
96             logger.warn("Could not connect webSocket URI {} message {}", uri, e.getMessage());
97             return;
98         }
99     }
100
101     public void dispose() {
102         isShutDown = true;
103         try {
104             client.stop();
105             ScheduledFuture<?> mySocketReconnect = socketReconnect;
106             if (mySocketReconnect != null) {
107                 mySocketReconnect.cancel(true);
108             }
109         } catch (Exception e) {
110             logger.warn("Could not stop webSocketClient,  message {}", e.getMessage());
111         }
112     }
113
114     public void registerListener(TouchWandUnitStatusUpdateListener listener) {
115         if (!listeners.contains(listener)) {
116             logger.debug("Adding TouchWandWebSocket listener {}", listener);
117             listeners.add(listener);
118         }
119     }
120
121     public void unregisterListener(TouchWandUnitStatusUpdateListener listener) {
122         logger.debug("Removing TouchWandWebSocket listener {}", listener);
123         listeners.remove(listener);
124     }
125
126     @WebSocket(maxIdleTime = WEBSOCKET_IDLE_TIMEOUT_MS)
127     public class TouchWandSocket {
128
129         @OnWebSocketClose
130         public void onClose(int statusCode, String reason) {
131             logger.debug("Connection closed: {} - {}", statusCode, reason);
132             if (!isShutDown) {
133                 logger.debug("weSocket Closed - reconnecting");
134                 asyncWeb();
135             }
136         }
137
138         @OnWebSocketConnect
139         public void onConnect(Session session) {
140             logger.debug("TouchWandWebSockets connected to {}", session.getRemoteAddress().toString());
141             try {
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());
147             }
148         }
149
150         @OnWebSocketMessage
151         public void onMessage(String msg) {
152             TouchWandUnitData touchWandUnit;
153             try {
154                 JsonObject unitObj = JsonParser.parseString(msg).getAsJsonObject();
155                 boolean eventUnitChanged = "UNIT_CHANGED".equals(unitObj.get("type").getAsString());
156                 if (!eventUnitChanged) {
157                     return;
158                 }
159                 touchWandUnit = TouchWandUnitFromJson.parseResponse(unitObj.get("unit").getAsJsonObject());
160                 if (!"ALIVE".equals(touchWandUnit.getStatus())) {
161                     logger.debug("UNIT_CHANGED unit status not ALIVE : {}", touchWandUnit.getStatus());
162                 }
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());
166                     return;
167                 }
168                 logger.debug("UNIT_CHANGED: name {} id {} status {}", touchWandUnit.getName(), touchWandUnit.getId(),
169                         touchWandUnit.getCurrStatus());
170
171                 for (TouchWandUnitStatusUpdateListener listener : listeners) {
172                     listener.onDataReceived(touchWandUnit);
173
174                 }
175             } catch (JsonSyntaxException e) {
176                 logger.warn("jsonParser.parse {} ", e.getMessage());
177             }
178         }
179
180         @OnWebSocketError
181         public void onError(Throwable cause) {
182             logger.warn("WebSocket Error: {}", cause.getMessage());
183             if (!isShutDown) {
184                 logger.debug("WebSocket onError - reconnecting");
185                 asyncWeb();
186             }
187         }
188
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);
194             }
195         }
196     }
197 }