[OpenUV] Correcting representation property (#8761)

* [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>
This commit is contained in:
Gaël L'hopital 2020-10-29 22:49:16 +01:00 committed by GitHub
parent d1803dc723
commit 74ee8e76cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 233 deletions

View File

@ -28,13 +28,9 @@ import org.openhab.core.thing.ThingTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenUVBindingConstants { 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 BINDING_ID = "openuv";
public static final String LOCAL = "local"; public static final String LOCAL = "local";
public static final String LOCATION = "location";
public static final String APIKEY = "apikey";
// List of Bridge Type UIDs // List of Bridge Type UIDs
public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "openuvapi"); public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "openuvapi");

View File

@ -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);
}
}

View File

@ -14,15 +14,15 @@ package org.openhab.binding.openuv.internal;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.OpenUVBridgeHandler;
import org.openhab.binding.openuv.internal.handler.OpenUVReportHandler; 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.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.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; 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.Component;
import org.osgi.service.component.annotations.Reference; 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 * The {@link OpenUVHandlerFactory} is responsible for creating things and thing
* handlers. * handlers.
@ -44,10 +49,21 @@ import org.osgi.service.component.annotations.Reference;
public class OpenUVHandlerFactory extends BaseThingHandlerFactory { public class OpenUVHandlerFactory extends BaseThingHandlerFactory {
private final LocationProvider locationProvider; private final LocationProvider locationProvider;
private final Gson gson;
@Activate @Activate
public OpenUVHandlerFactory(@Reference LocationProvider locationProvider) { public OpenUVHandlerFactory(@Reference TimeZoneProvider timeZoneProvider,
@Reference LocationProvider locationProvider) {
this.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 @Override
@ -59,20 +75,8 @@ public class OpenUVHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) { return APIBRIDGE_THING_TYPE.equals(thingTypeUID)
OpenUVBridgeHandler handler = new OpenUVBridgeHandler((Bridge) thing); ? new OpenUVBridgeHandler((Bridge) thing, locationProvider, gson)
registerOpenUVDiscoveryService(handler); : LOCATION_REPORT_THING_TYPE.equals(thingTypeUID) ? new OpenUVReportHandler(thing) : null;
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<>()));
} }
} }

View File

@ -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;
}
}
}

View File

@ -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 = "";
}

View File

@ -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 = "";
}

View File

@ -10,7 +10,9 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.openuv.internal; package org.openhab.binding.openuv.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link SafeExposureConfiguration} is the class used to match the * The {@link SafeExposureConfiguration} is the class used to match the
@ -18,6 +20,7 @@ package org.openhab.binding.openuv.internal;
* *
* @author Gaël L"hopital - Initial contribution * @author Gaël L"hopital - Initial contribution
*/ */
@NonNullByDefault
public class SafeExposureConfiguration { public class SafeExposureConfiguration {
public int index = -1; public int index = -1;
} }

View File

@ -14,21 +14,16 @@ package org.openhab.binding.openuv.internal.discovery;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.binding.openuv.internal.handler.OpenUVBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ThingUID; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -38,83 +33,54 @@ import org.slf4j.LoggerFactory;
* @author Gaël L'hopital - Initial Contribution * @author Gaël L'hopital - Initial Contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenUVDiscoveryService extends AbstractDiscoveryService { public class OpenUVDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(OpenUVDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(OpenUVDiscoveryService.class);
private static final int DISCOVER_TIMEOUT_SECONDS = 10; private static final int DISCOVER_TIMEOUT_SECONDS = 2;
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
private final LocationProvider locationProvider; private @Nullable OpenUVBridgeHandler bridgeHandler;
private final OpenUVBridgeHandler bridgeHandler;
private @Nullable ScheduledFuture<?> discoveryJob;
private @Nullable PointType previousLocation;
/** /**
* Creates a OpenUVDiscoveryService with enabled autostart. * Creates a OpenUVDiscoveryService with enabled autostart.
*/ */
public OpenUVDiscoveryService(OpenUVBridgeHandler bridgeHandler, LocationProvider locationProvider) { public OpenUVDiscoveryService() {
super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true); super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS);
this.locationProvider = locationProvider;
this.bridgeHandler = bridgeHandler;
} }
@Override @Override
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) { public void setThingHandler(ThingHandler handler) {
super.activate(configProperties); if (handler instanceof OpenUVBridgeHandler) {
this.bridgeHandler = (OpenUVBridgeHandler) handler;
}
} }
@Override @Override
@Modified public @Nullable ThingHandler getThingHandler() {
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) { return bridgeHandler;
super.modified(configProperties); }
@Override
public void deactivate() {
super.deactivate();
} }
@Override @Override
protected void startScan() { protected void startScan() {
logger.debug("Starting OpenUV discovery scan"); logger.debug("Starting OpenUV discovery scan");
PointType location = locationProvider.getLocation(); OpenUVBridgeHandler bridge = bridgeHandler;
if (location == null) { 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"); logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
return;
}
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 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());
}
@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");
} }
} else {
logger.debug("OpenUV Bridge Handler is not set -> Will not provide any discovery results");
} }
} }
} }

View File

@ -12,43 +12,38 @@
*/ */
package org.openhab.binding.openuv.internal.handler; package org.openhab.binding.openuv.internal.handler;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.BASE_URL;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.util.Collection;
import java.util.HashMap; import java.util.Collections;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.OpenUVResponse;
import org.openhab.binding.openuv.internal.json.OpenUVResult; 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.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.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; 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.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson; 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 * {@link OpenUVBridgeHandler} is the handler for OpenUV API and connects it
@ -60,41 +55,45 @@ import com.google.gson.JsonDeserializer;
@NonNullByDefault @NonNullByDefault
public class OpenUVBridgeHandler extends BaseBridgeHandler { public class OpenUVBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(OpenUVBridgeHandler.class); 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() private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
.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 Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final Properties header = new Properties(); private final Properties header = new Properties();
private final Gson gson;
public OpenUVBridgeHandler(Bridge bridge) { private final LocationProvider locationProvider;
private @Nullable ScheduledFuture<?> reconnectJob;
public OpenUVBridgeHandler(Bridge bridge, LocationProvider locationProvider, Gson gson) {
super(bridge); super(bridge);
this.gson = gson;
this.locationProvider = locationProvider;
} }
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing OpenUV API bridge handler."); logger.debug("Initializing OpenUV API bridge handler.");
Configuration config = getThing().getConfiguration(); BridgeConfiguration config = getConfigAs(BridgeConfiguration.class);
String apiKey = (String) config.get(OpenUVBindingConstants.APIKEY); if (config.apikey.isEmpty()) {
if (StringUtils.trimToNull(apiKey) == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'apikey' must be configured."); "Parameter 'apikey' must be configured.");
} else { } else {
header.put("x-access-token", apiKey); header.put("x-access-token", config.apikey);
initiateConnexion(); initiateConnexion();
} }
} }
@Override
public void dispose() {
ScheduledFuture<?> job = this.reconnectJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
}
reconnectJob = null;
}
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
@ -106,62 +105,49 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
private void initiateConnexion() { private void initiateConnexion() {
// Check if the provided api key is valid for use with the OpenUV service // 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() { public @Nullable OpenUVResult getUVData(String latitude, String longitude, String altitude) {
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;
try { 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); OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class);
if (uvResponse.getError() == null) { if (uvResponse.getError() == null) {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
return uvResponse.getResult(); return uvResponse.getResult();
} else { } else {
errorMessage = uvResponse.getError(); throw new OpenUVException(uvResponse.getError());
} }
} catch (IOException e) { } catch (IOException e) {
errorMessage = e.getMessage(); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} } catch (OpenUVException e) {
if (e.isQuotaError()) {
if (errorMessage.startsWith(ERROR_QUOTA_EXCEEDED)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
LocalDate today = LocalDate.now(); LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1); LocalDate tomorrow = today.plusDays(1);
LocalDateTime tomorrowMidnight = tomorrow.atStartOfDay().plusMinutes(2); LocalDateTime tomorrowMidnight = tomorrow.atStartOfDay().plusMinutes(2);
logger.warn("Quota Exceeded, going OFFLINE for today, will retry at : {} ", tomorrowMidnight); String message = "Quota Exceeded, going OFFLINE for today, will retry at : "
scheduler.schedule(this::initiateConnexion, + tomorrowMidnight.toString();
Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
} else if (errorMessage.startsWith(ERROR_WRONG_KEY)) { reconnectJob = scheduler.schedule(this::initiateConnexion,
logger.error("Error occured during API query : {}", errorMessage); Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage); } else if (e.isApiKeyError()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
}
} }
return null; return null;
} }
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OpenUVDiscoveryService.class);
}
public @Nullable PointType getLocation() {
return locationProvider.getLocation();
}
} }

View File

@ -23,11 +23,13 @@ import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Angle; import javax.measure.quantity.Angle;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.openuv.internal.ReportConfiguration; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.SafeExposureConfiguration; 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.binding.openuv.internal.json.OpenUVResult;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.HSBType; 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.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits; import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -54,13 +56,11 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenUVReportHandler extends BaseThingHandler { public class OpenUVReportHandler extends BaseThingHandler {
private static final int DEFAULT_REFRESH_PERIOD = 30;
private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class); private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler; private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
private @NonNullByDefault({}) ScheduledFuture<?> refreshJob; private @Nullable ScheduledFuture<?> refreshJob;
private @NonNullByDefault({}) ScheduledFuture<?> uvMaxJob; private @Nullable ScheduledFuture<?> uvMaxJob;
private boolean suspendUpdates = false; private boolean suspendUpdates = false;
public OpenUVReportHandler(Thing thing) { public OpenUVReportHandler(Thing thing) {
@ -73,7 +73,7 @@ public class OpenUVReportHandler extends BaseThingHandler {
ReportConfiguration config = getConfigAs(ReportConfiguration.class); ReportConfiguration config = getConfigAs(ReportConfiguration.class);
if (config.refresh != null && config.refresh < 3) { if (config.refresh < 3) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'refresh' must be higher than 3 minutes to stay in free API plan"); "Parameter 'refresh' must be higher than 3 minutes to stay in free API plan");
} else { } else {
@ -94,7 +94,8 @@ public class OpenUVReportHandler extends BaseThingHandler {
* @param openUVData * @param openUVData
*/ */
private void scheduleUVMaxEvent(OpenUVResult openUVData) { private void scheduleUVMaxEvent(OpenUVResult openUVData) {
if ((uvMaxJob == null || uvMaxJob.isCancelled())) { ScheduledFuture<?> job = this.uvMaxJob;
if ((job == null || job.isCancelled())) {
State uvMaxTime = openUVData.getUVMaxTime(); State uvMaxTime = openUVData.getUVMaxTime();
if (uvMaxTime != UnDefType.NULL) { if (uvMaxTime != UnDefType.NULL) {
ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime(); ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
@ -114,22 +115,23 @@ public class OpenUVReportHandler extends BaseThingHandler {
* Start the job refreshing the data * Start the job refreshing the data
*/ */
private void startAutomaticRefresh() { private void startAutomaticRefresh() {
if (refreshJob == null || refreshJob.isCancelled()) { ScheduledFuture<?> job = this.refreshJob;
if (job == null || job.isCancelled()) {
ReportConfiguration config = getConfigAs(ReportConfiguration.class); ReportConfiguration config = getConfigAs(ReportConfiguration.class);
int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
refreshJob = scheduler.scheduleWithFixedDelay(() -> { refreshJob = scheduler.scheduleWithFixedDelay(() -> {
if (!suspendUpdates) { if (!suspendUpdates) {
updateChannels(config); updateChannels(config);
} }
}, 0, delay, TimeUnit.MINUTES); }, 0, config.refresh, TimeUnit.MINUTES);
} }
} }
private void updateChannels(ReportConfiguration config) { private void updateChannels(ReportConfiguration config) {
ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo(); ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) { if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
OpenUVResult openUVData = bridgeHandler.getUVData(config.getLatitude(), config.getLongitude(), PointType location = new PointType(config.location);
config.getAltitude()); OpenUVResult openUVData = bridgeHandler.getUVData(location.getLatitude().toString(),
location.getLongitude().toString(), location.getAltitude().toString());
if (openUVData != null) { if (openUVData != null) {
scheduleUVMaxEvent(openUVData); scheduleUVMaxEvent(openUVData);
getThing().getChannels().forEach(channel -> { getThing().getChannels().forEach(channel -> {
@ -146,16 +148,17 @@ public class OpenUVReportHandler extends BaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
logger.debug("Disposing the OpenUV handler."); logger.debug("Disposing the OpenUV handler.");
ScheduledFuture<?> refresh = this.refreshJob;
if (refreshJob != null && !refreshJob.isCancelled()) { if (refresh != null && !refresh.isCancelled()) {
refreshJob.cancel(true); refresh.cancel(true);
}
refreshJob = null; refreshJob = null;
}
if (uvMaxJob != null && !uvMaxJob.isCancelled()) { ScheduledFuture<?> uxMax = this.uvMaxJob;
uvMaxJob.cancel(true); if (uxMax != null && !uxMax.isCancelled()) {
uvMaxJob = null; uxMax.cancel(true);
} }
uvMaxJob = null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")