]> git.basschouten.com Git - openhab-addons.git/blob
8cf00f440e82c5a1eb3ca8b281cb0015f8b0c7ed
[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.nanoleaf.internal;
14
15 import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.API_ADD_USER;
16 import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.API_V1_BASE_URL;
17
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.nio.ByteBuffer;
21 import java.nio.charset.StandardCharsets;
22 import java.util.Iterator;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.TimeoutException;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.eclipse.jetty.client.HttpResponseException;
33 import org.eclipse.jetty.client.api.ContentResponse;
34 import org.eclipse.jetty.client.api.Request;
35 import org.eclipse.jetty.http.HttpMethod;
36 import org.eclipse.jetty.http.HttpScheme;
37 import org.eclipse.jetty.http.HttpStatus;
38 import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link OpenAPIUtils} offers helper methods to support API communication with the controller
44  *
45  * @author Martin Raepple - Initial contribution
46  */
47 @NonNullByDefault
48 public class OpenAPIUtils {
49     private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIUtils.class);
50     private static final Pattern FIRMWARE_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)");
51     private static final Pattern FIRMWARE_VERSION_PATTERN_BETA = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)-(\\d+)");
52     private static final long CONNECT_TIMEOUT = 10L;
53
54     public static Request requestBuilder(HttpClient httpClient, NanoleafControllerConfig controllerConfig,
55             String apiOperation, HttpMethod method) throws NanoleafException {
56         URI requestURI = getUri(controllerConfig, apiOperation, null);
57         LOGGER.trace("RequestBuilder: Sending Request {}:{} {} \n op: {}  method: {}", new Object[] {
58                 requestURI.getHost(), requestURI.getPort(), requestURI.getPath(), apiOperation, method.toString() });
59         return httpClient.newRequest(requestURI).method(method).timeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
60     }
61
62     public static URI getUri(NanoleafControllerConfig controllerConfig, String apiOperation, @Nullable String query)
63             throws NanoleafException {
64         String path;
65
66         // get network settings from configuration
67         String address = controllerConfig.address;
68         int port = controllerConfig.port;
69
70         if (API_ADD_USER.equals(apiOperation)) {
71             path = String.format("%s%s", API_V1_BASE_URL, apiOperation);
72         } else {
73             String authToken = controllerConfig.authToken;
74             if (authToken == null) {
75                 throw new NanoleafUnauthorizedException("No authentication token found in configuration");
76             }
77
78             path = String.format("%s/%s%s", API_V1_BASE_URL, authToken, apiOperation);
79         }
80
81         try {
82             return new URI(HttpScheme.HTTP.asString(), (String) null, address, port, path, query, (String) null);
83         } catch (URISyntaxException var8) {
84             LOGGER.warn("URI could not be parsed with path {}", path);
85             throw new NanoleafException("Wrong URI format for API request");
86         }
87     }
88
89     public static ContentResponse sendOpenAPIRequest(Request request) throws NanoleafException {
90         try {
91             traceSendRequest(request);
92             ContentResponse openAPIResponse = request.send();
93             if (LOGGER.isTraceEnabled()) {
94                 LOGGER.trace("API response from Nanoleaf controller: {}", openAPIResponse.getContentAsString());
95             }
96             LOGGER.debug("API response code: {}", openAPIResponse.getStatus());
97             int responseStatus = openAPIResponse.getStatus();
98             if (responseStatus != HttpStatus.OK_200 && responseStatus != HttpStatus.NO_CONTENT_204) {
99                 if (openAPIResponse.getStatus() == HttpStatus.UNAUTHORIZED_401) {
100                     throw new NanoleafUnauthorizedException("OpenAPI request unauthorized");
101                 } else if (openAPIResponse.getStatus() == HttpStatus.NOT_FOUND_404) {
102                     throw new NanoleafNotFoundException("OpenAPI request did not get any result back");
103                 } else if (openAPIResponse.getStatus() == HttpStatus.BAD_REQUEST_400) {
104                     throw new NanoleafBadRequestException(
105                             String.format("Nanoleaf did not expect this request. HTTP response code %s",
106                                     openAPIResponse.getStatus()));
107                 } else {
108                     throw new NanoleafException(String.format("OpenAPI request failed. HTTP response code %s",
109                             openAPIResponse.getStatus()));
110                 }
111             } else {
112                 return openAPIResponse;
113             }
114         } catch (ExecutionException ee) {
115             Throwable cause = ee.getCause();
116             if (cause instanceof HttpResponseException httpResponseException
117                     && httpResponseException.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401) {
118                 LOGGER.debug("OpenAPI request unauthorized. Invalid authorization token.");
119                 throw new NanoleafUnauthorizedException("Invalid authorization token");
120             } else {
121                 throw new NanoleafException("Failed to send OpenAPI request (final)", ee);
122             }
123         } catch (TimeoutException te) {
124             LOGGER.debug("OpenAPI request failed with timeout", te);
125             throw new NanoleafException("Failed to send OpenAPI request: Timeout", te);
126         } catch (InterruptedException ie) {
127             throw new NanoleafInterruptedException("OpenAPI request has been interrupted", ie);
128         }
129     }
130
131     private static void traceSendRequest(Request request) {
132         if (LOGGER.isTraceEnabled()) {
133             LOGGER.trace("Sending Request {} {}", request.getURI(),
134                     request.getQuery() == null ? "no query parameters" : request.getQuery());
135             LOGGER.trace("Request method:{} uri:{} params{}\n", request.getMethod(), request.getURI(),
136                     request.getParams());
137             if (request.getContent() != null) {
138                 Iterator<ByteBuffer> iter = request.getContent().iterator();
139                 while (iter.hasNext()) {
140                     ByteBuffer buffer = iter.next();
141                     LOGGER.trace("Content {}", StandardCharsets.UTF_8.decode(buffer).toString());
142                 }
143             }
144
145         }
146     }
147
148     public static boolean checkRequiredFirmware(@Nullable String modelId, @Nullable String currentFirmwareVersion) {
149         if (modelId != null && currentFirmwareVersion != null) {
150             int[] currentVer = getFirmwareVersionNumbers(currentFirmwareVersion);
151             int[] requiredVer = getFirmwareVersionNumbers("NL22".equals(modelId) ? "1.5.0" : "1.1.0");
152
153             for (int i = 0; i < currentVer.length; ++i) {
154                 if (currentVer[i] != requiredVer[i]) {
155                     return (currentVer[i] > requiredVer[i]);
156                 }
157             }
158
159             return true;
160         } else {
161             return false;
162         }
163     }
164
165     public static int[] getFirmwareVersionNumbers(String firmwareVersion) throws IllegalArgumentException {
166         LOGGER.debug("firmwareVersion: {}", firmwareVersion);
167         Matcher m = FIRMWARE_VERSION_PATTERN.matcher(firmwareVersion);
168         if (m.matches()) {
169             return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
170                     Integer.parseInt(m.group(3)) };
171         } else {
172             m = FIRMWARE_VERSION_PATTERN_BETA.matcher(firmwareVersion);
173             if (m.matches()) {
174                 return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
175                         Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)) };
176             } else {
177                 throw new IllegalArgumentException("Malformed controller firmware version " + firmwareVersion);
178             }
179         }
180     }
181 }