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