]> git.basschouten.com Git - openhab-addons.git/blob
6e350ca94486c5b0af92c2122a9122c8ae506d2e
[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         } catch (IOException | InterruptedException e) {
72             throw new LuxomConnectionException(e);
73         }
74     }
75
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
80             Thread.sleep(1000);
81         }
82         if (stillListeningToEvents) {
83             throw new IOException("starting but previous connection still active after 5000ms");
84         }
85     }
86
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;
92
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());
99         } else {
100             logger.warn("Luxom: ip bridge not initialized");
101         }
102     }
103
104     /**
105      * Cleanup socket when the communication with Luxom IP-interface is closed.
106      */
107     public synchronized void stopCommunication() {
108         listenerStopped = true;
109
110         closeSocket();
111     }
112
113     private void closeSocket() {
114         if (luxomSocket != null) {
115             try {
116                 luxomSocket.close();
117             } catch (IOException ignore) {
118                 // ignore IO Error when trying to close the socket if the intention is to close it anyway
119             }
120         }
121         luxomSocket = null;
122
123         logger.debug("Luxom: communication stopped");
124     }
125
126     /**
127      * Method that handles inbound communication from Luxom, to be called on a separate thread.
128      * <p>
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.
132      */
133     private void runLuxomEvents() {
134         StringBuilder luxomMessage = new StringBuilder();
135
136         logger.debug("Luxom: listening for events");
137         listenerStopped = false;
138         stillListeningToEvents = true;
139
140         try {
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
151                         this.closeSocket();
152                         this.initializeSocket();
153                         // followed by forced update of status
154                         bridgeHandler.forceRefreshThings();
155                     } else {
156                         listenerStopped = true;
157                         mustDoFullReconnect = true;
158                     }
159                 } else {
160                     mayUseFastReconnect = true; // reset
161                     char c = (char) nextChar;
162                     logger.trace("Luxom: read char {}", c);
163
164                     luxomMessage.append(c);
165
166                     if (';' == c) {
167                         String message = luxomMessage.toString();
168                         bridgeHandler.handleIncomingLuxomMessage(message.substring(0, message.length() - 1));
169                         luxomMessage = new StringBuilder();
170                     }
171                 }
172             }
173             if (mustDoFullReconnect) {
174                 // I want to do this out of the loop
175                 bridgeHandler.reconnect();
176             }
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);
185                 return;
186             }
187         } finally {
188             stillListeningToEvents = false;
189         }
190
191         // this is a stop from outside the runnable, so just log it and stop
192         logger.debug("Luxom: event listener thread stopped");
193     }
194
195     public synchronized void sendMessage(String message) throws IOException {
196         logger.debug("Luxom: send {}", message);
197         if (luxomOut != null) {
198             luxomOut.print(message + ";");
199             luxomOut.flush();
200             if (luxomOut.checkError()) {
201                 throw new IOException(String.format("luxom communication error when sending message: %s", message));
202             }
203         }
204     }
205
206     public boolean isConnected() {
207         return luxomSocket != null && luxomSocket.isConnected();
208     }
209 }