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();
71 } catch (IOException | InterruptedException e) {
72 throw new LuxomConnectionException(e);
76 private void waitForEventListenerThreadToStop() throws InterruptedException, IOException {
77 for (int i = 1; stillListeningToEvents && (i <= 5); i++) {
78 // the events listener thread did not finish yet, so wait max 5000ms before restarting
79 // noinspection BusyWait
82 if (stillListeningToEvents) {
83 throw new IOException("starting but previous connection still active after 5000ms");
87 private void initializeSocket() throws IOException {
88 LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
89 if (luxomBridgeConfig != null) {
90 InetAddress addr = InetAddress.getByName(luxomBridgeConfig.ipAddress);
91 int port = luxomBridgeConfig.port;
93 luxomSocket = new Socket(addr, port);
94 luxomSocket.setReuseAddress(true);
95 luxomSocket.setKeepAlive(true);
96 luxomOut = new PrintWriter(luxomSocket.getOutputStream());
97 luxomIn = new BufferedReader(new InputStreamReader(luxomSocket.getInputStream()));
98 logger.debug("Luxom: connected via local port {}", luxomSocket.getLocalPort());
100 logger.warn("Luxom: ip bridge not initialized");
105 * Cleanup socket when the communication with Luxom IP-interface is closed.
107 public synchronized void stopCommunication() {
108 listenerStopped = true;
113 private void closeSocket() {
114 if (luxomSocket != null) {
117 } catch (IOException ignore) {
118 // ignore IO Error when trying to close the socket if the intention is to close it anyway
123 logger.debug("Luxom: communication stopped");
127 * Method that handles inbound communication from Luxom, to be called on a separate thread.
129 * The thread listens to the TCP socket opened at instantiation of the {@link LuxomCommunication} class
130 * and interprets all inbound json messages. It triggers state updates for active channels linked to the Niko Home
131 * Control actions. It is started after initialization of the communication.
133 private void runLuxomEvents() {
134 StringBuilder luxomMessage = new StringBuilder();
136 logger.debug("Luxom: listening for events");
137 listenerStopped = false;
138 stillListeningToEvents = true;
141 boolean mayUseFastReconnect = false;
142 boolean mustDoFullReconnect = false;
143 while (!listenerStopped && (luxomIn != null)) {
144 int nextChar = luxomIn.read();
145 if (nextChar == -1) {
146 logger.trace("Luxom: stream ends unexpectedly...");
147 LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
148 if (mayUseFastReconnect && luxomBridgeConfig != null && luxomBridgeConfig.useFastReconnect) {
149 // we stay in the loop and just reinitialize socket
150 mayUseFastReconnect = false; // just once use fast reconnect
152 this.initializeSocket();
153 // followed by forced update of status
154 bridgeHandler.forceRefreshThings();
156 listenerStopped = true;
157 mustDoFullReconnect = true;
160 mayUseFastReconnect = true; // reset
161 char c = (char) nextChar;
162 logger.trace("Luxom: read char {}", c);
164 luxomMessage.append(c);
167 String message = luxomMessage.toString();
168 bridgeHandler.handleIncomingLuxomMessage(message.substring(0, message.length() - 1));
169 luxomMessage = new StringBuilder();
173 if (mustDoFullReconnect) {
174 // I want to do this out of the loop
175 bridgeHandler.reconnect();
177 logger.trace("Luxom: stopped listening to events");
178 } catch (IOException e) {
179 logger.warn("Luxom: listening to events - IO exception", e);
180 if (!listenerStopped) {
181 stillListeningToEvents = false;
182 // this is a socket error, not a communication stop triggered from outside this runnable
183 // the IO has stopped working, so we need to close cleanly and try to restart
184 bridgeHandler.handleCommunicationError(e);
188 stillListeningToEvents = false;
191 // this is a stop from outside the runnable, so just log it and stop
192 logger.debug("Luxom: event listener thread stopped");
195 public synchronized void sendMessage(String message) throws IOException {
196 logger.debug("Luxom: send {}", message);
197 if (luxomOut != null) {
198 luxomOut.print(message + ";");
200 if (luxomOut.checkError()) {
201 throw new IOException(String.format("luxom communication error when sending message: %s", message));
206 public boolean isConnected() {
207 return luxomSocket != null && luxomSocket.isConnected();