]> git.basschouten.com Git - openhab-addons.git/blob
f62dc860e5e166c24ff4cd42aa300112360fb825
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.neohub.internal;
14
15 import java.io.IOException;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.util.concurrent.ExecutionException;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.eclipse.jetty.util.ssl.SslContextFactory;
24 import org.eclipse.jetty.websocket.api.Session;
25 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
26 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
27 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
28 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
29 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
30 import org.eclipse.jetty.websocket.client.WebSocketClient;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import com.google.gson.Gson;
35
36 /**
37  * Handles the ASCII based communication via web socket between openHAB and NeoHub
38  *
39  * @author Andrew Fiddian-Green - Initial contribution
40  *
41  */
42 @NonNullByDefault
43 @WebSocket
44 public class NeoHubWebSocket extends NeoHubSocketBase {
45
46     private static final int SLEEP_MILLISECONDS = 100;
47     private static final String REQUEST_OUTER = "{\"message_type\":\"hm_get_command_queue\",\"message\":\"%s\"}";
48     private static final String REQUEST_INNER = "{\"token\":\"%s\",\"COMMANDS\":[{\"COMMAND\":\"%s\",\"COMMANDID\":1}]}";
49
50     private final Logger logger = LoggerFactory.getLogger(NeoHubWebSocket.class);
51     private final Gson gson = new Gson();
52     private final WebSocketClient webSocketClient;
53
54     private @Nullable Session session = null;
55     private String responseOuter = "";
56     private boolean responseWaiting;
57
58     /**
59      * DTO to receive and parse the response JSON.
60      *
61      * @author Andrew Fiddian-Green - Initial contribution
62      */
63     private static class Response {
64         @SuppressWarnings("unused")
65         public @Nullable String command_id;
66         @SuppressWarnings("unused")
67         public @Nullable String device_id;
68         public @Nullable String message_type;
69         public @Nullable String response;
70     }
71
72     public NeoHubWebSocket(NeoHubConfiguration config) throws NeoHubException {
73         super(config);
74
75         // initialise and start ssl context factory, http client, web socket client
76         SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
77         sslContextFactory.setTrustAll(true);
78         HttpClient httpClient = new HttpClient(sslContextFactory);
79         try {
80             httpClient.start();
81         } catch (Exception e) {
82             throw new NeoHubException(String.format("Error starting http client: '%s'", e.getMessage()));
83         }
84         webSocketClient = new WebSocketClient(httpClient);
85         webSocketClient.setConnectTimeout(config.socketTimeout * 1000);
86         try {
87             webSocketClient.start();
88         } catch (Exception e) {
89             throw new NeoHubException(String.format("Error starting web socket client: '%s'", e.getMessage()));
90         }
91     }
92
93     /**
94      * Open the web socket session.
95      *
96      * @throws NeoHubException
97      */
98     private void startSession() throws NeoHubException {
99         Session session = this.session;
100         if (session == null || !session.isOpen()) {
101             closeSession();
102             try {
103                 int port = config.portNumber > 0 ? config.portNumber : NeoHubBindingConstants.PORT_WSS;
104                 URI uri = new URI(String.format("wss://%s:%d", config.hostName, port));
105                 webSocketClient.connect(this, uri).get();
106             } catch (InterruptedException e) {
107                 Thread.currentThread().interrupt();
108                 throw new NeoHubException(String.format("Error starting session: '%s'", e.getMessage(), e));
109             } catch (ExecutionException | IOException | URISyntaxException e) {
110                 throw new NeoHubException(String.format("Error starting session: '%s'", e.getMessage(), e));
111             }
112         }
113     }
114
115     /**
116      * Close the web socket session.
117      */
118     private void closeSession() {
119         Session session = this.session;
120         if (session != null) {
121             session.close();
122             this.session = null;
123         }
124     }
125
126     /**
127      * Helper to escape the quote marks in a JSON string.
128      *
129      * @param json the input JSON string.
130      * @return the escaped JSON version.
131      */
132     private String jsonEscape(String json) {
133         return json.replace("\"", "\\\"");
134     }
135
136     /**
137      * Helper to remove quote escape marks from an escaped JSON string.
138      *
139      * @param escapedJson the escaped input string.
140      * @return the clean JSON version.
141      */
142     private String jsonUnEscape(String escapedJson) {
143         return escapedJson.replace("\\\"", "\"");
144     }
145
146     /**
147      * Helper to replace double quote marks in a JSON string with single quote marks.
148      *
149      * @param json the input string.
150      * @return the modified version.
151      */
152     private String jsonReplaceQuotes(String json) {
153         return json.replace("\"", "'");
154     }
155
156     @Override
157     public synchronized String sendMessage(final String requestJson) throws IOException, NeoHubException {
158         // start the session
159         startSession();
160
161         // session start failed
162         Session session = this.session;
163         if (session == null) {
164             throw new NeoHubException("Session is null.");
165         }
166
167         // wrap the inner request in an outer request string
168         String requestOuter = String.format(REQUEST_OUTER,
169                 jsonEscape(String.format(REQUEST_INNER, config.apiToken, jsonReplaceQuotes(requestJson))));
170
171         // initialise the response
172         responseOuter = "";
173         responseWaiting = true;
174
175         // send the request
176         logger.trace("Sending request: {}", requestOuter);
177         session.getRemote().sendString(requestOuter);
178
179         // sleep and loop until we get a response or the socket is closed
180         int sleepRemainingMilliseconds = config.socketTimeout * 1000;
181         while (responseWaiting && (sleepRemainingMilliseconds > 0)) {
182             try {
183                 Thread.sleep(SLEEP_MILLISECONDS);
184                 sleepRemainingMilliseconds = sleepRemainingMilliseconds - SLEEP_MILLISECONDS;
185             } catch (InterruptedException e) {
186                 throw new NeoHubException(String.format("Read timeout '%s'", e.getMessage()));
187             }
188         }
189
190         // extract the inner response from the outer response string
191         Response responseDto = gson.fromJson(responseOuter, Response.class);
192         if (responseDto != null && NeoHubBindingConstants.HM_SET_COMMAND_RESPONSE.equals(responseDto.message_type)) {
193             String responseJson = responseDto.response;
194             if (responseJson != null) {
195                 responseJson = jsonUnEscape(responseJson);
196                 logger.trace("Received response: {}", responseJson);
197                 return responseJson;
198             }
199         }
200         logger.debug("Null or invalid response.");
201         return "";
202     }
203
204     @Override
205     public void close() {
206         closeSession();
207         try {
208             webSocketClient.stop();
209         } catch (Exception e) {
210         }
211     }
212
213     @OnWebSocketConnect
214     public void onConnect(Session session) {
215         logger.trace("onConnect: ok");
216         this.session = session;
217     }
218
219     @OnWebSocketClose
220     public void onClose(int statusCode, String reason) {
221         logger.trace("onClose: code:{}, reason:{}", statusCode, reason);
222         responseWaiting = false;
223         this.session = null;
224     }
225
226     @OnWebSocketError
227     public void onError(Throwable cause) {
228         logger.trace("onError: cause:{}", cause.getMessage());
229         closeSession();
230     }
231
232     @OnWebSocketMessage
233     public void onMessage(String msg) {
234         logger.trace("onMessage: msg:{}", msg);
235         responseOuter = msg;
236         responseWaiting = false;
237     }
238 }