2 * Copyright (c) 2010-2021 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.foobot.internal;
15 import static org.openhab.binding.foobot.internal.FoobotBindingConstants.*;
17 import java.io.UnsupportedEncodingException;
18 import java.lang.reflect.Type;
19 import java.net.URLEncoder;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.eclipse.jetty.client.HttpClient;
31 import org.eclipse.jetty.client.api.ContentResponse;
32 import org.eclipse.jetty.client.api.Request;
33 import org.eclipse.jetty.http.HttpField;
34 import org.eclipse.jetty.http.HttpHeader;
35 import org.eclipse.jetty.http.HttpStatus;
36 import org.openhab.binding.foobot.internal.json.FoobotDevice;
37 import org.openhab.binding.foobot.internal.json.FoobotJsonData;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.gson.Gson;
42 import com.google.gson.JsonParseException;
43 import com.google.gson.reflect.TypeToken;
46 * Connector class communicating with Foobot api and parsing returned json.
48 * @author Hilbrand Bouwkamp - Initial contribution
51 public class FoobotApiConnector {
53 public static final String API_RATE_LIMIT_EXCEEDED_MESSAGE = "Api rate limit exceeded";
54 public static final int API_RATE_LIMIT_EXCEEDED = -2;
56 private static final int UNKNOWN_REMAINING = -1;
57 private static final String HEADER_X_API_KEY_TOKEN = "X-API-KEY-TOKEN";
58 private static final String HEADER_X_API_KEY_LIMIT_REMAINING = "x-api-key-limit-remaining";
59 private static final int REQUEST_TIMEOUT_SECONDS = 3;
60 private static final Gson GSON = new Gson();
61 private static final Type FOOTBOT_DEVICE_LIST_TYPE = new TypeToken<ArrayList<FoobotDevice>>() {
64 private final Logger logger = LoggerFactory.getLogger(FoobotApiConnector.class);
66 private @Nullable HttpClient httpClient;
67 private String apiKey = "";
68 private int apiKeyLimitRemaining = UNKNOWN_REMAINING;
70 public void setHttpClient(@Nullable HttpClient httpClient) {
71 this.httpClient = httpClient;
74 public void setApiKey(String apiKey) {
79 * @return Returns the last known api remaining limit or -1 if not known.
81 public int getApiKeyLimitRemaining() {
82 return apiKeyLimitRemaining;
86 * Retrieves the list of associated devices with the given username from the foobot api.
88 * @param username to get the associated devices for
89 * @return List of devices
90 * @throws FoobotApiException in case there was a problem communicating or parsing the response
92 public synchronized List<FoobotDevice> getAssociatedDevices(String username) throws FoobotApiException {
94 final String url = URL_TO_FETCH_DEVICES.replace("%username%",
95 URLEncoder.encode(username, StandardCharsets.UTF_8.toString()));
96 logger.debug("URL = {}", url);
98 List<FoobotDevice> foobotDevices = GSON.fromJson(request(url, apiKey), FOOTBOT_DEVICE_LIST_TYPE);
99 return Objects.requireNonNull(foobotDevices);
100 } catch (JsonParseException | UnsupportedEncodingException e) {
101 throw new FoobotApiException(0, e.getMessage());
106 * Retrieves the sensor data for the device with the given uuid from the foobot api.
108 * @param uuid of the device to get the sensor data for
109 * @return sensor data of the device
110 * @throws FoobotApiException in case there was a problem communicating or parsing the response
112 public synchronized @Nullable FoobotJsonData getSensorData(String uuid) throws FoobotApiException {
114 final String url = URL_TO_FETCH_SENSOR_DATA.replace("%uuid%",
115 URLEncoder.encode(uuid, StandardCharsets.UTF_8.toString()));
116 logger.debug("URL = {}", url);
118 return GSON.fromJson(request(url, apiKey), FoobotJsonData.class);
119 } catch (JsonParseException | UnsupportedEncodingException e) {
120 throw new FoobotApiException(0, e.getMessage());
124 protected String request(String url, String apiKey) throws FoobotApiException {
125 apiKeyLimitRemaining = UNKNOWN_REMAINING;
126 if (httpClient == null) {
127 logger.debug("No http connection possible: httpClient == null");
128 throw new FoobotApiException(0, "No http connection possible");
130 final Request request = httpClient.newRequest(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
132 request.header(HttpHeader.ACCEPT, "application/json");
133 request.header(HttpHeader.ACCEPT_ENCODING, StandardCharsets.UTF_8.name());
134 request.header(HEADER_X_API_KEY_TOKEN, apiKey);
135 final ContentResponse response;
138 response = request.send();
139 } catch (InterruptedException e) {
140 Thread.currentThread().interrupt();
141 throw new FoobotApiException(0, e.getMessage());
142 } catch (TimeoutException | ExecutionException e) {
143 throw new FoobotApiException(0, e.getMessage());
145 final String content = response.getContentAsString();
147 logger.trace("Foobot content = {}", content);
148 logger.debug("Foobot response = {}", response);
149 setApiKeyLimitRemaining(response);
150 switch (response.getStatus()) {
151 case HttpStatus.FORBIDDEN_403:
152 throw new FoobotApiException(response.getStatus(),
153 "Access denied. Did you set the correct api-key and/or username?");
154 case HttpStatus.TOO_MANY_REQUESTS_429:
155 apiKeyLimitRemaining = API_RATE_LIMIT_EXCEEDED;
156 throw new FoobotApiException(response.getStatus(), API_RATE_LIMIT_EXCEEDED_MESSAGE);
157 case HttpStatus.OK_200:
158 if (content == null || content.isBlank()) {
159 throw new FoobotApiException(0, "No data returned");
163 logger.trace("Foobot returned status '{}', reason: {}, content = {}", response.getStatus(),
164 response.getReason(), content);
165 throw new FoobotApiException(response.getStatus(), response.getReason());
169 private void setApiKeyLimitRemaining(ContentResponse response) {
170 final HttpField field = response.getHeaders().getField(HEADER_X_API_KEY_LIMIT_REMAINING);
173 apiKeyLimitRemaining = field.getIntValue();