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.darksky-${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-darksky" description="Dark Sky Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.darksky/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.darksky.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyBindingConstants} class defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "darksky";
|
||||
|
||||
public static final String API = "api";
|
||||
public static final String LOCAL = "local";
|
||||
|
||||
// Bridge
|
||||
public static final ThingTypeUID THING_TYPE_WEATHER_API = new ThingTypeUID(BINDING_ID, "weather-api");
|
||||
|
||||
// Thing
|
||||
public static final ThingTypeUID THING_TYPE_WEATHER_AND_FORECAST = new ThingTypeUID(BINDING_ID,
|
||||
"weather-and-forecast");
|
||||
|
||||
// List of all properties
|
||||
public static final String CONFIG_API_KEY = "apikey";
|
||||
public static final String CONFIG_LANGUAGE = "language";
|
||||
public static final String CONFIG_LOCATION = "location";
|
||||
|
||||
// Channel group types
|
||||
public static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_HOURLY_FORECAST = new ChannelGroupTypeUID(BINDING_ID,
|
||||
"hourlyForecast");
|
||||
public static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_DAILY_FORECAST = new ChannelGroupTypeUID(BINDING_ID,
|
||||
"dailyForecast");
|
||||
public static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_ALERTS = new ChannelGroupTypeUID(BINDING_ID, "alerts");
|
||||
|
||||
// List of all channel groups
|
||||
public static final String CHANNEL_GROUP_CURRENT_WEATHER = "current";
|
||||
public static final String CHANNEL_GROUP_FORECAST_TODAY = "forecastToday";
|
||||
public static final String CHANNEL_GROUP_FORECAST_TOMORROW = "forecastTomorrow";
|
||||
public static final String CHANNEL_GROUP_ALERTS = "alerts";
|
||||
|
||||
// List of all channels
|
||||
public static final String CHANNEL_TIME_STAMP = "time-stamp";
|
||||
public static final String CHANNEL_CONDITION = "condition";
|
||||
public static final String CHANNEL_CONDITION_ICON = "icon";
|
||||
public static final String CHANNEL_CONDITION_ICON_ID = "icon-id";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_MIN_TEMPERATURE = "min-temperature";
|
||||
public static final String CHANNEL_MAX_TEMPERATURE = "max-temperature";
|
||||
public static final String CHANNEL_APPARENT_TEMPERATURE = "apparent-temperature";
|
||||
public static final String CHANNEL_MIN_APPARENT_TEMPERATURE = "min-apparent-temperature";
|
||||
public static final String CHANNEL_MAX_APPARENT_TEMPERATURE = "max-apparent-temperature";
|
||||
public static final String CHANNEL_PRESSURE = "pressure";
|
||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
||||
public static final String CHANNEL_WIND_SPEED = "wind-speed";
|
||||
public static final String CHANNEL_WIND_DIRECTION = "wind-direction";
|
||||
public static final String CHANNEL_GUST_SPEED = "gust-speed";
|
||||
public static final String CHANNEL_CLOUDINESS = "cloudiness";
|
||||
public static final String CHANNEL_VISIBILITY = "visibility";
|
||||
public static final String CHANNEL_RAIN = "rain";
|
||||
public static final String CHANNEL_SNOW = "snow";
|
||||
public static final String CHANNEL_PRECIPITATION_INTENSITY = "precip-intensity";
|
||||
public static final String CHANNEL_PRECIPITATION_PROBABILITY = "precip-probability";
|
||||
public static final String CHANNEL_PRECIPITATION_TYPE = "precip-type";
|
||||
public static final String CHANNEL_UVINDEX = "uvindex";
|
||||
public static final String CHANNEL_OZONE = "ozone";
|
||||
public static final String CHANNEL_SUNRISE = "sunrise";
|
||||
public static final String CHANNEL_SUNSET = "sunset";
|
||||
public static final String CHANNEL_ALERT_TITLE = "title";
|
||||
public static final String CHANNEL_ALERT_ISSUED = "issued";
|
||||
public static final String CHANNEL_ALERT_EXPIRES = "expires";
|
||||
public static final String CHANNEL_ALERT_DESCRIPTION = "description";
|
||||
public static final String CHANNEL_ALERT_SEVERITY = "severity";
|
||||
public static final String CHANNEL_ALERT_URI = "uri";
|
||||
|
||||
public static final String TRIGGER_SUNRISE = CHANNEL_GROUP_CURRENT_WEATHER + "#sunrise-event";
|
||||
public static final String TRIGGER_SUNSET = CHANNEL_GROUP_CURRENT_WEATHER + "#sunset-event";
|
||||
|
||||
public static final String EVENT_START = "START";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.darksky.internal.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyAPIHandler;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyAPIConfiguration} is the class used to match the {@link DarkSkyAPIHandler}s configuration.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyAPIConfiguration {
|
||||
|
||||
// supported languages (see https://darksky.net/dev/docs#forecast-request)
|
||||
public static final Set<String> SUPPORTED_LANGUAGES = Collections.unmodifiableSet(Stream.of("ar", "az", "be", "bg",
|
||||
"bn", "bs", "ca", "cs", "da", "de", "el", "en", "eo", "es", "et", "fi", "fr", "he", "hi", "hr", "hu", "id",
|
||||
"is", "it", "ja", "ka", "kn", "ko", "kw", "lv", "mr", "nb", "nl", "no", "pa", "pl", "pt", "ro", "ru", "sk",
|
||||
"sl", "sr", "sv", "ta", "te", "tet", "tr", "uk", "x-pig-latin", "zh", "zh-tw").collect(Collectors.toSet()));
|
||||
|
||||
public @Nullable String apikey;
|
||||
public int refreshInterval = 60;
|
||||
public @Nullable String language;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.darksky.internal.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyChannelConfiguration} is the class used to match the sunrise and sunset event configuration.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyChannelConfiguration {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DarkSkyChannelConfiguration.class);
|
||||
|
||||
private static final Pattern HHMM_PATTERN = Pattern.compile("^([0-1][0-9]|2[0-3])(:[0-5][0-9])$");
|
||||
private static final String TIME_SEPARATOR = ":";
|
||||
|
||||
private int offset;
|
||||
private @Nullable String earliest;
|
||||
private @Nullable String latest;
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public @Nullable String getEarliest() {
|
||||
return earliest;
|
||||
}
|
||||
|
||||
public long getEarliestInMinutes() {
|
||||
return getMinutesFromTime(earliest);
|
||||
}
|
||||
|
||||
public void setEarliest(String earliest) {
|
||||
this.earliest = earliest;
|
||||
}
|
||||
|
||||
public @Nullable String getLatest() {
|
||||
return latest;
|
||||
}
|
||||
|
||||
public long getLatestInMinutes() {
|
||||
return getMinutesFromTime(latest);
|
||||
}
|
||||
|
||||
public void setLatest(String latest) {
|
||||
this.latest = latest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[offset=%d, earliest='%s', latest='%s']", offset, earliest, latest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a hh:mm string and returns the minutes.
|
||||
*/
|
||||
private long getMinutesFromTime(@Nullable String configTime) {
|
||||
String time = configTime;
|
||||
if (time != null && !(time = time.trim()).isEmpty()) {
|
||||
try {
|
||||
if (!HHMM_PATTERN.matcher(time).matches()) {
|
||||
throw new NumberFormatException();
|
||||
} else {
|
||||
String[] splittedConfigTime = time.split(TIME_SEPARATOR);
|
||||
int hour = Integer.parseInt(splittedConfigTime[0]);
|
||||
int minutes = Integer.parseInt(splittedConfigTime[1]);
|
||||
return Duration.ofMinutes(minutes).plusHours(hour).toMinutes();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Cannot parse channel configuration '{}' to hour and minutes, use pattern hh:mm, ignoring!",
|
||||
time);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.darksky.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyWeatherAndForecastHandler;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyWeatherAndForecastConfiguration} is the class used to match the
|
||||
* {@link DarkSkyWeatherAndForecastHandler}s configuration.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyWeatherAndForecastConfiguration {
|
||||
public @NonNullByDefault({}) String location;
|
||||
public int forecastHours;
|
||||
public int forecastDays;
|
||||
public int numberOfAlerts;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.darksky.internal.connection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyCommunicationException} is a communication exception for the connections to Dark Sky API.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyCommunicationException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with null as its detail message.
|
||||
*/
|
||||
public DarkSkyCommunicationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
*
|
||||
* @param message Detail message
|
||||
*/
|
||||
public DarkSkyCommunicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause.
|
||||
*
|
||||
* @param cause The cause
|
||||
*/
|
||||
public DarkSkyCommunicationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
*
|
||||
* @param message Detail message
|
||||
* @param cause The cause
|
||||
*/
|
||||
public DarkSkyCommunicationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.darksky.internal.connection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyConfigurationException} is a configuration exception for the connections to Dark Sky API.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyConfigurationException extends IllegalArgumentException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with null as its detail message.
|
||||
*/
|
||||
public DarkSkyConfigurationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
*
|
||||
* @param message Detail message
|
||||
*/
|
||||
public DarkSkyConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause.
|
||||
*
|
||||
* @param cause The cause
|
||||
*/
|
||||
public DarkSkyConfigurationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
*
|
||||
* @param message Detail message
|
||||
* @param cause The cause
|
||||
*/
|
||||
public DarkSkyConfigurationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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.darksky.internal.connection;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpStatus.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.darksky.internal.config.DarkSkyAPIConfiguration;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyAPIHandler;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyJsonWeatherData;
|
||||
import org.openhab.binding.darksky.internal.utils.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyConnection} is responsible for handling the connections to Dark Sky API.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DarkSkyConnection.class);
|
||||
|
||||
private static final String PNG_CONTENT_TYPE = "image/png";
|
||||
|
||||
private static final String PARAM_EXCLUDE = "exclude";
|
||||
private static final String PARAM_UNITS = "units";
|
||||
private static final String PARAM_LANG = "lang";
|
||||
|
||||
// Current weather data (see https://darksky.net/dev/docs#forecast-request)
|
||||
private static final String WEATHER_URL = "https://api.darksky.net/forecast/%s/%f,%f";
|
||||
// Weather icons (see https://darksky.net/dev/docs/faq#icons)
|
||||
private static final String ICON_URL = "https://darksky.net/images/weather-icons/%s.png";
|
||||
|
||||
private final DarkSkyAPIHandler handler;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private static final ByteArrayFileCache IMAGE_CACHE = new ByteArrayFileCache("org.openhab.binding.darksky");
|
||||
private final ExpiringCacheMap<String, String> cache;
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public DarkSkyConnection(DarkSkyAPIHandler handler, HttpClient httpClient) {
|
||||
this.handler = handler;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
DarkSkyAPIConfiguration config = handler.getDarkSkyAPIConfig();
|
||||
cache = new ExpiringCacheMap<>(TimeUnit.MINUTES.toMillis(config.refreshInterval));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the current weather data for the given location (see https://darksky.net/dev/docs#forecast-request).
|
||||
*
|
||||
* @param location location represented as {@link PointType}
|
||||
* @return the current weather data
|
||||
* @throws JsonSyntaxException
|
||||
* @throws DarkSkyCommunicationException
|
||||
* @throws DarkSkyConfigurationException
|
||||
*/
|
||||
public synchronized @Nullable DarkSkyJsonWeatherData getWeatherData(@Nullable PointType location)
|
||||
throws JsonSyntaxException, DarkSkyCommunicationException, DarkSkyConfigurationException {
|
||||
if (location == null) {
|
||||
throw new DarkSkyConfigurationException("@text/offline.conf-error-missing-location");
|
||||
}
|
||||
|
||||
DarkSkyAPIConfiguration config = handler.getDarkSkyAPIConfig();
|
||||
String apikey = config.apikey;
|
||||
if (apikey == null || (apikey = apikey.trim()).isEmpty()) {
|
||||
throw new DarkSkyConfigurationException("@text/offline.conf-error-missing-apikey");
|
||||
}
|
||||
|
||||
String url = String.format(Locale.ROOT, WEATHER_URL, apikey, location.getLatitude().doubleValue(),
|
||||
location.getLongitude().doubleValue());
|
||||
|
||||
return gson.fromJson(getResponseFromCache(buildURL(url, getRequestParams(config))),
|
||||
DarkSkyJsonWeatherData.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the icon for the given icon id (see https://darksky.net/dev/docs/faq#icons).
|
||||
*
|
||||
* @param iconId the id of the icon
|
||||
* @return the weather icon as {@link RawType}
|
||||
*/
|
||||
public static @Nullable RawType getWeatherIcon(String iconId) {
|
||||
if (iconId.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot download weather icon as icon id is null.");
|
||||
}
|
||||
|
||||
return downloadWeatherIconFromCache(String.format(ICON_URL, iconId));
|
||||
}
|
||||
|
||||
private static @Nullable RawType downloadWeatherIconFromCache(String url) {
|
||||
if (IMAGE_CACHE.containsKey(url)) {
|
||||
return new RawType(IMAGE_CACHE.get(url), PNG_CONTENT_TYPE);
|
||||
} else {
|
||||
RawType image = downloadWeatherIcon(url);
|
||||
if (image != null) {
|
||||
IMAGE_CACHE.put(url, image.getBytes());
|
||||
return image;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable RawType downloadWeatherIcon(String url) {
|
||||
return HttpUtil.downloadImage(url);
|
||||
}
|
||||
|
||||
private Map<String, String> getRequestParams(DarkSkyAPIConfiguration config) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put(PARAM_EXCLUDE, "minutely,flags");
|
||||
|
||||
// params.put(PARAM_EXTEND, "hourly");
|
||||
|
||||
params.put(PARAM_UNITS, "si");
|
||||
|
||||
String language = config.language;
|
||||
if (language != null && !(language = language.trim()).isEmpty()) {
|
||||
params.put(PARAM_LANG, language.toLowerCase());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private String buildURL(String url, Map<String, String> requestParams) {
|
||||
return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key)))
|
||||
.collect(joining("&", url + "?", ""));
|
||||
}
|
||||
|
||||
private String encodeParam(String value) {
|
||||
try {
|
||||
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.debug("UnsupportedEncodingException occurred during execution: {}", e.getLocalizedMessage(), e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String getResponseFromCache(String url) {
|
||||
return cache.putIfAbsentAndGet(url, () -> getResponse(url));
|
||||
}
|
||||
|
||||
private String getResponse(String url) {
|
||||
try {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Dark Sky request: URL = '{}'", uglifyApikey(url));
|
||||
}
|
||||
ContentResponse contentResponse = httpClient.newRequest(url).method(GET).timeout(10, TimeUnit.SECONDS)
|
||||
.send();
|
||||
int httpStatus = contentResponse.getStatus();
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.trace("Dark Sky response: status = {}, content = '{}'", httpStatus, content);
|
||||
switch (httpStatus) {
|
||||
case OK_200:
|
||||
return content;
|
||||
case BAD_REQUEST_400:
|
||||
case UNAUTHORIZED_401:
|
||||
case NOT_FOUND_404:
|
||||
logger.debug("Dark Sky server responded with status code {}: {}", httpStatus, content);
|
||||
throw new DarkSkyConfigurationException(content);
|
||||
default:
|
||||
logger.debug("Dark Sky server responded with status code {}: {}", httpStatus, content);
|
||||
throw new DarkSkyCommunicationException(content);
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
String errorMessage = e.getLocalizedMessage();
|
||||
logger.trace("Exception occurred during execution: {}", errorMessage, e);
|
||||
if (e.getCause() instanceof HttpResponseException) {
|
||||
logger.debug("Dark Sky server responded with status code {}: Invalid API key.", UNAUTHORIZED_401);
|
||||
throw new DarkSkyConfigurationException("@text/offline.conf-error-invalid-apikey", e.getCause());
|
||||
} else {
|
||||
throw new DarkSkyCommunicationException(errorMessage, e.getCause());
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
|
||||
throw new DarkSkyCommunicationException(e.getLocalizedMessage(), e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
private String uglifyApikey(String url) {
|
||||
return url.replaceAll("(appid=)+\\w+", "appid=*****");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.darksky.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.darksky.internal.DarkSkyBindingConstants.*;
|
||||
|
||||
import java.util.Date;
|
||||
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.darksky.internal.handler.DarkSkyAPIHandler;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyWeatherAndForecastHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyDiscoveryService} creates things based on the configured location.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DarkSkyDiscoveryService.class);
|
||||
|
||||
private static final int DISCOVERY_TIMEOUT_SECONDS = 2;
|
||||
private static final int DISCOVERY_INTERVAL_SECONDS = 60;
|
||||
private @Nullable ScheduledFuture<?> discoveryJob;
|
||||
private final LocationProvider locationProvider;
|
||||
private @Nullable PointType previousLocation;
|
||||
|
||||
private final DarkSkyAPIHandler bridgeHandler;
|
||||
|
||||
/**
|
||||
* Creates an DarkSkyLocationDiscoveryService.
|
||||
*/
|
||||
public DarkSkyDiscoveryService(DarkSkyAPIHandler bridgeHandler, LocationProvider locationProvider,
|
||||
LocaleProvider localeProvider, TranslationProvider i18nProvider) {
|
||||
super(DarkSkyWeatherAndForecastHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SECONDS);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.locationProvider = locationProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
this.i18nProvider = i18nProvider;
|
||||
activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
removeOlderResults(new Date().getTime(), bridgeHandler.getThing().getUID());
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Start manual Dark Sky Location discovery scan.");
|
||||
scanForNewLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
logger.debug("Stop manual Dark Sky Location discovery scan.");
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
if (discoveryJob == null || discoveryJob.isCancelled()) {
|
||||
logger.debug("Start Dark Sky Location background discovery job at interval {} s.",
|
||||
DISCOVERY_INTERVAL_SECONDS);
|
||||
discoveryJob = scheduler.scheduleWithFixedDelay(this::scanForNewLocation, 0, DISCOVERY_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
if (discoveryJob != null && !discoveryJob.isCancelled()) {
|
||||
logger.debug("Stop Dark Sky Location background discovery job.");
|
||||
if (discoveryJob.cancel(true)) {
|
||||
discoveryJob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanForNewLocation() {
|
||||
PointType currentLocation = locationProvider.getLocation();
|
||||
if (currentLocation == null) {
|
||||
logger.debug("Location is not set -> Will not provide any discovery results.");
|
||||
} else if (!Objects.equals(currentLocation, previousLocation)) {
|
||||
logger.debug("Location has been changed from {} to {} -> Creating new discovery results.", previousLocation,
|
||||
currentLocation);
|
||||
createResults(currentLocation);
|
||||
previousLocation = currentLocation;
|
||||
} else {
|
||||
createResults(currentLocation);
|
||||
}
|
||||
}
|
||||
|
||||
private void createResults(PointType location) {
|
||||
String locationString = location.toFullString();
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
createWeatherAndForecastResult(locationString, bridgeUID);
|
||||
}
|
||||
|
||||
private void createWeatherAndForecastResult(String location, ThingUID bridgeUID) {
|
||||
thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_WEATHER_AND_FORECAST, bridgeUID, LOCAL))
|
||||
.withLabel("Local weather and forecast").withProperty(CONFIG_LOCATION, location)
|
||||
.withRepresentationProperty(CONFIG_LOCATION).withBridge(bridgeUID).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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.darksky.internal.factory;
|
||||
|
||||
import static org.openhab.binding.darksky.internal.DarkSkyBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.darksky.internal.discovery.DarkSkyDiscoveryService;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyAPIHandler;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyWeatherAndForecastHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.darksky")
|
||||
public class DarkSkyHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream
|
||||
.concat(DarkSkyAPIHandler.SUPPORTED_THING_TYPES.stream(),
|
||||
DarkSkyWeatherAndForecastHandler.SUPPORTED_THING_TYPES.stream())
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
private final HttpClient httpClient;
|
||||
private final LocaleProvider localeProvider;
|
||||
private final LocationProvider locationProvider;
|
||||
private final TranslationProvider i18nProvider;
|
||||
|
||||
@Activate
|
||||
public DarkSkyHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference LocationProvider locationProvider,
|
||||
final @Reference TranslationProvider i18nProvider) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.localeProvider = localeProvider;
|
||||
this.locationProvider = locationProvider;
|
||||
this.i18nProvider = i18nProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_WEATHER_API.equals(thingTypeUID)) {
|
||||
DarkSkyAPIHandler handler = new DarkSkyAPIHandler((Bridge) thing, httpClient, localeProvider);
|
||||
// register discovery service
|
||||
DarkSkyDiscoveryService discoveryService = new DarkSkyDiscoveryService(handler, locationProvider,
|
||||
localeProvider, i18nProvider);
|
||||
discoveryServiceRegs.put(handler.getThing().getUID(), bundleContext
|
||||
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
return handler;
|
||||
} else if (THING_TYPE_WEATHER_AND_FORECAST.equals(thingTypeUID)) {
|
||||
return new DarkSkyWeatherAndForecastHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof DarkSkyAPIHandler) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
// remove discovery service, if bridge handler is removed
|
||||
DarkSkyDiscoveryService discoveryService = (DarkSkyDiscoveryService) bundleContext
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (discoveryService != null) {
|
||||
discoveryService.deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 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.darksky.internal.handler;
|
||||
|
||||
import static org.openhab.binding.darksky.internal.DarkSkyBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.darksky.internal.config.DarkSkyAPIConfiguration;
|
||||
import org.openhab.binding.darksky.internal.connection.DarkSkyConnection;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyAPIHandler} is responsible for accessing the Dark Sky API.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyAPIHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DarkSkyAPIHandler.class);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_WEATHER_API);
|
||||
|
||||
private static final long INITIAL_DELAY_IN_SECONDS = 15;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final LocaleProvider localeProvider;
|
||||
private @NonNullByDefault({}) DarkSkyConnection connection;
|
||||
|
||||
// keeps track of the parsed config
|
||||
private @NonNullByDefault({}) DarkSkyAPIConfiguration config;
|
||||
|
||||
public DarkSkyAPIHandler(Bridge bridge, HttpClient httpClient, LocaleProvider localeProvider) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initialize Dark Sky API handler '{}'.", getThing().getUID());
|
||||
config = getConfigAs(DarkSkyAPIConfiguration.class);
|
||||
|
||||
boolean configValid = true;
|
||||
if (config.apikey == null || config.apikey.trim().isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-missing-apikey");
|
||||
configValid = false;
|
||||
}
|
||||
int refreshInterval = config.refreshInterval;
|
||||
if (refreshInterval < 1) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-not-supported-refreshInterval");
|
||||
configValid = false;
|
||||
}
|
||||
String language = config.language;
|
||||
if (language != null && !(language = language.trim()).isEmpty()) {
|
||||
if (!DarkSkyAPIConfiguration.SUPPORTED_LANGUAGES.contains(language.toLowerCase())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-not-supported-language");
|
||||
configValid = false;
|
||||
}
|
||||
} else {
|
||||
language = localeProvider.getLocale().getLanguage();
|
||||
if (DarkSkyAPIConfiguration.SUPPORTED_LANGUAGES.contains(language)) {
|
||||
logger.debug("Language set to '{}'.", language);
|
||||
Configuration editConfig = editConfiguration();
|
||||
editConfig.put(CONFIG_LANGUAGE, language);
|
||||
updateConfiguration(editConfig);
|
||||
}
|
||||
}
|
||||
|
||||
if (configValid) {
|
||||
connection = new DarkSkyConnection(this, httpClient);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
ScheduledFuture<?> localRefreshJob = refreshJob;
|
||||
if (localRefreshJob == null || localRefreshJob.isCancelled()) {
|
||||
logger.debug("Start refresh job at interval {} min.", refreshInterval);
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(this::updateThings, INITIAL_DELAY_IN_SECONDS,
|
||||
TimeUnit.MINUTES.toSeconds(refreshInterval), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Dispose Dark Sky API handler '{}'.", getThing().getUID());
|
||||
ScheduledFuture<?> localRefreshJob = refreshJob;
|
||||
if (localRefreshJob != null && !localRefreshJob.isCancelled()) {
|
||||
logger.debug("Stop refresh job.");
|
||||
if (localRefreshJob.cancel(true)) {
|
||||
refreshJob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
scheduler.schedule(this::updateThings, INITIAL_DELAY_IN_SECONDS, TimeUnit.SECONDS);
|
||||
} else {
|
||||
logger.debug("The Dark Sky binding is a read-only binding and cannot handle command '{}'.", command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
scheduler.schedule(() -> {
|
||||
updateThing((DarkSkyWeatherAndForecastHandler) childHandler, childThing);
|
||||
determineBridgeStatus();
|
||||
}, INITIAL_DELAY_IN_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||
determineBridgeStatus();
|
||||
}
|
||||
|
||||
private void determineBridgeStatus() {
|
||||
ThingStatus status = ThingStatus.OFFLINE;
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (ThingStatus.ONLINE.equals(thing.getStatus())) {
|
||||
status = ThingStatus.ONLINE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateStatus(status);
|
||||
}
|
||||
|
||||
private void updateThings() {
|
||||
ThingStatus status = ThingStatus.OFFLINE;
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (ThingStatus.ONLINE.equals(updateThing((DarkSkyWeatherAndForecastHandler) thing.getHandler(), thing))) {
|
||||
status = ThingStatus.ONLINE;
|
||||
}
|
||||
}
|
||||
updateStatus(status);
|
||||
}
|
||||
|
||||
private ThingStatus updateThing(@Nullable DarkSkyWeatherAndForecastHandler handler, Thing thing) {
|
||||
if (handler != null && connection != null) {
|
||||
handler.updateData(connection);
|
||||
return thing.getStatus();
|
||||
} else {
|
||||
logger.debug("Cannot update weather data of thing '{}' as location handler is null.", thing.getUID());
|
||||
return ThingStatus.OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
public DarkSkyAPIConfiguration getDarkSkyAPIConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
/**
|
||||
* 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.darksky.internal.handler;
|
||||
|
||||
import static org.openhab.binding.darksky.internal.DarkSkyBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
import static org.openhab.core.library.unit.SIUnits.*;
|
||||
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.darksky.internal.config.DarkSkyChannelConfiguration;
|
||||
import org.openhab.binding.darksky.internal.config.DarkSkyWeatherAndForecastConfiguration;
|
||||
import org.openhab.binding.darksky.internal.connection.DarkSkyCommunicationException;
|
||||
import org.openhab.binding.darksky.internal.connection.DarkSkyConfigurationException;
|
||||
import org.openhab.binding.darksky.internal.connection.DarkSkyConnection;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyCurrentlyData;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyDailyData.DailyData;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyHourlyData.HourlyData;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyJsonWeatherData;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyJsonWeatherData.AlertsData;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
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.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
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;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyWeatherAndForecastHandler} is responsible for handling commands, which are sent to one of the
|
||||
* channels.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DarkSkyWeatherAndForecastHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DarkSkyWeatherAndForecastHandler.class);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
|
||||
.singleton(THING_TYPE_WEATHER_AND_FORECAST);
|
||||
|
||||
private static final String PRECIP_TYPE_SNOW = "snow";
|
||||
private static final String PRECIP_TYPE_RAIN = "rain";
|
||||
|
||||
private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "forecastHours";
|
||||
private static final String CHANNEL_GROUP_DAILY_FORECAST_PREFIX = "forecastDay";
|
||||
private static final String CHANNEL_GROUP_ALERTS_PREFIX = "alerts";
|
||||
private static final Pattern CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN = Pattern
|
||||
.compile(CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + "([0-9]*)");
|
||||
private static final Pattern CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN = Pattern
|
||||
.compile(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + "([0-9]*)");
|
||||
private static final Pattern CHANNEL_GROUP_ALERTS_PREFIX_PATTERN = Pattern
|
||||
.compile(CHANNEL_GROUP_ALERTS_PREFIX + "([0-9]*)");
|
||||
|
||||
// keeps track of all jobs
|
||||
private static final Map<String, Job> JOBS = new ConcurrentHashMap<>();
|
||||
|
||||
// keeps track of the parsed location
|
||||
protected @Nullable PointType location;
|
||||
// keeps track of the parsed counts
|
||||
private int forecastHours = 24;
|
||||
private int forecastDays = 8;
|
||||
private int numberOfAlerts = 0;
|
||||
|
||||
private @Nullable DarkSkyChannelConfiguration sunriseTriggerChannelConfig;
|
||||
private @Nullable DarkSkyChannelConfiguration sunsetTriggerChannelConfig;
|
||||
private @Nullable DarkSkyJsonWeatherData weatherData;
|
||||
|
||||
public DarkSkyWeatherAndForecastHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initialize DarkSkyWeatherAndForecastHandler handler '{}'.", getThing().getUID());
|
||||
DarkSkyWeatherAndForecastConfiguration config = getConfigAs(DarkSkyWeatherAndForecastConfiguration.class);
|
||||
|
||||
boolean configValid = true;
|
||||
if (config.location == null || config.location.trim().isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-missing-location");
|
||||
configValid = false;
|
||||
}
|
||||
|
||||
try {
|
||||
location = new PointType(config.location);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Error parsing 'location' parameter: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-parsing-location");
|
||||
location = null;
|
||||
configValid = false;
|
||||
}
|
||||
|
||||
int newForecastHours = config.forecastHours;
|
||||
if (newForecastHours < 0 || newForecastHours > 48) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-not-supported-number-of-hours");
|
||||
configValid = false;
|
||||
}
|
||||
int newForecastDays = config.forecastDays;
|
||||
if (newForecastDays < 0 || newForecastDays > 8) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-not-supported-number-of-days");
|
||||
configValid = false;
|
||||
}
|
||||
int newNumberOfAlerts = config.numberOfAlerts;
|
||||
if (newNumberOfAlerts < 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-not-supported-number-of-alerts");
|
||||
configValid = false;
|
||||
}
|
||||
|
||||
if (configValid) {
|
||||
logger.debug("Rebuilding thing '{}'.", getThing().getUID());
|
||||
List<Channel> toBeAddedChannels = new ArrayList<>();
|
||||
List<Channel> toBeRemovedChannels = new ArrayList<>();
|
||||
if (forecastHours != newForecastHours) {
|
||||
logger.debug("Rebuilding hourly forecast channel groups.");
|
||||
if (forecastHours > newForecastHours) {
|
||||
for (int i = newForecastHours + 1; i <= forecastHours; ++i) {
|
||||
toBeRemovedChannels.addAll(removeChannelsOfGroup(
|
||||
CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
|
||||
}
|
||||
} else {
|
||||
for (int i = forecastHours + 1; i <= newForecastHours; ++i) {
|
||||
toBeAddedChannels.addAll(createChannelsForGroup(
|
||||
CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
|
||||
CHANNEL_GROUP_TYPE_HOURLY_FORECAST));
|
||||
}
|
||||
}
|
||||
forecastHours = newForecastHours;
|
||||
}
|
||||
if (forecastDays != newForecastDays) {
|
||||
logger.debug("Rebuilding daily forecast channel groups.");
|
||||
if (forecastDays > newForecastDays) {
|
||||
if (newForecastDays < 1) {
|
||||
toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TODAY));
|
||||
}
|
||||
if (newForecastDays < 2) {
|
||||
toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TOMORROW));
|
||||
}
|
||||
for (int i = newForecastDays; i < forecastDays; ++i) {
|
||||
toBeRemovedChannels.addAll(
|
||||
removeChannelsOfGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i)));
|
||||
}
|
||||
} else {
|
||||
if (forecastDays == 0 && newForecastDays > 0) {
|
||||
toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TODAY,
|
||||
CHANNEL_GROUP_TYPE_DAILY_FORECAST));
|
||||
}
|
||||
if (forecastDays <= 1 && newForecastDays > 1) {
|
||||
toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TOMORROW,
|
||||
CHANNEL_GROUP_TYPE_DAILY_FORECAST));
|
||||
}
|
||||
for (int i = (forecastDays < 2) ? 2 : forecastDays; i < newForecastDays; ++i) {
|
||||
toBeAddedChannels.addAll(
|
||||
createChannelsForGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i),
|
||||
CHANNEL_GROUP_TYPE_DAILY_FORECAST));
|
||||
}
|
||||
}
|
||||
forecastDays = newForecastDays;
|
||||
}
|
||||
if (numberOfAlerts != newNumberOfAlerts) {
|
||||
logger.debug("Rebuilding alerts channel groups.");
|
||||
if (numberOfAlerts > newNumberOfAlerts) {
|
||||
for (int i = newNumberOfAlerts + 1; i <= numberOfAlerts; ++i) {
|
||||
toBeRemovedChannels
|
||||
.addAll(removeChannelsOfGroup(CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i)));
|
||||
}
|
||||
} else {
|
||||
for (int i = numberOfAlerts + 1; i <= newNumberOfAlerts; ++i) {
|
||||
toBeAddedChannels.addAll(createChannelsForGroup(
|
||||
CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i), CHANNEL_GROUP_TYPE_ALERTS));
|
||||
}
|
||||
}
|
||||
numberOfAlerts = newNumberOfAlerts;
|
||||
}
|
||||
ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
|
||||
for (Channel channel : toBeAddedChannels) {
|
||||
builder.withChannel(channel);
|
||||
}
|
||||
updateThing(builder.build());
|
||||
|
||||
Channel sunriseTriggerChannel = getThing().getChannel(TRIGGER_SUNRISE);
|
||||
sunriseTriggerChannelConfig = (sunriseTriggerChannel == null) ? null
|
||||
: sunriseTriggerChannel.getConfiguration().as(DarkSkyChannelConfiguration.class);
|
||||
Channel sunsetTriggerChannel = getThing().getChannel(TRIGGER_SUNSET);
|
||||
sunsetTriggerChannelConfig = (sunsetTriggerChannel == null) ? null
|
||||
: sunsetTriggerChannel.getConfiguration().as(DarkSkyChannelConfiguration.class);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelAllJobs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateChannel(channelUID);
|
||||
} else {
|
||||
logger.debug("The Dark Sky binding is a read-only binding and cannot handle command '{}'.", command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (ThingStatus.ONLINE.equals(bridgeStatusInfo.getStatus())
|
||||
&& ThingStatusDetail.BRIDGE_OFFLINE.equals(getThing().getStatusInfo().getStatusDetail())) {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
} else if (ThingStatus.OFFLINE.equals(bridgeStatusInfo.getStatus())
|
||||
&& !ThingStatus.OFFLINE.equals(getThing().getStatus())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all {@link Channel}s for the given {@link ChannelGroupTypeUID}.
|
||||
*
|
||||
* @param channelGroupId the channel group id
|
||||
* @param channelGroupTypeUID the {@link ChannelGroupTypeUID}
|
||||
* @return a list of all {@link Channel}s for the channel group
|
||||
*/
|
||||
private List<Channel> createChannelsForGroup(String channelGroupId, ChannelGroupTypeUID channelGroupTypeUID) {
|
||||
logger.debug("Building channel group '{}' for thing '{}'.", channelGroupId, getThing().getUID());
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
ThingHandlerCallback callback = getCallback();
|
||||
if (callback != null) {
|
||||
for (ChannelBuilder channelBuilder : callback.createChannelBuilders(
|
||||
new ChannelGroupUID(getThing().getUID(), channelGroupId), channelGroupTypeUID)) {
|
||||
Channel newChannel = channelBuilder.build(),
|
||||
existingChannel = getThing().getChannel(newChannel.getUID().getId());
|
||||
if (existingChannel != null) {
|
||||
logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
|
||||
getThing().getUID(), existingChannel.getUID(), newChannel.getUID());
|
||||
continue;
|
||||
}
|
||||
channels.add(newChannel);
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all {@link Channel}s of the given channel group.
|
||||
*
|
||||
* @param channelGroupId the channel group id
|
||||
* @return a list of all {@link Channel}s in the given channel group
|
||||
*/
|
||||
private List<Channel> removeChannelsOfGroup(String channelGroupId) {
|
||||
logger.debug("Removing channel group '{}' from thing '{}'.", channelGroupId, getThing().getUID());
|
||||
return getThing().getChannelsOfGroup(channelGroupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Dark Sky data for this location.
|
||||
*
|
||||
* @param connection {@link DarkSkyConnection} instance
|
||||
*/
|
||||
public void updateData(DarkSkyConnection connection) {
|
||||
try {
|
||||
if (requestData(connection)) {
|
||||
updateChannels();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (DarkSkyCommunicationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
|
||||
} catch (DarkSkyConfigurationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the data from Dark Sky API.
|
||||
*
|
||||
* @param connection {@link DarkSkyConnection} instance
|
||||
* @return true, if the request for the Dark Sky data was successful
|
||||
* @throws DarkSkyCommunicationException
|
||||
* @throws DarkSkyConfigurationException
|
||||
*/
|
||||
private boolean requestData(DarkSkyConnection connection)
|
||||
throws DarkSkyCommunicationException, DarkSkyConfigurationException {
|
||||
logger.debug("Update weather and forecast data of thing '{}'.", getThing().getUID());
|
||||
try {
|
||||
weatherData = connection.getWeatherData(location);
|
||||
return true;
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.debug("JsonSyntaxException occurred during execution: {}", e.getLocalizedMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all channels of this handler from the latest Dark Sky data retrieved.
|
||||
*/
|
||||
private void updateChannels() {
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
|
||||
&& isLinked(channelUID)) {
|
||||
updateChannel(channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel with the given UID from the latest Dark Sky data retrieved.
|
||||
*
|
||||
* @param channelUID UID of the channel
|
||||
*/
|
||||
private void updateChannel(ChannelUID channelUID) {
|
||||
String channelGroupId = channelUID.getGroupId();
|
||||
switch (channelGroupId) {
|
||||
case CHANNEL_GROUP_CURRENT_WEATHER:
|
||||
updateCurrentChannel(channelUID);
|
||||
break;
|
||||
case CHANNEL_GROUP_FORECAST_TODAY:
|
||||
updateDailyForecastChannel(channelUID, 0);
|
||||
break;
|
||||
case CHANNEL_GROUP_FORECAST_TOMORROW:
|
||||
updateDailyForecastChannel(channelUID, 1);
|
||||
break;
|
||||
default:
|
||||
int i;
|
||||
Matcher hourlyForecastMatcher = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
|
||||
if (hourlyForecastMatcher.find() && (i = Integer.parseInt(hourlyForecastMatcher.group(1))) >= 1
|
||||
&& i <= 48) {
|
||||
updateHourlyForecastChannel(channelUID, i);
|
||||
break;
|
||||
}
|
||||
Matcher dailyForecastMatcher = CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
|
||||
if (dailyForecastMatcher.find() && (i = Integer.parseInt(dailyForecastMatcher.group(1))) > 1
|
||||
&& i <= 8) {
|
||||
updateDailyForecastChannel(channelUID, i);
|
||||
break;
|
||||
}
|
||||
Matcher alertsMatcher = CHANNEL_GROUP_ALERTS_PREFIX_PATTERN.matcher(channelGroupId);
|
||||
if (alertsMatcher.find() && (i = Integer.parseInt(alertsMatcher.group(1))) >= 1) {
|
||||
updateAlertsChannel(channelUID, i);
|
||||
break;
|
||||
}
|
||||
logger.warn("Unknown channel group '{}'. Cannot update channel '{}'.", channelGroupId, channelUID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Dark Sky data retrieved.
|
||||
*
|
||||
* @param channelUID the id identifying the channel to be updated
|
||||
*/
|
||||
private void updateCurrentChannel(ChannelUID channelUID) {
|
||||
String channelId = channelUID.getIdWithoutGroup();
|
||||
String channelGroupId = channelUID.getGroupId();
|
||||
if (weatherData != null && weatherData.getCurrently() != null) {
|
||||
DarkSkyCurrentlyData currentData = weatherData.getCurrently();
|
||||
State state = UnDefType.UNDEF;
|
||||
switch (channelId) {
|
||||
case CHANNEL_TIME_STAMP:
|
||||
state = getDateTimeTypeState(currentData.getTime());
|
||||
break;
|
||||
case CHANNEL_CONDITION:
|
||||
state = getStringTypeState(currentData.getSummary());
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON:
|
||||
state = getRawTypeState(DarkSkyConnection.getWeatherIcon(currentData.getIcon()));
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON_ID:
|
||||
state = getStringTypeState(currentData.getIcon());
|
||||
break;
|
||||
case CHANNEL_TEMPERATURE:
|
||||
state = getQuantityTypeState(currentData.getTemperature(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_APPARENT_TEMPERATURE:
|
||||
state = getQuantityTypeState(currentData.getApparentTemperature(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_PRESSURE:
|
||||
state = getQuantityTypeState(currentData.getPressure(), HECTO(PASCAL));
|
||||
break;
|
||||
case CHANNEL_HUMIDITY:
|
||||
state = getQuantityTypeState(currentData.getHumidity() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_WIND_SPEED:
|
||||
state = getQuantityTypeState(currentData.getWindSpeed(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_WIND_DIRECTION:
|
||||
state = getQuantityTypeState(currentData.getWindBearing(), DEGREE_ANGLE);
|
||||
break;
|
||||
case CHANNEL_GUST_SPEED:
|
||||
state = getQuantityTypeState(currentData.getWindGust(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_CLOUDINESS:
|
||||
state = getQuantityTypeState(currentData.getCloudCover() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_VISIBILITY:
|
||||
state = getQuantityTypeState(currentData.getVisibility(), KILO(METRE));
|
||||
break;
|
||||
case CHANNEL_RAIN:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_RAIN.equals(currentData.getPrecipType()) ? currentData.getPrecipIntensity() : 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_SNOW:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_SNOW.equals(currentData.getPrecipType()) ? currentData.getPrecipIntensity() : 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_INTENSITY:
|
||||
state = getQuantityTypeState(currentData.getPrecipIntensity(), MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_PROBABILITY:
|
||||
state = getQuantityTypeState(currentData.getPrecipProbability() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_TYPE:
|
||||
state = getStringTypeState(currentData.getPrecipType());
|
||||
break;
|
||||
case CHANNEL_UVINDEX:
|
||||
state = getDecimalTypeState(currentData.getUvIndex());
|
||||
break;
|
||||
case CHANNEL_OZONE:
|
||||
state = getQuantityTypeState(currentData.getOzone(), DOBSON_UNIT);
|
||||
break;
|
||||
case CHANNEL_SUNRISE:
|
||||
case CHANNEL_SUNSET:
|
||||
updateDailyForecastChannel(channelUID, 0);
|
||||
return;
|
||||
}
|
||||
logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
|
||||
updateState(channelUID, state);
|
||||
} else {
|
||||
logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Dark Sky data retrieved.
|
||||
*
|
||||
* @param channelUID the id identifying the channel to be updated
|
||||
* @param count
|
||||
*/
|
||||
private void updateHourlyForecastChannel(ChannelUID channelUID, int count) {
|
||||
String channelId = channelUID.getIdWithoutGroup();
|
||||
String channelGroupId = channelUID.getGroupId();
|
||||
if (weatherData != null && weatherData.getHourly() != null
|
||||
&& weatherData.getHourly().getData().size() > count) {
|
||||
HourlyData forecastData = weatherData.getHourly().getData().get(count);
|
||||
State state = UnDefType.UNDEF;
|
||||
switch (channelId) {
|
||||
case CHANNEL_TIME_STAMP:
|
||||
state = getDateTimeTypeState(forecastData.getTime());
|
||||
break;
|
||||
case CHANNEL_CONDITION:
|
||||
state = getStringTypeState(forecastData.getSummary());
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON:
|
||||
state = getRawTypeState(DarkSkyConnection.getWeatherIcon(forecastData.getIcon()));
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON_ID:
|
||||
state = getStringTypeState(forecastData.getIcon());
|
||||
break;
|
||||
case CHANNEL_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getTemperature(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_APPARENT_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getApparentTemperature(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_PRESSURE:
|
||||
state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
|
||||
break;
|
||||
case CHANNEL_HUMIDITY:
|
||||
state = getQuantityTypeState(forecastData.getHumidity() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_WIND_SPEED:
|
||||
state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_WIND_DIRECTION:
|
||||
state = getQuantityTypeState(forecastData.getWindBearing(), DEGREE_ANGLE);
|
||||
break;
|
||||
case CHANNEL_GUST_SPEED:
|
||||
state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_CLOUDINESS:
|
||||
state = getQuantityTypeState(forecastData.getCloudCover() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_VISIBILITY:
|
||||
state = getQuantityTypeState(forecastData.getVisibility(), KILO(METRE));
|
||||
break;
|
||||
case CHANNEL_RAIN:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_RAIN.equals(forecastData.getPrecipType()) ? forecastData.getPrecipIntensity()
|
||||
: 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_SNOW:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_SNOW.equals(forecastData.getPrecipType()) ? forecastData.getPrecipIntensity()
|
||||
: 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_INTENSITY:
|
||||
state = getQuantityTypeState(forecastData.getPrecipIntensity(), MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_PROBABILITY:
|
||||
state = getQuantityTypeState(forecastData.getPrecipProbability() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_TYPE:
|
||||
state = getStringTypeState(forecastData.getPrecipType());
|
||||
break;
|
||||
case CHANNEL_UVINDEX:
|
||||
state = getDecimalTypeState(forecastData.getUvIndex());
|
||||
break;
|
||||
case CHANNEL_OZONE:
|
||||
state = getQuantityTypeState(forecastData.getOzone(), DOBSON_UNIT);
|
||||
break;
|
||||
}
|
||||
logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
|
||||
updateState(channelUID, state);
|
||||
} else {
|
||||
logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Dark Sky data retrieved.
|
||||
*
|
||||
* @param channelUID the id identifying the channel to be updated
|
||||
* @param count
|
||||
*/
|
||||
private void updateDailyForecastChannel(ChannelUID channelUID, int count) {
|
||||
String channelId = channelUID.getIdWithoutGroup();
|
||||
String channelGroupId = channelUID.getGroupId();
|
||||
if (weatherData != null && weatherData.getDaily() != null && weatherData.getDaily().getData().size() > count) {
|
||||
DailyData forecastData = weatherData.getDaily().getData().get(count);
|
||||
State state = UnDefType.UNDEF;
|
||||
switch (channelId) {
|
||||
case CHANNEL_TIME_STAMP:
|
||||
state = getDateTimeTypeState(forecastData.getTime());
|
||||
break;
|
||||
case CHANNEL_CONDITION:
|
||||
state = getStringTypeState(forecastData.getSummary());
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON:
|
||||
state = getRawTypeState(DarkSkyConnection.getWeatherIcon(forecastData.getIcon()));
|
||||
break;
|
||||
case CHANNEL_CONDITION_ICON_ID:
|
||||
state = getStringTypeState(forecastData.getIcon());
|
||||
break;
|
||||
case CHANNEL_MIN_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getTemperatureMin(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_MAX_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getTemperatureMax(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_MIN_APPARENT_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getApparentTemperatureMin(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_MAX_APPARENT_TEMPERATURE:
|
||||
state = getQuantityTypeState(forecastData.getApparentTemperatureMax(), CELSIUS);
|
||||
break;
|
||||
case CHANNEL_PRESSURE:
|
||||
state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
|
||||
break;
|
||||
case CHANNEL_HUMIDITY:
|
||||
state = getQuantityTypeState(forecastData.getHumidity() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_WIND_SPEED:
|
||||
state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_WIND_DIRECTION:
|
||||
state = getQuantityTypeState(forecastData.getWindBearing(), DEGREE_ANGLE);
|
||||
break;
|
||||
case CHANNEL_GUST_SPEED:
|
||||
state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
|
||||
break;
|
||||
case CHANNEL_CLOUDINESS:
|
||||
state = getQuantityTypeState(forecastData.getCloudCover() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_VISIBILITY:
|
||||
state = getQuantityTypeState(forecastData.getVisibility(), KILO(METRE));
|
||||
break;
|
||||
case CHANNEL_RAIN:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_RAIN.equals(forecastData.getPrecipType()) ? forecastData.getPrecipIntensity()
|
||||
: 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_SNOW:
|
||||
state = getQuantityTypeState(
|
||||
PRECIP_TYPE_SNOW.equals(forecastData.getPrecipType()) ? forecastData.getPrecipIntensity()
|
||||
: 0,
|
||||
MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_INTENSITY:
|
||||
state = getQuantityTypeState(forecastData.getPrecipIntensity(), MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_PROBABILITY:
|
||||
state = getQuantityTypeState(forecastData.getPrecipProbability() * 100, PERCENT);
|
||||
break;
|
||||
case CHANNEL_PRECIPITATION_TYPE:
|
||||
state = getStringTypeState(forecastData.getPrecipType());
|
||||
break;
|
||||
case CHANNEL_UVINDEX:
|
||||
state = getDecimalTypeState(forecastData.getUvIndex());
|
||||
break;
|
||||
case CHANNEL_OZONE:
|
||||
state = getQuantityTypeState(forecastData.getOzone(), DOBSON_UNIT);
|
||||
break;
|
||||
case CHANNEL_SUNRISE:
|
||||
state = getDateTimeTypeState(forecastData.getSunriseTime());
|
||||
if (count == 0 && state instanceof DateTimeType) {
|
||||
scheduleJob(TRIGGER_SUNRISE, applyChannelConfig(((DateTimeType) state).getZonedDateTime(),
|
||||
sunriseTriggerChannelConfig));
|
||||
}
|
||||
break;
|
||||
case CHANNEL_SUNSET:
|
||||
state = getDateTimeTypeState(forecastData.getSunsetTime());
|
||||
if (count == 0 && state instanceof DateTimeType) {
|
||||
scheduleJob(TRIGGER_SUNSET, applyChannelConfig(((DateTimeType) state).getZonedDateTime(),
|
||||
sunsetTriggerChannelConfig));
|
||||
}
|
||||
break;
|
||||
}
|
||||
logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
|
||||
updateState(channelUID, state);
|
||||
} else {
|
||||
logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the channel from the last Dark Sky data retrieved.
|
||||
*
|
||||
* @param channelUID the id identifying the channel to be updated
|
||||
* @param count
|
||||
*/
|
||||
private void updateAlertsChannel(ChannelUID channelUID, int count) {
|
||||
String channelId = channelUID.getIdWithoutGroup();
|
||||
String channelGroupId = channelUID.getGroupId();
|
||||
List<AlertsData> alerts = weatherData != null ? weatherData.getAlerts() : null;
|
||||
State state = UnDefType.UNDEF;
|
||||
if (alerts != null && alerts.size() > count) {
|
||||
AlertsData alertsData = alerts.get(count - 1);
|
||||
switch (channelId) {
|
||||
case CHANNEL_ALERT_TITLE:
|
||||
state = getStringTypeState(alertsData.title);
|
||||
break;
|
||||
case CHANNEL_ALERT_DESCRIPTION:
|
||||
state = getStringTypeState(alertsData.description);
|
||||
break;
|
||||
case CHANNEL_ALERT_SEVERITY:
|
||||
state = getStringTypeState(alertsData.severity);
|
||||
break;
|
||||
case CHANNEL_ALERT_ISSUED:
|
||||
state = getDateTimeTypeState(alertsData.time);
|
||||
break;
|
||||
case CHANNEL_ALERT_EXPIRES:
|
||||
state = getDateTimeTypeState(alertsData.expires);
|
||||
break;
|
||||
case CHANNEL_ALERT_URI:
|
||||
state = getStringTypeState(alertsData.uri);
|
||||
break;
|
||||
}
|
||||
logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
|
||||
} else {
|
||||
logger.debug("No data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
|
||||
}
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
|
||||
private State getDateTimeTypeState(int value) {
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
private State getDecimalTypeState(int value) {
|
||||
return new DecimalType(value);
|
||||
}
|
||||
|
||||
private State getRawTypeState(@Nullable RawType image) {
|
||||
return (image == null) ? UnDefType.UNDEF : image;
|
||||
}
|
||||
|
||||
private State getStringTypeState(@Nullable String value) {
|
||||
return (value == null) ? UnDefType.UNDEF : new StringType(value);
|
||||
}
|
||||
|
||||
private State getQuantityTypeState(double value, Unit<?> unit) {
|
||||
return new QuantityType<>(value, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given configuration to the given timestamp.
|
||||
*
|
||||
* @param dateTime timestamp represented as {@link ZonedDateTime}
|
||||
* @param config {@link DarkSkyChannelConfiguration} instance
|
||||
* @return the modified timestamp
|
||||
*/
|
||||
private ZonedDateTime applyChannelConfig(ZonedDateTime dateTime, @Nullable DarkSkyChannelConfiguration config) {
|
||||
ZonedDateTime modifiedDateTime = dateTime;
|
||||
if (config != null) {
|
||||
if (config.getOffset() != 0) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Apply offset of {} min to timestamp '{}'.", config.getOffset(),
|
||||
modifiedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
modifiedDateTime = modifiedDateTime.plusMinutes(config.getOffset());
|
||||
}
|
||||
long earliestInMinutes = config.getEarliestInMinutes();
|
||||
if (earliestInMinutes > 0) {
|
||||
ZonedDateTime earliestDateTime = modifiedDateTime.truncatedTo(ChronoUnit.DAYS)
|
||||
.plusMinutes(earliestInMinutes);
|
||||
if (modifiedDateTime.isBefore(earliestDateTime)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Use earliest timestamp '{}' instead of '{}'.",
|
||||
earliestDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
modifiedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
return earliestDateTime;
|
||||
}
|
||||
}
|
||||
long latestInMinutes = config.getLatestInMinutes();
|
||||
if (latestInMinutes > 0) {
|
||||
ZonedDateTime latestDateTime = modifiedDateTime.truncatedTo(ChronoUnit.DAYS)
|
||||
.plusMinutes(latestInMinutes);
|
||||
if (modifiedDateTime.isAfter(latestDateTime)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Use latest timestamp '{}' instead of '{}'.",
|
||||
latestDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
modifiedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
return latestDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
return modifiedDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules or reschedules a job for the channel with the given id if the given timestamp is in the future.
|
||||
*
|
||||
* @param channelId id of the channel
|
||||
* @param dateTime timestamp of the job represented as {@link ZonedDateTime}
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
private synchronized void scheduleJob(String channelId, ZonedDateTime dateTime) {
|
||||
long delay = dateTime.toEpochSecond() - ZonedDateTime.now().toEpochSecond();
|
||||
if (delay > 0) {
|
||||
Job job = JOBS.get(channelId);
|
||||
if (job == null || job.getFuture().isCancelled()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Schedule job for '{}' in {} s (at '{}').", channelId, delay,
|
||||
dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
JOBS.put(channelId, new Job(channelId, delay));
|
||||
} else {
|
||||
if (delay != job.getDelay()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reschedule job for '{}' in {} s (at '{}').", channelId, delay,
|
||||
dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
job.getFuture().cancel(true);
|
||||
JOBS.put(channelId, new Job(channelId, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all jobs.
|
||||
*/
|
||||
private void cancelAllJobs() {
|
||||
logger.debug("Cancel all jobs.");
|
||||
JOBS.keySet().forEach(this::cancelJob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the job for the channel with the given id.
|
||||
*
|
||||
* @param channelId id of the channel
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
private synchronized void cancelJob(String channelId) {
|
||||
Job job = JOBS.remove(channelId);
|
||||
if (job != null && !job.getFuture().isCancelled()) {
|
||||
logger.debug("Cancel job for '{}'.", channelId);
|
||||
job.getFuture().cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the job for the channel with the given id.
|
||||
*
|
||||
* @param channelId id of the channel
|
||||
*/
|
||||
private void executeJob(String channelId) {
|
||||
logger.debug("Trigger channel '{}' with event '{}'.", channelId, EVENT_START);
|
||||
triggerChannel(channelId, EVENT_START);
|
||||
}
|
||||
|
||||
private final class Job {
|
||||
private final long delay;
|
||||
private final ScheduledFuture<?> future;
|
||||
|
||||
public Job(String event, long delay) {
|
||||
this.delay = delay;
|
||||
this.future = scheduler.schedule(() -> {
|
||||
executeJob(event);
|
||||
}, delay, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public long getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> getFuture() {
|
||||
return future;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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.darksky.internal.model;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyCurrentlyData} is the Java class used to map the JSON response to an Dark Sky request.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class DarkSkyCurrentlyData {
|
||||
private int time;
|
||||
private String summary;
|
||||
private String icon;
|
||||
private double precipIntensity;
|
||||
private double precipProbability;
|
||||
private String precipType;
|
||||
private double temperature;
|
||||
private double apparentTemperature;
|
||||
private double dewPoint;
|
||||
private double humidity;
|
||||
private double pressure;
|
||||
private double windSpeed;
|
||||
private double windGust;
|
||||
private int windBearing;
|
||||
private double cloudCover;
|
||||
private int uvIndex;
|
||||
private double visibility;
|
||||
private double ozone;
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public double getPrecipIntensity() {
|
||||
return precipIntensity;
|
||||
}
|
||||
|
||||
public void setPrecipIntensity(double precipIntensity) {
|
||||
this.precipIntensity = precipIntensity;
|
||||
}
|
||||
|
||||
public double getPrecipProbability() {
|
||||
return precipProbability;
|
||||
}
|
||||
|
||||
public void setPrecipProbability(double precipProbability) {
|
||||
this.precipProbability = precipProbability;
|
||||
}
|
||||
|
||||
public String getPrecipType() {
|
||||
return precipType;
|
||||
}
|
||||
|
||||
public void setPrecipType(String precipType) {
|
||||
this.precipType = precipType;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public double getApparentTemperature() {
|
||||
return apparentTemperature;
|
||||
}
|
||||
|
||||
public void setApparentTemperature(double apparentTemperature) {
|
||||
this.apparentTemperature = apparentTemperature;
|
||||
}
|
||||
|
||||
public double getDewPoint() {
|
||||
return dewPoint;
|
||||
}
|
||||
|
||||
public void setDewPoint(double dewPoint) {
|
||||
this.dewPoint = dewPoint;
|
||||
}
|
||||
|
||||
public double getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public void setHumidity(double humidity) {
|
||||
this.humidity = humidity;
|
||||
}
|
||||
|
||||
public double getPressure() {
|
||||
return pressure;
|
||||
}
|
||||
|
||||
public void setPressure(double pressure) {
|
||||
this.pressure = pressure;
|
||||
}
|
||||
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
public void setWindSpeed(double windSpeed) {
|
||||
this.windSpeed = windSpeed;
|
||||
}
|
||||
|
||||
public double getWindGust() {
|
||||
return windGust;
|
||||
}
|
||||
|
||||
public void setWindGust(double windGust) {
|
||||
this.windGust = windGust;
|
||||
}
|
||||
|
||||
public int getWindBearing() {
|
||||
return windBearing;
|
||||
}
|
||||
|
||||
public void setWindBearing(int windBearing) {
|
||||
this.windBearing = windBearing;
|
||||
}
|
||||
|
||||
public double getCloudCover() {
|
||||
return cloudCover;
|
||||
}
|
||||
|
||||
public void setCloudCover(double cloudCover) {
|
||||
this.cloudCover = cloudCover;
|
||||
}
|
||||
|
||||
public int getUvIndex() {
|
||||
return uvIndex;
|
||||
}
|
||||
|
||||
public void setUvIndex(int uvIndex) {
|
||||
this.uvIndex = uvIndex;
|
||||
}
|
||||
|
||||
public double getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(double visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public double getOzone() {
|
||||
return ozone;
|
||||
}
|
||||
|
||||
public void setOzone(double ozone) {
|
||||
this.ozone = ozone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* 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.darksky.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyDailyData} is the Java class used to map the JSON response to an Dark Sky request.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class DarkSkyDailyData {
|
||||
private String summary;
|
||||
private String icon;
|
||||
private @Nullable List<DailyData> data;
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public @Nullable List<DailyData> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(@Nullable List<DailyData> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public class DailyData {
|
||||
private int time;
|
||||
private String summary;
|
||||
private String icon;
|
||||
private int sunriseTime;
|
||||
private int sunsetTime;
|
||||
private double moonPhase;
|
||||
private double precipIntensity;
|
||||
private double precipIntensityMax;
|
||||
private int precipIntensityMaxTime;
|
||||
private double precipProbability;
|
||||
private String precipType;
|
||||
private double temperatureHigh;
|
||||
private int temperatureHighTime;
|
||||
private double temperatureLow;
|
||||
private int temperatureLowTime;
|
||||
private double apparentTemperatureHigh;
|
||||
private int apparentTemperatureHighTime;
|
||||
private double apparentTemperatureLow;
|
||||
private int apparentTemperatureLowTime;
|
||||
private double dewPoint;
|
||||
private double humidity;
|
||||
private double pressure;
|
||||
private double windSpeed;
|
||||
private double windGust;
|
||||
private int windGustTime;
|
||||
private int windBearing;
|
||||
private double cloudCover;
|
||||
private int uvIndex;
|
||||
private int uvIndexTime;
|
||||
private double visibility;
|
||||
private double ozone;
|
||||
private double temperatureMin;
|
||||
private int temperatureMinTime;
|
||||
private double temperatureMax;
|
||||
private int temperatureMaxTime;
|
||||
private double apparentTemperatureMin;
|
||||
private int apparentTemperatureMinTime;
|
||||
private double apparentTemperatureMax;
|
||||
private int apparentTemperatureMaxTime;
|
||||
private double precipAccumulation;
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public int getSunriseTime() {
|
||||
return sunriseTime;
|
||||
}
|
||||
|
||||
public void setSunriseTime(int sunriseTime) {
|
||||
this.sunriseTime = sunriseTime;
|
||||
}
|
||||
|
||||
public int getSunsetTime() {
|
||||
return sunsetTime;
|
||||
}
|
||||
|
||||
public void setSunsetTime(int sunsetTime) {
|
||||
this.sunsetTime = sunsetTime;
|
||||
}
|
||||
|
||||
public double getMoonPhase() {
|
||||
return moonPhase;
|
||||
}
|
||||
|
||||
public void setMoonPhase(double moonPhase) {
|
||||
this.moonPhase = moonPhase;
|
||||
}
|
||||
|
||||
public double getPrecipIntensity() {
|
||||
return precipIntensity;
|
||||
}
|
||||
|
||||
public void setPrecipIntensity(double precipIntensity) {
|
||||
this.precipIntensity = precipIntensity;
|
||||
}
|
||||
|
||||
public double getPrecipIntensityMax() {
|
||||
return precipIntensityMax;
|
||||
}
|
||||
|
||||
public void setPrecipIntensityMax(double precipIntensityMax) {
|
||||
this.precipIntensityMax = precipIntensityMax;
|
||||
}
|
||||
|
||||
public int getPrecipIntensityMaxTime() {
|
||||
return precipIntensityMaxTime;
|
||||
}
|
||||
|
||||
public void setPrecipIntensityMaxTime(int precipIntensityMaxTime) {
|
||||
this.precipIntensityMaxTime = precipIntensityMaxTime;
|
||||
}
|
||||
|
||||
public double getPrecipProbability() {
|
||||
return precipProbability;
|
||||
}
|
||||
|
||||
public void setPrecipProbability(double precipProbability) {
|
||||
this.precipProbability = precipProbability;
|
||||
}
|
||||
|
||||
public String getPrecipType() {
|
||||
return precipType;
|
||||
}
|
||||
|
||||
public void setPrecipType(String precipType) {
|
||||
this.precipType = precipType;
|
||||
}
|
||||
|
||||
public double getTemperatureHigh() {
|
||||
return temperatureHigh;
|
||||
}
|
||||
|
||||
public void setTemperatureHigh(double temperatureHigh) {
|
||||
this.temperatureHigh = temperatureHigh;
|
||||
}
|
||||
|
||||
public int getTemperatureHighTime() {
|
||||
return temperatureHighTime;
|
||||
}
|
||||
|
||||
public void setTemperatureHighTime(int temperatureHighTime) {
|
||||
this.temperatureHighTime = temperatureHighTime;
|
||||
}
|
||||
|
||||
public double getTemperatureLow() {
|
||||
return temperatureLow;
|
||||
}
|
||||
|
||||
public void setTemperatureLow(double temperatureLow) {
|
||||
this.temperatureLow = temperatureLow;
|
||||
}
|
||||
|
||||
public int getTemperatureLowTime() {
|
||||
return temperatureLowTime;
|
||||
}
|
||||
|
||||
public void setTemperatureLowTime(int temperatureLowTime) {
|
||||
this.temperatureLowTime = temperatureLowTime;
|
||||
}
|
||||
|
||||
public double getApparentTemperatureHigh() {
|
||||
return apparentTemperatureHigh;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureHigh(double apparentTemperatureHigh) {
|
||||
this.apparentTemperatureHigh = apparentTemperatureHigh;
|
||||
}
|
||||
|
||||
public int getApparentTemperatureHighTime() {
|
||||
return apparentTemperatureHighTime;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureHighTime(int apparentTemperatureHighTime) {
|
||||
this.apparentTemperatureHighTime = apparentTemperatureHighTime;
|
||||
}
|
||||
|
||||
public double getApparentTemperatureLow() {
|
||||
return apparentTemperatureLow;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureLow(double apparentTemperatureLow) {
|
||||
this.apparentTemperatureLow = apparentTemperatureLow;
|
||||
}
|
||||
|
||||
public int getApparentTemperatureLowTime() {
|
||||
return apparentTemperatureLowTime;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureLowTime(int apparentTemperatureLowTime) {
|
||||
this.apparentTemperatureLowTime = apparentTemperatureLowTime;
|
||||
}
|
||||
|
||||
public double getDewPoint() {
|
||||
return dewPoint;
|
||||
}
|
||||
|
||||
public void setDewPoint(double dewPoint) {
|
||||
this.dewPoint = dewPoint;
|
||||
}
|
||||
|
||||
public double getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public void setHumidity(double humidity) {
|
||||
this.humidity = humidity;
|
||||
}
|
||||
|
||||
public double getPressure() {
|
||||
return pressure;
|
||||
}
|
||||
|
||||
public void setPressure(double pressure) {
|
||||
this.pressure = pressure;
|
||||
}
|
||||
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
public void setWindSpeed(double windSpeed) {
|
||||
this.windSpeed = windSpeed;
|
||||
}
|
||||
|
||||
public double getWindGust() {
|
||||
return windGust;
|
||||
}
|
||||
|
||||
public void setWindGust(double windGust) {
|
||||
this.windGust = windGust;
|
||||
}
|
||||
|
||||
public int getWindGustTime() {
|
||||
return windGustTime;
|
||||
}
|
||||
|
||||
public void setWindGustTime(int windGustTime) {
|
||||
this.windGustTime = windGustTime;
|
||||
}
|
||||
|
||||
public int getWindBearing() {
|
||||
return windBearing;
|
||||
}
|
||||
|
||||
public void setWindBearing(int windBearing) {
|
||||
this.windBearing = windBearing;
|
||||
}
|
||||
|
||||
public double getCloudCover() {
|
||||
return cloudCover;
|
||||
}
|
||||
|
||||
public void setCloudCover(double cloudCover) {
|
||||
this.cloudCover = cloudCover;
|
||||
}
|
||||
|
||||
public int getUvIndex() {
|
||||
return uvIndex;
|
||||
}
|
||||
|
||||
public void setUvIndex(int uvIndex) {
|
||||
this.uvIndex = uvIndex;
|
||||
}
|
||||
|
||||
public int getUvIndexTime() {
|
||||
return uvIndexTime;
|
||||
}
|
||||
|
||||
public void setUvIndexTime(int uvIndexTime) {
|
||||
this.uvIndexTime = uvIndexTime;
|
||||
}
|
||||
|
||||
public double getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(double visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public double getOzone() {
|
||||
return ozone;
|
||||
}
|
||||
|
||||
public void setOzone(double ozone) {
|
||||
this.ozone = ozone;
|
||||
}
|
||||
|
||||
public double getTemperatureMin() {
|
||||
return temperatureMin;
|
||||
}
|
||||
|
||||
public void setTemperatureMin(double temperatureMin) {
|
||||
this.temperatureMin = temperatureMin;
|
||||
}
|
||||
|
||||
public int getTemperatureMinTime() {
|
||||
return temperatureMinTime;
|
||||
}
|
||||
|
||||
public void setTemperatureMinTime(int temperatureMinTime) {
|
||||
this.temperatureMinTime = temperatureMinTime;
|
||||
}
|
||||
|
||||
public double getTemperatureMax() {
|
||||
return temperatureMax;
|
||||
}
|
||||
|
||||
public void setTemperatureMax(double temperatureMax) {
|
||||
this.temperatureMax = temperatureMax;
|
||||
}
|
||||
|
||||
public int getTemperatureMaxTime() {
|
||||
return temperatureMaxTime;
|
||||
}
|
||||
|
||||
public void setTemperatureMaxTime(int temperatureMaxTime) {
|
||||
this.temperatureMaxTime = temperatureMaxTime;
|
||||
}
|
||||
|
||||
public double getApparentTemperatureMin() {
|
||||
return apparentTemperatureMin;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureMin(double apparentTemperatureMin) {
|
||||
this.apparentTemperatureMin = apparentTemperatureMin;
|
||||
}
|
||||
|
||||
public int getApparentTemperatureMinTime() {
|
||||
return apparentTemperatureMinTime;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureMinTime(int apparentTemperatureMinTime) {
|
||||
this.apparentTemperatureMinTime = apparentTemperatureMinTime;
|
||||
}
|
||||
|
||||
public double getApparentTemperatureMax() {
|
||||
return apparentTemperatureMax;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureMax(double apparentTemperatureMax) {
|
||||
this.apparentTemperatureMax = apparentTemperatureMax;
|
||||
}
|
||||
|
||||
public int getApparentTemperatureMaxTime() {
|
||||
return apparentTemperatureMaxTime;
|
||||
}
|
||||
|
||||
public void setApparentTemperatureMaxTime(int apparentTemperatureMaxTime) {
|
||||
this.apparentTemperatureMaxTime = apparentTemperatureMaxTime;
|
||||
}
|
||||
|
||||
public double getPrecipAccumulation() {
|
||||
return precipAccumulation;
|
||||
}
|
||||
|
||||
public void setPrecipAccumulation(double precipAccumulation) {
|
||||
this.precipAccumulation = precipAccumulation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* 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.darksky.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyHourlyData} is the Java class used to map the JSON response to an Dark Sky request.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class DarkSkyHourlyData {
|
||||
private String summary;
|
||||
private String icon;
|
||||
private @Nullable List<HourlyData> data;
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public @Nullable List<HourlyData> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(@Nullable List<HourlyData> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public class HourlyData {
|
||||
private int time;
|
||||
private String summary;
|
||||
private String icon;
|
||||
private double precipIntensity;
|
||||
private double precipProbability;
|
||||
private String precipType;
|
||||
private double temperature;
|
||||
private double apparentTemperature;
|
||||
private double dewPoint;
|
||||
private double humidity;
|
||||
private double pressure;
|
||||
private double windSpeed;
|
||||
private double windGust;
|
||||
private int windBearing;
|
||||
private double cloudCover;
|
||||
private int uvIndex;
|
||||
private double visibility;
|
||||
private double ozone;
|
||||
private double precipAccumulation;
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public double getPrecipIntensity() {
|
||||
return precipIntensity;
|
||||
}
|
||||
|
||||
public void setPrecipIntensity(double precipIntensity) {
|
||||
this.precipIntensity = precipIntensity;
|
||||
}
|
||||
|
||||
public double getPrecipProbability() {
|
||||
return precipProbability;
|
||||
}
|
||||
|
||||
public void setPrecipProbability(double precipProbability) {
|
||||
this.precipProbability = precipProbability;
|
||||
}
|
||||
|
||||
public String getPrecipType() {
|
||||
return precipType;
|
||||
}
|
||||
|
||||
public void setPrecipType(String precipType) {
|
||||
this.precipType = precipType;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public double getApparentTemperature() {
|
||||
return apparentTemperature;
|
||||
}
|
||||
|
||||
public void setApparentTemperature(double apparentTemperature) {
|
||||
this.apparentTemperature = apparentTemperature;
|
||||
}
|
||||
|
||||
public double getDewPoint() {
|
||||
return dewPoint;
|
||||
}
|
||||
|
||||
public void setDewPoint(double dewPoint) {
|
||||
this.dewPoint = dewPoint;
|
||||
}
|
||||
|
||||
public double getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public void setHumidity(double humidity) {
|
||||
this.humidity = humidity;
|
||||
}
|
||||
|
||||
public double getPressure() {
|
||||
return pressure;
|
||||
}
|
||||
|
||||
public void setPressure(double pressure) {
|
||||
this.pressure = pressure;
|
||||
}
|
||||
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
public void setWindSpeed(double windSpeed) {
|
||||
this.windSpeed = windSpeed;
|
||||
}
|
||||
|
||||
public double getWindGust() {
|
||||
return windGust;
|
||||
}
|
||||
|
||||
public void setWindGust(double windGust) {
|
||||
this.windGust = windGust;
|
||||
}
|
||||
|
||||
public int getWindBearing() {
|
||||
return windBearing;
|
||||
}
|
||||
|
||||
public void setWindBearing(int windBearing) {
|
||||
this.windBearing = windBearing;
|
||||
}
|
||||
|
||||
public double getCloudCover() {
|
||||
return cloudCover;
|
||||
}
|
||||
|
||||
public void setCloudCover(double cloudCover) {
|
||||
this.cloudCover = cloudCover;
|
||||
}
|
||||
|
||||
public int getUvIndex() {
|
||||
return uvIndex;
|
||||
}
|
||||
|
||||
public void setUvIndex(int uvIndex) {
|
||||
this.uvIndex = uvIndex;
|
||||
}
|
||||
|
||||
public double getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(double visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public double getOzone() {
|
||||
return ozone;
|
||||
}
|
||||
|
||||
public void setOzone(double ozone) {
|
||||
this.ozone = ozone;
|
||||
}
|
||||
|
||||
public double getPrecipAccumulation() {
|
||||
return precipAccumulation;
|
||||
}
|
||||
|
||||
public void setPrecipAccumulation(double precipAccumulation) {
|
||||
this.precipAccumulation = precipAccumulation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.darksky.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link DarkSkyJsonWeatherData} is the Java class used to map the JSON response to an Dark Sky request.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class DarkSkyJsonWeatherData {
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private String timezone;
|
||||
private DarkSkyCurrentlyData currently;
|
||||
private DarkSkyHourlyData hourly;
|
||||
private DarkSkyDailyData daily;
|
||||
private @Nullable List<AlertsData> alerts;
|
||||
private int offset;
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return timezone;
|
||||
}
|
||||
|
||||
public void setTimezone(String timezone) {
|
||||
this.timezone = timezone;
|
||||
}
|
||||
|
||||
public DarkSkyCurrentlyData getCurrently() {
|
||||
return currently;
|
||||
}
|
||||
|
||||
public void setCurrently(DarkSkyCurrentlyData currently) {
|
||||
this.currently = currently;
|
||||
}
|
||||
|
||||
public DarkSkyHourlyData getHourly() {
|
||||
return hourly;
|
||||
}
|
||||
|
||||
public void setHourly(DarkSkyHourlyData hourly) {
|
||||
this.hourly = hourly;
|
||||
}
|
||||
|
||||
public DarkSkyDailyData getDaily() {
|
||||
return daily;
|
||||
}
|
||||
|
||||
public void setDaily(DarkSkyDailyData daily) {
|
||||
this.daily = daily;
|
||||
}
|
||||
|
||||
public @Nullable List<AlertsData> getAlerts() {
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public void setAlerts(List<AlertsData> alerts) {
|
||||
this.alerts = alerts;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public class AlertsData {
|
||||
public String title;
|
||||
public int time;
|
||||
public int expires;
|
||||
public String description;
|
||||
public String severity;
|
||||
public String uri;
|
||||
public List<String> regions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* 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.darksky.internal.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a simple file based cache implementation.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ByteArrayFileCache {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
|
||||
static final String CACHE_FOLDER_NAME = "cache";
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
private static final char UNIX_SEPARATOR = '/';
|
||||
private static final char WINDOWS_SEPARATOR = '\\';
|
||||
|
||||
private final File cacheFolder;
|
||||
|
||||
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
|
||||
private int expiry = 0;
|
||||
|
||||
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID) {
|
||||
// TODO track and limit folder size
|
||||
// TODO support user specific folder
|
||||
cacheFolder = new File(new File(ConfigConstants.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
|
||||
if (!cacheFolder.exists()) {
|
||||
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
cacheFolder.mkdirs();
|
||||
}
|
||||
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID/</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
|
||||
* disables this functionality.
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID, int expiry) {
|
||||
this(servicePID);
|
||||
if (expiry < 0) {
|
||||
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
|
||||
}
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||
* new content.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void put(String key, byte[] content) {
|
||||
writeFile(getUniqueFile(key), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void putIfAbsent(String key, byte[] content) {
|
||||
File fileInCache = getUniqueFile(key);
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
} else {
|
||||
writeFile(fileInCache, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache and returns the content of the file.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] putIfAbsentAndGet(String key, byte[] content) {
|
||||
putIfAbsent(key, content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given content to the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @param content the content to be written
|
||||
*/
|
||||
private void writeFile(File fileInCache, byte[] content) {
|
||||
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||
try {
|
||||
Files.write(fileInCache.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the key is present in the cache.
|
||||
*
|
||||
* @param key the key whose presence in the cache is to be tested
|
||||
* @return true if the cache contains a file for the specified key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getUniqueFile(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file associated with the given key from the cache.
|
||||
*
|
||||
* @param key the key whose associated file is to be removed
|
||||
*/
|
||||
public void remove(String key) {
|
||||
deleteFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
*/
|
||||
private void deleteFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||
fileInCache.delete();
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all files from the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting all files from cache");
|
||||
Arrays.stream(filesInCache).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired files from the cache.
|
||||
*/
|
||||
public void clearExpired() {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return;
|
||||
}
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting expired files from cache");
|
||||
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link File} is expired.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
|
||||
*/
|
||||
private boolean isExpired(File fileInCache) {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return false;
|
||||
}
|
||||
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the file associated with the given key, if it is present.
|
||||
*
|
||||
* @param key the key whose associated file is to be returned
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] get(String key) {
|
||||
return readFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content from the given {@link File}, if it is present.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return the content of the file
|
||||
*/
|
||||
private byte[] readFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
try {
|
||||
return Files.readAllBytes(fileInCache.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file for the file associated with the given key
|
||||
*/
|
||||
File getUniqueFile(String key) {
|
||||
String uniqueFileName = getUniqueFileName(key);
|
||||
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
|
||||
return FILES_IN_CACHE.get(uniqueFileName);
|
||||
} else {
|
||||
String fileExtension = getFileExtension(key);
|
||||
File fileInCache = new File(cacheFolder,
|
||||
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
|
||||
return fileInCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name.
|
||||
*
|
||||
* @param fileName the file name to retrieve the extension of
|
||||
* @return the extension of the file or null if none exists
|
||||
*/
|
||||
@Nullable
|
||||
String getFileExtension(String fileName) {
|
||||
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
|
||||
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file name from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file name for the file associated with the given key
|
||||
*/
|
||||
String getUniqueFileName(String key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
byte[] bytesOfKey = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] md5Hash = md.digest(bytesOfKey);
|
||||
BigInteger bigInt = new BigInteger(1, md5Hash);
|
||||
String fileNameHash = bigInt.toString(16);
|
||||
// Now we need to zero pad it if you actually want the full 32 chars
|
||||
while (fileNameHash.length() < 32) {
|
||||
fileNameHash = "0" + fileNameHash;
|
||||
}
|
||||
return fileNameHash;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// should not happen
|
||||
logger.error("Could not create MD5 hash for key '{}'", key, ex);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="darksky" 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>Dark Sky Binding</name>
|
||||
<description>Dark Sky - The easiest, most advanced, weather API on the web.</description>
|
||||
<author>Christoph Weitkamp</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<!-- Config for Dark Sky Binding -->
|
||||
<config-description uri="bridge-type:darksky:weather-api">
|
||||
<parameter name="apikey" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>API Key</label>
|
||||
<description>API key to access the Dark Sky API.</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" min="1" unit="min">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval (in minutes).</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
<parameter name="language" type="text">
|
||||
<label>Language</label>
|
||||
<description>Language to be used by the Dark Sky API.</description>
|
||||
<options>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="az">Azerbaijani</option>
|
||||
<option value="be">Belarusian</option>
|
||||
<option value="bg">Bulgarian</option>
|
||||
<option value="bn">Bengali</option>
|
||||
<option value="bs">Bosnian</option>
|
||||
<option value="ca">Catalan</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="da">Danish</option>
|
||||
<option value="de">German</option>
|
||||
<option value="el">Greek</option>
|
||||
<option value="en">English</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="et">Estonian</option>
|
||||
<option value="fi">Finnish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="he">Hebrew</option>
|
||||
<option value="hi">Hindi</option>
|
||||
<option value="hr">Croatian</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="id">Indonesian</option>
|
||||
<option value="is">Icelandic</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="ka">Georgian</option>
|
||||
<option value="kn">Kannada</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="kw">Cornish</option>
|
||||
<option value="lv">Latvian</option>
|
||||
<option value="mr">Marathi</option>
|
||||
<option value="nb">Norwegian Bokmål</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="no">Norwegian Bokmål (alias for 'nb')</option>
|
||||
<option value="pa">Punjabi</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="ro">Romanian</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="sk">Slovak</option>
|
||||
<option value="sl">Slovenian</option>
|
||||
<option value="sr">Serbian</option>
|
||||
<option value="sv">Swedish</option>
|
||||
<option value="ta">Tamil</option>
|
||||
<option value="te">Telugu</option>
|
||||
<option value="tet">Tetum</option>
|
||||
<option value="tr">Turkish</option>
|
||||
<option value="uk">Ukrainian</option>
|
||||
<option value="x-pig-latin">Igpay Atinlay</option>
|
||||
<option value="zh">Simplified Chinese</option>
|
||||
<option value="zh-tw">Traditional Chinese</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:darksky:weather-and-forecast">
|
||||
<parameter name="location" type="text" required="true">
|
||||
<context>location</context>
|
||||
<label>Location of Weather</label>
|
||||
<description>Location of weather in geographical coordinates (latitude/longitude/altitude).</description>
|
||||
</parameter>
|
||||
<parameter name="forecastHours" type="integer" min="0" max="48" step="1">
|
||||
<label>Number of Hours</label>
|
||||
<description>Number of hours for hourly forecast.</description>
|
||||
<default>24</default>
|
||||
</parameter>
|
||||
<parameter name="forecastDays" type="integer" min="0" max="8" step="1">
|
||||
<label>Number of Days</label>
|
||||
<description>Number of days for daily forecast.</description>
|
||||
<default>8</default>
|
||||
</parameter>
|
||||
<parameter name="numberOfAlerts" type="integer" min="0" max="8" step="1">
|
||||
<label>Number of Alerts</label>
|
||||
<description>Number of alerts to be shown.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:darksky:sunrise-sunset-event">
|
||||
<parameter name="offset" type="integer" min="-1440" max="1440" unit="min">
|
||||
<label>Offset</label>
|
||||
<description>Moves an event forward or backward (in minutes).</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="earliest" type="text" pattern="^([0-1][0-9]|2[0-3])(:[0-5][0-9])$">
|
||||
<label>Earliest</label>
|
||||
<description>The earliest time of the day for the event (in hh:mm).</description>
|
||||
</parameter>
|
||||
<parameter name="latest" type="text" pattern="^([0-1][0-9]|2[0-3])(:[0-5][0-9])$">
|
||||
<label>Latest</label>
|
||||
<description>The latest time of the day for the event (in hh:mm).</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,14 @@
|
||||
# thing status
|
||||
offline.conf-error-missing-apikey = The 'apikey' parameter must be configured.
|
||||
offline.conf-error-invalid-apikey = Invalid API key. Please see https://darksky.net/dev/docs/faq#faq-general for more info.
|
||||
offline.conf-error-not-supported-refreshInterval = The 'refreshInterval' parameter must be at least 1 minute.
|
||||
offline.conf-error-not-supported-language = The given 'language' parameter is not supported.
|
||||
|
||||
offline.conf-error-missing-location = The 'location' parameter must be configured.
|
||||
offline.conf-error-parsing-location = The 'location' parameter could not be split into latitude and longitude.
|
||||
offline.conf-error-not-supported-number-of-hours = The 'forecastHours' parameter must be between 0 and 120 - increment 3.
|
||||
offline.conf-error-not-supported-number-of-days = The 'forecastDays' parameter must be between 0 and 16.
|
||||
offline.conf-error-not-supported-number-of-alerts = The 'numberOfAlerts' parameter must be greater than 0.
|
||||
|
||||
# discovery result
|
||||
discovery.darksky.weather-and-forecast.api.local.label = Local Weather And Forecast
|
||||
@@ -0,0 +1,389 @@
|
||||
# binding
|
||||
binding.darksky.name = Dark Sky Binding
|
||||
binding.darksky.description = Dark Sky - Die einfachste und fortschrittlichste Wetter-API im Web.
|
||||
|
||||
# bridge types
|
||||
thing-type.darksky.weather-api.label = Dark Sky Konto
|
||||
thing-type.darksky.weather-api.description = Ermöglicht den Zugriff auf die Dark Sky API.
|
||||
|
||||
# bridge types config
|
||||
bridge-type.config.darksky.weather-api.apikey.label = API Schlüssel
|
||||
bridge-type.config.darksky.weather-api.apikey.description = API Schlüssel für den Zugriff auf die Dark Sky API.
|
||||
|
||||
bridge-type.config.darksky.weather-api.refreshInterval.label = Abfrageintervall
|
||||
bridge-type.config.darksky.weather-api.refreshInterval.description = Intervall zur Abfrage der Dark Sky API (in min).
|
||||
|
||||
bridge-type.config.darksky.weather-api.language.label = Sprache
|
||||
bridge-type.config.darksky.weather-api.language.description = Sprache zur Anzeige der Daten.
|
||||
bridge-type.config.darksky.weather-api.language.option.ar = Arabisch
|
||||
bridge-type.config.darksky.weather-api.language.option.az = Aserbaidschaner
|
||||
bridge-type.config.darksky.weather-api.language.option.be = Belarussisch
|
||||
bridge-type.config.darksky.weather-api.language.option.bg = Bulgarisch
|
||||
bridge-type.config.darksky.weather-api.language.option.bn = Bengali
|
||||
bridge-type.config.darksky.weather-api.language.option.bs = Bosnisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ca = Katalanisch
|
||||
bridge-type.config.darksky.weather-api.language.option.cz = Tschechisch
|
||||
bridge-type.config.darksky.weather-api.language.option.da = Dänish
|
||||
bridge-type.config.darksky.weather-api.language.option.de = Deutsch
|
||||
bridge-type.config.darksky.weather-api.language.option.el = Griechisch
|
||||
bridge-type.config.darksky.weather-api.language.option.en = Englisch
|
||||
bridge-type.config.darksky.weather-api.language.option.eo = Esperanto
|
||||
bridge-type.config.darksky.weather-api.language.option.es = Spanisch
|
||||
bridge-type.config.darksky.weather-api.language.option.et = Estnisch
|
||||
bridge-type.config.darksky.weather-api.language.option.fi = Finnisch
|
||||
bridge-type.config.darksky.weather-api.language.option.fr = Französisch
|
||||
bridge-type.config.darksky.weather-api.language.option.he = Hebräisch
|
||||
bridge-type.config.darksky.weather-api.language.option.hi = Hindi
|
||||
bridge-type.config.darksky.weather-api.language.option.hr = Kroatisch
|
||||
bridge-type.config.darksky.weather-api.language.option.hu = Ungarisch
|
||||
bridge-type.config.darksky.weather-api.language.option.id = Indonesian
|
||||
bridge-type.config.darksky.weather-api.language.option.is = Isländisch
|
||||
bridge-type.config.darksky.weather-api.language.option.it = Italienisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ja = Japanisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ka = Georgisch
|
||||
bridge-type.config.darksky.weather-api.language.option.kn = Kanadisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ko = Koreanisch
|
||||
bridge-type.config.darksky.weather-api.language.option.kw = Kornisch
|
||||
bridge-type.config.darksky.weather-api.language.option.lv = Lettisch
|
||||
bridge-type.config.darksky.weather-api.language.option.mr = Marathi
|
||||
bridge-type.config.darksky.weather-api.language.option.nb = Norwegischer Bokmål
|
||||
bridge-type.config.darksky.weather-api.language.option.nl = Holländisch
|
||||
bridge-type.config.darksky.weather-api.language.option.no = Norwegischer Bokmål (Alias für 'nb')
|
||||
bridge-type.config.darksky.weather-api.language.option.pa = Punjabi
|
||||
bridge-type.config.darksky.weather-api.language.option.pl = Polnisch
|
||||
bridge-type.config.darksky.weather-api.language.option.pt = Portugiesisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ro = Rumänisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ru = Russisch
|
||||
bridge-type.config.darksky.weather-api.language.option.sk = Slowakisch
|
||||
bridge-type.config.darksky.weather-api.language.option.sl = Slowenisch
|
||||
bridge-type.config.darksky.weather-api.language.option.sr = Serbisch
|
||||
bridge-type.config.darksky.weather-api.language.option.sv = Schwedisch
|
||||
bridge-type.config.darksky.weather-api.language.option.ta = Tamil
|
||||
bridge-type.config.darksky.weather-api.language.option.te = Telugu
|
||||
bridge-type.config.darksky.weather-api.language.option.tet = Tetum
|
||||
bridge-type.config.darksky.weather-api.language.option.tr = Türkisch
|
||||
bridge-type.config.darksky.weather-api.language.option.uk = Ukrainisch
|
||||
bridge-type.config.darksky.weather-api.language.option.x-pig-latin = Igpay Atinlay
|
||||
bridge-type.config.darksky.weather-api.language.option.zh = Chinesisch - Simplified
|
||||
bridge-type.config.darksky.weather-api.language.option.zh-tw = Chinesisch - Traditional
|
||||
|
||||
# thing types
|
||||
thing-type.darksky.weather-and-forecast.label = Wetterinformationen
|
||||
thing-type.darksky.weather-and-forecast.description = Ermöglicht die Anzeige der aktuellen Wetterinformationen und der Wettervorhersage.
|
||||
|
||||
# thing types config
|
||||
thing-type.config.darksky.weather-and-forecast.location.label = Ort der Wetterdaten
|
||||
thing-type.config.darksky.weather-and-forecast.location.description = Ort der Wetterdaten in geographischen Koordinaten (Breitengrad/Längengrad/Höhe).
|
||||
|
||||
thing-type.config.darksky.weather-and-forecast.forecastHours.label = Stunden
|
||||
thing-type.config.darksky.weather-and-forecast.forecastHours.description = Anzahl der Stunden für die Wettervorhersage.
|
||||
|
||||
thing-type.config.darksky.weather-and-forecast.forecastDays.label = Tage
|
||||
thing-type.config.darksky.weather-and-forecast.forecastDays.description = Anzahl der Tage für die Wettervorhersage.
|
||||
|
||||
thing-type.config.darksky.weather-and-forecast.numberOfAlerts.label = Wetterwarnungen
|
||||
thing-type.config.darksky.weather-and-forecast.numberOfAlerts.description = Anzahl der Wetterwarnungen.
|
||||
|
||||
# channel group types
|
||||
channel-group-type.darksky.current.label = Aktuelles Wetter
|
||||
channel-group-type.darksky.current.description = Fasst aktuelle Wetterdaten zusammen.
|
||||
|
||||
channel-group-type.darksky.hourlyForecast.label = 3 Stunden Wettervorhersage
|
||||
channel-group-type.darksky.hourlyForecast.description = Fasst Daten der 5 Tage / 3 Stunden Wettervorhersage zusammen.
|
||||
|
||||
channel-group-type.darksky.dailyForecast.label = Tägliche Wettervorhersage
|
||||
channel-group-type.darksky.dailyForecast.description = Fasst Daten der 16 Tage / täglichen Wettervorhersage zusammen.
|
||||
|
||||
channel-group-type.darksky.alerts.label = Wetterwarnungen
|
||||
channel-group-type.darksky.alerts.description = Fasst Daten von Wetterwarnungen zusammen.
|
||||
|
||||
# channel groups
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours01.label = Wettervorhersage für 1 Stunde
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours01.description = Fasst Daten der Wettervorhersage in der nächsten Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours02.label = Wettervorhersage für 2 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours02.description = Fasst Daten der Wettervorhersage zwei Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours03.label = Wettervorhersage für 3 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours03.description = Fasst Daten der Wettervorhersage drei Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours04.label = Wettervorhersage für 4 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours04.description = Fasst Daten der Wettervorhersage in vier Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours05.label = Wettervorhersage für 5 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours05.description = Fasst Daten der Wettervorhersage in fünf Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours06.label = Wettervorhersage für 6 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours06.description = Fasst Daten der Wettervorhersage in sechs Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours07.label = Wettervorhersage für 7 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours07.description = Fasst Daten der Wettervorhersage in sieben Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours08.label = Wettervorhersage für 8 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours08.description = Fasst Daten der Wettervorhersage in acht Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours09.label = Wettervorhersage für 9 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours09.description = Fasst Daten der Wettervorhersage in neun Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours10.label = Wettervorhersage für 10 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours10.description = Fasst Daten der Wettervorhersage in zehn Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours11.label = Wettervorhersage für 11 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours11.description = Fasst Daten der Wettervorhersage in els Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours12.label = Wettervorhersage für 12 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours12.description = Fasst Daten der Wettervorhersage in zwölf Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours13.label = Wettervorhersage für 13 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours13.description = Fasst Daten der Wettervorhersage in 13 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours14.label = Wettervorhersage für 14 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours14.description = Fasst Daten der Wettervorhersage in 14 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours15.label = Wettervorhersage für 15 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours15.description = Fasst Daten der Wettervorhersage in 15 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours16.label = Wettervorhersage für 16 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours16.description = Fasst Daten der Wettervorhersage in 16 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours17.label = Wettervorhersage für 17 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours17.description = Fasst Daten der Wettervorhersage in 17 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours18.label = Wettervorhersage für 18 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours18.description = Fasst Daten der Wettervorhersage in 18 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours19.label = Wettervorhersage für 19 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours19.description = Fasst Daten der Wettervorhersage in 19 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours20.label = Wettervorhersage für 20 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours20.description = Fasst Daten der Wettervorhersage in 20 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours21.label = Wettervorhersage für 21 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours21.description = Fasst Daten der Wettervorhersage in 21 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours22.label = Wettervorhersage für 22 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours22.description = Fasst Daten der Wettervorhersage in 22 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours23.label = Wettervorhersage für 23 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours23.description = Fasst Daten der Wettervorhersage in 23 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours24.label = Wettervorhersage für 24 Stunden
|
||||
thing-type.darksky.weather-and-forecast.group.forecastHours24.description = Fasst Daten der Wettervorhersage in 24 Stunden zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastToday.label = Wettervorhersage für heute
|
||||
thing-type.darksky.weather-and-forecast.group.forecastToday.description = Fasst Daten der heutigen Wettervorhersage zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastTomorrow.label = Wettervorhersage für morgen
|
||||
thing-type.darksky.weather-and-forecast.group.forecastTomorrow.description = Fasst Daten der morgigen Wettervorhersage zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay2.label = Wettervorhersage für übermorgen
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay2.description = Fasst Daten der übermorgigen Wettervorhersage zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay3.label = Wettervorhersage für 3 Tage
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay3.description = Fasst Daten der Wettervorhersage in drei Tagen zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay4.label = Wettervorhersage für 4 Tage
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay4.description = Fasst Daten der Wettervorhersage in vier Tagen zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay5.label = Wettervorhersage für 5 Tage
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay5.description = Fasst Daten der Wettervorhersage in fünf Tagen zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay6.label = Wettervorhersage für 6 Tage
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay6.description = Fasst Daten der Wettervorhersage in sechs Tagen zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay7.label = Wettervorhersage für 7 Tage
|
||||
thing-type.darksky.weather-and-forecast.group.forecastDay7.description = Fasst Daten der Wettervorhersage in sieben Tagen zusammen.
|
||||
|
||||
thing-type.darksky.weather-and-forecast.group.alerts1.label = 1. Wetterwarnungen
|
||||
thing-type.darksky.weather-and-forecast.group.alerts1.description = Fasst Daten der 1. Wetterwarnungen zusammen.
|
||||
|
||||
# channel types
|
||||
channel-type.darksky.time-stamp.label = Letzte Messung
|
||||
channel-type.darksky.time-stamp.description = Zeigt den Zeitpunkt der letzten Messung an.
|
||||
channel-type.darksky.time-stamp.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.hourly-forecast-time-stamp.label = Vorhersage Zeit
|
||||
channel-type.darksky.hourly-forecast-time-stamp.description = Zeigt den Zeitpunkt der Wettervorhersage an.
|
||||
channel-type.darksky.hourly-forecast-time-stamp.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.daily-forecast-time-stamp.label = Vorhersage Datum
|
||||
channel-type.darksky.daily-forecast-time-stamp.description = Zeigt das Datum der Wettervorhersage an.
|
||||
channel-type.darksky.daily-forecast-time-stamp.state.pattern = %1$td.%1$tm.%1$tY
|
||||
|
||||
channel-type.darksky.condition.label = Wetterlage
|
||||
channel-type.darksky.condition.description = Zeigt die aktuelle Wetterlage an.
|
||||
|
||||
channel-type.darksky.forecasted-condition.label = Vorhergesagte Wetterlage
|
||||
channel-type.darksky.forecasted-condition.description = Zeigt die vorhergesagte Wetterlage an.
|
||||
|
||||
channel-type.darksky.condition-icon.label = Icon
|
||||
channel-type.darksky.condition-icon.description = Zeigt das Icon der Wetterlage an.
|
||||
|
||||
channel-type.darksky.condition-icon-id.label = Icon-ID
|
||||
channel-type.darksky.condition-icon-id.description = Zeigt die ID für das Icon der Wetterlage an.
|
||||
|
||||
channel-type.darksky.forecasted-outdoor-temperature.label = Vorhergesagte Temperatur
|
||||
channel-type.darksky.forecasted-outdoor-temperature.description = Zeigt die vorhergesagte Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-min-outdoor-temperature.label = Minimale Temperatur
|
||||
channel-type.darksky.forecasted-min-outdoor-temperature.description = Zeigt die vorhergesagte minimale Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-max-outdoor-temperature.label = Maximale Temperatur
|
||||
channel-type.darksky.forecasted-max-outdoor-temperature.description = Zeigt die vorhergesagte maximale Außentemperatur an.
|
||||
|
||||
channel-type.darksky.apparent-temperature.label = Gefühlte Temperatur
|
||||
channel-type.darksky.apparent-temperature.description = Zeigt die gefühlte Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-apparent-temperature.label = Vorhergesagte Gefühlte Temperatur
|
||||
channel-type.darksky.forecasted-apparent-temperature.description = Zeigt die vorhergesagte gefühlte Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-min-apparent-temperature.label = Minimale Gefühlte Temperatur
|
||||
channel-type.darksky.forecasted-min-apparent-temperature.description = Zeigt die vorhergesagte minimale gefühlte Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-max-apparent-temperature.label = Maximale Gefühlte Temperatur
|
||||
channel-type.darksky.forecasted-max-apparent-temperature.description = Zeigt die vorhergesagte maximale gefühlte Außentemperatur an.
|
||||
|
||||
channel-type.darksky.forecasted-barometric-pressure.label = Vorhergesagter Luftdruck
|
||||
channel-type.darksky.forecasted-barometric-pressure.description = Zeigt den vorhergesagten Luftdruck an.
|
||||
|
||||
channel-type.darksky.forecasted-atmospheric-humidity.label = Vorhergesagte Luftfeuchtigkeit
|
||||
channel-type.darksky.forecasted-atmospheric-humidity.description = Zeigt die vorhergesagte Luftfeuchtigkeit an.
|
||||
|
||||
channel-type.darksky.forecasted-wind-speed.label = Vorhergesagte Windgeschwindigkeit
|
||||
channel-type.darksky.forecasted-wind-speed.description = Zeigt die vorhergesagte Windgeschwindigkeit an.
|
||||
|
||||
channel-type.darksky.forecasted-wind-direction.label = Vorhergesagte Windrichtung
|
||||
channel-type.darksky.forecasted-wind-direction.description = Zeigt die vorhergesagte Windrichtung an.
|
||||
|
||||
channel-type.darksky.gust-speed.label = Windböengeschwindigkeit
|
||||
channel-type.darksky.gust-speed.description = Zeigt die aktuelle Windböengeschwindigkeit an.
|
||||
|
||||
channel-type.darksky.forecasted-gust-speed.label = Vorhergesagte Windböengeschwindigkeit
|
||||
channel-type.darksky.forecasted-gust-speed.description = Zeigt die vorhergesagte Windböengeschwindigkeit an.
|
||||
|
||||
channel-type.darksky.cloudiness.label = Bewölkung
|
||||
channel-type.darksky.cloudiness.description = Zeigt die aktuelle Bewölkung an.
|
||||
|
||||
channel-type.darksky.forecasted-cloudiness.label = Vorhergesagte Bewölkung
|
||||
channel-type.darksky.forecasted-cloudiness.description = Zeigt die vorhergesagte Bewölkung an.
|
||||
|
||||
channel-type.darksky.visibility.label = Sichtweite
|
||||
channel-type.darksky.visibility.description = Zeigt die aktuelle Sichtweite an.
|
||||
|
||||
channel-type.darksky.forecasted-visibility.label = Vorhergesagte Sichtweite
|
||||
channel-type.darksky.forecasted-visibility.description = Zeigt die vorhergesagte Sichtweite an.
|
||||
|
||||
channel-type.darksky.rain.label = Regen
|
||||
channel-type.darksky.rain.description = Zeigt die Niederschlagsintensität für Regen an.
|
||||
|
||||
channel-type.darksky.forecasted-rain.label = Vorhergesagter Regen
|
||||
channel-type.darksky.forecasted-rain.description = Zeigt die vorhergesagte Niederschlagsintensität für Regen an.
|
||||
|
||||
channel-type.darksky.snow.label = Schnee
|
||||
channel-type.darksky.snow.description = Zeigt die Niederschlagsintensität für Schnee an.
|
||||
|
||||
channel-type.darksky.forecasted-snow.label = Vorhergesagter Schnee
|
||||
channel-type.darksky.forecasted-snow.description = Zeigt die vorhergesagte Niederschlagsintensität für Schnee an.
|
||||
|
||||
channel-type.darksky.precip-intensity.label = Niederschlagsintensität
|
||||
channel-type.darksky.precip-intensity.description = Zeigt die Niederschlagsintensität an.
|
||||
|
||||
channel-type.darksky.forecasted-precip-intensity.label = Vorhergesagte Niederschlagsintensität
|
||||
channel-type.darksky.forecasted-precip-intensity.description = Zeigt die vorhergesagte Niederschlagsintensität an.
|
||||
|
||||
channel-type.darksky.precip-probability.label = Niederschlagswahrscheinlichkeit
|
||||
channel-type.darksky.precip-probability.description = Zeigt die Niederschlagswahrscheinlichkeit an.
|
||||
|
||||
channel-type.darksky.forecasted-precip-probability.label = Vorhergesagte Niederschlagswahrscheinlichkeit
|
||||
channel-type.darksky.forecasted-precip-probability.description = Zeigt die vorhergesagte Niederschlagswahrscheinlichkeit an.
|
||||
|
||||
channel-type.darksky.precip-type.label = Art des Niederschlages
|
||||
channel-type.darksky.precip-type.description = Zeigt die Art des Niederschlages an.
|
||||
channel-type.darksky.precip-type.state.option.rain = Regen
|
||||
channel-type.darksky.precip-type.state.option.snow = Schnee
|
||||
channel-type.darksky.precip-type.state.option.sleet = Schneeregen
|
||||
|
||||
channel-type.darksky.forecasted-precip-type.label = Vorhergesagte Art des Niederschlages
|
||||
channel-type.darksky.forecasted-precip-type.description = Zeigt die vorhergesagte Art das Niederschlages an.
|
||||
channel-type.darksky.forecasted-precip-type.state.option.rain = Regen
|
||||
channel-type.darksky.forecasted-precip-type.state.option.snow = Schnee
|
||||
channel-type.darksky.forecasted-precip-type.state.option.sleet = Schneeregen
|
||||
|
||||
channel-type.darksky.uvindex.label = UV-Index
|
||||
channel-type.darksky.uvindex.description = Zeigt den aktuellen UV-Index an.
|
||||
|
||||
channel-type.darksky.forecasted-uvindex.label = Vorhergesagter UV-Index
|
||||
channel-type.darksky.forecasted-uvindex.description = Zeigt den vorhergesagten UV-Index an.
|
||||
|
||||
channel-type.darksky.ozone.label = Ozon-Wert
|
||||
channel-type.darksky.ozone.description = Zeigt den aktuellen Ozon-Wert an.
|
||||
|
||||
channel-type.darksky.forecasted-ozone.label = Vorhergesagter Ozon-Wert
|
||||
channel-type.darksky.forecasted-ozone.description = Zeigt den vorhergesagten Ozon-Wert an.
|
||||
|
||||
channel-type.darksky.sunrise.label = Sonnenaufgang
|
||||
channel-type.darksky.sunrise.description = Zeigt den Zeitpunkt des Sonnenaufgangs des aktuellen Tages an.
|
||||
channel-type.darksky.sunrise.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.forecasted-sunrise.label = Vorhergesagter Sonnenaufgang
|
||||
channel-type.darksky.forecasted-sunrise.description = Zeigt den vorhergesagten Zeitpunkt des Sonnenaufgangs des Tages an.
|
||||
channel-type.darksky.forecasted-sunrise.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.sunset.label = Sonnenuntergang
|
||||
channel-type.darksky.sunset.description = Zeigt den Zeitpunkt des Sonnenuntergangs des aktuellen Tages an.
|
||||
channel-type.darksky.sunset.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.forecasted-sunset.label = Vorhergesagter Sonnenuntergang
|
||||
channel-type.darksky.forecasted-sunset.description = Zeigt den vorhergesagten Zeitpunkt des Sonnenuntergangs des Tages an.
|
||||
channel-type.darksky.forecasted-sunset.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.sunrise-event.label = Sonnenaufgang-Trigger
|
||||
channel-type.darksky.sunrise-event.description = Trigger für den Sonnenaufgang.
|
||||
|
||||
channel-type.darksky.sunset-event.label = Sonnenuntergang-Trigger
|
||||
channel-type.darksky.sunset-event.description = Trigger für den Sonnenuntergang.
|
||||
|
||||
channel-type.darksky.alert-title.label = Kurzbeschreibung
|
||||
channel-type.darksky.alert-title.description = Zeigt die Kurzbeschreibung der Wetterwarnung an.
|
||||
|
||||
channel-type.darksky.alert-description.label = Beschreibung
|
||||
channel-type.darksky.alert-description.description = Zeigt die Beschreibung der Wetterwarnung an.
|
||||
|
||||
channel-type.darksky.alert-severity.label = Schwere
|
||||
channel-type.darksky.alert-severity.description = Zeigt die Schwere der Wetterwarnung an.
|
||||
|
||||
channel-type.darksky.alert-issued.label = Ausgegeben
|
||||
channel-type.darksky.alert-issued.description = Zeigt den Zeitpunkt der Ausgabe der Wetterwarnung an.
|
||||
channel-type.darksky.alert-issued.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.alert-expires.label = Ablauf
|
||||
channel-type.darksky.alert-expires.description = Zeigt den Zeitpunkt des Ablaufs der Wetterwarnung an.
|
||||
channel-type.darksky.alert-expires.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
|
||||
|
||||
channel-type.darksky.alert-uri.label = Link
|
||||
channel-type.darksky.alert-uri.description = Zeigt einen externen Link mit detaillierte Informationen zu der Wetterwarnung an.
|
||||
|
||||
# channel types config
|
||||
channel-type.config.darksky.sunrise-sunset-event.offset.label = Verschiebung
|
||||
channel-type.config.darksky.sunrise-sunset-event.offset.description = Verschiebt den Zeitpunkt eines Triggers vor oder zurück (in Minuten).
|
||||
|
||||
channel-type.config.darksky.sunrise-sunset-event.earliest.label = Frühester Zeitpunkt
|
||||
channel-type.config.darksky.sunrise-sunset-event.earliest.description = Frühester Zeitpunkt eines Triggers am aktuellen Tag (in hh:mm).
|
||||
|
||||
channel-type.config.darksky.sunrise-sunset-event.latest.label = Spätester Zeitpunkt
|
||||
channel-type.config.darksky.sunrise-sunset-event.latest.description = Spätester Zeitpunkt eines Triggers am aktuellen Tag (in hh:mm).
|
||||
|
||||
# thing status
|
||||
offline.conf-error-missing-apikey = Der Parameter 'apikey' muss konfiguriert werden.
|
||||
offline.conf-error-invalid-apikey = Ungültiger API Schlüssel. Mehr Infos unter https://darksky.net/dev/docs/faq#faq-general.
|
||||
offline.conf-error-not-supported-refreshInterval = Der Parameter 'refreshInterval' muss mindestens 1 min betragen.
|
||||
offline.conf-error-not-supported-language = Der angegebene Parameter 'language' wird nicht unterstützt.
|
||||
|
||||
offline.conf-error-missing-location = Der Parameter 'location' muss konfiguriert werden.
|
||||
offline.conf-error-parsing-location = Der Parameter 'location' kann nicht in Latitude und Longitude getrennt werden.
|
||||
offline.conf-error-not-supported-number-of-hours = Der Parameter 'forecastHours' muss zwischen 0 und 120 liegen - Schrittweite: 3.
|
||||
offline.conf-error-not-supported-number-of-days = Der Parameter 'forecastDays' muss zwischen 0 und 16 liegen.
|
||||
offline.conf-error-not-supported-number-of-alerts = Der Parameter 'numberOfAlerts' muss größer als 0 sein.
|
||||
|
||||
# discovery result
|
||||
discovery.darksky.weather-and-forecast.api.local.label = Lokales Wetter und Wettervorhersage
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="darksky"
|
||||
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">
|
||||
|
||||
<!-- Dark Sky Binding -->
|
||||
<bridge-type id="weather-api">
|
||||
<label>Dark Sky Account</label>
|
||||
<description>Provides access to the Dark Sky API.</description>
|
||||
|
||||
<representation-property>apikey</representation-property>
|
||||
|
||||
<config-description-ref uri="bridge-type:darksky:weather-api"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,513 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="darksky"
|
||||
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">
|
||||
|
||||
<!-- Channel groups for Dark Sky Binding -->
|
||||
<channel-group-type id="current">
|
||||
<label>Current Weather</label>
|
||||
<description>This is the current weather.</description>
|
||||
<channels>
|
||||
<channel id="time-stamp" typeId="time-stamp"/>
|
||||
<channel id="condition" typeId="condition"/>
|
||||
<channel id="icon" typeId="condition-icon"/>
|
||||
<channel id="icon-id" typeId="condition-icon-id"/>
|
||||
<channel id="temperature" typeId="system.outdoor-temperature"/>
|
||||
<channel id="apparent-temperature" typeId="apparent-temperature"/>
|
||||
<channel id="pressure" typeId="system.barometric-pressure"/>
|
||||
<channel id="humidity" typeId="system.atmospheric-humidity"/>
|
||||
<channel id="wind-speed" typeId="system.wind-speed"/>
|
||||
<channel id="wind-direction" typeId="system.wind-direction"/>
|
||||
<channel id="gust-speed" typeId="gust-speed"/>
|
||||
<channel id="cloudiness" typeId="cloudiness"/>
|
||||
<channel id="visibility" typeId="visibility"/>
|
||||
<channel id="rain" typeId="rain"/>
|
||||
<channel id="snow" typeId="snow"/>
|
||||
<channel id="precip-intensity" typeId="precip-intensity"/>
|
||||
<channel id="precip-probability" typeId="precip-probability"/>
|
||||
<channel id="precip-type" typeId="precip-type"/>
|
||||
<channel id="uvindex" typeId="uvindex"/>
|
||||
<channel id="ozone" typeId="ozone"/>
|
||||
<channel id="sunrise" typeId="sunrise"/>
|
||||
<channel id="sunrise-event" typeId="sunrise-event"/>
|
||||
<channel id="sunset" typeId="sunset"/>
|
||||
<channel id="sunset-event" typeId="sunset-event"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="hourlyForecast">
|
||||
<label>3 Hour Forecast</label>
|
||||
<description>This is the 5 day / 3 hour weather forecast.</description>
|
||||
<channels>
|
||||
<channel id="time-stamp" typeId="hourly-forecast-time-stamp"/>
|
||||
<channel id="condition" typeId="forecasted-condition"/>
|
||||
<channel id="icon" typeId="condition-icon"/>
|
||||
<channel id="icon-id" typeId="condition-icon-id"/>
|
||||
<channel id="temperature" typeId="forecasted-outdoor-temperature"/>
|
||||
<channel id="apparent-temperature" typeId="forecasted-apparent-temperature"/>
|
||||
<channel id="pressure" typeId="forecasted-barometric-pressure"/>
|
||||
<channel id="humidity" typeId="forecasted-atmospheric-humidity"/>
|
||||
<channel id="wind-speed" typeId="forecasted-wind-speed"/>
|
||||
<channel id="wind-direction" typeId="forecasted-wind-direction"/>
|
||||
<channel id="gust-speed" typeId="forecasted-gust-speed"/>
|
||||
<channel id="cloudiness" typeId="forecasted-cloudiness"/>
|
||||
<channel id="visibility" typeId="forecasted-visibility"/>
|
||||
<channel id="rain" typeId="forecasted-rain"/>
|
||||
<channel id="snow" typeId="forecasted-snow"/>
|
||||
<channel id="precip-intensity" typeId="forecasted-precip-intensity"/>
|
||||
<channel id="precip-probability" typeId="forecasted-precip-probability"/>
|
||||
<channel id="precip-type" typeId="forecasted-precip-type"/>
|
||||
<channel id="uvindex" typeId="forecasted-uvindex"/>
|
||||
<channel id="ozone" typeId="forecasted-ozone"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="dailyForecast">
|
||||
<label>Daily Forecast</label>
|
||||
<description>This is the 16 day / daily weather forecast.</description>
|
||||
<channels>
|
||||
<channel id="time-stamp" typeId="daily-forecast-time-stamp"/>
|
||||
<channel id="condition" typeId="forecasted-condition"/>
|
||||
<channel id="icon" typeId="condition-icon"/>
|
||||
<channel id="icon-id" typeId="condition-icon-id"/>
|
||||
<channel id="min-temperature" typeId="forecasted-min-outdoor-temperature"/>
|
||||
<channel id="max-temperature" typeId="forecasted-max-outdoor-temperature"/>
|
||||
<channel id="min-apparent-temperature" typeId="forecasted-min-apparent-temperature"/>
|
||||
<channel id="max-apparent-temperature" typeId="forecasted-max-apparent-temperature"/>
|
||||
<channel id="pressure" typeId="forecasted-barometric-pressure"/>
|
||||
<channel id="humidity" typeId="forecasted-atmospheric-humidity"/>
|
||||
<channel id="wind-speed" typeId="forecasted-wind-speed"/>
|
||||
<channel id="wind-direction" typeId="forecasted-wind-direction"/>
|
||||
<channel id="gust-speed" typeId="forecasted-gust-speed"/>
|
||||
<channel id="cloudiness" typeId="forecasted-cloudiness"/>
|
||||
<channel id="visibility" typeId="forecasted-visibility"/>
|
||||
<channel id="rain" typeId="forecasted-rain"/>
|
||||
<channel id="snow" typeId="forecasted-snow"/>
|
||||
<channel id="precip-intensity" typeId="forecasted-precip-intensity"/>
|
||||
<channel id="precip-probability" typeId="forecasted-precip-probability"/>
|
||||
<channel id="precip-type" typeId="forecasted-precip-type"/>
|
||||
<channel id="uvindex" typeId="forecasted-uvindex"/>
|
||||
<channel id="ozone" typeId="forecasted-ozone"/>
|
||||
<channel id="sunrise" typeId="forecasted-sunrise"/>
|
||||
<channel id="sunset" typeId="forecasted-sunset"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="alerts">
|
||||
<label>Weather Warnings</label>
|
||||
<description>Weather warnings issued for the requested location.</description>
|
||||
<channels>
|
||||
<channel id="title" typeId="alert-title"/>
|
||||
<channel id="description" typeId="alert-description"/>
|
||||
<channel id="severity" typeId="alert-severity"/>
|
||||
<channel id="issued" typeId="alert-issued"/>
|
||||
<channel id="expires" typeId="alert-expires"/>
|
||||
<channel id="uri" typeId="alert-uri"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- Channels for Dark Sky Binding -->
|
||||
<channel-type id="time-stamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Observation Time</label>
|
||||
<description>Time of data observation.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="hourly-forecast-time-stamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecast Time</label>
|
||||
<description>Time of data forecasted.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="daily-forecast-time-stamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecast Date</label>
|
||||
<description>Date of data forecasted.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="condition">
|
||||
<item-type>String</item-type>
|
||||
<label>Weather Condition</label>
|
||||
<description>Current weather condition.</description>
|
||||
<category>Sun_Clouds</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-condition">
|
||||
<item-type>String</item-type>
|
||||
<label>Forecasted Weather Condition</label>
|
||||
<description>Forecasted weather condition.</description>
|
||||
<category>Sun_Clouds</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="condition-icon">
|
||||
<item-type>Image</item-type>
|
||||
<label>Icon</label>
|
||||
<description>Icon representing the weather condition.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="condition-icon-id" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Icon Id</label>
|
||||
<description>Id of the icon to create the URL.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-outdoor-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Forecasted Temperature</label>
|
||||
<description>Forecasted outdoor temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-min-outdoor-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Minimum Temperature</label>
|
||||
<description>Minimum forecasted outdoor temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-max-outdoor-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Maximum Temperature</label>
|
||||
<description>Maximum forecasted outdoor temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="apparent-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Apparent Temperature</label>
|
||||
<description>Current apparent temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-apparent-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Forecasted Apparent Temperature</label>
|
||||
<description>Forecasted apparent temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-min-apparent-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Minimum Apparent Temperature</label>
|
||||
<description>Minimum forecasted apparent temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-max-apparent-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Maximum Apparent Temperature</label>
|
||||
<description>Maximum forecasted apparent temperature.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-barometric-pressure">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Forecasted Pressure</label>
|
||||
<description>Forecasted barometric pressure.</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.3f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-atmospheric-humidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Forecasted Humidity</label>
|
||||
<description>Forecasted atmospheric relative humidity.</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" pattern="%.0f %%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-wind-speed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Forecasted Wind Speed</label>
|
||||
<description>Forecasted wind speed.</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-wind-direction">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Forecasted Wind Direction</label>
|
||||
<description>Forecasted wind direction expressed as an angle.</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="gust-speed" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Gust Speed</label>
|
||||
<description>Current gust speed.</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-gust-speed" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Forecasted Gust Speed</label>
|
||||
<description>Forecasted gust speed.</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cloudiness">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Cloudiness</label>
|
||||
<description>Current cloudiness.</description>
|
||||
<category>Clouds</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-cloudiness">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Forecasted Cloudiness</label>
|
||||
<description>Forecasted cloudiness.</description>
|
||||
<category>Clouds</category>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="visibility">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Visibility</label>
|
||||
<description>Current visibility.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-visibility">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Forecasted Visibility</label>
|
||||
<description>Forecasted visibility.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Rain</label>
|
||||
<description>Current rain intensity.</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-rain">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Forecasted Rain Intensity</label>
|
||||
<description>Forecasted rain intensity.</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="snow">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Snow Intensity</label>
|
||||
<description>Current snow intensity.</description>
|
||||
<category>Snow</category>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-snow">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Forecasted Snow Intensity</label>
|
||||
<description>Forecasted snow intensity.</description>
|
||||
<category>Snow</category>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="precip-intensity">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Precipitation Intensity</label>
|
||||
<description>Current precipitation intensity.</description>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-precip-intensity">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Forecasted Precipitation Intensity</label>
|
||||
<description>Forecasted precipitation intensity.</description>
|
||||
<state readOnly="true" pattern="%.2f mm/h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="precip-probability">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Precipitation Probability</label>
|
||||
<description>Current precipitation probability.</description>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-precip-probability">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Forecasted Precipitation Probability</label>
|
||||
<description>Forecasted precipitation probability.</description>
|
||||
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="precip-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Precipitation Type</label>
|
||||
<description>Current precipitation type.</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="rain">Rain</option>
|
||||
<option value="snow">Snow</option>
|
||||
<option value="sleet">Sleet</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-precip-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Forecasted Precipitation Type</label>
|
||||
<description>Forecasted precipitation type.</description>
|
||||
<state readOnly="true" pattern="%s">
|
||||
<options>
|
||||
<option value="rain">Rain</option>
|
||||
<option value="snow">Snow</option>
|
||||
<option value="sleet">Sleet</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="uvindex">
|
||||
<item-type>Number</item-type>
|
||||
<label>UV Index</label>
|
||||
<description>Current UV index.</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-uvindex">
|
||||
<item-type>Number</item-type>
|
||||
<label>Forecasted UV Index</label>
|
||||
<description>Forecasted UV index.</description>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ozone">
|
||||
<item-type>Number:ArealDensity</item-type>
|
||||
<label>Ozone</label>
|
||||
<description>Current ozone.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-ozone">
|
||||
<item-type>Number:ArealDensity</item-type>
|
||||
<label>Forecasted Ozone</label>
|
||||
<description>Forecasted ozone.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sunrise">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Sunrise</label>
|
||||
<description>Sunrise of the current day.</description>
|
||||
<category>Sun</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-sunrise">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecasted Sunrise</label>
|
||||
<description>Forecasted sunrise of the day.</description>
|
||||
<category>Sun</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sunrise-event">
|
||||
<kind>trigger</kind>
|
||||
<label>Sunrise Event</label>
|
||||
<description>Event for sunrise.</description>
|
||||
<category>Sun</category>
|
||||
<event>
|
||||
<options>
|
||||
<option value="START">START</option>
|
||||
</options>
|
||||
</event>
|
||||
<config-description-ref uri="channel-type:darksky:sunrise-sunset-event"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sunset">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Sunset</label>
|
||||
<description>Sunset of the current day.</description>
|
||||
<category>Sun</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="forecasted-sunset">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Forecasted Sunset</label>
|
||||
<description>Forecasted sunset of the day.</description>
|
||||
<category>Sun</category>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sunset-event">
|
||||
<kind>trigger</kind>
|
||||
<label>Sunset Event</label>
|
||||
<description>Event for sunset.</description>
|
||||
<category>Sun</category>
|
||||
<event>
|
||||
<options>
|
||||
<option value="START">START</option>
|
||||
</options>
|
||||
</event>
|
||||
<config-description-ref uri="channel-type:darksky:sunrise-sunset-event"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-title">
|
||||
<item-type>String</item-type>
|
||||
<label>Title</label>
|
||||
<description>A brief description of the alert.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-description">
|
||||
<item-type>String</item-type>
|
||||
<label>Description</label>
|
||||
<description>A detailed description of the alert.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-severity">
|
||||
<item-type>String</item-type>
|
||||
<label>Severity</label>
|
||||
<description>The severity of the alert.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-issued">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Issued</label>
|
||||
<description>The time at which the alert was issued.</description>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-expires">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Expires</label>
|
||||
<description>The time at which the alert will expire.</description>
|
||||
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alert-uri" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>URI</label>
|
||||
<description>An external URI that one may refer to for detailed information about the alert.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="darksky"
|
||||
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">
|
||||
|
||||
<!-- Dark Sky Binding -->
|
||||
<thing-type id="weather-and-forecast">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="weather-api"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Weather and Forecast</label>
|
||||
<description>Provides current weather and forecast data from the Dark Sky API.</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="current" typeId="current"/>
|
||||
<channel-group id="forecastHours01" typeId="hourlyForecast">
|
||||
<label>1 Hour Forecast</label>
|
||||
<description>This is the weather forecast for the next hour.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours02" typeId="hourlyForecast">
|
||||
<label>2 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 2 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours03" typeId="hourlyForecast">
|
||||
<label>3 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 3 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours04" typeId="hourlyForecast">
|
||||
<label>4 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 4 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours05" typeId="hourlyForecast">
|
||||
<label>5 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 5 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours06" typeId="hourlyForecast">
|
||||
<label>6 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 6 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours07" typeId="hourlyForecast">
|
||||
<label>7 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 7 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours08" typeId="hourlyForecast">
|
||||
<label>8 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 8 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours09" typeId="hourlyForecast">
|
||||
<label>9 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 9 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours10" typeId="hourlyForecast">
|
||||
<label>10 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 10 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours11" typeId="hourlyForecast">
|
||||
<label>11 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 11 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours12" typeId="hourlyForecast">
|
||||
<label>12 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 12 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours13" typeId="hourlyForecast">
|
||||
<label>13 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 13 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours14" typeId="hourlyForecast">
|
||||
<label>14 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 14 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours15" typeId="hourlyForecast">
|
||||
<label>15 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 15 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours16" typeId="hourlyForecast">
|
||||
<label>16 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 16 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours17" typeId="hourlyForecast">
|
||||
<label>17 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 17 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours18" typeId="hourlyForecast">
|
||||
<label>18 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 18 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours19" typeId="hourlyForecast">
|
||||
<label>19 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 19 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours20" typeId="hourlyForecast">
|
||||
<label>20 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 20 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours21" typeId="hourlyForecast">
|
||||
<label>21 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 21 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours22" typeId="hourlyForecast">
|
||||
<label>22 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 22 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours23" typeId="hourlyForecast">
|
||||
<label>23 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 23 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastHours24" typeId="hourlyForecast">
|
||||
<label>24 Hours Forecast</label>
|
||||
<description>This is the weather forecast in 24 hours.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastToday" typeId="dailyForecast">
|
||||
<label>Todays Forecast</label>
|
||||
<description>This is the weather forecast for today.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastTomorrow" typeId="dailyForecast">
|
||||
<label>Tomorrows Forecast</label>
|
||||
<description>This is the weather forecast for tomorrow.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay2" typeId="dailyForecast">
|
||||
<label>2 Day Forecast</label>
|
||||
<description>This is the weather forecast in two days.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay3" typeId="dailyForecast">
|
||||
<label>3 Day Forecast</label>
|
||||
<description>This is the weather forecast in three days.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay4" typeId="dailyForecast">
|
||||
<label>4 Day Forecast</label>
|
||||
<description>This is the weather forecast in four days.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay5" typeId="dailyForecast">
|
||||
<label>5 Day Forecast</label>
|
||||
<description>This is the weather forecast in five days.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay6" typeId="dailyForecast">
|
||||
<label>6 Day Forecast</label>
|
||||
<description>This is the weather forecast in six days.</description>
|
||||
</channel-group>
|
||||
<channel-group id="forecastDay7" typeId="dailyForecast">
|
||||
<label>7 Day Forecast</label>
|
||||
<description>This is the weather forecast in seven days.</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>location</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:darksky:weather-and-forecast"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 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.darksky.internal.utils;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.config.core.ConfigConstants;
|
||||
|
||||
/**
|
||||
* Test class for the {@link ByteArrayFileCache} class.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class ByteArrayFileCacheTest {
|
||||
|
||||
private static final String SERVICE_PID = "org.openhab.binding.darksky";
|
||||
|
||||
private static final File USERDATA_FOLDER = new File(ConfigConstants.getUserDataFolder());
|
||||
private static final File CACHE_FOLDER = new File(USERDATA_FOLDER, ByteArrayFileCache.CACHE_FOLDER_NAME);
|
||||
private static final File SERVICE_CACHE_FOLDER = new File(CACHE_FOLDER, SERVICE_PID);
|
||||
|
||||
private static final String MP3_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.mp3";
|
||||
private static final String TXT_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.txt";
|
||||
|
||||
private static final byte[] EMPTY_BUFFER = new byte[0];
|
||||
|
||||
private ByteArrayFileCache subject;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// delete all files
|
||||
subject.clear();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanUp() {
|
||||
// delete all folders
|
||||
SERVICE_CACHE_FOLDER.delete();
|
||||
CACHE_FOLDER.delete();
|
||||
USERDATA_FOLDER.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileExtension() {
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("/var/log/foo.bar/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension("/tmp/doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(MP3_FILE_NAME), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(TXT_FILE_NAME), is(equalTo("txt")));
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/.."), is(""));
|
||||
assertThat(subject.getFileExtension(".hidden"), is(equalTo("hidden")));
|
||||
assertThat(subject.getFileExtension("C:\\Program Files (x86)\\java\\bin\\javaw.exe"), is(equalTo("exe")));
|
||||
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=qYrpPrLY868"), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUniqueFileName() {
|
||||
String mp3UniqueFileName = subject.getUniqueFileName(MP3_FILE_NAME);
|
||||
assertThat(mp3UniqueFileName, is(equalTo(subject.getUniqueFileName(MP3_FILE_NAME))));
|
||||
|
||||
String txtUniqueFileName = subject.getUniqueFileName(TXT_FILE_NAME);
|
||||
assertThat(txtUniqueFileName, is(equalTo(subject.getUniqueFileName(TXT_FILE_NAME))));
|
||||
|
||||
assertThat(mp3UniqueFileName, is(not(equalTo(txtUniqueFileName))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsent() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.putIfAbsent(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsentAndGet() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
|
||||
assertThat(subject.putIfAbsentAndGet(MP3_FILE_NAME, buffer), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsKey() throws IOException {
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(false));
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.remove(MP3_FILE_NAME);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.clear();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpiredClearsNothing() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpired() throws IOException {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID, 1);
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
// manipulate time of last use
|
||||
File fileInCache = subject.getUniqueFile(MP3_FILE_NAME);
|
||||
fileInCache.setLastModified(System.currentTimeMillis() - 2 * ByteArrayFileCache.ONE_DAY_IN_MILLIS);
|
||||
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
private byte[] readFile() throws IOException {
|
||||
byte[] buffer;
|
||||
try (InputStream is = ByteArrayFileCacheTest.class.getResourceAsStream("/sounds/doorbell.mp3")) {
|
||||
buffer = new byte[is.available()];
|
||||
is.read(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user