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.concurrent.ConcurrentHashMap;
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;
33 import com.google.gson.Gson;
36 * Establishes and keeps a websocket connection to the deCONZ software.
38 * The connection is closed by deCONZ now and then and needs to be re-established.
40 * @author David Graeff - Initial contribution
44 public class WebSocketConnection {
45 private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
47 private final WebSocketClient client;
48 private final String socketName;
49 private final Gson gson;
51 private final WebSocketConnectionListener connectionListener;
52 private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
54 private ConnectionState connectionState = ConnectionState.DISCONNECTED;
56 public WebSocketConnection(WebSocketConnectionListener listener, WebSocketClient client, Gson gson) {
57 this.connectionListener = listener;
59 this.client.setMaxIdleTimeout(0);
61 this.socketName = ((QueuedThreadPool) client.getExecutor()).getName() + "$" + this.hashCode();
64 public void start(String ip) {
65 if (connectionState == ConnectionState.CONNECTED) {
67 } else if (connectionState == ConnectionState.CONNECTING) {
68 logger.debug("{} already connecting", socketName);
72 URI destUri = URI.create("ws://" + ip);
74 logger.debug("Trying to connect {} to {}", socketName, destUri);
75 client.connect(this, destUri).get();
76 } catch (Exception e) {
77 connectionListener.connectionError(e);
83 connectionState = ConnectionState.DISCONNECTING;
85 } catch (Exception e) {
86 logger.debug("{} encountered an error while closing connection", socketName, e);
91 public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
92 listeners.put(Map.entry(resourceType, sensorID), listener);
95 public void unregisterListener(ResourceType resourceType, String sensorID) {
96 listeners.remove(Map.entry(resourceType, sensorID));
99 @SuppressWarnings("unused")
101 public void onConnect(Session session) {
102 connectionState = ConnectionState.CONNECTED;
103 logger.debug("{} successfully connected to {}", socketName, session.getRemoteAddress().getAddress());
104 connectionListener.connectionEstablished();
107 @SuppressWarnings("null, unused")
109 public void onMessage(String message) {
110 logger.trace("Raw data received by websocket {}: {}", socketName, message);
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.");
118 WebSocketMessageListener listener = listeners.get(Map.entry(changedMessage.r, changedMessage.id));
119 if (listener == null) {
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);
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.",
133 DeconzBaseMessage deconzMessage = gson.fromJson(message, expectedMessageType);
134 if (deconzMessage != null) {
135 listener.messageReceived(changedMessage.id, deconzMessage);
139 @SuppressWarnings("unused")
141 public void onError(Throwable cause) {
142 connectionState = ConnectionState.DISCONNECTED;
143 connectionListener.connectionError(cause);
146 @SuppressWarnings("unused")
148 public void onClose(int statusCode, String reason) {
149 connectionState = ConnectionState.DISCONNECTED;
150 connectionListener.connectionLost(reason);
154 * check connection state (successfully connected)
156 * @return true if connected, false if connecting, disconnecting or disconnected
158 public boolean isConnected() {
159 return connectionState == ConnectionState.CONNECTED;
163 * used internally to represent the connection state
165 private enum ConnectionState {