2 * Copyright (c) 2010-2022 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.json;
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.nio.charset.StandardCharsets;
19 import java.util.Properties;
20 import java.util.TreeSet;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
24 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
25 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
26 import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
27 import org.openhab.core.io.net.http.HttpUtil;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import com.google.gson.Gson;
32 import com.google.gson.JsonSyntaxException;
35 * JSON-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
37 * It provides methods for pre- and postcommunication
38 * as well as a common method for the real communication.
40 * The following class access methods exist:
42 * <LI>{@link VeluxBridge#bridgeLogin} for pre-communication,</LI>
43 * <LI>{@link VeluxBridge#bridgeLogout} for post-communication,</LI>
44 * <LI>{@link VeluxBridge#bridgeCommunicate} as method for the common communication.</LI>
47 * As root of several inheritance levels it predefines an
48 * interfacing method {@link VeluxBridge#bridgeAPI} which
49 * has to be implemented by any kind of protocol-specific
50 * communication returning the appropriate base (1st) level
51 * communication method as well as any other gateway
54 * @author Guenther Schreiner - Initial contribution.
57 public class JsonVeluxBridge extends VeluxBridge {
58 private final Logger logger = LoggerFactory.getLogger(JsonVeluxBridge.class);
61 * Timestamp of last communication in milliseconds.
64 private long lastCommunicationInMSecs = 0;
67 * Timestamp of last successful communication in milliseconds.
70 private long lastSuccessfulCommunicationInMSecs = 0;
73 * Handler passing the interface methods to other classes.
74 * Can be accessed via method {@link org.openhab.binding.velux.internal.bridge.common.BridgeAPI BridgeAPI}.
77 private BridgeAPI bridgeAPI;
82 * Inherits the initialization of the binding-wide instance for dealing for common informations and
83 * initializes the Velux bridge connectivity settings.
85 * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
87 public JsonVeluxBridge(VeluxBridgeHandler bridgeInstance) {
88 super(bridgeInstance);
89 logger.trace("JsonVeluxBridge(constructor) called.");
90 bridgeAPI = new JsonBridgeAPI(bridgeInstance);
91 supportedProtocols = new TreeSet<>();
92 supportedProtocols.add("http");
93 supportedProtocols.add("https");
94 logger.trace("JsonVeluxBridge(constructor) done.");
98 * Provides information about the base-level communication method and
99 * any kind of available gateway interactions.
101 * <B>Note:</B> the implementation within this class {@link JsonVeluxBridge} as inherited from {@link VeluxBridge}
102 * will return the protocol-specific class implementations.
104 * The information will be initialized by the corresponding API class {@link JsonBridgeAPI}.
106 * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
109 public BridgeAPI bridgeAPI() {
110 logger.trace("bridgeAPI() called.");
115 * <B>Method as implementation of abstract superclass method.</B>
117 * Initializes a client/server communication towards <b>Velux</b> veluxBridge
118 * based on the Basic I/O interface {@link #io} and parameters
119 * passed as arguments (see below).
121 * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the intended
122 * communication, that is request and response interactions as well as appropriate URL definition.
123 * @param useAuthentication boolean flag to decide whether to use authenticated communication.
124 * @return <b>success</b> of type boolean which signals the success of the communication.
128 protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
129 logger.trace("bridgeDirectCommunicate(BCP: {},{}authenticated) called.", communication.name(),
130 useAuthentication ? "" : "un");
131 return bridgeDirectCommunicate((JsonBridgeCommunicationProtocol) communication, useAuthentication);
135 * Returns the timestamp in milliseconds since Unix epoch
136 * of last (potentially faulty) communication.
138 * @return timestamp in milliseconds.
141 public long lastCommunication() {
142 return lastCommunicationInMSecs;
146 * Returns the timestamp in milliseconds since Unix epoch
147 * of last successful communication.
149 * @return timestamp in milliseconds.
152 public long lastSuccessfulCommunication() {
153 return lastSuccessfulCommunicationInMSecs;
157 * Initializes a client/server communication towards <b>Velux</b> veluxBridge
158 * based on the Basic I/O interface {@link VeluxBridge} and parameters
159 * passed as arguments (see below).
161 * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the
164 * that is request and response interactions as well as appropriate URL definition.
165 * @param useAuthentication boolean flag to decide whether to use authenticated communication.
166 * @return <b>response</b> of type boolean will indicate the success of the communication.
168 private synchronized boolean bridgeDirectCommunicate(JsonBridgeCommunicationProtocol communication,
169 boolean useAuthentication) {
170 logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
171 useAuthentication ? "" : "un");
173 String sapURL = this.bridgeInstance.veluxBridgeConfiguration().protocol.concat("://")
174 .concat(this.bridgeInstance.veluxBridgeConfiguration().ipAddress).concat(":")
175 .concat(Integer.toString(this.bridgeInstance.veluxBridgeConfiguration().tcpPort))
176 .concat(communication.getURL());
177 logger.trace("bridgeCommunicate(): using SAP {}.", sapURL);
178 Object getRequest = communication.getObjectOfRequest();
179 Class<?> classOfResponse = communication.getClassOfResponse();
183 if (useAuthentication) {
184 response = ioAuthenticated(sapURL, authenticationToken, getRequest, classOfResponse);
186 response = ioUnauthenticated(sapURL, getRequest, classOfResponse);
188 if (response == null) {
189 throw new IOException("Failed to create 'response' object");
191 communication.setResponse(response);
192 logger.trace("bridgeCommunicate(): communication result is {}, returning details.",
193 communication.isCommunicationSuccessful());
195 } catch (IOException ioe) {
196 logger.warn("bridgeCommunicate(): Exception occurred on accessing {}: {}.", sapURL, ioe.getMessage());
198 } catch (JsonSyntaxException jse) {
199 logger.warn("bridgeCommunicate(): Exception occurred on (de-)serialization during accessing {}: {}.",
200 sapURL, jse.getMessage());
206 * Base level communication with the <b>Velux</b> bridge.
208 * @param <T> This describes the request type parameter.
209 * @param <U> This describes the response type parameter.
210 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
211 * @param authentication as String providing the Authentication token to be passed with the request header.
212 * @param request as Object representing the structure of the message request body to be converted into
214 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
216 * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
219 * <B>null</B> in case of communication or decoding error.
220 * @throws java.io.IOException in case of continuous communication I/O failures.
221 * @throws JsonSyntaxException in case of unusual communication failures.
223 private <T, U> T io(String url, String authentication, U request, Class<T> classOfResponse)
224 throws JsonSyntaxException, IOException {
228 lastCommunicationInMSecs = System.currentTimeMillis();
231 Gson gson = new Gson();
232 String jsonRequest = gson.toJson(request);
233 logger.trace("io() to {} using request {}.", url, jsonRequest);
235 Properties headerItems = new Properties();
236 if (authentication.length() > 0) {
237 headerItems.setProperty("Authorization", String.format("Bearer %s", authentication));
239 InputStream content = new ByteArrayInputStream(jsonRequest.getBytes(StandardCharsets.UTF_8));
241 String jsonResponse = HttpUtil.executeUrl("PUT", url, headerItems, content, "application/json",
242 this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
243 if (jsonResponse == null) {
244 throw new IOException("transport error");
246 logger.trace("io(): wait time {} msecs.", this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
247 // Give the bridge some time to breathe
249 Thread.sleep(this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
250 } catch (InterruptedException ie) {
251 logger.trace("io() wait interrupted.");
253 logger.trace("io() got response {}.", jsonResponse.replaceAll("\\p{C}", "."));
254 jsonResponse = jsonResponse.replaceAll("^.+,\n", "");
255 logger.trace("io() cleaned response {}.", jsonResponse);
256 T response = gson.fromJson(jsonResponse, classOfResponse);
257 lastCommunicationInMSecs = lastSuccessfulCommunicationInMSecs = System.currentTimeMillis();
259 } catch (IOException ioe) {
260 logger.trace("io(): Exception occurred during I/O: {}.", ioe.getMessage());
261 // Error Retries with Exponential Backoff
262 long waitTime = ((long) Math.pow(2, retryCount)
263 * this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
264 logger.trace("io(): wait time {} msecs.", waitTime);
266 Thread.sleep(waitTime);
267 } catch (InterruptedException ie) {
268 logger.trace("io() wait interrupted.");
270 } catch (JsonSyntaxException jse) {
271 logger.info("io(): Exception occurred on deserialization: {}, aborting.", jse.getMessage());
274 } while (retryCount++ < this.bridgeInstance.veluxBridgeConfiguration().retries);
275 throw new IOException(String.format("io(): socket I/O failed (%d times).",
276 this.bridgeInstance.veluxBridgeConfiguration().retries));
280 * Initializes an authenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
282 * @param <T> This describes the request type parameter.
283 * @param <U> This describes the response type parameter.
284 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
285 * @param authentication as String providing the Authentication token to be passed with the request header.
286 * @param request as Object representing the structure of the message request body to be converted into
288 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
290 * @return <b>response</b> of type T containing all resulting informations, i.e. device status, errors a.s.o. Will
292 * <B>null</B> in case of communication or decoding error.
293 * @throws java.io.IOException in case of continuous communication I/O failures.
294 * @throws JsonSyntaxException in case of unusual communication failures.
296 private <T, U> T ioAuthenticated(String url, String authentication, U request, Class<T> classOfResponse)
297 throws JsonSyntaxException, IOException {
298 return io(url, authentication, request, classOfResponse);
302 * Initializes an unauthenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
304 * @param <T> This describes the request type parameter.
305 * @param <U> This describes the response type parameter.
306 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
307 * @param request as Object representing the structure of the message request body to be converted into
309 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
311 * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
314 * <B>null</B> in case of communication or decoding error.
315 * @throws java.io.IOException in case of continuous communication I/O failures.
316 * @throws JsonSyntaxException in case of unusual communication failures.
318 private <T, U> T ioUnauthenticated(String url, U request, Class<T> classOfResponse)
319 throws JsonSyntaxException, IOException {
320 return io(url, "", request, classOfResponse);