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