mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 23:22:02 +01:00
[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:
parent
d1803dc723
commit
74ee8e76cf
@ -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");
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<>()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 = "";
|
||||||
|
}
|
@ -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 = "";
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user