]> git.basschouten.com Git - openhab-addons.git/blob
f0958e41a9d6da0123f4085c769bb630e562634e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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                 // Error Retries with Exponential Backoff
151                 long waitTime = ((long) Math.pow(2, retryCount)
152                         * bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
153                 logger.trace("io() on {}: wait time {} msecs.", host, waitTime);
154                 try {
155                     Thread.sleep(waitTime);
156                 } catch (InterruptedException ie) {
157                     logger.trace("io() on {}: wait interrupted.", host);
158                 }
159             }
160         } while (retryCount++ < bridgeInstance.veluxBridgeConfiguration().retries);
161         if (retryCount >= bridgeInstance.veluxBridgeConfiguration().retries) {
162             logger.info("io() on {}: socket I/O failed {} times.", host,
163                     bridgeInstance.veluxBridgeConfiguration().retries);
164         }
165         logger.trace("io() on {}: shutting down connection.", host);
166         resetConnection();
167         logger.trace("io() on {}: finishes with failure by throwing exception.", host);
168         throw lastIOE;
169     }
170
171     /**
172      * Returns the status of the current connection.
173      *
174      * @return state as boolean.
175      */
176     public boolean isAlive() {
177         logger.trace("isAlive() on {}: called.", host);
178         return connectivity.isReady();
179     }
180
181     /**
182      * Returns the availability of an incoming message.
183      *
184      * @return state as boolean.
185      */
186     public synchronized boolean isMessageAvailable() {
187         logger.trace("isMessageAvailable() on {}: called.", host);
188         if (!connectivity.isReady()) {
189             logger.trace("isMessageAvailable() on {}: lost connection, there may be messages", host);
190             return false;
191         }
192         boolean result = connectivity.available();
193         logger.trace("isMessageAvailable() on {}: there are {}messages waiting.", host, result ? "" : "no ");
194         return result;
195     }
196
197     /**
198      * Returns the timestamp in milliseconds since Unix epoch
199      * of last successful communication.
200      *
201      * @return timestamp in milliseconds.
202      */
203     public long lastSuccessfulCommunication() {
204         return lastSuccessfulCommunicationInMSecs;
205     }
206
207     /**
208      * Returns the timestamp in milliseconds since Unix epoch
209      * of last communication.
210      *
211      * @return timestamp in milliseconds.
212      */
213     public long lastCommunication() {
214         return lastCommunicationInMSecs;
215     }
216
217     /**
218      * Resets an open connectivity by closing the socket and resetting the authentication information.
219      */
220     public synchronized void resetConnection() {
221         logger.trace("resetConnection() on {}: called.", host);
222         try {
223             connectivity.close();
224         } catch (IOException e) {
225             logger.info("resetConnection() on {}: raised an error during connection close: {}.", host, e.getMessage());
226         }
227         logger.trace("resetConnection() on {}: done.", host);
228     }
229
230     @Override
231     public void close() throws IOException {
232         resetConnection();
233     }
234 }