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.sonyaudio.internal.protocol;
15 import java.io.IOException;
17 import java.nio.ByteBuffer;
18 import java.util.concurrent.CountDownLatch;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.TimeoutException;
23 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
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.ClientUpgradeRequest;
31 import org.eclipse.jetty.websocket.client.WebSocketClient;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import com.google.gson.Gson;
36 import com.google.gson.GsonBuilder;
37 import com.google.gson.JsonElement;
38 import com.google.gson.JsonObject;
39 import com.google.gson.JsonParseException;
40 import com.google.gson.JsonParser;
43 * The {@link SonyAudioConnection} is responsible for communicating with SONY audio products
46 * @author David Ã…berg - Initial contribution
48 public class SonyAudioClientSocket {
49 private final Logger logger = LoggerFactory.getLogger(SonyAudioClientSocket.class);
51 private final ScheduledExecutorService scheduler;
52 private static final int REQUEST_TIMEOUT_MS = 60000;
54 private CountDownLatch commandLatch = null;
55 private JsonObject commandResponse = null;
56 private int nextMessageId = 1;
58 private boolean connected = false;
60 private final URI uri;
61 private Session session;
63 private final JsonParser parser = new JsonParser();
64 private final Gson mapper;
66 private static int ping = 0;
68 private final SonyAudioClientSocketEventListener eventHandler;
70 public SonyAudioClientSocket(SonyAudioClientSocketEventListener eventHandler, URI uri,
71 ScheduledExecutorService scheduler) {
72 mapper = new GsonBuilder().disableHtmlEscaping().create();
73 this.eventHandler = eventHandler;
75 this.scheduler = scheduler;
78 public synchronized void open(WebSocketClient webSocketClient) {
81 logger.warn("connect: connection is already open");
84 SonyAudioWebSocketListener socket = new SonyAudioWebSocketListener();
85 ClientUpgradeRequest request = new ClientUpgradeRequest();
88 webSocketClient.connect(socket, uri, request).get(1, TimeUnit.SECONDS);
89 } catch (TimeoutException e) {
90 logger.debug("Could not establish websocket within a second.");
92 } catch (Exception e) {
93 logger.debug("Exception then trying to start the websocket {}", e.getMessage(), e);
98 logger.debug("Closing socket {}", uri);
99 // if there is an old web socket then clean up and destroy
100 if (session != null) {
103 } catch (Exception e) {
104 logger.debug("Exception during closing the websocket session {}", e.getMessage(), e);
110 public boolean isConnected() {
111 if (session == null || !session.isOpen()) {
116 RemoteEndpoint remote = session.getRemote();
118 ByteBuffer payload = ByteBuffer.allocate(4).putInt(ping++);
120 remote.sendPing(payload);
121 } catch (IOException e) {
122 logger.warn("Connection to {} lost: {}", uri, e.getMessage());
130 public class SonyAudioWebSocketListener {
133 public void onConnect(Session wssession) {
134 logger.debug("Connected to server");
137 if (eventHandler != null) {
138 scheduler.submit(() -> {
140 eventHandler.onConnectionOpened(uri);
141 } catch (Exception e) {
142 logger.error("Error handling onConnectionOpened() {}", e.getMessage(), e);
149 public void onMessage(String message) {
150 logger.debug("Message received from server: {}", message);
152 final JsonObject json = parser.parse(message).getAsJsonObject();
153 if (json.has("id")) {
154 logger.debug("Response received from server: {}", json);
155 int messageId = json.get("id").getAsInt();
156 if (messageId == nextMessageId - 1) {
157 commandResponse = json;
158 commandLatch.countDown();
161 logger.debug("Event received from server: {}", json);
163 if (eventHandler != null) {
164 scheduler.submit(() -> {
166 eventHandler.handleEvent(json);
167 } catch (Exception e) {
168 logger.error("Error handling event {} player state change message: {}", json,
173 } catch (Exception e) {
174 logger.error("Error handling player state change message", e);
177 } catch (JsonParseException e) {
178 logger.debug("Not valid JSON message: {}", e.getMessage(), e);
183 public void onClose(int statusCode, String reason) {
186 logger.debug("Closing a WebSocket due to {}", reason);
187 scheduler.submit(() -> {
189 eventHandler.onConnectionClosed();
190 } catch (Exception e) {
191 logger.error("Error handling onConnectionClosed()", e);
197 public void onError(Throwable error) {
198 onClose(0, error.getMessage());
202 private void sendMessage(String str) throws IOException {
204 logger.debug("send message fo {}: {}", uri.toString(), str);
205 session.getRemote().sendString(str);
208 stack += "Printing stack trace:\n";
209 StackTraceElement[] elements = Thread.currentThread().getStackTrace();
210 for (int i = 1; i < elements.length; i++) {
211 StackTraceElement s = elements[i];
212 stack += "\tat " + s.getClassName() + "." + s.getMethodName() + "(" + s.getFileName() + ":"
213 + s.getLineNumber() + ")\n";
215 logger.error("Socket not initialized, trying to send {} {}", str, stack);
216 throw new IOException("Socket not initialized");
220 public synchronized JsonElement callMethod(SonyAudioMethod method) throws IOException {
222 method.id = nextMessageId;
223 String message = mapper.toJson(method);
224 logger.debug("callMethod send {}", message);
226 commandLatch = new CountDownLatch(1);
227 commandResponse = null;
230 sendMessage(message);
231 if (commandLatch.await(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
232 logger.debug("callMethod {} returns {}", uri.toString(), commandResponse.toString());
233 return commandResponse.get("result");
235 logger.debug("Timeout during callMethod({}, {})", method.method, message);
236 throw new IOException("Timeout during callMethod");
238 } catch (InterruptedException e) {
239 throw new IOException("Timeout in callMethod");
243 public URI getURI() {