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.ecowatt.internal.restapi;
15 import java.io.IOException;
16 import java.time.Instant;
17 import java.time.OffsetDateTime;
18 import java.time.ZonedDateTime;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.TimeoutException;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentResponse;
26 import org.eclipse.jetty.client.api.Request;
27 import org.eclipse.jetty.http.HttpHeader;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.openhab.binding.ecowatt.internal.exception.EcowattApiLimitException;
31 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
32 import org.openhab.core.auth.client.oauth2.OAuthClientService;
33 import org.openhab.core.auth.client.oauth2.OAuthException;
34 import org.openhab.core.auth.client.oauth2.OAuthFactory;
35 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
36 import org.openhab.core.i18n.CommunicationException;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.gson.Gson;
41 import com.google.gson.GsonBuilder;
42 import com.google.gson.JsonDeserializer;
43 import com.google.gson.JsonSyntaxException;
46 * The {@link EcowattRestApi} is responsible for handling all communication with the Ecowatt REST API
48 * @author Laurent Garnier - Initial contribution
49 * @author Laurent Garnier - Add support for different API versions
52 public class EcowattRestApi {
54 private static final String ECOWATT_API_TOKEN_URL = "https://digital.iservices.rte-france.com/token/oauth/";
55 private static final String ECOWATT_API_GET_SIGNALS_URL = "https://digital.iservices.rte-france.com/open_api/ecowatt/v%d/signals";
57 private final Logger logger = LoggerFactory.getLogger(EcowattRestApi.class);
59 private final OAuthFactory oAuthFactory;
60 private final HttpClient httpClient;
61 private final Gson gson;
62 private final String apiUrl;
63 private OAuthClientService authService;
64 private String authServiceHandle;
66 public EcowattRestApi(OAuthFactory oAuthFactory, HttpClient httpClient, String authServiceHandle, String idClient,
67 String idSecret, int apiVersion) {
68 this.oAuthFactory = oAuthFactory;
69 this.httpClient = httpClient;
70 GsonBuilder gsonBuilder = new GsonBuilder();
71 gson = gsonBuilder.registerTypeAdapter(ZonedDateTime.class,
72 (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> OffsetDateTime
73 .parse(json.getAsJsonPrimitive().getAsString()).toZonedDateTime())
75 this.authService = oAuthFactory.createOAuthClientService(authServiceHandle, ECOWATT_API_TOKEN_URL, null,
76 idClient, idSecret, null, true);
77 this.authServiceHandle = authServiceHandle;
78 this.apiUrl = ECOWATT_API_GET_SIGNALS_URL.formatted(apiVersion);
81 public EcowattApiResponse getSignals() throws CommunicationException, EcowattApiLimitException {
82 logger.debug("API request {}", apiUrl);
83 String token = authenticate().getAccessToken();
85 final Request request = httpClient.newRequest(apiUrl).method(HttpMethod.GET)
86 .header(HttpHeader.AUTHORIZATION, "Bearer " + token).timeout(10, TimeUnit.SECONDS);
88 ContentResponse response;
90 response = request.send();
91 } catch (TimeoutException | ExecutionException e) {
92 throw new CommunicationException("@text/exception.api-request-failed", e);
93 } catch (InterruptedException e) {
94 Thread.currentThread().interrupt();
95 throw new CommunicationException("@text/exception.api-request-failed", e);
98 int statusCode = response.getStatus();
100 logger.trace("API response statusCode={} content={}", statusCode, response.getContentAsString());
102 if (statusCode == HttpStatus.TOO_MANY_REQUESTS_429) {
104 if (response.getHeaders().contains(HttpHeader.RETRY_AFTER)) {
106 retryAfter = Integer.parseInt(response.getHeaders().get(HttpHeader.RETRY_AFTER));
107 } catch (NumberFormatException e) {
110 throw new EcowattApiLimitException(retryAfter, "@text/exception.api-limit-reached");
111 } else if (statusCode != HttpStatus.OK_200) {
112 throw new CommunicationException("@text/exception.api-request-failed-params", statusCode,
113 response.getContentAsString());
117 EcowattApiResponse deserializedResp = gson.fromJson(response.getContentAsString(),
118 EcowattApiResponse.class);
119 if (deserializedResp == null) {
120 throw new CommunicationException("@text/exception.empty-api-response");
122 return deserializedResp;
123 } catch (JsonSyntaxException e) {
124 throw new CommunicationException("@text/exception.parsing-api-response-failed", e);
128 private AccessTokenResponse authenticate() throws CommunicationException {
130 AccessTokenResponse result = authService.getAccessTokenResponse();
131 if (result == null || result.isExpired(Instant.now(), 120)) {
132 logger.debug("Authentication required");
133 result = authService.getAccessTokenByClientCredentials(null);
135 logger.debug("Token {} of type {} created on {} expiring after {} seconds", result.getAccessToken(),
136 result.getTokenType(), result.getCreatedOn(), result.getExpiresIn());
138 } catch (OAuthException | IOException | OAuthResponseException e) {
139 throw new CommunicationException("@text/exception.authentication-failed", e);
143 public void dispose() {
144 oAuthFactory.ungetOAuthService(authServiceHandle);