]> git.basschouten.com Git - openhab-addons.git/blob
147411efa534d8f4fb35d500b627023e28bb7f5d
[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.json;
14
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;
21
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;
30
31 import com.google.gson.Gson;
32 import com.google.gson.JsonSyntaxException;
33
34 /**
35  * JSON-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
36  * <P>
37  * It provides methods for pre- and postcommunication
38  * as well as a common method for the real communication.
39  * <P>
40  * The following class access methods exist:
41  * <UL>
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>
45  * </UL>
46  * <P>
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
52  * interaction.
53  *
54  * @author Guenther Schreiner - Initial contribution.
55  */
56 @NonNullByDefault
57 public class JsonVeluxBridge extends VeluxBridge {
58     private final Logger logger = LoggerFactory.getLogger(JsonVeluxBridge.class);
59
60     /**
61      * Timestamp of last communication in milliseconds.
62      *
63      */
64     private long lastCommunicationInMSecs = 0;
65
66     /**
67      * Timestamp of last successful communication in milliseconds.
68      *
69      */
70     private long lastSuccessfulCommunicationInMSecs = 0;
71
72     /**
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}.
75      *
76      */
77     private BridgeAPI bridgeAPI;
78
79     /**
80      * Constructor.
81      * <P>
82      * Inherits the initialization of the binding-wide instance for dealing for common informations and
83      * initializes the Velux bridge connectivity settings.
84      *
85      * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
86      */
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.");
95     }
96
97     /**
98      * Provides information about the base-level communication method and
99      * any kind of available gateway interactions.
100      * <P>
101      * <B>Note:</B> the implementation within this class {@link JsonVeluxBridge} as inherited from {@link VeluxBridge}
102      * will return the protocol-specific class implementations.
103      * <P>
104      * The information will be initialized by the corresponding API class {@link JsonBridgeAPI}.
105      *
106      * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
107      */
108     @Override
109     public BridgeAPI bridgeAPI() {
110         logger.trace("bridgeAPI() called.");
111         return bridgeAPI;
112     }
113
114     /**
115      * <B>Method as implementation of abstract superclass method.</B>
116      * <P>
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).
120      *
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.
125      *
126      */
127     @Override
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);
132     }
133
134     /**
135      * Returns the timestamp in milliseconds since Unix epoch
136      * of last (potentially faulty) communication.
137      *
138      * @return timestamp in milliseconds.
139      */
140     @Override
141     public long lastCommunication() {
142         return lastCommunicationInMSecs;
143     }
144
145     /**
146      * Returns the timestamp in milliseconds since Unix epoch
147      * of last successful communication.
148      *
149      * @return timestamp in milliseconds.
150      */
151     @Override
152     public long lastSuccessfulCommunication() {
153         return lastSuccessfulCommunicationInMSecs;
154     }
155
156     /**
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).
160      *
161      * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the
162      *            intended
163      *            communication,
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.
167      */
168     private synchronized boolean bridgeDirectCommunicate(JsonBridgeCommunicationProtocol communication,
169             boolean useAuthentication) {
170         logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
171                 useAuthentication ? "" : "un");
172
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();
180         Object response;
181
182         try {
183             if (useAuthentication) {
184                 response = ioAuthenticated(sapURL, authenticationToken, getRequest, classOfResponse);
185             } else {
186                 response = ioUnauthenticated(sapURL, getRequest, classOfResponse);
187             }
188             if (response == null) {
189                 throw new IOException("Failed to create 'response' object");
190             }
191             communication.setResponse(response);
192             logger.trace("bridgeCommunicate(): communication result is {}, returning details.",
193                     communication.isCommunicationSuccessful());
194             return true;
195         } catch (IOException ioe) {
196             logger.warn("bridgeCommunicate(): Exception occurred on accessing {}: {}.", sapURL, ioe.getMessage());
197             return false;
198         } catch (JsonSyntaxException jse) {
199             logger.warn("bridgeCommunicate(): Exception occurred on (de-)serialization during accessing {}: {}.",
200                     sapURL, jse.getMessage());
201             return false;
202         }
203     }
204
205     /**
206      * Base level communication with the <b>Velux</b> bridge.
207      *
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
213      *            JSON.
214      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
215      *            from JSON.
216      * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
217      *         Will
218      *         return
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.
222      */
223     private <T, U> T io(String url, String authentication, U request, Class<T> classOfResponse)
224             throws JsonSyntaxException, IOException {
225         /** Local handles */
226         int retryCount = 0;
227
228         lastCommunicationInMSecs = System.currentTimeMillis();
229         do {
230             try {
231                 Gson gson = new Gson();
232                 String jsonRequest = gson.toJson(request);
233                 logger.trace("io() to {} using request {}.", url, jsonRequest);
234
235                 Properties headerItems = new Properties();
236                 if (authentication.length() > 0) {
237                     headerItems.setProperty("Authorization", String.format("Bearer %s", authentication));
238                 }
239                 InputStream content = new ByteArrayInputStream(jsonRequest.getBytes(StandardCharsets.UTF_8));
240
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");
245                 }
246                 logger.trace("io(): wait time {} msecs.", this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
247                 // Give the bridge some time to breathe
248                 try {
249                     Thread.sleep(this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
250                 } catch (InterruptedException ie) {
251                     logger.trace("io() wait interrupted.");
252                 }
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();
258                 return response;
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);
265                 try {
266                     Thread.sleep(waitTime);
267                 } catch (InterruptedException ie) {
268                     logger.trace("io() wait interrupted.");
269                 }
270             } catch (JsonSyntaxException jse) {
271                 logger.info("io(): Exception occurred on deserialization: {}, aborting.", jse.getMessage());
272                 throw jse;
273             }
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));
277     }
278
279     /**
280      * Initializes an authenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
281      *
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
287      *            JSON.
288      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
289      *            from JSON.
290      * @return <b>response</b> of type T containing all resulting informations, i.e. device status, errors a.s.o. Will
291      *         return
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.
295      */
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);
299     }
300
301     /**
302      * Initializes an unauthenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
303      *
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
308      *            JSON.
309      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
310      *            from JSON.
311      * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
312      *         Will
313      *         return
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.
317      */
318     private <T, U> T ioUnauthenticated(String url, U request, Class<T> classOfResponse)
319             throws JsonSyntaxException, IOException {
320         return io(url, "", request, classOfResponse);
321     }
322 }