]> git.basschouten.com Git - openhab-addons.git/blob
05c318941ea8f9e9d2b9b5a2b144bdc4f514958b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.luxom.internal.protocol;
14
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;
21
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;
29
30 /**
31  * The {@link LuxomCommunication} class is able to do the following tasks with Luxom IP
32  * systems:
33  * <ul>
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.
38  * </ul>
39  *
40  * @author Kris Jespers - Initial contribution
41  */
42 @NonNullByDefault
43 public class LuxomCommunication {
44
45     private final Logger logger = LoggerFactory.getLogger(LuxomCommunication.class);
46
47     private final LuxomBridgeHandler bridgeHandler;
48
49     private @Nullable Socket luxomSocket;
50     private @Nullable PrintWriter luxomOut;
51     private @Nullable BufferedReader luxomIn;
52
53     private volatile boolean listenerStopped;
54     private volatile boolean stillListeningToEvents;
55
56     public LuxomCommunication(LuxomBridgeHandler luxomBridgeHandler) {
57         super();
58         bridgeHandler = luxomBridgeHandler;
59     }
60
61     public synchronized void startCommunication() throws LuxomConnectionException {
62         try {
63             waitForEventListenerThreadToStop();
64
65             initializeSocket();
66
67             // Start Luxom event listener. This listener will act on all messages coming from
68             // IP-interface.
69             (new Thread(this::runLuxomEvents,
70                     "OH-binding-" + bridgeHandler.getThing().getBridgeUID() + "-listen-for-events")).start();
71
72         } catch (IOException | InterruptedException e) {
73             throw new LuxomConnectionException(e);
74         }
75     }
76
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
81             Thread.sleep(1000);
82         }
83         if (stillListeningToEvents) {
84             throw new IOException("starting but previous connection still active after 5000ms");
85         }
86     }
87
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;
93
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());
100         } else {
101             logger.warn("Luxom: ip bridge not initialized");
102         }
103     }
104
105     /**
106      * Cleanup socket when the communication with Luxom IP-interface is closed.
107      */
108     public synchronized void stopCommunication() {
109         listenerStopped = true;
110
111         closeSocket();
112     }
113
114     private void closeSocket() {
115         if (luxomSocket != null) {
116             try {
117                 luxomSocket.close();
118             } catch (IOException ignore) {
119                 // ignore IO Error when trying to close the socket if the intention is to close it anyway
120             }
121         }
122         luxomSocket = null;
123
124         logger.debug("Luxom: communication stopped");
125     }
126
127     /**
128      * Method that handles inbound communication from Luxom, to be called on a separate thread.
129      * <p>
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.
133      */
134     private void runLuxomEvents() {
135         StringBuilder luxomMessage = new StringBuilder();
136
137         logger.debug("Luxom: listening for events");
138         listenerStopped = false;
139         stillListeningToEvents = true;
140
141         try {
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
152                         this.closeSocket();
153                         this.initializeSocket();
154                         // followed by forced update of status
155                         bridgeHandler.forceRefreshThings();
156                     } else {
157                         listenerStopped = true;
158                         mustDoFullReconnect = true;
159                     }
160                 } else {
161                     mayUseFastReconnect = true; // reset
162                     char c = (char) nextChar;
163                     logger.trace("Luxom: read char {}", c);
164
165                     luxomMessage.append(c);
166
167                     if (';' == c) {
168                         String message = luxomMessage.toString();
169                         bridgeHandler.handleIncomingLuxomMessage(message.substring(0, message.length() - 1));
170                         luxomMessage = new StringBuilder();
171                     }
172                 }
173             }
174             if (mustDoFullReconnect) {
175                 // I want to do this out of the loop
176                 bridgeHandler.reconnect();
177             }
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);
186                 return;
187             }
188         } finally {
189             stillListeningToEvents = false;
190         }
191
192         // this is a stop from outside the runnable, so just log it and stop
193         logger.debug("Luxom: event listener thread stopped");
194     }
195
196     public synchronized void sendMessage(String message) throws IOException {
197         logger.debug("Luxom: send {}", message);
198         if (luxomOut != null) {
199             luxomOut.print(message + ";");
200             luxomOut.flush();
201             if (luxomOut.checkError()) {
202                 throw new IOException(String.format("luxom communication error when sending message: %s", message));
203             }
204         }
205     }
206
207     public boolean isConnected() {
208         return luxomSocket != null && luxomSocket.isConnected();
209     }
210 }