2 * Copyright (c) 2010-2020 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.deconz.internal.netutils;
17 import java.util.Objects;
18 import java.util.concurrent.ConcurrentHashMap;
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;
34 import com.google.gson.Gson;
37 * Establishes and keeps a websocket connection to the deCONZ software.
39 * The connection is closed by deCONZ now and then and needs to be re-established.
41 * @author David Graeff - Initial contribution
45 public class WebSocketConnection {
46 private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
48 private final WebSocketClient client;
49 private final String socketName;
50 private final Gson gson;
52 private final WebSocketConnectionListener connectionListener;
53 private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
55 private ConnectionState connectionState = ConnectionState.DISCONNECTED;
57 public WebSocketConnection(WebSocketConnectionListener listener, WebSocketClient client, Gson gson) {
58 this.connectionListener = listener;
60 this.client.setMaxIdleTimeout(0);
62 this.socketName = ((QueuedThreadPool) client.getExecutor()).getName() + "$" + this.hashCode();
65 public void start(String ip) {
66 if (connectionState == ConnectionState.CONNECTED) {
68 } else if (connectionState == ConnectionState.CONNECTING) {
69 logger.debug("{} already connecting", socketName);
73 URI destUri = URI.create("ws://" + ip);
75 logger.debug("Trying to connect {} to {}", socketName, destUri);
76 client.connect(this, destUri).get();
77 } catch (Exception e) {
78 connectionListener.connectionError(e);
84 connectionState = ConnectionState.DISCONNECTING;
86 } catch (Exception e) {
87 logger.debug("{} encountered an error while closing connection", socketName, e);
92 public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
93 listeners.put(Map.entry(resourceType, sensorID), listener);
96 public void unregisterListener(ResourceType resourceType, String sensorID) {
97 listeners.remove(Map.entry(resourceType, sensorID));
100 @SuppressWarnings("unused")
102 public void onConnect(Session session) {
103 connectionState = ConnectionState.CONNECTED;
104 logger.debug("{} successfully connected to {}", socketName, session.getRemoteAddress().getAddress());
105 connectionListener.connectionEstablished();
108 @SuppressWarnings({ "null", "unused" })
110 public void onMessage(String message) {
111 logger.trace("Raw data received by websocket {}: {}", socketName, message);
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.");
119 WebSocketMessageListener listener = listeners.get(Map.entry(changedMessage.r, changedMessage.id));
120 if (listener == null) {
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);
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.",
134 DeconzBaseMessage deconzMessage = gson.fromJson(message, expectedMessageType);
135 if (deconzMessage != null) {
136 listener.messageReceived(changedMessage.id, deconzMessage);
140 @SuppressWarnings("unused")
142 public void onError(Throwable cause) {
143 connectionState = ConnectionState.DISCONNECTED;
144 connectionListener.connectionError(cause);
147 @SuppressWarnings("unused")
149 public void onClose(int statusCode, String reason) {
150 connectionState = ConnectionState.DISCONNECTED;
151 connectionListener.connectionLost(reason);
155 * check connection state (successfully connected)
157 * @return true if connected, false if connecting, disconnecting or disconnected
159 public boolean isConnected() {
160 return connectionState == ConnectionState.CONNECTED;
164 * used internally to represent the connection state
166 private enum ConnectionState {