From 41c5ca0789309a9d66601252acc2b1127ccdc3b0 Mon Sep 17 00:00:00 2001 From: clinique Date: Tue, 5 Mar 2024 14:42:09 +0100 Subject: [PATCH 1/9] Enhance API limit reached handling Signed-off-by: clinique --- .../internal/handler/ApiBridgeHandler.java | 28 ++++++++++++------- .../capability/AlarmEventCapability.java | 1 + .../resources/OH-INF/i18n/netatmo.properties | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index cec105c56712..9d6c791b0d77 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -102,6 +102,7 @@ @NonNullByDefault public class ApiBridgeHandler extends BaseBridgeHandler { private static final int TIMEOUT_S = 20; + private static final int API_LIMIT_INTERVAL_S = 3600; private final Logger logger = LoggerFactory.getLogger(ApiBridgeHandler.class); private final AuthenticationApi connectApi = new AuthenticationApi(this); @@ -210,8 +211,7 @@ private boolean authenticate(@Nullable String code, @Nullable String redirectUri startAuthorizationFlow(); return false; } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - prepareReconnection(code, redirectUri); + prepareReconnection(getConfiguration().reconnectInterval, e.getMessage(), code, redirectUri); return false; } @@ -239,11 +239,13 @@ public ApiHandlerConfiguration getConfiguration() { return getConfigAs(ApiHandlerConfiguration.class); } - private void prepareReconnection(@Nullable String code, @Nullable String redirectUri) { + private void prepareReconnection(int delay, @Nullable String message, @Nullable String code, + @Nullable String redirectUri) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); connectApi.dispose(); freeConnectJob(); - connectJob = Optional.of(scheduler.schedule(() -> openConnection(code, redirectUri), - getConfiguration().reconnectInterval, TimeUnit.SECONDS)); + connectJob = Optional.of(scheduler.schedule(() -> openConnection(code, redirectUri), delay, TimeUnit.SECONDS)); + logger.debug("Reconnection scheduled in {} seconds", delay); } private void freeConnectJob() { @@ -302,12 +304,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, @Nullable String payload, @Nullable String contentType, int retryCount) throws NetatmoException { try { + boolean fail = false; logger.debug("executeUri {} {} ", method.toString(), uri); Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS); if (!authenticate(null, null)) { - prepareReconnection(null, null); + prepareReconnection(getConfiguration().reconnectInterval, null, null, "@text/status-bridge-offline"); throw new NetatmoException("Not authenticated"); } connectApi.getAuthorization().ifPresent(auth -> request.header(HttpHeader.AUTHORIZATION, auth)); @@ -339,6 +342,12 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, String responseBody = new String(response.getContent(), StandardCharsets.UTF_8); logger.trace(" -returned: code {} body {}", statusCode, responseBody); + // DEBUG, TO REMOVE + if (fail) { + statusCode = Code.FORBIDDEN; + responseBody = "{\"error\":{\"code\":26,\"message\":\"User usage reached\"}}"; + } + if (statusCode == Code.OK) { updateStatus(ThingStatus.ONLINE); return deserializer.deserialize(clazz, responseBody); @@ -353,8 +362,8 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, throw exception; } catch (NetatmoException e) { if (e.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/maximum-usage-reached"); - prepareReconnection(null, null); + prepareReconnection(API_LIMIT_INTERVAL_S, + "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } throw e; } catch (InterruptedException e) { @@ -366,8 +375,7 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, logger.debug("Request timedout, retry counter: {}", retryCount); return executeUri(uri, method, clazz, payload, contentType, retryCount - 1); } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/request-time-out"); - prepareReconnection(null, null); + prepareReconnection(getConfiguration().reconnectInterval, "@text/request-time-out", null, null); throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage())); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java index 44a27ccb1b37..239f4f27c30b 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java @@ -49,6 +49,7 @@ protected void updateWebhookEvent(WebhookEvent event) { handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime())); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE, Objects.requireNonNull( event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL))); + final String message = event.getName(); handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE, message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties index 820a91b98e73..10af71ca7c30 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties @@ -465,7 +465,7 @@ device-not-connected = Thing is not reachable data-over-limit = Data seems quite old request-time-out = Request timed out - will attempt reconnection later deserialization-unknown = Deserialization lead to an unknown code -maximum-usage-reached = Maximum usage reached. Will try reconnection after `reconnectInterval` seconds. +maximum-usage-reached = Maximum usage reached, will reconnect in {0} seconds. homestatus-unknown-error = Unknown error homestatus-internal-error = Internal error From 6b791990980bf36ec8bd0696a14d873c4728f48a Mon Sep 17 00:00:00 2001 From: clinique Date: Tue, 5 Mar 2024 14:51:11 +0100 Subject: [PATCH 2/9] Debugging leftower removed Signed-off-by: clinique --- .../binding/netatmo/internal/handler/ApiBridgeHandler.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 9d6c791b0d77..fd458c40ea80 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -304,7 +304,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, @Nullable String payload, @Nullable String contentType, int retryCount) throws NetatmoException { try { - boolean fail = false; logger.debug("executeUri {} {} ", method.toString(), uri); Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS); @@ -342,12 +341,6 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, String responseBody = new String(response.getContent(), StandardCharsets.UTF_8); logger.trace(" -returned: code {} body {}", statusCode, responseBody); - // DEBUG, TO REMOVE - if (fail) { - statusCode = Code.FORBIDDEN; - responseBody = "{\"error\":{\"code\":26,\"message\":\"User usage reached\"}}"; - } - if (statusCode == Code.OK) { updateStatus(ThingStatus.ONLINE); return deserializer.deserialize(clazz, responseBody); From 7438b3a9dd3050a12aeadbed15f9aad715c1d5bd Mon Sep 17 00:00:00 2001 From: clinique Date: Tue, 5 Mar 2024 15:26:22 +0100 Subject: [PATCH 3/9] Improve handling of interrupted request by alllowing retries Signed-off-by: clinique --- .../internal/handler/ApiBridgeHandler.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index fd458c40ea80..732654af8d52 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -359,17 +359,16 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } throw e; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - throw new NetatmoException("Request interrupted"); - } catch (TimeoutException | ExecutionException e) { + } catch (InterruptedException | TimeoutException | ExecutionException e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } if (retryCount > 0) { - logger.debug("Request timedout, retry counter: {}", retryCount); + logger.debug("Request error, retry counter: {}", retryCount); return executeUri(uri, method, clazz, payload, contentType, retryCount - 1); } - prepareReconnection(getConfiguration().reconnectInterval, "@text/request-time-out", null, null); - throw new NetatmoException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage())); + prepareReconnection(getConfiguration().reconnectInterval, "@text/request-time-out", null, e.getMessage()); + throw new NetatmoException("%s: \"%s\"".formatted(e.getClass().getName(), e.getMessage())); } } From 0d00a7718b3900aac17e51ff74b19200cdcb2e80 Mon Sep 17 00:00:00 2001 From: clinique Date: Wed, 6 Mar 2024 21:55:57 +0100 Subject: [PATCH 4/9] Working on InterruptedException Signed-off-by: clinique --- .../binding/netatmo/internal/handler/ApiBridgeHandler.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 732654af8d52..3807e78e4776 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -359,10 +359,11 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } throw e; + // } catch (InterruptedException e) { + // Thread.currentThread().interrupt(); + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + // throw new NetatmoException("Request interrupted"); } catch (InterruptedException | TimeoutException | ExecutionException e) { - if (e instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } if (retryCount > 0) { logger.debug("Request error, retry counter: {}", retryCount); return executeUri(uri, method, clazz, payload, contentType, retryCount - 1); From 57b6a7e7ea09e19cb007c0f16a112cf1e5454a88 Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Mon, 25 Mar 2024 10:02:08 +0100 Subject: [PATCH 5/9] Improve handling of interrupted request by alllowing retries Rebased Signed-off-by: clinique Signed-off-by: gael@lhopital.org --- .../binding/netatmo/internal/handler/ApiBridgeHandler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 3807e78e4776..3c67797f1a78 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -359,10 +359,6 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } throw e; - // } catch (InterruptedException e) { - // Thread.currentThread().interrupt(); - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - // throw new NetatmoException("Request interrupted"); } catch (InterruptedException | TimeoutException | ExecutionException e) { if (retryCount > 0) { logger.debug("Request error, retry counter: {}", retryCount); From be652a13be26f899db21cc2c9cbb7c95ba856e1c Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Mon, 25 Mar 2024 17:20:19 +0100 Subject: [PATCH 6/9] Improve handling of interrupted request by alllowing retries Rebased Reintroducing TOO_MANY_REQUESTS Signed-off-by: clinique Signed-off-by: gael@lhopital.org --- .../netatmo/internal/handler/ApiBridgeHandler.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 3c67797f1a78..66ae5bc868eb 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -350,15 +350,19 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, try { exception = new NetatmoException(deserializer.deserialize(ApiError.class, responseBody)); } catch (NetatmoException e) { - exception = new NetatmoException("Error deserializing error: %s".formatted(statusCode.getMessage())); + if (statusCode == Code.TOO_MANY_REQUESTS) { + exception = new NetatmoException(statusCode.getMessage()); + } else { + exception = new NetatmoException( + "Error deserializing error: %s".formatted(statusCode.getMessage())); + } } - throw exception; - } catch (NetatmoException e) { - if (e.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) { + if (statusCode == Code.TOO_MANY_REQUESTS + || exception.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) { prepareReconnection(API_LIMIT_INTERVAL_S, "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } - throw e; + throw exception; } catch (InterruptedException | TimeoutException | ExecutionException e) { if (retryCount > 0) { logger.debug("Request error, retry counter: {}", retryCount); From 6ef1549997f41a14267559464cf52c61efc3423a Mon Sep 17 00:00:00 2001 From: "gael@lhopital.org" Date: Fri, 29 Mar 2024 10:59:02 +0100 Subject: [PATCH 7/9] Improve handling of interrupted request by alllowing retries Rebased Reintroducing TOO_MANY_REQUESTS Rebased Signed-off-by: clinique Signed-off-by: gael@lhopital.org --- .../netatmo/internal/handler/capability/CameraCapability.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java index 398bd9a87648..acd77dea35b2 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java @@ -73,7 +73,7 @@ public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider desc List channelHelpers) { super(handler, descriptionProvider, channelHelpers); this.personChannelUID = new ChannelUID(thingUID, GROUP_LAST_EVENT, CHANNEL_EVENT_PERSON_ID); - this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(c -> c instanceof CameraChannelHelper) + this.cameraHelper = (CameraChannelHelper) channelHelpers.stream().filter(CameraChannelHelper.class::isInstance) .findFirst().orElseThrow(() -> new IllegalArgumentException( "CameraCapability must find a CameraChannelHelper, please file a bug report.")); } From 09cd57efa22114a81a7a6c87863eae0d31932b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Wed, 25 Sep 2024 10:25:38 +0200 Subject: [PATCH 8/9] Resolving conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../binding/netatmo/internal/NetatmoBindingConstants.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java index f7d9a2972b54..bf034a98e0e7 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java @@ -25,7 +25,6 @@ */ @NonNullByDefault public class NetatmoBindingConstants { - public static final String BINDING_ID = "netatmo"; public static final String VENDOR = "Netatmo"; From 5747ec81f6ea500954ec0d52daa41c662dc82ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Thu, 26 Sep 2024 18:19:25 +0200 Subject: [PATCH 9/9] Code review corrections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../internal/api/AuthenticationApi.java | 5 +- .../config/ApiHandlerConfiguration.java | 9 +++ .../internal/handler/ApiBridgeHandler.java | 69 +++++++++---------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java index 638c8c988244..1c11443bea7f 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/AuthenticationApi.java @@ -15,7 +15,6 @@ import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; import static org.openhab.core.auth.oauth2client.internal.Keyword.*; -import java.net.URI; import java.util.List; import java.util.Optional; import java.util.Set; @@ -37,8 +36,8 @@ */ @NonNullByDefault public class AuthenticationApi extends RestManager { - public static final URI TOKEN_URI = getApiBaseBuilder(PATH_OAUTH, SUB_PATH_TOKEN).build(); - public static final URI AUTH_URI = getApiBaseBuilder(PATH_OAUTH, SUB_PATH_AUTHORIZE).build(); + public static final String TOKEN_URI = getApiBaseBuilder(PATH_OAUTH, SUB_PATH_TOKEN).build().toString(); + public static final String AUTH_URI = getApiBaseBuilder(PATH_OAUTH, SUB_PATH_AUTHORIZE).build().toString(); private List grantedScope = List.of(); private @Nullable String authorization; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java index 754b4aa0cc02..6e5b3be1a694 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/config/ApiHandlerConfiguration.java @@ -29,4 +29,13 @@ public class ApiHandlerConfiguration { public String webHookUrl = ""; public String webHookPostfix = ""; public int reconnectInterval = 300; + + public ConfigurationLevel check() { + if (clientId.isBlank()) { + return ConfigurationLevel.EMPTY_CLIENT_ID; + } else if (clientSecret.isBlank()) { + return ConfigurationLevel.EMPTY_CLIENT_SECRET; + } + return ConfigurationLevel.COMPLETED; + } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 66ae5bc868eb..16eda6a48ab8 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -18,7 +18,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Constructor; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; @@ -35,6 +34,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -128,8 +128,7 @@ public ApiBridgeHandler(Bridge bridge, HttpClient httpClient, NADeserializer des this.deserializer = deserializer; this.httpService = httpService; this.oAuthFactory = oAuthFactory; - - requestCountChannelUID = new ChannelUID(thing.getUID(), GROUP_MONITORING, CHANNEL_REQUEST_COUNT); + this.requestCountChannelUID = new ChannelUID(thing.getUID(), GROUP_MONITORING, CHANNEL_REQUEST_COUNT); } @Override @@ -138,22 +137,16 @@ public void initialize() { ApiHandlerConfiguration configuration = getConfiguration(); - if (configuration.clientId.isBlank()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - ConfigurationLevel.EMPTY_CLIENT_ID.message); - return; - } - - if (configuration.clientSecret.isBlank()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - ConfigurationLevel.EMPTY_CLIENT_SECRET.message); + ConfigurationLevel confLevel = configuration.check(); + if (!ConfigurationLevel.COMPLETED.equals(confLevel)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, confLevel.message); return; } oAuthClientService = oAuthFactory - .createOAuthClientService(this.getThing().getUID().getAsString(), - AuthenticationApi.TOKEN_URI.toString(), AuthenticationApi.AUTH_URI.toString(), - configuration.clientId, configuration.clientSecret, FeatureArea.ALL_SCOPES, false) + .createOAuthClientService(this.getThing().getUID().getAsString(), AuthenticationApi.TOKEN_URI, + AuthenticationApi.AUTH_URI, configuration.clientId, configuration.clientSecret, + FeatureArea.ALL_SCOPES, false) .withGsonBuilder(new GsonBuilder().registerTypeAdapter(AccessTokenResponse.class, new AccessTokenResponseDeserializer())); @@ -170,15 +163,13 @@ public void openConnection(@Nullable String code, @Nullable String redirectUri) logger.debug("Connecting to Netatmo API."); ApiHandlerConfiguration configuration = getConfiguration(); - if (!configuration.webHookUrl.isBlank()) { - SecurityApi securityApi = getRestManager(SecurityApi.class); - if (securityApi != null) { - webHookServlet.ifPresent(servlet -> servlet.dispose()); - WebhookServlet servlet = new WebhookServlet(this, httpService, deserializer, securityApi, - configuration.webHookUrl, configuration.webHookPostfix); - servlet.startListening(); - this.webHookServlet = Optional.of(servlet); - } + if (!configuration.webHookUrl.isBlank() + && getRestManager(SecurityApi.class) instanceof SecurityApi securityApi) { + webHookServlet.ifPresent(servlet -> servlet.dispose()); + WebhookServlet servlet = new WebhookServlet(this, httpService, deserializer, securityApi, + configuration.webHookUrl, configuration.webHookPostfix); + servlet.startListening(); + this.webHookServlet = Optional.of(servlet); } updateStatus(ThingStatus.ONLINE); @@ -201,8 +192,7 @@ private boolean authenticate(@Nullable String code, @Nullable String redirectUri accessTokenResponse = oAuthClientService.getAccessTokenResponseByAuthorizationCode(code, redirectUri); // Dispose grant servlet upon completion of authorization flow. - grantServlet.ifPresent(servlet -> servlet.dispose()); - grantServlet = Optional.empty(); + freeGrantServlet(); } else { accessTokenResponse = oAuthClientService.getAccessTokenResponse(); } @@ -241,7 +231,9 @@ public ApiHandlerConfiguration getConfiguration() { private void prepareReconnection(int delay, @Nullable String message, @Nullable String code, @Nullable String redirectUri) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); + if (!ThingStatus.OFFLINE.equals(thing.getStatus())) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); + } connectApi.dispose(); freeConnectJob(); connectJob = Optional.of(scheduler.schedule(() -> openConnection(code, redirectUri), delay, TimeUnit.SECONDS)); @@ -253,6 +245,11 @@ private void freeConnectJob() { connectJob = Optional.empty(); } + private void freeGrantServlet() { + grantServlet.ifPresent(servlet -> servlet.dispose()); + grantServlet = Optional.empty(); + } + @Override public void dispose() { logger.debug("Shutting down Netatmo API bridge handler."); @@ -260,8 +257,7 @@ public void dispose() { webHookServlet.ifPresent(servlet -> servlet.dispose()); webHookServlet = Optional.empty(); - grantServlet.ifPresent(servlet -> servlet.dispose()); - grantServlet = Optional.empty(); + freeGrantServlet(); connectApi.dispose(); freeConnectJob(); @@ -286,13 +282,12 @@ public void handleCommand(ChannelUID channelUID, Command command) { public @Nullable T getRestManager(Class clazz) { if (!managers.containsKey(clazz)) { try { - Constructor constructor = clazz.getConstructor(getClass()); - T instance = constructor.newInstance(this); + T instance = clazz.getConstructor(getClass()).newInstance(this); Set expected = instance.getRequiredScopes(); if (connectApi.matchesScopes(expected)) { managers.put(clazz, instance); } else { - logger.info("Unable to instantiate {}, expected scope {} is not active", clazz, expected); + logger.warn("Unable to instantiate {}, expected scope {} is not active", clazz, expected); } } catch (SecurityException | ReflectiveOperationException e) { logger.warn("Error invoking RestManager constructor for class {}: {}", clazz, e.getMessage()); @@ -309,7 +304,7 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, Request request = httpClient.newRequest(uri).method(method).timeout(TIMEOUT_S, TimeUnit.SECONDS); if (!authenticate(null, null)) { - prepareReconnection(getConfiguration().reconnectInterval, null, null, "@text/status-bridge-offline"); + prepareReconnection(getConfiguration().reconnectInterval, "@text/status-bridge-offline", null, null); throw new NetatmoException("Not authenticated"); } connectApi.getAuthorization().ifPresent(auth -> request.header(HttpHeader.AUTHORIZATION, auth)); @@ -319,7 +314,7 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)); try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) { request.content(inputStreamContentProvider, contentType); - request.header(HttpHeader.ACCEPT, "application/json"); + request.header(HttpHeader.ACCEPT, MediaType.APPLICATION_JSON); } logger.trace(" -with payload: {} ", payload); } @@ -363,7 +358,11 @@ public synchronized T executeUri(URI uri, HttpMethod method, Class clazz, "@text/maximum-usage-reached [ \"%d\" ]".formatted(API_LIMIT_INTERVAL_S), null, null); } throw exception; - } catch (InterruptedException | TimeoutException | ExecutionException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + throw new NetatmoException(e, "Request interrupted"); + } catch (TimeoutException | ExecutionException e) { if (retryCount > 0) { logger.debug("Request error, retry counter: {}", retryCount); return executeUri(uri, method, clazz, payload, contentType, retryCount - 1);