]> git.basschouten.com Git - openhab-addons.git/commitdiff
[OpenUV] Correcting representation property (#8761)
authorGaël L'hopital <gael@lhopital.org>
Thu, 29 Oct 2020 21:49:16 +0000 (22:49 +0100)
committerGitHub <noreply@github.com>
Thu, 29 Oct 2020 21:49:16 +0000 (14:49 -0700)
* [OpenUV] Correcting representation property
Removing org.apache.commons.stringutils usage
* Corrected
* While on it, reviewed completely the binding.
* spotless applied
* Code review
* Code review enhancement

Signed-off-by: clinique <gael@lhopital.org>
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/OpenUVBindingConstants.java
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/OpenUVException.java [new file with mode: 0644]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/OpenUVHandlerFactory.java
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/ReportConfiguration.java [deleted file]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/SafeExposureConfiguration.java [deleted file]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/BridgeConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/ReportConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/SafeExposureConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/discovery/OpenUVDiscoveryService.java
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/handler/OpenUVBridgeHandler.java
bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/handler/OpenUVReportHandler.java

index 4a1e887fe0f84d9017d14ac073f26fbe5699b5a6..9383e8709799a3e8125d601dc7a581881e8854fd 100644 (file)
@@ -28,13 +28,9 @@ import org.openhab.core.thing.ThingTypeUID;
  */
 @NonNullByDefault
 public class OpenUVBindingConstants {
-    public static final String BASE_URL = "https://api.openuv.io/api/v1/uv";
     public static final String BINDING_ID = "openuv";
     public static final String LOCAL = "local";
 
-    public static final String LOCATION = "location";
-    public static final String APIKEY = "apikey";
-
     // List of Bridge Type UIDs
     public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "openuvapi");
 
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/OpenUVException.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/OpenUVException.java
new file mode 100644 (file)
index 0000000..e51aeb4
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openuv.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Will be thrown for cloud errors
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class OpenUVException extends Exception {
+    private static final long serialVersionUID = -1411477662081482350L;
+    private static final String ERROR_QUOTA_EXCEEDED = "Daily API quota exceeded";
+    private static final String ERROR_WRONG_KEY = "User with API Key not found";
+
+    public OpenUVException(String message) {
+        super(message);
+    }
+
+    public boolean isApiKeyError() {
+        return this.getMessage().startsWith(ERROR_WRONG_KEY);
+    }
+
+    public boolean isQuotaError() {
+        return this.getMessage().startsWith(ERROR_QUOTA_EXCEEDED);
+    }
+}
index c498e14d1233a18ceb01f4fd1b40693caa20db6d..3e66d08db83c8bcc24d67f7e4425d06a8dd32fc5 100644 (file)
@@ -14,15 +14,15 @@ package org.openhab.binding.openuv.internal;
 
 import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
 
-import java.util.Hashtable;
+import java.time.ZonedDateTime;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openuv.internal.discovery.OpenUVDiscoveryService;
 import org.openhab.binding.openuv.internal.handler.OpenUVBridgeHandler;
 import org.openhab.binding.openuv.internal.handler.OpenUVReportHandler;
-import org.openhab.core.config.discovery.DiscoveryService;
 import org.openhab.core.i18n.LocationProvider;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
@@ -33,6 +33,11 @@ import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+
 /**
  * The {@link OpenUVHandlerFactory} is responsible for creating things and thing
  * handlers.
@@ -44,10 +49,21 @@ import org.osgi.service.component.annotations.Reference;
 public class OpenUVHandlerFactory extends BaseThingHandlerFactory {
 
     private final LocationProvider locationProvider;
+    private final Gson gson;
 
     @Activate
-    public OpenUVHandlerFactory(@Reference LocationProvider locationProvider) {
+    public OpenUVHandlerFactory(@Reference TimeZoneProvider timeZoneProvider,
+            @Reference LocationProvider locationProvider) {
         this.locationProvider = locationProvider;
+        this.gson = new GsonBuilder()
+                .registerTypeAdapter(DecimalType.class,
+                        (JsonDeserializer<DecimalType>) (json, type, jsonDeserializationContext) -> DecimalType
+                                .valueOf(json.getAsJsonPrimitive().getAsString()))
+                .registerTypeAdapter(ZonedDateTime.class,
+                        (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
+                                .parse(json.getAsJsonPrimitive().getAsString())
+                                .withZoneSameInstant(timeZoneProvider.getTimeZone()))
+                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
     }
 
     @Override
@@ -59,20 +75,8 @@ public class OpenUVHandlerFactory extends BaseThingHandlerFactory {
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
-        if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
-            OpenUVBridgeHandler handler = new OpenUVBridgeHandler((Bridge) thing);
-            registerOpenUVDiscoveryService(handler);
-            return handler;
-        } else if (LOCATION_REPORT_THING_TYPE.equals(thingTypeUID)) {
-            return new OpenUVReportHandler(thing);
-        }
-
-        return null;
-    }
-
-    private void registerOpenUVDiscoveryService(OpenUVBridgeHandler bridgeHandler) {
-        OpenUVDiscoveryService discoveryService = new OpenUVDiscoveryService(bridgeHandler, locationProvider);
-        bridgeHandler.getDiscoveryServiceRegs().put(bridgeHandler.getThing().getUID(),
-                bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
+        return APIBRIDGE_THING_TYPE.equals(thingTypeUID)
+                ? new OpenUVBridgeHandler((Bridge) thing, locationProvider, gson)
+                : LOCATION_REPORT_THING_TYPE.equals(thingTypeUID) ? new OpenUVReportHandler(thing) : null;
     }
 }
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/ReportConfiguration.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/ReportConfiguration.java
deleted file mode 100644 (file)
index 00d933b..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openuv.internal;
-
-/**
- * The {@link ReportConfiguration} is the class used to match the
- * thing configuration.
- *
- * @author Gaël L"hopital - Initial contribution
- */
-public class ReportConfiguration {
-    String[] elements = null;
-
-    private String location;
-    public Integer refresh;
-
-    public String getLatitude() {
-        return getElement(0);
-    }
-
-    public String getLongitude() {
-        return getElement(1);
-    }
-
-    public String getAltitude() {
-        return getElement(2);
-    }
-
-    private String getElement(int index) {
-        if (elements == null) {
-            elements = location.split(",");
-        }
-        if (index < elements.length) {
-            return elements[index].trim();
-        } else {
-            return null;
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/SafeExposureConfiguration.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/SafeExposureConfiguration.java
deleted file mode 100644 (file)
index 5a4514c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openuv.internal;
-
-/**
- * The {@link SafeExposureConfiguration} is the class used to match the
- * SafeExposure channel configuration.
- *
- * @author Gaël L"hopital - Initial contribution
- */
-public class SafeExposureConfiguration {
-    public int index = -1;
-}
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/BridgeConfiguration.java
new file mode 100644 (file)
index 0000000..745603b
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openuv.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link BridgeConfiguration} is the class used to match the
+ * bridge configuration.
+ *
+ * @author Gaël L"hopital - Initial contribution
+ */
+@NonNullByDefault
+public class BridgeConfiguration {
+    public String apikey = "";
+}
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/ReportConfiguration.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/ReportConfiguration.java
new file mode 100644 (file)
index 0000000..56ac872
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openuv.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ReportConfiguration} is the class used to match the
+ * thing configuration.
+ *
+ * @author Gaël L"hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ReportConfiguration {
+    public static final String LOCATION = "location";
+
+    public int refresh = 10;
+    public String location = "";
+}
diff --git a/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/SafeExposureConfiguration.java b/bundles/org.openhab.binding.openuv/src/main/java/org/openhab/binding/openuv/internal/config/SafeExposureConfiguration.java
new file mode 100644 (file)
index 0000000..a78eb3f
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openuv.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SafeExposureConfiguration} is the class used to match the
+ * SafeExposure channel configuration.
+ *
+ * @author Gaël L"hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SafeExposureConfiguration {
+    public int index = -1;
+}
index 4494c4e52125ef7ba284dfe9999db06494ec04db..25e595e2186fe0ef220a6bfe710f3ec91e8c78c9 100644 (file)
@@ -14,21 +14,16 @@ package org.openhab.binding.openuv.internal.discovery;
 
 import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openuv.internal.config.ReportConfiguration;
 import org.openhab.binding.openuv.internal.handler.OpenUVBridgeHandler;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.i18n.LocationProvider;
 import org.openhab.core.library.types.PointType;
 import org.openhab.core.thing.ThingUID;
-import org.osgi.service.component.annotations.Modified;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,83 +33,54 @@ import org.slf4j.LoggerFactory;
  * @author Gaël L'hopital - Initial Contribution
  */
 @NonNullByDefault
-public class OpenUVDiscoveryService extends AbstractDiscoveryService {
+public class OpenUVDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
     private final Logger logger = LoggerFactory.getLogger(OpenUVDiscoveryService.class);
 
-    private static final int DISCOVER_TIMEOUT_SECONDS = 10;
-    private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
+    private static final int DISCOVER_TIMEOUT_SECONDS = 2;
 
-    private final LocationProvider locationProvider;
-    private final OpenUVBridgeHandler bridgeHandler;
-    private @Nullable ScheduledFuture<?> discoveryJob;
-    private @Nullable PointType previousLocation;
+    private @Nullable OpenUVBridgeHandler bridgeHandler;
 
     /**
      * Creates a OpenUVDiscoveryService with enabled autostart.
      */
-    public OpenUVDiscoveryService(OpenUVBridgeHandler bridgeHandler, LocationProvider locationProvider) {
-        super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true);
-        this.locationProvider = locationProvider;
-        this.bridgeHandler = bridgeHandler;
-    }
-
-    @Override
-    protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
-        super.activate(configProperties);
-    }
-
-    @Override
-    @Modified
-    protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
-        super.modified(configProperties);
+    public OpenUVDiscoveryService() {
+        super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS);
     }
 
     @Override
-    protected void startScan() {
-        logger.debug("Starting OpenUV discovery scan");
-        PointType location = locationProvider.getLocation();
-        if (location == null) {
-            logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
-            return;
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof OpenUVBridgeHandler) {
+            this.bridgeHandler = (OpenUVBridgeHandler) handler;
         }
-        createResults(location);
     }
 
     @Override
-    protected void startBackgroundDiscovery() {
-        if (discoveryJob == null) {
-            discoveryJob = scheduler.scheduleWithFixedDelay(() -> {
-                PointType currentLocation = locationProvider.getLocation();
-                if (currentLocation != null && !Objects.equals(currentLocation, previousLocation)) {
-                    logger.debug("Location has been changed from {} to {}: Creating new discovery results",
-                            previousLocation, currentLocation);
-                    createResults(currentLocation);
-                    previousLocation = currentLocation;
-                }
-            }, 0, LOCATION_CHANGED_CHECK_INTERVAL, TimeUnit.SECONDS);
-            logger.debug("Scheduled OpenUV-changed job every {} seconds", LOCATION_CHANGED_CHECK_INTERVAL);
-        }
+    public @Nullable ThingHandler getThingHandler() {
+        return bridgeHandler;
     }
 
-    public void createResults(PointType location) {
-        ThingUID bridgeUID = bridgeHandler.getThing().getUID();
-        ThingUID localOpenUVThing = new ThingUID(LOCATION_REPORT_THING_TYPE, bridgeUID, LOCAL);
-        Map<String, Object> properties = new HashMap<>();
-        properties.put(LOCATION, location.toString());
-        thingDiscovered(DiscoveryResultBuilder.create(localOpenUVThing).withLabel("Local UV Information")
-                .withProperties(properties).withRepresentationProperty(location.toString()).withBridge(bridgeUID)
-                .build());
+    @Override
+    public void deactivate() {
+        super.deactivate();
     }
 
-    @SuppressWarnings("null")
     @Override
-    protected void stopBackgroundDiscovery() {
-        logger.debug("Stopping OpenUV background discovery");
-        if (discoveryJob != null && !discoveryJob.isCancelled()) {
-            if (discoveryJob.cancel(true)) {
-                discoveryJob = null;
-                logger.debug("Stopped OpenUV background discovery");
+    protected void startScan() {
+        logger.debug("Starting OpenUV discovery scan");
+        OpenUVBridgeHandler bridge = bridgeHandler;
+        if (bridge != null) {
+            PointType location = bridge.getLocation();
+            if (location != null) {
+                ThingUID bridgeUID = bridge.getThing().getUID();
+                thingDiscovered(DiscoveryResultBuilder
+                        .create(new ThingUID(LOCATION_REPORT_THING_TYPE, bridgeUID, LOCAL)).withLabel("Local UV Report")
+                        .withProperty(ReportConfiguration.LOCATION, location.toString())
+                        .withRepresentationProperty(ReportConfiguration.LOCATION).withBridge(bridgeUID).build());
+            } else {
+                logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
             }
+        } else {
+            logger.debug("OpenUV Bridge Handler is not set -> Will not provide any discovery results");
         }
     }
 }
index 5716b69edd8ec7660c1923e4e51cc42dc10f05d7..b3fb8a1614ccef5ebf555db58ef85e267b3a1a85 100644 (file)
  */
 package org.openhab.binding.openuv.internal.handler;
 
-import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.BASE_URL;
-
 import java.io.IOException;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.time.ZonedDateTime;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Properties;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.commons.lang.StringUtils;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openuv.internal.OpenUVBindingConstants;
+import org.openhab.binding.openuv.internal.OpenUVException;
+import org.openhab.binding.openuv.internal.config.BridgeConfiguration;
+import org.openhab.binding.openuv.internal.discovery.OpenUVDiscoveryService;
 import org.openhab.binding.openuv.internal.json.OpenUVResponse;
 import org.openhab.binding.openuv.internal.json.OpenUVResult;
-import org.openhab.core.config.core.Configuration;
+import org.openhab.core.i18n.LocationProvider;
 import org.openhab.core.io.net.http.HttpUtil;
-import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.PointType;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
-import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializer;
 
 /**
  * {@link OpenUVBridgeHandler} is the handler for OpenUV API and connects it
@@ -60,41 +55,45 @@ import com.google.gson.JsonDeserializer;
 @NonNullByDefault
 public class OpenUVBridgeHandler extends BaseBridgeHandler {
     private final Logger logger = LoggerFactory.getLogger(OpenUVBridgeHandler.class);
-    private static final String ERROR_QUOTA_EXCEEDED = "Daily API quota exceeded";
-    private static final String ERROR_WRONG_KEY = "User with API Key not found";
 
-    private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(30);
+    private static final String QUERY_URL = "https://api.openuv.io/api/v1/uv?lat=%s&lng=%s&alt=%s";
 
-    private final Gson gson = new GsonBuilder()
-            .registerTypeAdapter(DecimalType.class,
-                    (JsonDeserializer<DecimalType>) (json, type, jsonDeserializationContext) -> DecimalType
-                            .valueOf(json.getAsJsonPrimitive().getAsString()))
-            .registerTypeAdapter(ZonedDateTime.class,
-                    (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
-                            .parse(json.getAsJsonPrimitive().getAsString()))
-            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+    private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
 
-    private Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
     private final Properties header = new Properties();
+    private final Gson gson;
+
+    private final LocationProvider locationProvider;
+    private @Nullable ScheduledFuture<?> reconnectJob;
 
-    public OpenUVBridgeHandler(Bridge bridge) {
+    public OpenUVBridgeHandler(Bridge bridge, LocationProvider locationProvider, Gson gson) {
         super(bridge);
+        this.gson = gson;
+        this.locationProvider = locationProvider;
     }
 
     @Override
     public void initialize() {
         logger.debug("Initializing OpenUV API bridge handler.");
-        Configuration config = getThing().getConfiguration();
-        String apiKey = (String) config.get(OpenUVBindingConstants.APIKEY);
-        if (StringUtils.trimToNull(apiKey) == null) {
+        BridgeConfiguration config = getConfigAs(BridgeConfiguration.class);
+        if (config.apikey.isEmpty()) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "Parameter 'apikey' must be configured.");
         } else {
-            header.put("x-access-token", apiKey);
+            header.put("x-access-token", config.apikey);
             initiateConnexion();
         }
     }
 
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> job = this.reconnectJob;
+        if (job != null && !job.isCancelled()) {
+            job.cancel(true);
+        }
+        reconnectJob = null;
+    }
+
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
         if (command instanceof RefreshType) {
@@ -106,62 +105,49 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
 
     private void initiateConnexion() {
         // Check if the provided api key is valid for use with the OpenUV service
-        getUVData("0", "0", null);
+        getUVData("0", "0", "0");
     }
 
-    public Map<ThingUID, @Nullable ServiceRegistration<?>> getDiscoveryServiceRegs() {
-        return discoveryServiceRegs;
-    }
-
-    public void setDiscoveryServiceRegs(Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs) {
-        this.discoveryServiceRegs = discoveryServiceRegs;
-    }
-
-    @Override
-    public void handleRemoval() {
-        // removes the old registration service associated to the bridge, if existing
-        ServiceRegistration<?> dis = getDiscoveryServiceRegs().get(getThing().getUID());
-        if (dis != null) {
-            dis.unregister();
-        }
-        super.handleRemoval();
-    }
-
-    public @Nullable OpenUVResult getUVData(String latitude, String longitude, @Nullable String altitude) {
-        StringBuilder urlBuilder = new StringBuilder(BASE_URL).append("?lat=").append(latitude).append("&lng=")
-                .append(longitude);
-
-        if (altitude != null) {
-            urlBuilder.append("&alt=").append(altitude);
-        }
-        String errorMessage = null;
+    public @Nullable OpenUVResult getUVData(String latitude, String longitude, String altitude) {
         try {
-            String jsonData = HttpUtil.executeUrl("GET", urlBuilder.toString(), header, null, null, REQUEST_TIMEOUT);
+            String jsonData = HttpUtil.executeUrl("GET", String.format(QUERY_URL, latitude, longitude, altitude),
+                    header, null, null, REQUEST_TIMEOUT_MS);
             OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class);
             if (uvResponse.getError() == null) {
                 updateStatus(ThingStatus.ONLINE);
                 return uvResponse.getResult();
             } else {
-                errorMessage = uvResponse.getError();
+                throw new OpenUVException(uvResponse.getError());
             }
         } catch (IOException e) {
-            errorMessage = e.getMessage();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        } catch (OpenUVException e) {
+            if (e.isQuotaError()) {
+                LocalDate today = LocalDate.now();
+                LocalDate tomorrow = today.plusDays(1);
+                LocalDateTime tomorrowMidnight = tomorrow.atStartOfDay().plusMinutes(2);
+
+                String message = "Quota Exceeded, going OFFLINE for today, will retry at : "
+                        + tomorrowMidnight.toString();
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+
+                reconnectJob = scheduler.schedule(this::initiateConnexion,
+                        Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
+            } else if (e.isApiKeyError()) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+            } else {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
+            }
         }
+        return null;
+    }
 
-        if (errorMessage.startsWith(ERROR_QUOTA_EXCEEDED)) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
-            LocalDate today = LocalDate.now();
-            LocalDate tomorrow = today.plusDays(1);
-            LocalDateTime tomorrowMidnight = tomorrow.atStartOfDay().plusMinutes(2);
-
-            logger.warn("Quota Exceeded, going OFFLINE for today, will retry at : {} ", tomorrowMidnight);
-            scheduler.schedule(this::initiateConnexion,
-                    Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(OpenUVDiscoveryService.class);
+    }
 
-        } else if (errorMessage.startsWith(ERROR_WRONG_KEY)) {
-            logger.error("Error occured during API query : {}", errorMessage);
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
-        }
-        return null;
+    public @Nullable PointType getLocation() {
+        return locationProvider.getLocation();
     }
 }
index 74ee8acb72f7feb3ead28bdf88413aea8800cf53..582e3ca67d79494a13d2581fcf6837d8b4bd9819 100644 (file)
@@ -23,11 +23,13 @@ import java.util.concurrent.TimeUnit;
 import javax.measure.quantity.Angle;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.openuv.internal.ReportConfiguration;
-import org.openhab.binding.openuv.internal.SafeExposureConfiguration;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openuv.internal.config.ReportConfiguration;
+import org.openhab.binding.openuv.internal.config.SafeExposureConfiguration;
 import org.openhab.binding.openuv.internal.json.OpenUVResult;
 import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.HSBType;
+import org.openhab.core.library.types.PointType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SmartHomeUnits;
 import org.openhab.core.thing.Bridge;
@@ -54,13 +56,11 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class OpenUVReportHandler extends BaseThingHandler {
-    private static final int DEFAULT_REFRESH_PERIOD = 30;
-
     private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
 
     private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
-    private @NonNullByDefault({}) ScheduledFuture<?> refreshJob;
-    private @NonNullByDefault({}) ScheduledFuture<?> uvMaxJob;
+    private @Nullable ScheduledFuture<?> refreshJob;
+    private @Nullable ScheduledFuture<?> uvMaxJob;
     private boolean suspendUpdates = false;
 
     public OpenUVReportHandler(Thing thing) {
@@ -73,7 +73,7 @@ public class OpenUVReportHandler extends BaseThingHandler {
 
         ReportConfiguration config = getConfigAs(ReportConfiguration.class);
 
-        if (config.refresh != null && config.refresh < 3) {
+        if (config.refresh < 3) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "Parameter 'refresh' must be higher than 3 minutes to stay in free API plan");
         } else {
@@ -94,7 +94,8 @@ public class OpenUVReportHandler extends BaseThingHandler {
      * @param openUVData
      */
     private void scheduleUVMaxEvent(OpenUVResult openUVData) {
-        if ((uvMaxJob == null || uvMaxJob.isCancelled())) {
+        ScheduledFuture<?> job = this.uvMaxJob;
+        if ((job == null || job.isCancelled())) {
             State uvMaxTime = openUVData.getUVMaxTime();
             if (uvMaxTime != UnDefType.NULL) {
                 ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
@@ -114,22 +115,23 @@ public class OpenUVReportHandler extends BaseThingHandler {
      * Start the job refreshing the data
      */
     private void startAutomaticRefresh() {
-        if (refreshJob == null || refreshJob.isCancelled()) {
+        ScheduledFuture<?> job = this.refreshJob;
+        if (job == null || job.isCancelled()) {
             ReportConfiguration config = getConfigAs(ReportConfiguration.class);
-            int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
             refreshJob = scheduler.scheduleWithFixedDelay(() -> {
                 if (!suspendUpdates) {
                     updateChannels(config);
                 }
-            }, 0, delay, TimeUnit.MINUTES);
+            }, 0, config.refresh, TimeUnit.MINUTES);
         }
     }
 
     private void updateChannels(ReportConfiguration config) {
         ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
-            OpenUVResult openUVData = bridgeHandler.getUVData(config.getLatitude(), config.getLongitude(),
-                    config.getAltitude());
+            PointType location = new PointType(config.location);
+            OpenUVResult openUVData = bridgeHandler.getUVData(location.getLatitude().toString(),
+                    location.getLongitude().toString(), location.getAltitude().toString());
             if (openUVData != null) {
                 scheduleUVMaxEvent(openUVData);
                 getThing().getChannels().forEach(channel -> {
@@ -146,16 +148,17 @@ public class OpenUVReportHandler extends BaseThingHandler {
     @Override
     public void dispose() {
         logger.debug("Disposing the OpenUV handler.");
-
-        if (refreshJob != null && !refreshJob.isCancelled()) {
-            refreshJob.cancel(true);
-            refreshJob = null;
+        ScheduledFuture<?> refresh = this.refreshJob;
+        if (refresh != null && !refresh.isCancelled()) {
+            refresh.cancel(true);
         }
+        refreshJob = null;
 
-        if (uvMaxJob != null && !uvMaxJob.isCancelled()) {
-            uvMaxJob.cancel(true);
-            uvMaxJob = null;
+        ScheduledFuture<?> uxMax = this.uvMaxJob;
+        if (uxMax != null && !uxMax.isCancelled()) {
+            uxMax.cancel(true);
         }
+        uvMaxJob = null;
     }
 
     @SuppressWarnings("unchecked")