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,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>