2 * Copyright (c) 2010-2023 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.luxom.internal.protocol;
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.PrintWriter;
19 import java.net.InetAddress;
20 import java.net.Socket;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.luxom.internal.handler.LuxomBridgeHandler;
25 import org.openhab.binding.luxom.internal.handler.LuxomConnectionException;
26 import org.openhab.binding.luxom.internal.handler.config.LuxomBridgeConfig;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The {@link LuxomCommunication} class is able to do the following tasks with Luxom IP
34 * <li>Start and stop TCP socket connection with Luxom IP-interface.
35 * <li>Read all setup and status information from the Luxom Controller.
36 * <li>Execute Luxom commands.
37 * <li>Listen to events from Luxom.
40 * @author Kris Jespers - Initial contribution
43 public class LuxomCommunication {
45 private final Logger logger = LoggerFactory.getLogger(LuxomCommunication.class);
47 private final LuxomBridgeHandler bridgeHandler;
49 private @Nullable Socket luxomSocket;
50 private @Nullable PrintWriter luxomOut;
51 private @Nullable BufferedReader luxomIn;
53 private volatile boolean listenerStopped;
54 private volatile boolean stillListeningToEvents;
56 public LuxomCommunication(LuxomBridgeHandler luxomBridgeHandler) {
58 bridgeHandler = luxomBridgeHandler;
61 public synchronized void startCommunication() throws LuxomConnectionException {
63 waitForEventListenerThreadToStop();
67 // Start Luxom event listener. This listener will act on all messages coming from
69 (new Thread(this::runLuxomEvents,
70 "OH-binding-" + bridgeHandler.getThing().getBridgeUID() + "-listen-for-events")).start();
72 } catch (IOException | InterruptedException e) {
73 throw new LuxomConnectionException(e);
77 private void waitForEventListenerThreadToStop() throws InterruptedException, IOException {
78 for (int i = 1; stillListeningToEvents && (i <= 5); i++) {
79 // the events listener thread did not finish yet, so wait max 5000ms before restarting
80 // noinspection BusyWait
83 if (stillListeningToEvents) {
84 throw new IOException("starting but previous connection still active after 5000ms");
88 private void initializeSocket() throws IOException {
89 LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
90 if (luxomBridgeConfig != null) {
91 InetAddress addr = InetAddress.getByName(luxomBridgeConfig.ipAddress);
92 int port = luxomBridgeConfig.port;
94 luxomSocket = new Socket(addr, port);
95 luxomSocket.setReuseAddress(true);
96 luxomSocket.setKeepAlive(true);
97 luxomOut = new PrintWriter(luxomSocket.getOutputStream());
98 luxomIn = new BufferedReader(new InputStreamReader(luxomSocket.getInputStream()));
99 logger.debug("Luxom: connected via local port {}", luxomSocket.getLocalPort());
101 logger.warn("Luxom: ip bridge not initialized");
106 * Cleanup socket when the communication with Luxom IP-interface is closed.
108 public synchronized void stopCommunication() {
109 listenerStopped = true;
114 private void closeSocket() {
115 if (luxomSocket != null) {
118 } catch (IOException ignore) {
119 // ignore IO Error when trying to close the socket if the intention is to close it anyway
124 logger.debug("Luxom: communication stopped");
128 * Method that handles inbound communication from Luxom, to be called on a separate thread.
130 * The thread listens to the TCP socket opened at instantiation of the {@link LuxomCommunication} class
131 * and interprets all inbound json messages. It triggers state updates for active channels linked to the Niko Home
132 * Control actions. It is started after initialization of the communication.
134 private void runLuxomEvents() {
135 StringBuilder luxomMessage = new StringBuilder();
137 logger.debug("Luxom: listening for events");
138 listenerStopped = false;
139 stillListeningToEvents = true;
142 boolean mayUseFastReconnect = false;
143 boolean mustDoFullReconnect = false;
144 while (!listenerStopped && (luxomIn != null)) {
145 int nextChar = luxomIn.read();
146 if (nextChar == -1) {
147 logger.trace("Luxom: stream ends unexpectedly...");
148 LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
149 if (mayUseFastReconnect && luxomBridgeConfig != null && luxomBridgeConfig.useFastReconnect) {
150 // we stay in the loop and just reinitialize socket
151 mayUseFastReconnect = false; // just once use fast reconnect
153 this.initializeSocket();
154 // followed by forced update of status
155 bridgeHandler.forceRefreshThings();
157 listenerStopped = true;
158 mustDoFullReconnect = true;
161 mayUseFastReconnect = true; // reset
162 char c = (char) nextChar;
163 logger.trace("Luxom: read char {}", c);
165 luxomMessage.append(c);
168 String message = luxomMessage.toString();
169 bridgeHandler.handleIncomingLuxomMessage(message.substring(0, message.length() - 1));
170 luxomMessage = new StringBuilder();
174 if (mustDoFullReconnect) {
175 // I want to do this out of the loop
176 bridgeHandler.reconnect();
178 logger.trace("Luxom: stopped listening to events");
179 } catch (IOException e) {
180 logger.warn("Luxom: listening to events - IO exception", e);
181 if (!listenerStopped) {
182 stillListeningToEvents = false;
183 // this is a socket error, not a communication stop triggered from outside this runnable
184 // the IO has stopped working, so we need to close cleanly and try to restart
185 bridgeHandler.handleCommunicationError(e);
189 stillListeningToEvents = false;
192 // this is a stop from outside the runnable, so just log it and stop
193 logger.debug("Luxom: event listener thread stopped");
196 public synchronized void sendMessage(String message) throws IOException {
197 logger.debug("Luxom: send {}", message);
198 if (luxomOut != null) {
199 luxomOut.print(message + ";");
201 if (luxomOut.checkError()) {
202 throw new IOException(String.format("luxom communication error when sending message: %s", message));
207 public boolean isConnected() {
208 return luxomSocket != null && luxomSocket.isConnected();