]> git.basschouten.com Git - openhab-addons.git/blob
703276d08c8c8e425902fb2917638a9c374e95f1
[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.websocket.api.Session;
21 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
22 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
23 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
24 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
25 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
26 import org.eclipse.jetty.websocket.client.WebSocketClient;
27 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
28 import org.openhab.binding.deconz.internal.dto.GroupMessage;
29 import org.openhab.binding.deconz.internal.dto.LightMessage;
30 import org.openhab.binding.deconz.internal.dto.SensorMessage;
31 import org.openhab.binding.deconz.internal.types.ResourceType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.gson.Gson;
36
37 /**
38  * Establishes and keeps a websocket connection to the deCONZ software.
39  *
40  * The connection is closed by deCONZ now and then and needs to be re-established.
41  *
42  * @author David Graeff - Initial contribution
43  */
44 @WebSocket
45 @NonNullByDefault
46 public class WebSocketConnection {
47     private static final Map<ResourceType, Class<? extends DeconzBaseMessage>> EXPECTED_MESSAGE_TYPES = Map.of(
48             ResourceType.GROUPS, GroupMessage.class, ResourceType.LIGHTS, LightMessage.class, ResourceType.SENSORS,
49             SensorMessage.class);
50
51     private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
52
53     private final WebSocketClient client;
54     private final WebSocketConnectionListener connectionListener;
55     private final Map<Map.Entry<ResourceType, String>, WebSocketMessageListener> listeners = new ConcurrentHashMap<>();
56
57     private final Gson gson;
58     private boolean connected = false;
59
60     public WebSocketConnection(WebSocketConnectionListener listener, WebSocketClient client, Gson gson) {
61         this.connectionListener = listener;
62         this.client = client;
63         this.client.setMaxIdleTimeout(0);
64         this.gson = gson;
65     }
66
67     public void start(String ip) {
68         if (connected) {
69             return;
70         }
71         try {
72             URI destUri = URI.create("ws://" + ip);
73
74             client.start();
75
76             logger.debug("Connecting to: {}", destUri);
77             client.connect(this, destUri).get();
78         } catch (Exception e) {
79             connectionListener.connectionError(e);
80         }
81     }
82
83     public void close() {
84         try {
85             connected = false;
86             client.stop();
87         } catch (Exception e) {
88             logger.debug("Error while closing connection", e);
89         }
90         client.destroy();
91     }
92
93     public void registerListener(ResourceType resourceType, String sensorID, WebSocketMessageListener listener) {
94         listeners.put(Map.entry(resourceType, sensorID), listener);
95     }
96
97     public void unregisterListener(ResourceType resourceType, String sensorID) {
98         listeners.remove(Map.entry(resourceType, sensorID));
99     }
100
101     @OnWebSocketConnect
102     public void onConnect(Session session) {
103         connected = true;
104         logger.debug("Connect: {}", session.getRemoteAddress().getAddress());
105         connectionListener.connectionEstablished();
106     }
107
108     @SuppressWarnings("null")
109     @OnWebSocketMessage
110     public void onMessage(String message) {
111         logger.trace("Raw data received by websocket: {}", message);
112
113         DeconzBaseMessage changedMessage = 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 = EXPECTED_MESSAGE_TYPES.get(changedMessage.r);
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     @OnWebSocketError
141     public void onError(Throwable cause) {
142         connected = false;
143         connectionListener.connectionError(cause);
144     }
145
146     @OnWebSocketClose
147     public void onClose(int statusCode, String reason) {
148         connected = false;
149         connectionListener.connectionLost(reason);
150     }
151
152     public boolean isConnected() {
153         return connected;
154     }
155 }