2 * Copyright (c) 2010-2024 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.freeathomesystem.internal.handler;
15 import java.io.IOException;
16 import java.io.StringReader;
18 import java.net.URISyntaxException;
19 import java.util.ArrayList;
20 import java.util.Base64;
21 import java.util.Collection;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Locale;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.concurrent.locks.Condition;
34 import java.util.concurrent.locks.Lock;
35 import java.util.concurrent.locks.ReentrantLock;
37 import org.eclipse.jdt.annotation.NonNullByDefault;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.eclipse.jetty.client.HttpClient;
40 import org.eclipse.jetty.client.api.AuthenticationStore;
41 import org.eclipse.jetty.client.api.ContentResponse;
42 import org.eclipse.jetty.client.api.Request;
43 import org.eclipse.jetty.client.util.BasicAuthentication;
44 import org.eclipse.jetty.client.util.StringContentProvider;
45 import org.eclipse.jetty.http.HttpMethod;
46 import org.eclipse.jetty.http.HttpStatus;
47 import org.eclipse.jetty.util.thread.QueuedThreadPool;
48 import org.eclipse.jetty.websocket.api.Session;
49 import org.eclipse.jetty.websocket.api.StatusCode;
50 import org.eclipse.jetty.websocket.api.WebSocketListener;
51 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
52 import org.eclipse.jetty.websocket.client.WebSocketClient;
53 import org.openhab.binding.freeathomesystem.internal.FreeAtHomeSystemDiscoveryService;
54 import org.openhab.binding.freeathomesystem.internal.configuration.FreeAtHomeBridgeHandlerConfiguration;
55 import org.openhab.binding.freeathomesystem.internal.datamodel.FreeAtHomeDeviceDescription;
56 import org.openhab.binding.freeathomesystem.internal.util.FreeAtHomeHttpCommunicationException;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.ThingStatus;
60 import org.openhab.core.thing.ThingStatusDetail;
61 import org.openhab.core.thing.binding.BaseBridgeHandler;
62 import org.openhab.core.thing.binding.ThingHandlerService;
63 import org.openhab.core.types.Command;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
67 import com.google.gson.JsonArray;
68 import com.google.gson.JsonElement;
69 import com.google.gson.JsonObject;
70 import com.google.gson.JsonParseException;
71 import com.google.gson.JsonParser;
72 import com.google.gson.stream.JsonReader;
75 * The {@link FreeAtHomeBridgeHandler} is responsible for handling the free@home bridge and
76 * its main communication.
78 * @author Andras Uhrin - Initial contribution
82 public class FreeAtHomeBridgeHandler extends BaseBridgeHandler implements WebSocketListener {
84 private final Logger logger = LoggerFactory.getLogger(FreeAtHomeBridgeHandler.class);
86 private Map<String, FreeAtHomeDeviceHandler> mapEventListeners = new ConcurrentHashMap<>();
88 // Clients for the network communication
89 private HttpClient httpClient;
90 private @Nullable WebSocketClient websocketClient = null;
91 private FreeAtHomeWebsocketMonitorThread socketMonitor = new FreeAtHomeWebsocketMonitorThread();
92 private @Nullable QueuedThreadPool jettyThreadPool = null;
93 private volatile @Nullable Session websocketSession = null;
95 private String sysApUID = "00000000-0000-0000-0000-000000000000";
96 private String ipAddress = "";
97 private String username = "";
98 private String password = "";
100 private String baseUrl = "";
102 private String authField = "";
104 private Lock lock = new ReentrantLock();
105 private AtomicBoolean httpConnectionOK = new AtomicBoolean(false);
106 private Condition websocketSessionEstablished = lock.newCondition();
108 int numberOfComponents = 0;
110 private static final int BRIDGE_WEBSOCKET_RECONNECT_DELAY = 60;
111 private static final int BRIDGE_WEBSOCKET_TIMEOUT = 90;
112 private static final int BRIDGE_WEBSOCKET_KEEPALIVE = 50;
113 private static final String BRIDGE_URL_GETDEVICELIST = "/rest/devicelist";
115 public FreeAtHomeBridgeHandler(Bridge thing, HttpClient client) {
122 * stub method for handlCommand
125 public void handleCommand(ChannelUID channelUID, Command command) {
126 logger.warn("Unknown handle command for the bridge - channellUID {}, command {}", channelUID, command);
130 public Collection<Class<? extends ThingHandlerService>> getServices() {
131 return List.of(FreeAtHomeSystemDiscoveryService.class);
135 * Method to get the device list
137 public List<String> getDeviceDeviceList() throws FreeAtHomeHttpCommunicationException {
138 List<String> listOfComponentId = new ArrayList<String>();
141 listOfComponentId.clear();
143 String url = baseUrl + BRIDGE_URL_GETDEVICELIST;
145 // Perform a simple GET and wait for the response.
147 HttpClient client = httpClient;
149 Request req = client.newRequest(url);
152 throw new FreeAtHomeHttpCommunicationException(0,
153 "Invalid request object in getDeviceDeviceList with the URL [ " + url + " ]");
156 ContentResponse response = req.send();
158 // Get component List
159 String componentListString = new String(response.getContent());
161 JsonElement jsonTree = JsonParser.parseString(componentListString);
164 if (!jsonTree.isJsonObject()) {
165 throw new FreeAtHomeHttpCommunicationException(0,
166 "Invalid jsonObject in getDeviceDeviceList with the URL [ " + url + " ]");
169 JsonObject jsonObject = jsonTree.getAsJsonObject();
171 // Get the main object
172 JsonElement listOfComponents = jsonObject.get(sysApUID);
174 if (listOfComponents == null) {
175 throw new FreeAtHomeHttpCommunicationException(0,
176 "Devices Section is missing in getDeviceDeviceList with the URL [ " + url + " ]");
179 JsonArray array = listOfComponents.getAsJsonArray();
181 this.numberOfComponents = array.size();
183 for (int i = 0; i < array.size(); i++) {
184 JsonElement basicElement = array.get(i);
186 listOfComponentId.add(basicElement.getAsString());
190 } catch (InterruptedException e) {
191 Thread.currentThread().interrupt();
192 logger.debug("Error to build up the Component list [ {} ]", e.getMessage());
194 throw new FreeAtHomeHttpCommunicationException(0,
195 "Http communication interrupted [ " + e.getMessage() + " ]");
196 } catch (ExecutionException | TimeoutException e) {
197 logger.debug("Error to build up the Component list [ {} ]", e.getMessage());
199 throw new FreeAtHomeHttpCommunicationException(0,
200 "Http communication interrupted in getDeviceList [ " + e.getMessage() + " ]");
203 // Scan finished but error. clear the list
205 listOfComponentId.clear();
208 return listOfComponentId;
212 * Method to send http request to get the device description
214 public FreeAtHomeDeviceDescription getFreeatHomeDeviceDescription(String id)
215 throws FreeAtHomeHttpCommunicationException {
216 FreeAtHomeDeviceDescription device = new FreeAtHomeDeviceDescription();
218 String url = baseUrl + "/rest/device/" + sysApUID + "/" + id;
220 HttpClient client = httpClient;
221 Request req = client.newRequest(url);
224 throw new FreeAtHomeHttpCommunicationException(0,
225 "Invalid request object in getDatapoint with the URL [ " + url + " ]");
228 ContentResponse response;
229 response = req.send();
231 // Get component List
232 String deviceString = new String(response.getContent());
234 JsonReader reader = new JsonReader(new StringReader(deviceString));
235 reader.setLenient(true);
236 JsonElement jsonTree = JsonParser.parseReader(reader);
238 if (!jsonTree.isJsonObject()) {
239 throw new FreeAtHomeHttpCommunicationException(0,
240 "No data is received by getDatapoint with the URL [ " + url + " ]");
243 if (!jsonTree.isJsonObject()) {
244 throw new FreeAtHomeHttpCommunicationException(0,
245 "Invalid jsonObject in getFreeatHomeDeviceDescription with the URL [ " + url + " ]");
249 JsonObject jsonObject = jsonTree.getAsJsonObject();
251 if (!jsonObject.isJsonObject()) {
252 throw new FreeAtHomeHttpCommunicationException(0,
253 "Main jsonObject is invalid in getFreeatHomeDeviceDescription with the URL [ " + url + " ]");
256 jsonObject = jsonObject.getAsJsonObject(sysApUID);
258 if (!jsonObject.isJsonObject()) {
259 throw new FreeAtHomeHttpCommunicationException(0,
260 "jsonObject is invalid in getFreeatHomeDeviceDescription with the URL [ " + url + " ]");
263 jsonObject = jsonObject.getAsJsonObject("devices");
265 if (!jsonObject.isJsonObject()) {
266 throw new FreeAtHomeHttpCommunicationException(0,
267 "Devices Section is missing in getFreeatHomeDeviceDescription with the URL [ " + url + " ]");
270 device = new FreeAtHomeDeviceDescription(jsonObject, id);
271 } catch (InterruptedException e) {
272 Thread.currentThread().interrupt();
273 logger.debug("No communication possible to get device list - Communication interrupt [ {} ]",
276 throw new FreeAtHomeHttpCommunicationException(0,
277 "Http communication interrupted [ " + e.getMessage() + " ]");
278 } catch (ExecutionException | TimeoutException e) {
279 logger.debug("No communication possible to get device list - Communication interrupt [ {} ]",
282 throw new FreeAtHomeHttpCommunicationException(0,
283 "Http communication interrupted in getDeviceList [ " + e.getMessage() + " ]");
290 * Method to get datapoint values for devices
292 public String getDatapoint(String deviceId, String channel, String datapoint)
293 throws FreeAtHomeHttpCommunicationException {
294 String url = baseUrl + "/rest/datapoint/" + sysApUID + "/" + deviceId + "." + channel + "." + datapoint;
297 Request req = httpClient.newRequest(url);
299 logger.debug("Get datapoint by url: {}", url);
302 throw new FreeAtHomeHttpCommunicationException(0,
303 "Invalid request object in getDatapoint with the URL [ " + url + " ]");
306 ContentResponse response = req.send();
308 if (response.getStatus() != 200) {
309 throw new FreeAtHomeHttpCommunicationException(response.getStatus(), response.getReason());
312 String deviceString = new String(response.getContent());
314 JsonReader reader = new JsonReader(new StringReader(deviceString));
315 reader.setLenient(true);
316 JsonElement jsonTree = JsonParser.parseReader(reader);
318 if (!jsonTree.isJsonObject()) {
319 throw new FreeAtHomeHttpCommunicationException(0,
320 "No data is received by getDatapoint with the URL [ " + url + " ]");
323 JsonObject jsonObject = jsonTree.getAsJsonObject();
325 jsonObject = jsonObject.getAsJsonObject(sysApUID);
326 JsonArray jsonValueArray = jsonObject.getAsJsonArray("values");
328 JsonElement element = jsonValueArray.get(0);
329 String value = element.getAsString();
331 if (value.isEmpty()) {
336 } catch (InterruptedException e) {
337 Thread.currentThread().interrupt();
338 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
339 "@text/comm-error.error-in-sysap-com");
341 throw new FreeAtHomeHttpCommunicationException(0,
342 "Http communication interrupted [ " + e.getMessage() + " ]");
343 } catch (ExecutionException | TimeoutException e) {
344 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
345 "@text/comm-error.error-in-sysap-com");
347 throw new FreeAtHomeHttpCommunicationException(0,
348 "Http communication timout or execution interrupted [ " + e.getMessage() + " ]");
349 } catch (JsonParseException e) {
350 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
351 "@text/comm-error.error-in-sysap-com");
353 throw new FreeAtHomeHttpCommunicationException(0,
354 "Invalid JSON file is received by getDatapoint with the URL [ " + e.getMessage() + " ]");
359 * Method to set datapoint values in channels
361 public boolean setDatapoint(String deviceId, String channel, String datapoint, String valueString)
362 throws FreeAtHomeHttpCommunicationException {
363 String url = baseUrl + "/rest/datapoint/" + sysApUID + "/" + deviceId + "." + channel + "." + datapoint;
366 Request req = httpClient.newRequest(url);
369 throw new FreeAtHomeHttpCommunicationException(0,
370 "Invalid request object in getDatapoint with the URL [ " + url + " ]");
373 req.content(new StringContentProvider(valueString));
374 req.method(HttpMethod.PUT);
376 logger.debug("Set datapoint by url: {} value: {}", url, valueString);
378 ContentResponse response = req.send();
380 if (response.getStatus() != 200) {
381 throw new FreeAtHomeHttpCommunicationException(response.getStatus(), response.getReason());
383 } catch (InterruptedException e) {
384 Thread.currentThread().interrupt();
385 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
386 "@text/comm-error.error-in-sysap-com");
388 throw new FreeAtHomeHttpCommunicationException(0,
389 "Http communication interrupted [ " + e.getMessage() + " ]");
390 } catch (ExecutionException | TimeoutException e) {
391 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
392 "@text/comm-error.error-in-sysap-com");
394 throw new FreeAtHomeHttpCommunicationException(0,
395 "Http communication interrupted [ " + e.getMessage() + " ]");
402 * Method to process socket events
404 public void setDatapointOnWebsocketFeedback(String receivedText) {
405 JsonReader reader = new JsonReader(new StringReader(receivedText));
406 reader.setLenient(true);
407 JsonElement jsonTree = JsonParser.parseReader(reader);
410 if (jsonTree.isJsonObject()) {
411 JsonObject jsonObject = jsonTree.getAsJsonObject();
413 jsonObject = jsonObject.getAsJsonObject(sysApUID);
414 jsonObject = jsonObject.getAsJsonObject("datapoints");
416 Set<String> keys = jsonObject.keySet();
418 Iterator<String> iter = keys.iterator();
420 while (iter.hasNext()) {
421 String eventDatapointID = iter.next();
423 JsonElement element = jsonObject.get(eventDatapointID);
424 String value = element.getAsString();
426 String[] parts = eventDatapointID.split("/");
428 FreeAtHomeDeviceHandler deviceHandler = mapEventListeners.get(parts[0]);
430 if (deviceHandler != null) {
431 deviceHandler.onDeviceStateChanged(eventDatapointID, value);
434 logger.debug("Socket event processed: event-datapoint-ID {} value {}", eventDatapointID, value);
439 public void markDeviceRemovedOnWebsocketFeedback(String receivedText) {
440 JsonReader reader = new JsonReader(new StringReader(receivedText));
441 reader.setLenient(true);
442 JsonElement jsonTree = JsonParser.parseReader(reader);
445 if (jsonTree.isJsonObject()) {
446 JsonObject jsonObject = jsonTree.getAsJsonObject();
448 jsonObject = jsonObject.getAsJsonObject(sysApUID);
449 JsonArray jsonArray = jsonObject.getAsJsonArray("devicesRemoved");
451 for (JsonElement element : jsonArray) {
452 FreeAtHomeDeviceHandler deviceHandler = mapEventListeners.get(element.getAsString());
454 if (deviceHandler != null) {
455 deviceHandler.onDeviceRemoved();
457 logger.debug("Device removal processed");
463 public void registerDeviceStateListener(String deviceID, FreeAtHomeDeviceHandler deviceHandler) {
464 mapEventListeners.put(deviceID, deviceHandler);
467 public void unregisterDeviceStateListener(String deviceID) {
468 mapEventListeners.remove(deviceID);
472 * Method to open Http connection
474 public boolean openHttpConnection() {
478 // Add authentication credentials.
479 AuthenticationStore auth = httpClient.getAuthenticationStore();
481 URI uri1 = new URI(baseUrl);
482 auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri1, username, password));
484 String url = baseUrl + BRIDGE_URL_GETDEVICELIST;
486 Request req = httpClient.newRequest(url);
487 ContentResponse res = req.send();
490 if (res.getStatus() == HttpStatus.OK_200) {
492 httpConnectionOK.set(true);
496 logger.debug("HTTP connection to SysAP is OK");
498 // response NOK, set error
499 httpConnectionOK.set(false);
503 } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException ex) {
504 logger.debug("Initial HTTP connection to SysAP is not successful");
513 * Method to connect the websocket session
515 public boolean connectWebsocketSession() {
518 URI uri = URI.create("ws://" + ipAddress + "/fhapi/v1/api/ws");
520 String authString = username + ":" + password;
522 // create base64 encoder
523 Base64.Encoder bas64Encoder = Base64.getEncoder();
525 // Encoding string using encoder object
526 String authStringEnc = bas64Encoder.encodeToString(authString.getBytes());
528 authField = "Basic " + authStringEnc;
530 WebSocketClient localWebsocketClient = websocketClient;
533 // Start socket client
534 if (localWebsocketClient != null) {
535 localWebsocketClient.setMaxTextMessageBufferSize(8 * 1024);
536 localWebsocketClient.setMaxIdleTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 60 * 1000);
537 localWebsocketClient.setConnectTimeout(BRIDGE_WEBSOCKET_TIMEOUT * 60 * 1000);
538 localWebsocketClient.start();
539 ClientUpgradeRequest request = new ClientUpgradeRequest();
540 request.setHeader("Authorization", authField);
541 request.setTimeout(BRIDGE_WEBSOCKET_TIMEOUT, TimeUnit.MINUTES);
542 localWebsocketClient.connect(this, uri, request);
544 logger.debug("Websocket connection to SysAP is OK, timeout: {}", BRIDGE_WEBSOCKET_TIMEOUT);
550 } catch (Exception e) {
551 logger.debug("Error by opening Websocket connection [{}]", e.getMessage());
553 if (localWebsocketClient != null) {
555 localWebsocketClient.stop();
558 } catch (Exception e1) {
559 logger.debug("Error by opening Websocket connection [{}]", e1.getMessage());
572 * Method to close the websocket connection
574 public void closeWebSocketConnection() {
575 socketMonitor.interrupt();
577 QueuedThreadPool localThreadPool = jettyThreadPool;
579 if (localThreadPool != null) {
581 localThreadPool.stop();
582 } catch (Exception e1) {
583 logger.debug("Error by closing Websocket connection [{}]", e1.getMessage());
585 jettyThreadPool = null;
588 WebSocketClient localWebSocketClient = websocketClient;
590 if (localWebSocketClient != null) {
592 localWebSocketClient.stop();
593 } catch (Exception e2) {
594 logger.debug("Error by closing Websocket connection [{}]", e2.getMessage());
596 websocketClient = null;
601 * Method to open the websocket connection
603 public boolean openWebSocketConnection() {
606 QueuedThreadPool localThreadPool = jettyThreadPool;
608 if (localThreadPool == null) {
609 jettyThreadPool = new QueuedThreadPool();
611 localThreadPool = jettyThreadPool;
613 if (localThreadPool != null) {
614 localThreadPool.setName(FreeAtHomeBridgeHandler.class.getSimpleName());
615 localThreadPool.setDaemon(true);
616 localThreadPool.setStopTimeout(0);
622 WebSocketClient localWebSocketClient = websocketClient;
624 if (localWebSocketClient == null) {
625 websocketClient = new WebSocketClient(httpClient);
627 localWebSocketClient = websocketClient;
629 if (localWebSocketClient != null) {
630 localWebSocketClient.setExecutor(jettyThreadPool);
632 socketMonitor.start();
646 * Method to initialize the bridge
649 public void initialize() {
650 httpConnectionOK.set(false);
652 // load configuration
653 FreeAtHomeBridgeHandlerConfiguration locConfig = getConfigAs(FreeAtHomeBridgeHandlerConfiguration.class);
655 ipAddress = locConfig.ipAddress;
656 if (ipAddress.isBlank()) {
657 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
658 "@text/conf-error.ip-address-missing");
662 password = locConfig.password;
663 if (password.isBlank()) {
664 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
665 "@text/conf-error.password-missing");
669 username = locConfig.username;
670 if (username.isBlank()) {
671 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
672 "@text/conf-error.username-missing");
677 baseUrl = "http://" + ipAddress + "/fhapi/v1/api";
679 updateStatus(ThingStatus.UNKNOWN);
681 scheduler.execute(() -> {
682 boolean thingReachable = true;
684 // Open Http connection
685 if (!openHttpConnection()) {
686 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
687 "@text/comm-error.http-wrongpass-or-ip");
689 thingReachable = false;
692 // Open the websocket connection for immediate status updates
693 if (!openWebSocketConnection()) {
694 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
695 "@text/comm-error.not-able-open-websocketconnection");
697 thingReachable = false;
700 if (thingReachable) {
701 updateStatus(ThingStatus.ONLINE);
710 public void dispose() {
711 // let run out the thread
712 socketMonitor.interrupt();
714 closeWebSocketConnection();
718 * Thread that maintains connection via Websocket.
720 private class FreeAtHomeWebsocketMonitorThread extends Thread {
722 // initial delay to initiate connection
723 private AtomicInteger reconnectDelay = new AtomicInteger();
725 public FreeAtHomeWebsocketMonitorThread() {
730 // set initial connect delay to 0
731 reconnectDelay.set(0);
734 while (!isInterrupted()) {
735 if (httpConnectionOK.get()) {
736 if (connectSession()) {
737 while (isSocketConnectionAlive()) {
738 TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_KEEPALIVE);
740 logger.debug("Sending keep-alive message {}", System.currentTimeMillis());
741 sendWebsocketKeepAliveMessage("keep-alive");
744 logger.debug("Socket connection closed");
745 reconnectDelay.set(BRIDGE_WEBSOCKET_RECONNECT_DELAY);
747 TimeUnit.SECONDS.sleep(BRIDGE_WEBSOCKET_RECONNECT_DELAY);
750 } catch (InterruptedException e) {
751 Thread.currentThread().interrupt();
753 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
754 "@text/comm-error.general-websocket-issue");
755 } catch (IOException e) {
756 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
757 "@text/comm-error.websocket-keep-alive-error");
761 private boolean connectSession() throws InterruptedException {
762 int delay = reconnectDelay.get();
765 logger.debug("Delaying (re)connect request by {} seconds.", reconnectDelay);
766 TimeUnit.SECONDS.sleep(delay);
769 logger.debug("Server connecting to websocket");
771 if (!connectWebsocketSession()) {
772 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
773 "@text/comm-error.general-websocket-issue");
775 reconnectDelay.set(BRIDGE_WEBSOCKET_RECONNECT_DELAY);
780 if (websocketSession == null) {
783 websocketSessionEstablished.await();
794 * Send keep-alive message to SysAp
796 public void sendWebsocketKeepAliveMessage(String message) throws IOException {
797 Session localSession = websocketSession;
799 if (localSession != null) {
800 localSession.getRemote().sendString(message);
805 * Get socket alive state
807 * @throws InterruptedException
809 public boolean isSocketConnectionAlive() throws InterruptedException {
810 Session localSession = websocketSession;
812 return (localSession != null) ? localSession.isOpen() : false;
816 * Socket closed. Report the state
819 public void onWebSocketClose(int statusCode, @Nullable String reason) {
820 websocketSession = null;
821 logger.debug("Socket Closed: [ {} ] {}", statusCode, reason);
825 * Socket connected. store the session for later use
828 public void onWebSocketConnect(@Nullable Session session) {
829 Session localSession = session;
831 if (localSession != null) {
832 websocketSession = localSession;
834 localSession.setIdleTimeout(-1);
836 logger.debug("Socket Connected - Timeout {} - sesson: {}", localSession.getIdleTimeout(), session);
838 logger.debug("Socket Connected - Timeout (invalid) - sesson: (invalid)");
843 websocketSessionEstablished.signal();
850 * Error caused. Report the state
853 public void onWebSocketError(@Nullable Throwable cause) {
854 websocketSession = null;
857 logger.debug("Socket Error: {}", cause.getLocalizedMessage());
859 logger.debug("Socket Error: unknown");
864 * Binary message received. It shall not happen with the free@home SysAp
867 @NonNullByDefault({})
868 public void onWebSocketBinary(byte[] payload, int offset, int len) {
869 logger.debug("Binary message received via websocket");
873 * Text message received. Processing will be started
876 public void onWebSocketText(@Nullable String message) {
877 if (message != null) {
878 if (message.toLowerCase(Locale.US).contains("bye")) {
879 Session localSession = websocketSession;
881 if (localSession != null) {
882 localSession.close(StatusCode.NORMAL, "Thanks");
885 logger.debug("Websocket connection closed: {} ", message);
887 logger.debug("Received websocket text: {} ", message);
889 setDatapointOnWebsocketFeedback(message);
891 markDeviceRemovedOnWebsocketFeedback(message);
894 logger.debug("Invalid message string");