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
51 public class EcowattRestApi {
53 private static final String ECOWATT_API_TOKEN_URL = "https://digital.iservices.rte-france.com/token/oauth/";
54 private static final String ECOWATT_API_GET_SIGNALS_URL = "https://digital.iservices.rte-france.com/open_api/ecowatt/v4/signals";
56 private final Logger logger = LoggerFactory.getLogger(EcowattRestApi.class);
58 private final OAuthFactory oAuthFactory;
59 private final HttpClient httpClient;
60 private final Gson gson;
61 private OAuthClientService authService;
62 private String authServiceHandle;
64 public EcowattRestApi(OAuthFactory oAuthFactory, HttpClient httpClient, String authServiceHandle, String idClient,
66 this.oAuthFactory = oAuthFactory;
67 this.httpClient = httpClient;
68 GsonBuilder gsonBuilder = new GsonBuilder();
69 gson = gsonBuilder.registerTypeAdapter(ZonedDateTime.class,
70 (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> OffsetDateTime
71 .parse(json.getAsJsonPrimitive().getAsString()).toZonedDateTime())
73 this.authService = oAuthFactory.createOAuthClientService(authServiceHandle, ECOWATT_API_TOKEN_URL, null,
74 idClient, idSecret, null, true);
75 this.authServiceHandle = authServiceHandle;
78 public EcowattApiResponse getSignals() throws CommunicationException, EcowattApiLimitException {
79 logger.debug("API request signals");
80 String token = authenticate().getAccessToken();
82 final Request request = httpClient.newRequest(ECOWATT_API_GET_SIGNALS_URL).method(HttpMethod.GET)
83 .header(HttpHeader.AUTHORIZATION, "Bearer " + token).timeout(10, TimeUnit.SECONDS);
85 ContentResponse response;
87 response = request.send();
88 } catch (TimeoutException | ExecutionException e) {
89 throw new CommunicationException("@text/exception.api-request-failed", e);
90 } catch (InterruptedException e) {
91 Thread.currentThread().interrupt();
92 throw new CommunicationException("@text/exception.api-request-failed", e);
95 int statusCode = response.getStatus();
97 logger.trace("API response statusCode={} content={}", statusCode, response.getContentAsString());
99 if (statusCode == HttpStatus.TOO_MANY_REQUESTS_429) {
101 if (response.getHeaders().contains(HttpHeader.RETRY_AFTER)) {
103 retryAfter = Integer.parseInt(response.getHeaders().get(HttpHeader.RETRY_AFTER));
104 } catch (NumberFormatException e) {
107 throw new EcowattApiLimitException(retryAfter, "@text/exception.api-limit-reached");
108 } else if (statusCode != HttpStatus.OK_200) {
109 throw new CommunicationException("@text/exception.api-request-failed-params", statusCode,
110 response.getContentAsString());
114 EcowattApiResponse deserializedResp = gson.fromJson(response.getContentAsString(),
115 EcowattApiResponse.class);
116 if (deserializedResp == null) {
117 throw new CommunicationException("@text/exception.empty-api-response");
119 return deserializedResp;
120 } catch (JsonSyntaxException e) {
121 throw new CommunicationException("@text/exception.parsing-api-response-failed", e);
125 private AccessTokenResponse authenticate() throws CommunicationException {
127 AccessTokenResponse result = authService.getAccessTokenResponse();
128 if (result == null || result.isExpired(Instant.now(), 120)) {
129 logger.debug("Authentication required");
130 result = authService.getAccessTokenByClientCredentials(null);
132 logger.debug("Token {} of type {} created on {} expiring after {} seconds", result.getAccessToken(),
133 result.getTokenType(), result.getCreatedOn(), result.getExpiresIn());
135 } catch (OAuthException | IOException | OAuthResponseException e) {
136 throw new CommunicationException("@text/exception.authentication-failed", e);
140 public void dispose() {
141 oAuthFactory.ungetOAuthService(authServiceHandle);
144 public void deleteServiceAndAccessToken() {
145 oAuthFactory.deleteServiceAndAccessToken(authServiceHandle);