2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.velux.internal.bridge.slip.io;
15 import java.io.Closeable;
16 import java.io.IOException;
17 import java.net.ConnectException;
18 import java.net.SocketTimeoutException;
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;
29 * 2nd Level I/O interface towards the <B>Velux</B> bridge.
30 * It provides methods for a pure client/server communication.
32 * The following class access methods exist:
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>
42 * @author Guenther Schreiner - Initial contribution.
45 public class Connection implements Closeable {
46 private final Logger logger = LoggerFactory.getLogger(Connection.class);
49 * ***************************
50 * ***** Private Objects *****
54 * Timestamp of last successful communication in milliseconds.
56 private long lastSuccessfulCommunicationInMSecs = 0;
58 * Timestamp of last communication in milliseconds.
60 private long lastCommunicationInMSecs = 0;
62 * SSL socket for communication.
64 private SSLconnection connectivity = SSLconnection.UNKNOWN;
66 private String host = VeluxBindingConstants.UNKNOWN_IP_ADDRESS;
69 * **************************
70 * ***** Public Methods *****
74 * Base level communication with the <B>SlipVeluxBridg</B>.
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.
82 public synchronized byte[] io(VeluxBridgeHandler bridgeInstance, byte[] request)
83 throws ConnectException, IOException {
84 VeluxBridgeConfiguration cfg = bridgeInstance.veluxBridgeConfiguration();
86 logger.trace("io() on {}: called.", host);
88 lastCommunicationInMSecs = System.currentTimeMillis();
92 IOException lastIOE = new IOException("Unexpected I/O exception.");
96 if (!connectivity.isReady()) {
97 // dispose old connectivity class instances (if any)
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()));
111 boolean sending = request.length > 0;
114 if (logger.isTraceEnabled()) {
115 logger.trace("io() on {}: sending packet with {} bytes: {}", host, request.length,
116 new Packet(request));
118 logger.debug("io() on {}: sending packet of size {}.", host, request.length);
120 if (connectivity.isReady()) {
121 connectivity.send(request);
123 } catch (IOException e) {
124 logger.info("io() on {}: raised an error during sending: {}.", host, e.getMessage());
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");
137 if (logger.isTraceEnabled()) {
138 logger.trace("io() on {}: received packet with {} bytes: {}", host, packet.length,
141 logger.debug("io() on {}: received packet with {} bytes.", host, packet.length);
143 lastSuccessfulCommunicationInMSecs = System.currentTimeMillis();
144 lastCommunicationInMSecs = lastSuccessfulCommunicationInMSecs;
145 logger.trace("io() on {}: finished.", host);
147 } catch (IOException ioe) {
148 logger.info("io() on {}: Exception occurred during I/O: {}.", host, ioe.getMessage());
150 if (bridgeInstance.isDisposing()) {
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);
158 Thread.sleep(waitTime);
159 } catch (InterruptedException ie) {
160 logger.trace("io() on {}: wait interrupted.", host);
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);
169 logger.trace("io() on {}: shutting down connection.", host);
171 logger.trace("io() on {}: finishes with failure by throwing exception.", host);
176 * Returns the status of the current connection.
178 * @return state as boolean.
180 public boolean isAlive() {
181 logger.trace("isAlive() on {}: called.", host);
182 return connectivity.isReady();
186 * Returns the availability of an incoming message.
188 * @return state as boolean.
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);
196 boolean result = connectivity.available();
197 logger.trace("isMessageAvailable() on {}: there are {}messages waiting.", host, result ? "" : "no ");
202 * Returns the timestamp in milliseconds since Unix epoch
203 * of last successful communication.
205 * @return timestamp in milliseconds.
207 public long lastSuccessfulCommunication() {
208 return lastSuccessfulCommunicationInMSecs;
212 * Returns the timestamp in milliseconds since Unix epoch
213 * of last communication.
215 * @return timestamp in milliseconds.
217 public long lastCommunication() {
218 return lastCommunicationInMSecs;
222 * Resets an open connectivity by closing the socket and resetting the authentication information.
224 public synchronized void resetConnection() {
225 logger.trace("resetConnection() on {}: called.", host);
227 connectivity.close();
228 } catch (IOException e) {
229 logger.info("resetConnection() on {}: raised an error during connection close: {}.", host, e.getMessage());
231 logger.trace("resetConnection() on {}: done.", host);
235 public void close() throws IOException {