added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.openuv</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,102 @@
# OpenUV Binding
This binding uses the [OpenUV Index API service](https://www.openuv.io/) for providing UV Index information for any location worldwide.
To use this binding, you first need to [register and get your API token](https://www.openuv.io/auth/google).
## Binding Installation
This binding can be installed via the Add-ons section of the Paper UI.
Go to Bindings and search for `OpenUV`. Click on install.
## Discovery
Once a bridge with the api Key has been created, Local UV Index informations can be autodiscovered based on system location.
## Binding Configuration
The binding has no configuration options, all configuration is done at Bridge and Thing level.
## Bridge Configuration
The bridge has only one configuration parameter :
| Parameter | Description |
|-----------|--------------------------------------------------------------|
| apikey | Data-platform token to access the OpenUV service. Mandatory. |
Will accept a Refresh command in order to reinitiate connexion (eg in case of Quota exceeded).
## Thing Configuration
The thing has a few configuration parameters :
| Parameter | Description |
|-----------|--------------------------------------------------------------|
| location | Geo coordinates to be considered by the service. |
| refresh | Refresh interval in minutes. Optional. |
For the location parameter, the following syntax is allowed (comma separated latitude, longitude and optional altitude):
```java
37.8,-122.4
37.8255,-122.456
37.8,-122.4,177
```
## Channels
The OpenUV Report thing that is retrieved has these channels:
| Channel ID | Item Type | Description |
|--------------|---------------------|-------------------------------------------------|
| UVIndex | Number | UV Index |
| UVColor | Color | Color associated to given UV Index. |
| UVMax | Number | Max UV Index for the day (at solar noon) |
| UVMaxTime | DateTime | Max UV Index datetime (solar noon) |
| Ozone | Number:ArealDensity | Ozone level in du (Dobson Units) from OMI data |
| OzoneTime | DateTime | Latest OMI ozone update datetime |
| UVTime | DateTime | UV Index datetime |
| SafeExposure | Number:Time | Safe exposure time for Fitzpatrick Skin Types. |
| elevation | Number:Angle | Current Sun elevation. |
The elevation channel will be used as an input in order to limit API queries to OpenUV. If not used,
the binding will not consider it. When value is provided queries will only be issued if the elevation is > 0°.
## Examples
demo.things:
```xtend
Bridge openuv:openuvapi:local "OpenUV Api" [ apikey="xxxxYYYxxxx" ] {
Thing uvreport city1 "UV In My City" [ location="52.5200066,13.4049540", refresh=10 ]{
Channels:
Type SafeExposure : Parents [
index=3
]
Type SafeExposure : Childs [
index=2
]
}
}
```
demo.items:
```xtend
Number UVIndex "UV Index" {channel="openuv:uvreport:local:city1:UVIndex" }
Number UVMax "UV Max" {channel="openuv:uvreport:local:city1:UVMaxEvent" }
Number:ArealDensity Ozone "Ozone" {channel="openuv:uvreport:local:city1:Ozone" }
```
astro.items:
```xtend
Number:Angle Elevation "Elevation" {channel="astro:sun:home:position#elevation",
channel="openuv:uvreport:local:city1:elevation" [profile="follow"] }
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.openuv</artifactId>
<name>openHAB Add-ons :: Bundles :: OpenUV Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.openuv-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-openuv" description="OpenUV Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.openuv/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,59 @@
/**
* 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 java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link OpenUVBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class OpenUVBindingConstants {
public static final String BASE_URL = "https://api.openuv.io/api/v1/uv";
public static final String BINDING_ID = "openuv";
public static final String LOCAL = "local";
public static final String LOCATION = "location";
public static final String APIKEY = "apikey";
// List of Bridge Type UIDs
public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "openuvapi");
// List of Things Type UIDs
public static final ThingTypeUID LOCATION_REPORT_THING_TYPE = new ThingTypeUID(BINDING_ID, "uvreport");
// List of all Channel id's
public static final String UV_INDEX = "UVIndex";
public static final String UV_COLOR = "UVColor";
public static final String UV_MAX = "UVMax";
public static final String UV_MAX_TIME = "UVMaxTime";
public static final String UV_MAX_EVENT = "UVMaxEvent";
public static final String OZONE = "Ozone";
public static final String OZONE_TIME = "OzoneTime";
public static final String UV_TIME = "UVTime";
public static final String SAFE_EXPOSURE = "SafeExposure";
public static final String ELEVATION = "elevation";
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(APIBRIDGE_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
Arrays.asList(LOCATION_REPORT_THING_TYPE));
}

View File

@@ -0,0 +1,78 @@
/**
* 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 static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
import java.util.Hashtable;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.discovery.OpenUVDiscoveryService;
import org.openhab.binding.openuv.internal.handler.OpenUVBridgeHandler;
import org.openhab.binding.openuv.internal.handler.OpenUVReportHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link OpenUVHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.openuv")
public class OpenUVHandlerFactory extends BaseThingHandlerFactory {
private final LocationProvider locationProvider;
@Activate
public OpenUVHandlerFactory(@Reference LocationProvider locationProvider) {
this.locationProvider = locationProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
OpenUVBridgeHandler handler = new OpenUVBridgeHandler((Bridge) thing);
registerOpenUVDiscoveryService(handler);
return handler;
} else if (LOCATION_REPORT_THING_TYPE.equals(thingTypeUID)) {
return new OpenUVReportHandler(thing);
}
return null;
}
private void registerOpenUVDiscoveryService(OpenUVBridgeHandler bridgeHandler) {
OpenUVDiscoveryService discoveryService = new OpenUVDiscoveryService(bridgeHandler, locationProvider);
bridgeHandler.getDiscoveryServiceRegs().put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.openuv.internal;
/**
* The {@link SafeExposureConfiguration} is the class used to match the
* SafeExposure channel configuration.
*
* @author Gaël L"hopital - Initial contribution
*/
public class SafeExposureConfiguration {
public int index = -1;
}

View File

@@ -0,0 +1,120 @@
/**
* 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.discovery;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.handler.OpenUVBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenUVDiscoveryService} creates things based on the configured location.
*
* @author Gaël L'hopital - Initial Contribution
*/
@NonNullByDefault
public class OpenUVDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(OpenUVDiscoveryService.class);
private static final int DISCOVER_TIMEOUT_SECONDS = 10;
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
private final LocationProvider locationProvider;
private final OpenUVBridgeHandler bridgeHandler;
private @Nullable ScheduledFuture<?> discoveryJob;
private @Nullable PointType previousLocation;
/**
* Creates a OpenUVDiscoveryService with enabled autostart.
*/
public OpenUVDiscoveryService(OpenUVBridgeHandler bridgeHandler, LocationProvider locationProvider) {
super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true);
this.locationProvider = locationProvider;
this.bridgeHandler = bridgeHandler;
}
@Override
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Override
@Modified
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
super.modified(configProperties);
}
@Override
protected void startScan() {
logger.debug("Starting OpenUV discovery scan");
PointType location = locationProvider.getLocation();
if (location == null) {
logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
return;
}
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");
}
}
}
}

View File

@@ -0,0 +1,167 @@
/**
* 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.handler;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.BASE_URL;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.OpenUVBindingConstants;
import org.openhab.binding.openuv.internal.json.OpenUVResponse;
import org.openhab.binding.openuv.internal.json.OpenUVResult;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
/**
* {@link OpenUVBridgeHandler} is the handler for OpenUV API and connects it
* to the webservice.
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class OpenUVBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(OpenUVBridgeHandler.class);
private static final String ERROR_QUOTA_EXCEEDED = "Daily API quota exceeded";
private static final String ERROR_WRONG_KEY = "User with API Key not found";
private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(30);
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(DecimalType.class,
(JsonDeserializer<DecimalType>) (json, type, jsonDeserializationContext) -> DecimalType
.valueOf(json.getAsJsonPrimitive().getAsString()))
.registerTypeAdapter(ZonedDateTime.class,
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
.parse(json.getAsJsonPrimitive().getAsString()))
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
private Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final Properties header = new Properties();
public OpenUVBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
logger.debug("Initializing OpenUV API bridge handler.");
Configuration config = getThing().getConfiguration();
String apiKey = (String) config.get(OpenUVBindingConstants.APIKEY);
if (StringUtils.trimToNull(apiKey) == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'apikey' must be configured.");
} else {
header.put("x-access-token", apiKey);
initiateConnexion();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
initiateConnexion();
} else {
logger.debug("The OpenUV bridge only handles Refresh command and not '{}'", command);
}
}
private void initiateConnexion() {
// Check if the provided api key is valid for use with the OpenUV service
getUVData("0", "0", null);
}
public Map<ThingUID, @Nullable ServiceRegistration<?>> getDiscoveryServiceRegs() {
return discoveryServiceRegs;
}
public void setDiscoveryServiceRegs(Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs) {
this.discoveryServiceRegs = discoveryServiceRegs;
}
@Override
public void handleRemoval() {
// removes the old registration service associated to the bridge, if existing
ServiceRegistration<?> dis = getDiscoveryServiceRegs().get(getThing().getUID());
if (dis != null) {
dis.unregister();
}
super.handleRemoval();
}
public @Nullable OpenUVResult getUVData(String latitude, String longitude, @Nullable String altitude) {
StringBuilder urlBuilder = new StringBuilder(BASE_URL).append("?lat=").append(latitude).append("&lng=")
.append(longitude);
if (altitude != null) {
urlBuilder.append("&alt=").append(altitude);
}
String errorMessage = null;
try {
String jsonData = HttpUtil.executeUrl("GET", urlBuilder.toString(), header, null, null, REQUEST_TIMEOUT);
OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class);
if (uvResponse.getError() == null) {
updateStatus(ThingStatus.ONLINE);
return uvResponse.getResult();
} else {
errorMessage = uvResponse.getError();
}
} catch (IOException e) {
errorMessage = e.getMessage();
}
if (errorMessage.startsWith(ERROR_QUOTA_EXCEEDED)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
LocalDateTime tomorrowMidnight = tomorrow.atStartOfDay().plusMinutes(2);
logger.warn("Quota Exceeded, going OFFLINE for today, will retry at : {} ", tomorrowMidnight);
scheduler.schedule(this::initiateConnexion,
Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
} else if (errorMessage.startsWith(ERROR_WRONG_KEY)) {
logger.error("Error occured during API query : {}", errorMessage);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
}
return null;
}
}

View File

@@ -0,0 +1,241 @@
/**
* 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.handler;
import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Angle;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.openuv.internal.ReportConfiguration;
import org.openhab.binding.openuv.internal.SafeExposureConfiguration;
import org.openhab.binding.openuv.internal.json.OpenUVResult;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenUVReportHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class OpenUVReportHandler extends BaseThingHandler {
private static final int DEFAULT_REFRESH_PERIOD = 30;
private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
private @NonNullByDefault({}) ScheduledFuture<?> refreshJob;
private @NonNullByDefault({}) ScheduledFuture<?> uvMaxJob;
private boolean suspendUpdates = false;
public OpenUVReportHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing OpenUV handler.");
ReportConfiguration config = getConfigAs(ReportConfiguration.class);
if (config.refresh != null && config.refresh < 3) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'refresh' must be higher than 3 minutes to stay in free API plan");
} else {
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid bridge");
} else {
bridgeHandler = (OpenUVBridgeHandler) bridge.getHandler();
updateStatus(ThingStatus.UNKNOWN);
startAutomaticRefresh();
}
}
}
/**
* Start the job screening UV Max reached
*
* @param openUVData
*/
private void scheduleUVMaxEvent(OpenUVResult openUVData) {
if ((uvMaxJob == null || uvMaxJob.isCancelled())) {
State uvMaxTime = openUVData.getUVMaxTime();
if (uvMaxTime != UnDefType.NULL) {
ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
long timeDiff = ChronoUnit.MINUTES.between(ZonedDateTime.now(ZoneId.systemDefault()), uvMaxZdt);
if (timeDiff > 0) {
logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
uvMaxJob = scheduler.schedule(() -> {
triggerChannel(UV_MAX_EVENT);
uvMaxJob = null;
}, timeDiff, TimeUnit.MINUTES);
}
}
}
}
/**
* Start the job refreshing the data
*/
private void startAutomaticRefresh() {
if (refreshJob == null || refreshJob.isCancelled()) {
ReportConfiguration config = getConfigAs(ReportConfiguration.class);
int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
refreshJob = scheduler.scheduleWithFixedDelay(() -> {
if (!suspendUpdates) {
updateChannels(config);
}
}, 0, delay, TimeUnit.MINUTES);
}
}
private void updateChannels(ReportConfiguration config) {
ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
OpenUVResult openUVData = bridgeHandler.getUVData(config.getLatitude(), config.getLongitude(),
config.getAltitude());
if (openUVData != null) {
scheduleUVMaxEvent(openUVData);
getThing().getChannels().forEach(channel -> {
updateChannel(channel.getUID(), openUVData);
});
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
bridgeStatusInfo.getDescription());
}
}
}
@Override
public void dispose() {
logger.debug("Disposing the OpenUV handler.");
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
if (uvMaxJob != null && !uvMaxJob.isCancelled()) {
uvMaxJob.cancel(true);
uvMaxJob = null;
}
}
@SuppressWarnings("unchecked")
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
scheduler.execute(() -> {
ReportConfiguration config = getConfigAs(ReportConfiguration.class);
updateChannels(config);
});
} else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
QuantityType<?> qtty = (QuantityType<?>) command;
if ("°".equals(qtty.getUnit().toString())) {
suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0;
} else {
logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
}
} else {
logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
}
}
/**
* Update the channel from the last OpenUV data retrieved
*
* @param channelUID the id identifying the channel to be updated
* @param openUVData
*
*/
private void updateChannel(ChannelUID channelUID, OpenUVResult openUVData) {
Channel channel = getThing().getChannel(channelUID.getId());
if (channel != null && isLinked(channelUID)) {
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID != null) {
switch (channelTypeUID.getId()) {
case UV_INDEX:
updateState(channelUID, openUVData.getUv());
break;
case UV_COLOR:
updateState(channelUID, getAsHSB(openUVData.getUv().intValue()));
break;
case UV_MAX:
updateState(channelUID, openUVData.getUvMax());
break;
case OZONE:
updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
break;
case OZONE_TIME:
updateState(channelUID, openUVData.getOzoneTime());
break;
case UV_MAX_TIME:
updateState(channelUID, openUVData.getUVMaxTime());
break;
case UV_TIME:
updateState(channelUID, openUVData.getUVTime());
break;
case SAFE_EXPOSURE:
SafeExposureConfiguration configuration = channel.getConfiguration()
.as(SafeExposureConfiguration.class);
if (configuration.index != -1) {
updateState(channelUID,
openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
}
break;
}
}
}
}
private State getAsHSB(int uv) {
if (uv >= 11) {
return HSBType.fromRGB(106, 27, 154);
} else if (uv >= 8) {
return HSBType.fromRGB(183, 28, 28);
} else if (uv >= 6) {
return HSBType.fromRGB(239, 108, 0);
} else if (uv >= 3) {
return HSBType.fromRGB(249, 168, 37);
} else {
return HSBType.fromRGB(85, 139, 47);
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.json;
/**
* The {@link OpenUVResponse} is the Java class used to map the JSON
* response to the OpenUV request.
*
* @author Gaël L'hopital - Initial contribution
*/
public class OpenUVResponse {
private String error;
private OpenUVResult result;
public OpenUVResult getResult() {
return result;
}
public String getError() {
return error;
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.json;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link OpenUVResult} is responsible for storing
* the "result" node from the OpenUV JSON response
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class OpenUVResult {
private final ZonedDateTime DEFAULT_ZDT = ZonedDateTime.of(LocalDateTime.MIN, ZoneId.systemDefault());
private DecimalType uv = new DecimalType(0);
private ZonedDateTime uvTime = DEFAULT_ZDT;
private DecimalType uvMax = new DecimalType(0);
private ZonedDateTime uvMaxTime = DEFAULT_ZDT;
private DecimalType ozone = new DecimalType(0);
private ZonedDateTime ozoneTime = DEFAULT_ZDT;
private SafeExposureTime safeExposureTime = new SafeExposureTime();
public DecimalType getUv() {
return uv;
}
public DecimalType getUvMax() {
return uvMax;
}
public DecimalType getOzone() {
return ozone;
}
public State getUVTime() {
return uvTime != DEFAULT_ZDT ? new DateTimeType(uvTime.withZoneSameInstant(ZoneId.systemDefault()))
: UnDefType.NULL;
}
public State getUVMaxTime() {
return uvMaxTime != DEFAULT_ZDT ? new DateTimeType(uvMaxTime.withZoneSameInstant(ZoneId.systemDefault()))
: UnDefType.NULL;
}
public State getOzoneTime() {
return ozoneTime != DEFAULT_ZDT ? new DateTimeType(ozoneTime.withZoneSameInstant(ZoneId.systemDefault()))
: UnDefType.NULL;
}
public SafeExposureTime getSafeExposureTime() {
return safeExposureTime;
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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.json;
import java.math.BigInteger;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Wrapper type around values reported by OpenUV safe exposure time.
*
* @author Gaël L'hopital - Initial contribution
*/
public class SafeExposureTime {
public @Nullable BigInteger st1;
public @Nullable BigInteger st2;
public @Nullable BigInteger st3;
public @Nullable BigInteger st4;
public @Nullable BigInteger st5;
public @Nullable BigInteger st6;
public State getSafeExposure(int index) {
BigInteger result;
switch (index) {
case 1:
result = st1;
break;
case 2:
result = st2;
break;
case 3:
result = st3;
break;
case 4:
result = st4;
break;
case 5:
result = st5;
break;
case 6:
result = st6;
break;
default:
result = null;
}
return (result != null) ? new QuantityType<>(result, SmartHomeUnits.MINUTE) : UnDefType.NULL;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="openuv" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>OpenUV Binding</name>
<description>Global Real-Time UV Index Forecast API</description>
<author>Gaël L'hopital</author>
</binding:binding>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openuv"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- OpenUV Bridge -->
<bridge-type id="openuvapi">
<label>Open UV API</label>
<description>
Bridge to the OpenUV Project API. In order to receive the data, you must register an account on
https://www.openuv.io/auth/google and get your API token.
</description>
<config-description>
<parameter name="apikey" type="text" required="true">
<context>password</context>
<label>API Key</label>
<description>Data-platform token to access the OpenUV API service</description>
</parameter>
</config-description>
</bridge-type>
<!-- OpenUV Report Thing -->
<thing-type id="uvreport">
<supported-bridge-type-refs>
<bridge-type-ref id="openuvapi"/>
</supported-bridge-type-refs>
<label>UV Report</label>
<description>
Provides various UV data from the OpenUV Project for a given location.
</description>
<channels>
<channel id="UVIndex" typeId="UVIndex"/>
<channel id="UVColor" typeId="UVColor"/>
<channel id="UVMax" typeId="UVMax"/>
<channel id="UVMaxTime" typeId="UVMaxTime"/>
<channel id="UVMaxEvent" typeId="UVMaxEvent"/>
<channel id="Ozone" typeId="Ozone"/>
<channel id="OzoneTime" typeId="OzoneTime"/>
<channel id="UVTime" typeId="UVTime"/>
<channel id="SafeExposure" typeId="SafeExposure"/>
<channel id="elevation" typeId="elevation"/>
</channels>
<representation-property>location</representation-property>
<config-description>
<parameter name="refresh" type="integer" min="3">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in minutes.</description>
<default>10</default>
</parameter>
<parameter name="location" type="text" required="true">
<label>Location</label>
<context>location</context>
<description>Your geo coordinates separated with comma (e.g. "37.8,-122.4,177").</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="UVIndex">
<item-type>Number</item-type>
<label>UV Index</label>
<description>UV Index</description>
<state readOnly="true" pattern="%.2f/16" min="0" max="16"/>
</channel-type>
<channel-type id="UVMax" advanced="true">
<item-type>Number</item-type>
<label>UV Max</label>
<description>Max UV Index for the day (at solar noon)</description>
<state readOnly="true" pattern="%.2f/16" min="0" max="16"/>
</channel-type>
<channel-type id="Ozone">
<item-type>Number:ArealDensity</item-type>
<label>Ozone</label>
<description>Ozone level from OMI data</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="OzoneTime" advanced="true">
<item-type>DateTime</item-type>
<label>Ozone Observation Time</label>
<description>Latest OMI ozone update time</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="UVMaxTime" advanced="true">
<item-type>DateTime</item-type>
<label>UV Max Time</label>
<description>Max UV Index time (solar noon)</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="UVTime" advanced="true">
<item-type>DateTime</item-type>
<label>UV Time</label>
<description>UV Index time</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="UVColor" advanced="true">
<item-type>Color</item-type>
<label>UV Color</label>
<description>Color associated to given UV Index.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="SafeExposure" advanced="false">
<item-type>Number:Time</item-type>
<label>Safe Exposure</label>
<description>Safe exposure time for Fitzpatrick Skin Types</description>
<state readOnly="true" pattern="%d %unit%"/>
<config-description>
<parameter name="index" type="integer">
<label>Skin Type</label>
<description>Fitzpatrick Skin Type.</description>
<options>
<option value="1">I White</option>
<option value="2">II White</option>
<option value="3">III Light brown</option>
<option value="4">IV Moderate brown</option>
<option value="5">V Dark brown</option>
<option value="6">VI Black</option>
</options>
<default>2</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="elevation">
<item-type>Number:Angle</item-type>
<label>Elevation</label>
<description>The elevation of the sun</description>
<state pattern="%.2f %unit%"/>
</channel-type>
<!-- UV Max Event Channel Type -->
<channel-type id="UVMaxEvent">
<kind>trigger</kind>
<label>UV Max Event</label>
<description>Triggers when current UV Index reaches maximum of the day</description>
</channel-type>
</thing:thing-descriptions>