mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[AirQuality] Enhance API error handling (#14602)
* Enhancing API error handling --------- Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
parent
275329d485
commit
edaf9581c0
@ -23,24 +23,18 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AirQualityException extends Exception {
|
public class AirQualityException extends Exception {
|
||||||
private static final long serialVersionUID = -3398100220952729815L;
|
private static final long serialVersionUID = -3398100220952729815L;
|
||||||
private int statusCode = -1;
|
|
||||||
|
|
||||||
public AirQualityException(String message, Exception e) {
|
public AirQualityException(String message, Exception e) {
|
||||||
super(message, e);
|
super(message, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AirQualityException(String message) {
|
public AirQualityException(String message, Object... params) {
|
||||||
super(message);
|
super(String.format(message, params));
|
||||||
}
|
|
||||||
|
|
||||||
public int getStatusCode() {
|
|
||||||
return statusCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable String getMessage() {
|
public @Nullable String getMessage() {
|
||||||
String message = super.getMessage();
|
String message = super.getMessage();
|
||||||
return message == null ? null
|
return message == null ? null : String.format("Rest call failed: message=%s", message);
|
||||||
: String.format("Rest call failed: statusCode=%d, message=%s", statusCode, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
package org.openhab.binding.airquality.internal.api;
|
package org.openhab.binding.airquality.internal.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.airquality.internal.AirQualityException;
|
import org.openhab.binding.airquality.internal.AirQualityException;
|
||||||
import org.openhab.binding.airquality.internal.api.dto.AirQualityData;
|
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;
|
||||||
import org.openhab.binding.airquality.internal.api.dto.AirQualityResponse.ResponseStatus;
|
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -66,21 +66,24 @@ public class ApiBridge {
|
|||||||
* @return an air quality data object mapping the JSON response
|
* @return an air quality data object mapping the JSON response
|
||||||
* @throws AirQualityException
|
* @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);
|
String urlStr = buildRequestURL(apiKey, stationId, location);
|
||||||
logger.debug("URL = {}", urlStr);
|
logger.debug("URL = {}", urlStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String response = HttpUtil.executeUrl("GET", urlStr, null, null, null, REQUEST_TIMEOUT_MS);
|
String response = HttpUtil.executeUrl("GET", urlStr, null, null, null, REQUEST_TIMEOUT_MS);
|
||||||
logger.debug("aqiResponse = {}", response);
|
if (response != null) {
|
||||||
AirQualityResponse result = GSON.fromJson(response, AirQualityResponse.class);
|
logger.debug("aqiResponse = {}", response);
|
||||||
if (result != null && result.getStatus() == ResponseStatus.OK) {
|
AirQualityResponse result = GSON.fromJson(response, AirQualityResponse.class);
|
||||||
return result.getData();
|
if (result != null) {
|
||||||
} else if (retryCounter == 0) {
|
String error = result.getErrorMessage();
|
||||||
logger.debug("Error in aqicn.org, retrying once");
|
if (error.isEmpty()) {
|
||||||
return getData(stationId, location, retryCounter + 1);
|
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) {
|
} catch (IOException | JsonSyntaxException e) {
|
||||||
throw new AirQualityException("Communication error", e);
|
throw new AirQualityException("Communication error", e);
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,17 @@ import org.openhab.core.types.State;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public enum Appreciation {
|
public enum Appreciation {
|
||||||
GOOD(HSBType.fromRGB(0, 228, 0)),
|
GOOD(0, 228, 0),
|
||||||
MODERATE(HSBType.fromRGB(255, 255, 0)),
|
MODERATE(255, 255, 0),
|
||||||
UNHEALTHY_FSG(HSBType.fromRGB(255, 126, 0)),
|
UNHEALTHY_FSG(255, 126, 0),
|
||||||
UNHEALTHY(HSBType.fromRGB(255, 0, 0)),
|
UNHEALTHY(255, 0, 0),
|
||||||
VERY_UNHEALTHY(HSBType.fromRGB(143, 63, 151)),
|
VERY_UNHEALTHY(143, 63, 151),
|
||||||
HAZARDOUS(HSBType.fromRGB(126, 0, 35));
|
HAZARDOUS(126, 0, 35);
|
||||||
|
|
||||||
private HSBType color;
|
private HSBType color;
|
||||||
|
|
||||||
Appreciation(HSBType color) {
|
Appreciation(int r, int g, int b) {
|
||||||
this.color = color;
|
this.color = HSBType.fromRGB(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public State getColor() {
|
public State getColor() {
|
||||||
|
@ -14,9 +14,11 @@ package org.openhab.binding.airquality.internal.api.dto;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.airquality.internal.api.Pollutant;
|
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
|
* @author Kuba Wolanin - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AirQualityData {
|
public class AirQualityData extends ResponseRoot {
|
||||||
|
|
||||||
private int aqi;
|
private int aqi;
|
||||||
private int idx;
|
private int idx;
|
||||||
|
|
||||||
private @NonNullByDefault({}) AirQualityTime time;
|
private @Nullable AirQualityTime time;
|
||||||
private @NonNullByDefault({}) AirQualityCity city;
|
private @Nullable AirQualityCity city;
|
||||||
private List<Attribution> attributions = List.of();
|
private List<Attribution> attributions = List.of();
|
||||||
private Map<String, AirQualityValue> iaqi = Map.of();
|
private Map<String, AirQualityValue> iaqi = Map.of();
|
||||||
private String dominentpol = "";
|
private String dominentpol = "";
|
||||||
@ -59,8 +62,8 @@ public class AirQualityData {
|
|||||||
*
|
*
|
||||||
* @return {AirQualityJsonTime}
|
* @return {AirQualityJsonTime}
|
||||||
*/
|
*/
|
||||||
public AirQualityTime getTime() {
|
public Optional<AirQualityTime> getTime() {
|
||||||
return time;
|
return Optional.ofNullable(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,8 +71,8 @@ public class AirQualityData {
|
|||||||
*
|
*
|
||||||
* @return {AirQualityJsonCity}
|
* @return {AirQualityJsonCity}
|
||||||
*/
|
*/
|
||||||
public AirQualityCity getCity() {
|
public Optional<AirQualityCity> getCity() {
|
||||||
return city;
|
return Optional.ofNullable(city);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,8 +13,7 @@
|
|||||||
package org.openhab.binding.airquality.internal.api.dto;
|
package org.openhab.binding.airquality.internal.api.dto;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link AirQualityResponse} is the Java class used to map the JSON
|
* 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
|
* @author Kuba Wolanin - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AirQualityResponse {
|
public class AirQualityResponse extends ResponseRoot {
|
||||||
|
|
||||||
public static enum ResponseStatus {
|
private @Nullable AirQualityData data;
|
||||||
NONE,
|
|
||||||
@SerializedName("error")
|
|
||||||
ERROR,
|
|
||||||
@SerializedName("ok")
|
|
||||||
OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResponseStatus status = ResponseStatus.NONE;
|
public @Nullable AirQualityData getData() {
|
||||||
private @NonNullByDefault({}) AirQualityData data;
|
|
||||||
|
|
||||||
public ResponseStatus getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AirQualityData getData() {
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ResponseStatus getStatus() {
|
||||||
|
AirQualityData localData = data;
|
||||||
|
return status == ResponseStatus.OK && localData != null && localData.status == ResponseStatus.OK
|
||||||
|
? ResponseStatus.OK
|
||||||
|
: ResponseStatus.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -84,14 +84,14 @@ public class AirQualityDiscoveryService extends AbstractDiscoveryService impleme
|
|||||||
PointType location = provider.getLocation();
|
PointType location = provider.getLocation();
|
||||||
AirQualityBridgeHandler bridge = this.bridgeHandler;
|
AirQualityBridgeHandler bridge = this.bridgeHandler;
|
||||||
if (location == null || bridge == null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
createResults(location, bridge.getThing().getUID());
|
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);
|
ThingUID localAirQualityThing = new ThingUID(THING_TYPE_STATION, bridgeUID, LOCAL);
|
||||||
thingDiscovered(DiscoveryResultBuilder.create(localAirQualityThing).withLabel("Local Air Quality")
|
thingDiscovered(DiscoveryResultBuilder.create(localAirQualityThing).withLabel("Local Air Quality")
|
||||||
.withProperty(LOCATION, String.format("%s,%s", location.getLatitude(), location.getLongitude()))
|
.withProperty(LOCATION, String.format("%s,%s", location.getLatitude(), location.getLongitude()))
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
package org.openhab.binding.airquality.internal.handler;
|
package org.openhab.binding.airquality.internal.handler;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -67,7 +67,7 @@ public class AirQualityBridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
return Collections.singleton(AirQualityDiscoveryService.class);
|
return Set.of(AirQualityDiscoveryService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocationProvider getLocationProvider() {
|
public LocationProvider getLocationProvider() {
|
||||||
|
@ -110,13 +110,13 @@ public class AirQualityStationHandler extends BaseThingHandler {
|
|||||||
private void discoverAttributes() {
|
private void discoverAttributes() {
|
||||||
getAirQualityData().ifPresent(data -> {
|
getAirQualityData().ifPresent(data -> {
|
||||||
// Update thing properties
|
// Update thing properties
|
||||||
Map<String, String> properties = new HashMap<>();
|
Map<String, String> properties = new HashMap<>(Map.of(ATTRIBUTIONS, data.getAttributions()));
|
||||||
properties.put(ATTRIBUTIONS, data.getAttributions());
|
|
||||||
PointType serverLocation = locationProvider.getLocation();
|
PointType serverLocation = locationProvider.getLocation();
|
||||||
if (serverLocation != null) {
|
if (serverLocation != null) {
|
||||||
PointType stationLocation = new PointType(data.getCity().getGeo());
|
data.getCity().ifPresent(city -> {
|
||||||
double distance = serverLocation.distanceFrom(stationLocation).doubleValue();
|
double distance = serverLocation.distanceFrom(new PointType(city.getGeo())).doubleValue();
|
||||||
properties.put(DISTANCE, new QuantityType<>(distance / 1000, KILO(SIUnits.METRE)).toString());
|
properties.put(DISTANCE, new QuantityType<>(distance / 1000, KILO(SIUnits.METRE)).toString());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search and remove missing pollutant channels
|
// Search and remove missing pollutant channels
|
||||||
@ -132,8 +132,8 @@ public class AirQualityStationHandler extends BaseThingHandler {
|
|||||||
config.put(AirQualityConfiguration.STATION_ID, data.getStationId());
|
config.put(AirQualityConfiguration.STATION_ID, data.getStationId());
|
||||||
|
|
||||||
ThingBuilder thingBuilder = editThing();
|
ThingBuilder thingBuilder = editThing();
|
||||||
thingBuilder.withChannels(channels).withConfiguration(config).withProperties(properties)
|
thingBuilder.withChannels(channels).withConfiguration(config).withProperties(properties);
|
||||||
.withLocation(data.getCity().getName());
|
data.getCity().map(city -> thingBuilder.withLocation(city.getName()));
|
||||||
updateThing(thingBuilder.build());
|
updateThing(thingBuilder.build());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ public class AirQualityStationHandler extends BaseThingHandler {
|
|||||||
if (apiBridge != null) {
|
if (apiBridge != null) {
|
||||||
AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
|
AirQualityConfiguration config = getConfigAs(AirQualityConfiguration.class);
|
||||||
try {
|
try {
|
||||||
result = apiBridge.getData(config.stationId, config.location, 0);
|
result = apiBridge.getData(config.stationId, config.location);
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
} catch (AirQualityException e) {
|
} catch (AirQualityException e) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||||
@ -253,24 +253,26 @@ public class AirQualityStationHandler extends BaseThingHandler {
|
|||||||
switch (channelId) {
|
switch (channelId) {
|
||||||
case TEMPERATURE:
|
case TEMPERATURE:
|
||||||
double temp = data.getIaqiValue("t");
|
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:
|
case PRESSURE:
|
||||||
double press = data.getIaqiValue("p");
|
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:
|
case HUMIDITY:
|
||||||
double hum = data.getIaqiValue("h");
|
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:
|
case TIMESTAMP:
|
||||||
return new DateTimeType(
|
return data.getTime()
|
||||||
data.getTime().getObservationTime().withZoneSameLocal(timeZoneProvider.getTimeZone()));
|
.map(time -> (State) new DateTimeType(
|
||||||
|
time.getObservationTime().withZoneSameLocal(timeZoneProvider.getTimeZone())))
|
||||||
|
.orElse(UnDefType.NULL);
|
||||||
case DOMINENT:
|
case DOMINENT:
|
||||||
return new StringType(data.getDominentPol());
|
return new StringType(data.getDominentPol());
|
||||||
case DEW_POINT:
|
case DEW_POINT:
|
||||||
double dp = data.getIaqiValue("dew");
|
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:
|
case WIND_SPEED:
|
||||||
double w = data.getIaqiValue("w");
|
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:
|
default:
|
||||||
if (groupId != null) {
|
if (groupId != null) {
|
||||||
double idx = -1;
|
double idx = -1;
|
||||||
@ -283,7 +285,7 @@ public class AirQualityStationHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
return indexedValue(channelId, idx, pollutant);
|
return indexedValue(channelId, idx, pollutant);
|
||||||
}
|
}
|
||||||
return UnDefType.UNDEF;
|
return UnDefType.NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user