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