]> git.basschouten.com Git - openhab-addons.git/blob
a42d7a12c3c9eec98d7272b93405f20119244778
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.velux.internal.bridge.slip.io;
14
15 import java.io.IOException;
16 import java.net.ConnectException;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
20 import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * 2nd Level I/O interface towards the <B>Velux</B> bridge.
26  * It provides methods for a pure client/server communication.
27  * <P>
28  * The following class access methods exist:
29  * <UL>
30  * <LI>{@link Connection#io} for a complete pair of request and response messages,</LI>
31  * <LI>{@link Connection#isAlive} to check the presence of a connection,</LI>
32  * <LI>{@link Connection#isMessageAvailable} to check the presence of an incoming message,</LI>
33  * <LI>{@link Connection#lastSuccessfulCommunication} returns the timestamp of the last successful communication,</LI>
34  * <LI>{@link Connection#lastCommunication} returns the timestamp of the last communication,</LI>
35  * <LI>{@link Connection#resetConnection} for resetting the current connection.</LI>
36  * </UL>
37  *
38  * @author Guenther Schreiner - Initial contribution.
39  */
40 @NonNullByDefault
41 public class Connection {
42     private final Logger logger = LoggerFactory.getLogger(Connection.class);
43
44     /*
45      * ***************************
46      * ***** Private Objects *****
47      */
48
49     /**
50      * Timestamp of last successful communication in milliseconds.
51      */
52     private long lastSuccessfulCommunicationInMSecs = 0;
53     /**
54      * Timestamp of last communication in milliseconds.
55      */
56     private long lastCommunicationInMSecs = 0;
57     /**
58      * SSL socket for communication.
59      */
60     private SSLconnection connectivity = SSLconnection.UNKNOWN;
61
62     /*
63      * **************************
64      * ***** Public Methods *****
65      */
66
67     /**
68      * Base level communication with the <B>SlipVeluxBridg</B>.
69      *
70      * @param bridgeInstance describing the Service Access Point location i.e. hostname and TCP port.
71      * @param request as Array of bytes representing the structure of the message to be send.
72      * @return <b>response</b> of type Array of byte containing all received informations.
73      * @throws java.net.ConnectException in case of unrecoverable communication failures.
74      * @throws java.io.IOException in case of continuous communication I/O failures.
75      */
76     public synchronized byte[] io(VeluxBridgeInstance bridgeInstance, byte[] request)
77             throws ConnectException, IOException {
78         logger.trace("io() called.");
79
80         lastCommunicationInMSecs = System.currentTimeMillis();
81
82         /** Local handles */
83         int retryCount = 0;
84         IOException lastIOE = new IOException("Unexpected I/O exception.");
85
86         do {
87             try {
88                 if (!connectivity.isReady()) {
89                     try {
90                         // From configuration
91                         String host = bridgeInstance.veluxBridgeConfiguration().ipAddress;
92                         int port = bridgeInstance.veluxBridgeConfiguration().tcpPort;
93                         int timeoutMsecs = bridgeInstance.veluxBridgeConfiguration().timeoutMsecs;
94
95                         logger.trace("io(): connecting to {}:{}.", host, port);
96                         connectivity = new SSLconnection(host, port);
97                         connectivity.setTimeout(timeoutMsecs);
98                     } catch (ConnectException ce) {
99                         throw new ConnectException(String
100                                 .format("raised a non-recoverable error during connection setup: %s", ce.getMessage()));
101                     } catch (IOException e) {
102                         logger.warn("io(): raised an error during connection setup: {}.", e.getMessage());
103                         lastIOE = new IOException(String.format("error during connection setup: %s.", e.getMessage()));
104                         continue;
105                     }
106                 }
107                 if (request.length > 0) {
108                     try {
109                         if (logger.isTraceEnabled()) {
110                             logger.trace("io(): sending packet with {} bytes: {}", request.length, new Packet(request));
111                         } else {
112                             logger.debug("io(): sending packet of size {}.", request.length);
113                         }
114                         if (connectivity.isReady()) {
115                             connectivity.send(request);
116                         }
117                     } catch (IOException e) {
118                         logger.info("io(): raised an error during sending: {}.", e.getMessage());
119                         break;
120                     }
121
122                     // Give the bridge some time to breathe
123                     if (bridgeInstance.veluxBridgeConfiguration().timeoutMsecs > 0) {
124                         logger.trace("io(): wait time {} msecs.",
125                                 bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
126                         try {
127                             Thread.sleep(bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
128                         } catch (InterruptedException ie) {
129                             logger.trace("io() wait interrupted.");
130                         }
131                     }
132                 }
133                 byte[] packet = new byte[0];
134                 logger.trace("io(): receiving bytes.");
135                 if (connectivity.isReady()) {
136                     packet = connectivity.receive();
137                 }
138                 if (logger.isTraceEnabled()) {
139                     logger.trace("io(): received packet with {} bytes: {}", packet.length, new Packet(packet));
140                 } else {
141                     logger.debug("io(): received packet with {} bytes.", packet.length);
142                 }
143                 lastSuccessfulCommunicationInMSecs = System.currentTimeMillis();
144                 lastCommunicationInMSecs = lastSuccessfulCommunicationInMSecs;
145                 logger.trace("io() finished.");
146                 return packet;
147             } catch (IOException ioe) {
148                 logger.info("io(): Exception occurred during I/O: {}.", ioe.getMessage());
149                 lastIOE = ioe;
150                 // Error Retries with Exponential Backoff
151                 long waitTime = ((long) Math.pow(2, retryCount)
152                         * bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
153                 logger.trace("io(): wait time {} msecs.", waitTime);
154                 try {
155                     Thread.sleep(waitTime);
156                 } catch (InterruptedException ie) {
157                     logger.trace("io(): wait interrupted.");
158                 }
159             }
160         } while (retryCount++ < bridgeInstance.veluxBridgeConfiguration().retries);
161         if (retryCount >= bridgeInstance.veluxBridgeConfiguration().retries) {
162             logger.info("io(): socket I/O failed {} times.", bridgeInstance.veluxBridgeConfiguration().retries);
163         }
164         logger.trace("io(): shutting down connection.");
165         if (connectivity.isReady()) {
166             connectivity.close();
167         }
168         logger.trace("io() finishes with failure by throwing exception.");
169         throw lastIOE;
170     }
171
172     /**
173      * Returns the status of the current connection.
174      *
175      * @return state as boolean.
176      */
177     public boolean isAlive() {
178         logger.trace("isAlive(): called.");
179         return connectivity.isReady();
180     }
181
182     /**
183      * Returns the availability of an incoming message.
184      *
185      * @return state as boolean.
186      */
187     public synchronized boolean isMessageAvailable() {
188         logger.trace("isMessageAvailable(): called.");
189         try {
190             if ((connectivity.isReady()) && (connectivity.available())) {
191                 logger.trace("isMessageAvailable(): there is a message waiting.");
192                 return true;
193             }
194         } catch (IOException e) {
195             logger.trace("isMessageAvailable(): lost connection due to {}.", e.getMessage());
196             resetConnection();
197         }
198         logger.trace("isMessageAvailable(): no message waiting.");
199         return false;
200     }
201
202     /**
203      * Returns the timestamp in milliseconds since Unix epoch
204      * of last successful communication.
205      *
206      * @return timestamp in milliseconds.
207      */
208     public long lastSuccessfulCommunication() {
209         return lastSuccessfulCommunicationInMSecs;
210     }
211
212     /**
213      * Returns the timestamp in milliseconds since Unix epoch
214      * of last communication.
215      *
216      * @return timestamp in milliseconds.
217      */
218     public long lastCommunication() {
219         return lastCommunicationInMSecs;
220     }
221
222     /**
223      * Resets an open connectivity by closing the socket and resetting the authentication information.
224      */
225     public synchronized void resetConnection() {
226         logger.trace("resetConnection() called.");
227         if (connectivity.isReady()) {
228             logger.trace("resetConnection(): shutting down connection.");
229             try {
230                 if (connectivity.isReady()) {
231                     connectivity.close();
232                 }
233             } catch (IOException e) {
234                 logger.info("resetConnection(): raised an error during connection close: {}.", e.getMessage());
235             }
236             logger.trace("resetConnection(): clearing authentication token.");
237         }
238         logger.trace("resetConnection() done.");
239     }
240 }