]> git.basschouten.com Git - openhab-addons.git/blob
03801bd011356336411ad8c764abb696b9112df0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.deconz.internal.netutils;
14
15 import java.net.URI;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jetty.util.thread.QueuedThreadPool;
21 import org.eclipse.jetty.websocket.api.Session;
22 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
23 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
24 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
25 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
26 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
27 import org.eclipse.jetty.websocket.client.WebSocketClient;
28 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
29 import org.openhab.binding.deconz.internal.dto.GroupMessage;
30 import org.openhab.binding.deconz.internal.dto.LightMessage;
31 import org.openhab.binding.deconz.internal.dto.SensorMessage;
32 import org.openhab.binding.deconz.internal.types.ResourceType;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import com.google.gson.Gson;
37
38 /**
39  * Establishes and keeps a websocket connection to the deCONZ software.
40  *
41  * The connection is closed by deCONZ now and then and needs to be re-established.
42  *
43  * @author David Graeff - Initial contribution
44  */
45 @WebSocket
46 @NonNullByDefault
47 public class WebSocketConnection {
48     private static final Map<ResourceType, Class<? extends DeconzBaseMessage>> EXPECTED_MESSAGE_TYPES = Map.of(
49             ResourceType.GROUPS, GroupMessage.class, ResourceType.LIGHTS, LightMessage.class, ResourceType.SENSORS,
50             SensorMessage.class);
51
52     private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
53
54     private final WebSocketClient client;
55     private final String socketName;
56     private final Gson gson;
57
58     private final WebSocketConnectionListener connectionListener;
59     private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
60
61     private ConnectionState connectionState = ConnectionState.DISCONNECTED;
62
63     public WebSocketConnection(WebSocketConnectionListener listener, WebSocketClient client, Gson gson) {
64         this.connectionListener = listener;
65         this.client = client;
66         this.client.setMaxIdleTimeout(0);
67         this.gson = gson;
68         this.socketName = ((QueuedThreadPool) client.getExecutor()).getName() + "$" + this.hashCode();
69     }
70
71     public void start(String ip) {
72         if (connectionState == ConnectionState.CONNECTED) {
73             return;
74         } else if (connectionState == ConnectionState.CONNECTING) {
75             logger.debug("{} already connecting", socketName);
76             return;
77         }
78         try {
79             URI destUri = URI.create("ws://" + ip);
80             client.start();
81             logger.debug("Trying to connect {} to {}", socketName, destUri);
82             client.connect(this, destUri).get();
83         } catch (Exception e) {
84             connectionListener.connectionError(e);
85         }
86     }
87
88     public void close() {
89         try {
90             connectionState = ConnectionState.DISCONNECTING;
91             client.stop();
92         } catch (Exception e) {
93             logger.debug("{} encountered an error while closing connection", socketName, e);
94         }
95         client.destroy();
96     }
97
98     public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
99         listeners.put(Map.entry(resourceType, sensorID), listener);
100     }
101
102     public void unregisterListener(ResourceType resourceType, String sensorID) {
103         listeners.remove(Map.entry(resourceType, sensorID));
104     }
105
106     @SuppressWarnings("unused")
107     @OnWebSocketConnect
108     public void onConnect(Session session) {
109         connectionState = ConnectionState.CONNECTED;
110         logger.debug("{} successfully connected to {}", socketName, session.getRemoteAddress().getAddress());
111         connectionListener.connectionEstablished();
112     }
113
114     @SuppressWarnings("null, unused")
115     @OnWebSocketMessage
116     public void onMessage(String message) {
117         logger.trace("Raw data received by websocket {}: {}", socketName, message);
118
119         DeconzBaseMessage changedMessage = gson.fromJson(message, DeconzBaseMessage.class);
120         if (changedMessage.r == ResourceType.UNKNOWN) {
121             logger.trace("Received message has unknown resource type. Skipping message.");
122             return;
123         }
124
125         WebSocketMessageListener listener = listeners.get(Map.entry(changedMessage.r, changedMessage.id));
126         if (listener == null) {
127             logger.debug(
128                     "Couldn't find listener for id {} with resource type {}. Either no thing for this id has been defined or this is a bug.",
129                     changedMessage.id, changedMessage.r);
130             return;
131         }
132
133         Class<? extends DeconzBaseMessage> expectedMessageType = EXPECTED_MESSAGE_TYPES.get(changedMessage.r);
134         if (expectedMessageType == null) {
135             logger.warn("BUG! Could not get expected message type for resource type {}. Please report this incident.",
136                     changedMessage.r);
137             return;
138         }
139
140         DeconzBaseMessage deconzMessage = gson.fromJson(message, expectedMessageType);
141         if (deconzMessage != null) {
142             listener.messageReceived(changedMessage.id, deconzMessage);
143         }
144     }
145
146     @SuppressWarnings("unused")
147     @OnWebSocketError
148     public void onError(Throwable cause) {
149         connectionState = ConnectionState.DISCONNECTED;
150         connectionListener.connectionError(cause);
151     }
152
153     @SuppressWarnings("unused")
154     @OnWebSocketClose
155     public void onClose(int statusCode, String reason) {
156         connectionState = ConnectionState.DISCONNECTED;
157         connectionListener.connectionLost(reason);
158     }
159
160     /**
161      * check connection state (successfully connected)
162      *
163      * @return true if connected, false if connecting, disconnecting or disconnected
164      */
165     public boolean isConnected() {
166         return connectionState == ConnectionState.CONNECTED;
167     }
168
169     /**
170      * used internally to represent the connection state
171      */
172     private enum ConnectionState {
173         CONNECTING,
174         CONNECTED,
175         DISCONNECTING,
176         DISCONNECTED
177     }
178 }