added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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<>()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user