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.nanoleaf.internal;
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;
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;
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;
43 * The {@link OpenAPIUtils} offers helper methods to support API communication with the controller
45 * @author Martin Raepple - Initial contribution
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;
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);
62 public static URI getUri(NanoleafControllerConfig controllerConfig, String apiOperation, @Nullable String query)
63 throws NanoleafException {
66 // get network settings from configuration
67 String address = controllerConfig.address;
68 int port = controllerConfig.port;
70 if (API_ADD_USER.equals(apiOperation)) {
71 path = String.format("%s%s", API_V1_BASE_URL, apiOperation);
73 String authToken = controllerConfig.authToken;
74 if (authToken == null) {
75 throw new NanoleafUnauthorizedException("No authentication token found in configuration");
78 path = String.format("%s/%s%s", API_V1_BASE_URL, authToken, apiOperation);
82 URI requestURI = new URI(HttpScheme.HTTP.asString(), (String) null, address, port, path, query,
85 } catch (URISyntaxException var8) {
86 LOGGER.warn("URI could not be parsed with path {}", path);
87 throw new NanoleafException("Wrong URI format for API request");
91 public static ContentResponse sendOpenAPIRequest(Request request) throws NanoleafException {
93 traceSendRequest(request);
94 ContentResponse openAPIResponse = request.send();
95 if (LOGGER.isTraceEnabled()) {
96 LOGGER.trace("API response from Nanoleaf controller: {}", openAPIResponse.getContentAsString());
98 LOGGER.debug("API response code: {}", openAPIResponse.getStatus());
99 int responseStatus = openAPIResponse.getStatus();
100 if (responseStatus != HttpStatus.OK_200 && responseStatus != HttpStatus.NO_CONTENT_204) {
101 if (openAPIResponse.getStatus() == HttpStatus.UNAUTHORIZED_401) {
102 throw new NanoleafUnauthorizedException("OpenAPI request unauthorized");
103 } else if (openAPIResponse.getStatus() == HttpStatus.NOT_FOUND_404) {
104 throw new NanoleafNotFoundException("OpenAPI request did not get any result back");
105 } else if (openAPIResponse.getStatus() == HttpStatus.BAD_REQUEST_400) {
106 throw new NanoleafBadRequestException(
107 String.format("Nanoleaf did not expect this request. HTTP response code %s",
108 openAPIResponse.getStatus()));
110 throw new NanoleafException(String.format("OpenAPI request failed. HTTP response code %s",
111 openAPIResponse.getStatus()));
114 return openAPIResponse;
116 } catch (ExecutionException ee) {
117 Throwable cause = ee.getCause();
118 if (cause != null && cause instanceof HttpResponseException
119 && ((HttpResponseException) cause).getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401) {
120 LOGGER.debug("OpenAPI request unauthorized. Invalid authorization token.");
121 throw new NanoleafUnauthorizedException("Invalid authorization token");
123 throw new NanoleafException("Failed to send OpenAPI request (final)", ee);
125 } catch (TimeoutException te) {
126 LOGGER.debug("OpenAPI request failed with timeout", te);
127 throw new NanoleafException("Failed to send OpenAPI request: Timeout", te);
128 } catch (InterruptedException ie) {
129 throw new NanoleafInterruptedException("OpenAPI request has been interrupted", ie);
133 private static void traceSendRequest(Request request) {
134 if (LOGGER.isTraceEnabled()) {
135 LOGGER.trace("Sending Request {} {}", request.getURI(),
136 request.getQuery() == null ? "no query parameters" : request.getQuery());
137 LOGGER.trace("Request method:{} uri:{} params{}\n", request.getMethod(), request.getURI(),
138 request.getParams());
139 if (request.getContent() != null) {
140 Iterator<ByteBuffer> iter = request.getContent().iterator();
141 while (iter.hasNext()) {
142 ByteBuffer buffer = iter.next();
143 LOGGER.trace("Content {}", StandardCharsets.UTF_8.decode(buffer).toString());
150 public static boolean checkRequiredFirmware(@Nullable String modelId, @Nullable String currentFirmwareVersion) {
151 if (modelId != null && currentFirmwareVersion != null) {
152 int[] currentVer = getFirmwareVersionNumbers(currentFirmwareVersion);
153 int[] requiredVer = getFirmwareVersionNumbers("NL22".equals(modelId) ? "1.5.0" : "1.1.0");
155 for (int i = 0; i < currentVer.length; ++i) {
156 if (currentVer[i] != requiredVer[i]) {
157 return (currentVer[i] > requiredVer[i]);
167 public static int[] getFirmwareVersionNumbers(String firmwareVersion) throws IllegalArgumentException {
168 LOGGER.debug("firmwareVersion: {}", firmwareVersion);
169 Matcher m = FIRMWARE_VERSION_PATTERN.matcher(firmwareVersion);
171 return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
172 Integer.parseInt(m.group(3)) };
174 m = FIRMWARE_VERSION_PATTERN_BETA.matcher(firmwareVersion);
176 return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)),
177 Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)) };
179 throw new IllegalArgumentException("Malformed controller firmware version " + firmwareVersion);