]> git.basschouten.com Git - openhab-addons.git/commitdiff
[AirQuality] Enhance API error handling (#14602)
authorGaël L'hopital <gael@lhopital.org>
Sun, 23 Apr 2023 09:14:10 +0000 (11:14 +0200)
committerGitHub <noreply@github.com>
Sun, 23 Apr 2023 09:14:10 +0000 (11:14 +0200)
* Enhancing API error handling

---------

Signed-off-by: clinique <gael@lhopital.org>
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/AirQualityException.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/ApiBridge.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/Appreciation.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/dto/AirQualityData.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/dto/AirQualityResponse.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/dto/ResponseRoot.java [new file with mode: 0644]
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/discovery/AirQualityDiscoveryService.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/handler/AirQualityBridgeHandler.java
bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/handler/AirQualityStationHandler.java

index 480e0a1fdf9bc11b214d3b30c7dc87822eabf926..03ce592d3e52ea46ac6e796eab4c3ec601cc07db 100644 (file)
@@ -23,24 +23,18 @@ import org.eclipse.jdt.annotation.Nullable;
 @NonNullByDefault
 public class AirQualityException extends Exception {
     private static final long serialVersionUID = -3398100220952729815L;
-    private int statusCode = -1;
 
     public AirQualityException(String message, Exception e) {
         super(message, e);
     }
 
-    public AirQualityException(String message) {
-        super(message);
-    }
-
-    public int getStatusCode() {
-        return statusCode;
+    public AirQualityException(String message, Object... params) {
+        super(String.format(message, params));
     }
 
     @Override
     public @Nullable String getMessage() {
         String message = super.getMessage();
-        return message == null ? null
-                : String.format("Rest call failed: statusCode=%d, message=%s", statusCode, message);
+        return message == null ? null : String.format("Rest call failed: message=%s", message);
     }
 }
index 884a453c19570407c2743e1ab3a25ec10b005d54..fd5ae4b79c2fbafc6dc7c2d4b6dd264da0c9c43e 100644 (file)
 package org.openhab.binding.airquality.internal.api;
 
 import java.io.IOException;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.airquality.internal.AirQualityException;
 import org.openhab.binding.airquality.internal.api.dto.AirQualityData;
 import org.openhab.binding.airquality.internal.api.dto.AirQualityResponse;
-import org.openhab.binding.airquality.internal.api.dto.AirQualityResponse.ResponseStatus;
 import org.openhab.core.io.net.http.HttpUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -66,21 +66,24 @@ public class ApiBridge {
      * @return an air quality data object mapping the JSON response
      * @throws AirQualityException
      */
-    public AirQualityData getData(int stationId, String location, int retryCounter) throws AirQualityException {
+    public AirQualityData getData(int stationId, String location) throws AirQualityException {
         String urlStr = buildRequestURL(apiKey, stationId, location);
         logger.debug("URL = {}", urlStr);
 
         try {
             String response = HttpUtil.executeUrl("GET", urlStr, null, null, null, REQUEST_TIMEOUT_MS);
-            logger.debug("aqiResponse = {}", response);
-            AirQualityResponse result = GSON.fromJson(response, AirQualityResponse.class);
-            if (result != null && result.getStatus() == ResponseStatus.OK) {
-                return result.getData();
-            } else if (retryCounter == 0) {
-                logger.debug("Error in aqicn.org, retrying once");
-                return getData(stationId, location, retryCounter + 1);
+            if (response != null) {
+                logger.debug("aqiResponse = {}", response);
+                AirQualityResponse result = GSON.fromJson(response, AirQualityResponse.class);
+                if (result != null) {
+                    String error = result.getErrorMessage();
+                    if (error.isEmpty()) {
+                        return Objects.requireNonNull(result.getData());
+                    }
+                    throw new AirQualityException("Error raised : %s", error);
+                }
             }
-            throw new AirQualityException("Error in aqicn.org response: Missing data sub-object");
+            throw new JsonSyntaxException("API response is null");
         } catch (IOException | JsonSyntaxException e) {
             throw new AirQualityException("Communication error", e);
         }
index 7a0d1c4dbd86ad4a15fb1ee7da0e0a72d94b5f70..2d9dcf01ec8fd4fa3e95739a6c144b958b184c7f 100644 (file)
@@ -24,17 +24,17 @@ import org.openhab.core.types.State;
  */
 @NonNullByDefault
 public enum Appreciation {
-    GOOD(HSBType.fromRGB(0, 228, 0)),
-    MODERATE(HSBType.fromRGB(255, 255, 0)),
-    UNHEALTHY_FSG(HSBType.fromRGB(255, 126, 0)),
-    UNHEALTHY(HSBType.fromRGB(255, 0, 0)),
-    VERY_UNHEALTHY(HSBType.fromRGB(143, 63, 151)),
-    HAZARDOUS(HSBType.fromRGB(126, 0, 35));
+    GOOD(0, 228, 0),
+    MODERATE(255, 255, 0),
+    UNHEALTHY_FSG(255, 126, 0),
+    UNHEALTHY(255, 0, 0),
+    VERY_UNHEALTHY(143, 63, 151),
+    HAZARDOUS(126, 0, 35);
 
     private HSBType color;
 
-    Appreciation(HSBType color) {
-        this.color = color;
+    Appreciation(int r, int g, int b) {
+        this.color = HSBType.fromRGB(r, g, b);
     }
 
     public State getColor() {
index 82364c6e43e5ab44b68d0b02432b55be0268e360..7b3f26f79a4e983d41e72365f916676de9584a41 100644 (file)
@@ -14,9 +14,11 @@ package org.openhab.binding.airquality.internal.api.dto;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.airquality.internal.api.Pollutant;
 
 /**
@@ -26,12 +28,13 @@ import org.openhab.binding.airquality.internal.api.Pollutant;
  * @author Kuba Wolanin - Initial contribution
  */
 @NonNullByDefault
-public class AirQualityData {
+public class AirQualityData extends ResponseRoot {
+
     private int aqi;
     private int idx;
 
-    private @NonNullByDefault({}) AirQualityTime time;
-    private @NonNullByDefault({}) AirQualityCity city;
+    private @Nullable AirQualityTime time;
+    private @Nullable AirQualityCity city;
     private List<Attribution> attributions = List.of();
     private Map<String, AirQualityValue> iaqi = Map.of();
     private String dominentpol = "";
@@ -59,8 +62,8 @@ public class AirQualityData {
      *
      * @return {AirQualityJsonTime}
      */
-    public AirQualityTime getTime() {
-        return time;
+    public Optional<AirQualityTime> getTime() {
+        return Optional.ofNullable(time);
     }
 
     /**
@@ -68,8 +71,8 @@ public class AirQualityData {
      *
      * @return {AirQualityJsonCity}
      */
-    public AirQualityCity getCity() {
-        return city;
+    public Optional<AirQualityCity> getCity() {
+        return Optional.ofNullable(city);
     }
 
     /**
index e157a395bc2f9abd5a28348488d7c2668f7088f7..5bfb8668c3215d0264282ef54d3090559e2d9b18 100644 (file)
@@ -13,8 +13,7 @@
 package org.openhab.binding.airquality.internal.api.dto;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-
-import com.google.gson.annotations.SerializedName;
+import org.eclipse.jdt.annotation.Nullable;
 
 /**
  * The {@link AirQualityResponse} is the Java class used to map the JSON
@@ -23,24 +22,40 @@ import com.google.gson.annotations.SerializedName;
  * @author Kuba Wolanin - Initial contribution
  */
 @NonNullByDefault
-public class AirQualityResponse {
+public class AirQualityResponse extends ResponseRoot {
 
-    public static enum ResponseStatus {
-        NONE,
-        @SerializedName("error")
-        ERROR,
-        @SerializedName("ok")
-        OK;
-    }
+    private @Nullable AirQualityData data;
 
-    private ResponseStatus status = ResponseStatus.NONE;
-    private @NonNullByDefault({}) AirQualityData data;
+    public @Nullable AirQualityData getData() {
+        return data;
+    }
 
-    public ResponseStatus getStatus() {
-        return status;
+    private ResponseStatus getStatus() {
+        AirQualityData localData = data;
+        return status == ResponseStatus.OK && localData != null && localData.status == ResponseStatus.OK
+                ? ResponseStatus.OK
+                : ResponseStatus.ERROR;
     }
 
-    public AirQualityData getData() {
-        return data;
+    public String getErrorMessage() {
+        if (getStatus() != ResponseStatus.OK) {
+            String localMsg = msg;
+            if (localMsg != null) {
+                return localMsg;
+            } else {
+                AirQualityData localData = data;
+                if (localData != null) {
+                    localMsg = localData.msg;
+                    if (localMsg != null) {
+                        return localMsg;
+                    } else {
+                        return "Unknown error";
+                    }
+                } else {
+                    return "No data provided";
+                }
+            }
+        }
+        return "";
     }
 }
diff --git a/bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/dto/ResponseRoot.java b/bundles/org.openhab.binding.airquality/src/main/java/org/openhab/binding/airquality/internal/api/dto/ResponseRoot.java
new file mode 100644 (file)
index 0000000..09c665f
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2023 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.airquality.internal.api.dto;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link ResponseRoot} is the common part of Air Quality API response objectss
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class ResponseRoot {
+    public static enum ResponseStatus {
+        @SerializedName("error")
+        ERROR,
+        @SerializedName("ok")
+        OK;
+    }
+
+    protected ResponseStatus status = ResponseStatus.OK;
+    protected @Nullable String msg;
+}
index ae334542da96f875f40681eb57d4901aa6efc8f1..0edc187ddfaa7df97bcfcd9490b39adda7ee6e59 100644 (file)
@@ -84,14 +84,14 @@ public class AirQualityDiscoveryService extends AbstractDiscoveryService impleme
             PointType location = provider.getLocation();
             AirQualityBridgeHandler bridge = this.bridgeHandler;
             if (location == null || bridge == null) {
-                logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
+                logger.info("openHAB server location is not defined, will not provide any discovery results");
                 return;
             }
             createResults(location, bridge.getThing().getUID());
         }
     }
 
-    public void createResults(PointType location, ThingUID bridgeUID) {
+    private void createResults(PointType location, ThingUID bridgeUID) {
         ThingUID localAirQualityThing = new ThingUID(THING_TYPE_STATION, bridgeUID, LOCAL);
         thingDiscovered(DiscoveryResultBuilder.create(localAirQualityThing).withLabel("Local Air Quality")
                 .withProperty(LOCATION, String.format("%s,%s", location.getLatitude(), location.getLongitude()))
index 90cd515f6d850404abbff3f4b20661be6d99f369..1ee591a2dcf6a283976294f7ac8dd7899b1aaaab 100644 (file)
@@ -13,7 +13,7 @@
 package org.openhab.binding.airquality.internal.handler;
 
 import java.util.Collection;
-import java.util.Collections;
+import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -67,7 +67,7 @@ public class AirQualityBridgeHandler extends BaseBridgeHandler {
 
     @Override
     public Collection<Class<? extends ThingHandlerService>> getServices() {
-        return Collections.singleton(AirQualityDiscoveryService.class);
+        return Set.of(AirQualityDiscoveryService.class);
     }
 
     public LocationProvider getLocationProvider() {
index f9555546589e5170b477ee581221c4d7238abb45..83017784f15a1273a89e9d988ca7af2ffb1e1c75 100644 (file)
@@ -110,13 +110,13 @@ public class AirQualityStationHandler extends BaseThingHandler {
     private void discoverAttributes() {
         getAirQualityData().ifPresent(data -> {
             // Update thing properties
-            Map<String, String> properties = new HashMap<>();
-            properties.put(ATTRIBUTIONS, data.getAttributions());
+            Map<String, String> properties = new HashMap<>(Map.of(ATTRIBUTIONS, data.getAttributions()));
             PointType serverLocation = locationProvider.getLocation();
             if (serverLocation != null) {
-                PointType stationLocation = new PointType(data.getCity().getGeo());
-                double distance = serverLocation.distanceFrom(stationLocation).doubleValue();
-                properties.put(DISTANCE, new QuantityType<>(distance / 1000, KILO(SIUnits.METRE)).toString());
+                data.getCity().ifPresent(city -> {
+                    double distance = serverLocation.distanceFrom(new PointType(city.getGeo())).doubleValue();
+                    properties.put(DISTANCE, new QuantityType<>(distance / 1000, KILO(SIUnits.METRE)).toString());
+                });
             }
 
             // Search and remove missing pollutant channels
@@ -132,8 +132,8 @@ public class AirQualityStationHandler extends BaseThingHandler {
             config.put(AirQualityConfiguration.STATION_ID, data.getStationId());
 
             ThingBuilder thingBuilder = editThing();
-            thingBuilder.withChannels(channels).withConfiguration(config).withProperties(properties)
-                    .withLocation(data.getCity().getName());
+            thingBuilder.withChannels(channels).withConfiguration(config).withProperties(properties);
+            data.getCity().map(city -> thingBuilder.withLocation(city.getName()));
             updateThing(thingBuilder.build());
         });
     }
@@ -190,7 +190,7 @@ public class AirQualityStationHandler extends BaseThingHandler {
         if (apiBridge != null) {
             AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
             try {
-                result = apiBridge.getData(config.stationId, config.location, 0);
+                result = apiBridge.getData(config.stationId, config.location);
                 updateStatus(ThingStatus.ONLINE);
             } catch (AirQualityException e) {
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
@@ -253,24 +253,26 @@ public class AirQualityStationHandler extends BaseThingHandler {
         switch (channelId) {
             case TEMPERATURE:
                 double temp = data.getIaqiValue("t");
-                return temp != -1 ? new QuantityType<>(temp, SIUnits.CELSIUS) : UnDefType.UNDEF;
+                return temp != -1 ? new QuantityType<>(temp, SIUnits.CELSIUS) : UnDefType.NULL;
             case PRESSURE:
                 double press = data.getIaqiValue("p");
-                return press != -1 ? new QuantityType<>(press, HECTO(SIUnits.PASCAL)) : UnDefType.UNDEF;
+                return press != -1 ? new QuantityType<>(press, HECTO(SIUnits.PASCAL)) : UnDefType.NULL;
             case HUMIDITY:
                 double hum = data.getIaqiValue("h");
-                return hum != -1 ? new QuantityType<>(hum, Units.PERCENT) : UnDefType.UNDEF;
+                return hum != -1 ? new QuantityType<>(hum, Units.PERCENT) : UnDefType.NULL;
             case TIMESTAMP:
-                return new DateTimeType(
-                        data.getTime().getObservationTime().withZoneSameLocal(timeZoneProvider.getTimeZone()));
+                return data.getTime()
+                        .map(time -> (State) new DateTimeType(
+                                time.getObservationTime().withZoneSameLocal(timeZoneProvider.getTimeZone())))
+                        .orElse(UnDefType.NULL);
             case DOMINENT:
                 return new StringType(data.getDominentPol());
             case DEW_POINT:
                 double dp = data.getIaqiValue("dew");
-                return dp != -1 ? new QuantityType<>(dp, SIUnits.CELSIUS) : UnDefType.UNDEF;
+                return dp != -1 ? new QuantityType<>(dp, SIUnits.CELSIUS) : UnDefType.NULL;
             case WIND_SPEED:
                 double w = data.getIaqiValue("w");
-                return w != -1 ? new QuantityType<>(w, Units.METRE_PER_SECOND) : UnDefType.UNDEF;
+                return w != -1 ? new QuantityType<>(w, Units.METRE_PER_SECOND) : UnDefType.NULL;
             default:
                 if (groupId != null) {
                     double idx = -1;
@@ -283,7 +285,7 @@ public class AirQualityStationHandler extends BaseThingHandler {
                     }
                     return indexedValue(channelId, idx, pollutant);
                 }
-                return UnDefType.UNDEF;
+                return UnDefType.NULL;
         }
     }