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.smhi-${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-smhi" description="SMHI Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.smhi/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A class containing a forecast for a specific point in time.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Forecast implements Comparable<Forecast> {
|
||||
private final ZonedDateTime validTime;
|
||||
private final Map<String, @Nullable BigDecimal> parameters;
|
||||
|
||||
public Forecast(ZonedDateTime validTime, Map<String, @Nullable BigDecimal> parameters) {
|
||||
this.validTime = validTime;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public ZonedDateTime getValidTime() {
|
||||
return validTime;
|
||||
}
|
||||
|
||||
public Map<String, @Nullable BigDecimal> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public @Nullable BigDecimal getParameter(String parameter) {
|
||||
return parameters.get(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Forecast o) {
|
||||
return validTime.compareTo(o.validTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Class with static methods for parsing json strings returned from Smhi
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Parser {
|
||||
|
||||
private static JsonParser parser = new JsonParser();
|
||||
|
||||
/**
|
||||
* Parse a json string received from Smhi containing forecasts.
|
||||
*
|
||||
* @param json A json string
|
||||
* @return A {@link TimeSeries} object
|
||||
*/
|
||||
public static TimeSeries parseTimeSeries(String json) {
|
||||
ZonedDateTime referenceTime;
|
||||
JsonObject object = parser.parse(json).getAsJsonObject();
|
||||
|
||||
referenceTime = parseApprovedTime(json);
|
||||
JsonArray timeSeries = object.get("timeSeries").getAsJsonArray();
|
||||
|
||||
List<Forecast> forecasts = StreamSupport.stream(timeSeries.spliterator(), false)
|
||||
.map(element -> parseForecast(element.getAsJsonObject())).sorted(Comparator.naturalOrder())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new TimeSeries(referenceTime, forecasts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a json string containing the approved time and reference time of the latest forecast
|
||||
*
|
||||
* @param json A json string
|
||||
* @return {@link ZonedDateTime} of the reference time
|
||||
*/
|
||||
public static ZonedDateTime parseApprovedTime(String json) {
|
||||
JsonObject timeObj = parser.parse(json).getAsJsonObject();
|
||||
|
||||
return ZonedDateTime.parse(timeObj.get("referenceTime").getAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single forecast, i.e. a forecast for a specific time.
|
||||
*
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
private static Forecast parseForecast(JsonObject object) {
|
||||
ZonedDateTime validTime = ZonedDateTime.parse(object.get("validTime").getAsString());
|
||||
Map<String, @Nullable BigDecimal> parameters = new HashMap<>();
|
||||
|
||||
JsonArray parameterArray = object.get("parameters").getAsJsonArray();
|
||||
|
||||
parameterArray.forEach(element -> {
|
||||
JsonObject parameterObj = element.getAsJsonObject();
|
||||
String name = parameterObj.get("name").getAsString().toLowerCase(Locale.ROOT);
|
||||
BigDecimal value = parameterObj.get("values").getAsJsonArray().get(0).getAsBigDecimal();
|
||||
|
||||
parameters.put(name, value);
|
||||
});
|
||||
|
||||
return new Forecast(validTime, parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PointOutOfBoundsException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 546566512L;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.smhi.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SmhiBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmhiBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "smhi";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_FORECAST = new ThingTypeUID(BINDING_ID, "forecast");
|
||||
|
||||
// Smhi's ids for parameters, also used as channel ids
|
||||
public static final String PRESSURE = "msl";
|
||||
public static final String TEMPERATURE = "t";
|
||||
public static final String VISIBILITY = "vis";
|
||||
public static final String WIND_DIRECTION = "wd";
|
||||
public static final String WIND_SPEED = "ws";
|
||||
public static final String RELATIVE_HUMIDITY = "r";
|
||||
public static final String THUNDER_PROBABILITY = "tstm";
|
||||
public static final String TOTAL_CLOUD_COVER = "tcc_mean";
|
||||
public static final String LOW_CLOUD_COVER = "lcc_mean";
|
||||
public static final String MEDIUM_CLOUD_COVER = "mcc_mean";
|
||||
public static final String HIGH_CLOUD_COVER = "hcc_mean";
|
||||
public static final String GUST = "gust";
|
||||
public static final String PRECIPITATION_MIN = "pmin";
|
||||
public static final String PRECIPITATION_MAX = "pmax";
|
||||
public static final String PRECIPITATION_MEAN = "pmean";
|
||||
public static final String PRECIPITATION_MEDIAN = "pmedian";
|
||||
public static final String PERCENT_FROZEN = "spp";
|
||||
public static final String PRECIPITATION_CATEGORY = "pcat";
|
||||
public static final String WEATHER_SYMBOL = "wsymb2";
|
||||
|
||||
public static final List<String> CHANNEL_IDS = Collections
|
||||
.unmodifiableList(Stream
|
||||
.of(PRESSURE, TEMPERATURE, VISIBILITY, WIND_DIRECTION, WIND_SPEED, RELATIVE_HUMIDITY,
|
||||
THUNDER_PROBABILITY, TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER,
|
||||
HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN, PRECIPITATION_MAX, PRECIPITATION_MEAN,
|
||||
PRECIPITATION_MEDIAN, PERCENT_FROZEN, PRECIPITATION_CATEGORY, WEATHER_SYMBOL)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
public static final String BASE_URL = "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/";
|
||||
public static final String APPROVED_TIME_URL = BASE_URL + "approvedtime.json";
|
||||
public static final String POINT_FORECAST_URL = BASE_URL + "geotype/point/lon/%.6f/lat/%.6f/data.json";
|
||||
|
||||
public static final BigDecimal OCTAS_TO_PERCENT = BigDecimal.valueOf(12.5);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link SmhiConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmhiConfiguration {
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
public @Nullable List<Integer> hourlyForecasts;
|
||||
public @Nullable List<Integer> dailyForecasts;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class for handling http requests to Smhi's API and return values.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmhiConnector {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmhiConnector.class);
|
||||
|
||||
private static final String ACCEPT = "application/json";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public SmhiConnector(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference time (the time when the forecast starts) of the latest published forecast
|
||||
*
|
||||
* @return A {@link ZonedDateTime} with the time of the latest forecast.
|
||||
*/
|
||||
public ZonedDateTime getReferenceTime() throws SmhiException {
|
||||
logger.debug("Fetching reference time");
|
||||
Request req = httpClient.newRequest(APPROVED_TIME_URL);
|
||||
req.accept(ACCEPT);
|
||||
ContentResponse resp;
|
||||
try {
|
||||
resp = req.send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new SmhiException(e);
|
||||
}
|
||||
logger.debug("Received response with status {} - {}", resp.getStatus(), resp.getReason());
|
||||
if (resp.getStatus() == 200) {
|
||||
return Parser.parseApprovedTime(resp.getContentAsString());
|
||||
} else {
|
||||
throw new SmhiException(resp.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a forecast for the specified WGS84 coordinates.
|
||||
*
|
||||
* @param lat Latitude
|
||||
* @param lon Longitude
|
||||
* @return A {@link TimeSeries} object containing the published forecasts.
|
||||
*/
|
||||
public TimeSeries getForecast(double lat, double lon) throws SmhiException, PointOutOfBoundsException {
|
||||
logger.debug("Fetching new forecast");
|
||||
String url = String.format(Locale.ROOT, POINT_FORECAST_URL, lon, lat);
|
||||
Request req = httpClient.newRequest(url);
|
||||
req.accept(ACCEPT);
|
||||
ContentResponse resp;
|
||||
try {
|
||||
resp = req.send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new SmhiException(e);
|
||||
}
|
||||
logger.debug("Received response with status {} - {}", resp.getStatus(), resp.getReason());
|
||||
switch (resp.getStatus()) {
|
||||
case 200:
|
||||
return Parser.parseTimeSeries(resp.getContentAsString());
|
||||
case 400:
|
||||
case 404:
|
||||
throw new PointOutOfBoundsException();
|
||||
default:
|
||||
throw new SmhiException(resp.getReason());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmhiException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 516565331L;
|
||||
|
||||
public SmhiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SmhiException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Future;
|
||||
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.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
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.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SmhiHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmhiHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SmhiHandler.class);
|
||||
|
||||
private SmhiConfiguration config = new SmhiConfiguration();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private @Nullable SmhiConnector connection;
|
||||
private ZonedDateTime currentHour;
|
||||
private ZonedDateTime currentDay;
|
||||
private @Nullable TimeSeries cachedTimeSeries;
|
||||
private boolean hasLatestForecast = false;
|
||||
private @Nullable Future<?> forecastUpdater;
|
||||
private @Nullable Future<?> instantUpdate;
|
||||
|
||||
public SmhiHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
this.currentHour = calculateCurrentHour();
|
||||
this.currentDay = calculateCurrentDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles commands sent to channels. Since all values are read-only, only REFRESH commands are allowed.
|
||||
* Sending REFRESH to any item updates all items, since all values are returned in the response from Smhi.
|
||||
* Therefore there's a wait of 5 seconds before the values are fetched, in which time all other commands are
|
||||
* blocked, to prevent spamming Smhi's API.
|
||||
*
|
||||
* @param channelUID
|
||||
* @param command
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
updateNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(SmhiConfiguration.class);
|
||||
|
||||
connection = new SmhiConnector(httpClient);
|
||||
|
||||
// Check which channel groups are selected in the config.
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
channels.addAll(createChannels());
|
||||
updateThing(editThing().withChannels(channels).build());
|
||||
|
||||
startPolling();
|
||||
updateNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start polling for updated weather forecast.
|
||||
*/
|
||||
private synchronized void startPolling() {
|
||||
logger.debug("Start polling");
|
||||
forecastUpdater = scheduler.scheduleWithFixedDelay(this::waitForForecast, 1, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all jobs.
|
||||
*/
|
||||
private synchronized void cancelPolling() {
|
||||
logger.debug("Cancelling polling");
|
||||
Future<?> localRef = forecastUpdater;
|
||||
if (localRef != null) {
|
||||
localRef.cancel(false);
|
||||
}
|
||||
localRef = instantUpdate;
|
||||
if (localRef != null) {
|
||||
localRef.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update channels with new forecast data.
|
||||
*
|
||||
* @param timeSeries A {@link TimeSeries} object containing forecasts.
|
||||
*/
|
||||
private void updateChannels(TimeSeries timeSeries) {
|
||||
// Loop through hourly forecasts and update those available
|
||||
for (int i = 0; i < 25; i++) {
|
||||
List<Channel> channels = thing.getChannelsOfGroup("hour_" + i);
|
||||
if (channels.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
Forecast forecast = timeSeries.getForecast(i);
|
||||
if (forecast != null) {
|
||||
channels.forEach(c -> {
|
||||
String id = c.getUID().getIdWithoutGroup();
|
||||
BigDecimal value = forecast.getParameter(id);
|
||||
updateChannel(c, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Loop through daily forecasts and updates those available
|
||||
for (int i = 0; i < 10; i++) {
|
||||
List<Channel> channels = thing.getChannelsOfGroup("day_" + i);
|
||||
if (channels.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int offset = 24 * i + 12;
|
||||
Forecast forecast = timeSeries.getForecast(currentDay, offset);
|
||||
|
||||
if (forecast == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No forecast yet for {}", currentDay.plusHours(offset));
|
||||
}
|
||||
channels.forEach(c -> {
|
||||
updateState(c.getUID(), UnDefType.NULL);
|
||||
});
|
||||
} else {
|
||||
channels.forEach(c -> {
|
||||
String id = c.getUID().getIdWithoutGroup();
|
||||
BigDecimal value = forecast.getParameter(id);
|
||||
updateChannel(c, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannel(Channel channel, @Nullable BigDecimal value) {
|
||||
String id = channel.getUID().getIdWithoutGroup();
|
||||
State newState = UnDefType.NULL;
|
||||
|
||||
if (value != null) {
|
||||
switch (id) {
|
||||
case PRESSURE:
|
||||
newState = new QuantityType<>(value, MetricPrefix.HECTO(SIUnits.PASCAL));
|
||||
break;
|
||||
case TEMPERATURE:
|
||||
newState = new QuantityType<>(value, SIUnits.CELSIUS);
|
||||
break;
|
||||
case VISIBILITY:
|
||||
newState = new QuantityType<>(value, MetricPrefix.KILO(SIUnits.METRE));
|
||||
break;
|
||||
case WIND_DIRECTION:
|
||||
newState = new QuantityType<>(value, SmartHomeUnits.DEGREE_ANGLE);
|
||||
break;
|
||||
case WIND_SPEED:
|
||||
case GUST:
|
||||
newState = new QuantityType<>(value, SmartHomeUnits.METRE_PER_SECOND);
|
||||
break;
|
||||
case RELATIVE_HUMIDITY:
|
||||
case THUNDER_PROBABILITY:
|
||||
newState = new QuantityType<>(value, SmartHomeUnits.PERCENT);
|
||||
break;
|
||||
case PERCENT_FROZEN:
|
||||
// Smhi returns -9 for spp if there's no precipitation, convert to UNDEF
|
||||
if (value.intValue() == -9) {
|
||||
newState = UnDefType.UNDEF;
|
||||
} else {
|
||||
newState = new QuantityType<>(value, SmartHomeUnits.PERCENT);
|
||||
}
|
||||
break;
|
||||
case HIGH_CLOUD_COVER:
|
||||
case MEDIUM_CLOUD_COVER:
|
||||
case LOW_CLOUD_COVER:
|
||||
case TOTAL_CLOUD_COVER:
|
||||
newState = new QuantityType<>(value.multiply(OCTAS_TO_PERCENT), SmartHomeUnits.PERCENT);
|
||||
break;
|
||||
case PRECIPITATION_MAX:
|
||||
case PRECIPITATION_MEAN:
|
||||
case PRECIPITATION_MEDIAN:
|
||||
case PRECIPITATION_MIN:
|
||||
newState = new QuantityType<>(value, SmartHomeUnits.MILLIMETRE_PER_HOUR);
|
||||
break;
|
||||
default:
|
||||
newState = new DecimalType(value);
|
||||
}
|
||||
}
|
||||
|
||||
updateState(channel.getUID(), newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the {@link org.openhab.core.thing.binding.ThingHandler}. Cancel scheduled jobs
|
||||
*/
|
||||
public void dispose() {
|
||||
cancelPolling();
|
||||
}
|
||||
|
||||
/**
|
||||
* First check if the time has shifted to a new hour, then start checking if a new forecast have been
|
||||
* published, in that case, fetch it and update channels.
|
||||
*/
|
||||
private void waitForForecast() {
|
||||
if (isItNewHour()) {
|
||||
currentHour = calculateCurrentHour();
|
||||
currentDay = calculateCurrentDay();
|
||||
// Update channels with cached forecasts - just shift an hour forward
|
||||
TimeSeries forecast = cachedTimeSeries;
|
||||
if (forecast != null) {
|
||||
updateChannels(forecast);
|
||||
}
|
||||
hasLatestForecast = false;
|
||||
}
|
||||
if (!hasLatestForecast && isForecastUpdated()) {
|
||||
getUpdatedForecast();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an imminent update, making it wait 5 seconds to catch any bursts of calls before executing.
|
||||
*/
|
||||
private synchronized void updateNow() {
|
||||
Future<?> localRef = instantUpdate;
|
||||
if (localRef == null || localRef.isDone()) {
|
||||
instantUpdate = scheduler.schedule(this::getUpdatedForecast, 5, TimeUnit.SECONDS);
|
||||
} else {
|
||||
logger.debug("Already waiting for scheduled refresh");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it is a new hour.
|
||||
*
|
||||
* @return true if the current time is more than one hour after currentHour, otherwise false.
|
||||
*/
|
||||
private boolean isItNewHour() {
|
||||
return ZonedDateTime.now().minusHours(1).isAfter(currentHour);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Smhi's endpoint to check for the time of the last forecast, to see if a new one is available.
|
||||
*
|
||||
* @return true if the time of the latest forecast is equal to or after currentHour, otherwise false
|
||||
*/
|
||||
private boolean isForecastUpdated() {
|
||||
ZonedDateTime referenceTime;
|
||||
SmhiConnector apiConnection = connection;
|
||||
if (apiConnection != null) {
|
||||
try {
|
||||
referenceTime = apiConnection.getReferenceTime();
|
||||
} catch (SmhiException e) {
|
||||
return false;
|
||||
}
|
||||
return referenceTime.isEqual(currentHour) || referenceTime.isAfter(currentHour);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches latest forecast from Smhi, update channels and check if it was published in the current hour.
|
||||
* If it is, set flag to indicate we have the latest forecast.
|
||||
*/
|
||||
private void getUpdatedForecast() {
|
||||
TimeSeries forecast;
|
||||
ZonedDateTime referenceTime;
|
||||
SmhiConnector apiConnection = connection;
|
||||
if (apiConnection != null) {
|
||||
try {
|
||||
forecast = apiConnection.getForecast(config.latitude, config.longitude);
|
||||
} catch (SmhiException e) {
|
||||
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
|
||||
logger.debug("Failed to get new forecast: {}", message);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
return;
|
||||
} catch (PointOutOfBoundsException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Coordinates outside valid area");
|
||||
cancelPolling();
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
referenceTime = forecast.getReferenceTime();
|
||||
updateChannels(forecast);
|
||||
if (referenceTime.isEqual(currentHour) || referenceTime.isAfter(currentHour)) {
|
||||
hasLatestForecast = true;
|
||||
}
|
||||
cachedTimeSeries = forecast;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time rounded down to hour
|
||||
*
|
||||
* @return A {@link ZonedDateTime} corresponding to the last even hour
|
||||
*/
|
||||
private ZonedDateTime calculateCurrentHour() {
|
||||
ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
|
||||
int y = now.getYear();
|
||||
int m = now.getMonth().getValue();
|
||||
int d = now.getDayOfMonth();
|
||||
int h = now.getHour();
|
||||
return ZonedDateTime.of(y, m, d, h, 0, 0, 0, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time rounded down to day
|
||||
*
|
||||
* @return A {@link ZonedDateTime} corresponding to the last even day.
|
||||
*/
|
||||
private ZonedDateTime calculateCurrentDay() {
|
||||
ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
|
||||
int y = now.getYear();
|
||||
int m = now.getMonth().getValue();
|
||||
int d = now.getDayOfMonth();
|
||||
return ZonedDateTime.of(y, m, d, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates channels based on selections in thing configuration
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private List<Channel> createChannels() {
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
|
||||
// There's currently a bug in PaperUI that can cause options to be added more than one time
|
||||
// to the list. Convert to a sorted set to work around this.
|
||||
// See https://github.com/openhab/openhab-webui/issues/212
|
||||
Set<Integer> hours = new TreeSet<>();
|
||||
Set<Integer> days = new TreeSet<>();
|
||||
if (config.hourlyForecasts != null) {
|
||||
hours.addAll(config.hourlyForecasts);
|
||||
}
|
||||
if (config.dailyForecasts != null) {
|
||||
days.addAll(config.dailyForecasts);
|
||||
}
|
||||
|
||||
for (int i : hours) {
|
||||
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "hour_" + i);
|
||||
CHANNEL_IDS.forEach(id -> {
|
||||
channels.add(createChannel(groupUID, id));
|
||||
});
|
||||
}
|
||||
|
||||
for (int i : days) {
|
||||
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "day_" + i);
|
||||
CHANNEL_IDS.forEach(id -> {
|
||||
channels.add(createChannel(groupUID, id));
|
||||
});
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a channel with the correct item type based on the channel ID
|
||||
*
|
||||
* @param channelGroupUID Channel group the channel belongs to
|
||||
* @param channelID ID of the channel (without group ID)
|
||||
* @return The created channel
|
||||
*/
|
||||
private Channel createChannel(ChannelGroupUID channelGroupUID, String channelID) {
|
||||
ChannelUID channelUID = new ChannelUID(channelGroupUID, channelID);
|
||||
String itemType = "Number";
|
||||
switch (channelID) {
|
||||
case TEMPERATURE:
|
||||
itemType += ":Temperature";
|
||||
break;
|
||||
case PRESSURE:
|
||||
itemType += ":Pressure";
|
||||
break;
|
||||
case VISIBILITY:
|
||||
itemType += ":Length";
|
||||
break;
|
||||
case WIND_DIRECTION:
|
||||
itemType += ":Angle";
|
||||
case WIND_SPEED:
|
||||
case GUST:
|
||||
case PRECIPITATION_MAX:
|
||||
case PRECIPITATION_MEAN:
|
||||
case PRECIPITATION_MEDIAN:
|
||||
case PRECIPITATION_MIN:
|
||||
itemType += ":Speed";
|
||||
break;
|
||||
case RELATIVE_HUMIDITY:
|
||||
case PERCENT_FROZEN:
|
||||
case TOTAL_CLOUD_COVER:
|
||||
case HIGH_CLOUD_COVER:
|
||||
case MEDIUM_CLOUD_COVER:
|
||||
case LOW_CLOUD_COVER:
|
||||
case THUNDER_PROBABILITY:
|
||||
itemType += ":Dimensionless";
|
||||
break;
|
||||
|
||||
}
|
||||
Channel channel = ChannelBuilder.create(channelUID, itemType)
|
||||
.withType(new ChannelTypeUID(BINDING_ID, channelID)).build();
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.THING_TYPE_FORECAST;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SmhiHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.smhi", service = ThingHandlerFactory.class)
|
||||
public class SmhiHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_FORECAST);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public SmhiHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@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_FORECAST.equals(thingTypeUID)) {
|
||||
return new SmhiHandler(thing, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.smhi.internal;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A collection class with utility methods to retrieve forecasts pertaining to a specified time.
|
||||
*
|
||||
* @author Anders Alfredsson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeSeries implements Iterable<Forecast> {
|
||||
|
||||
private final ZonedDateTime referenceTime;
|
||||
private final List<Forecast> forecasts;
|
||||
|
||||
public TimeSeries(ZonedDateTime referenceTime, List<Forecast> forecasts) {
|
||||
this.referenceTime = referenceTime;
|
||||
this.forecasts = forecasts;
|
||||
}
|
||||
|
||||
public ZonedDateTime getReferenceTime() {
|
||||
return referenceTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first {@link Forecast} that is equal to or after offset time (from now).
|
||||
*
|
||||
* @param hourOffset number of hours after now.
|
||||
* @return
|
||||
*/
|
||||
public @Nullable Forecast getForecast(int hourOffset) {
|
||||
return getForecast(ZonedDateTime.now(), hourOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first {@link Forecast} that is equal to or after the offset time (from startTime).
|
||||
*
|
||||
* @param hourOffset number of hours after now.
|
||||
* @return
|
||||
*/
|
||||
public @Nullable Forecast getForecast(ZonedDateTime startTime, int hourOffset) {
|
||||
if (hourOffset < 0) {
|
||||
throw new IllegalArgumentException("Offset must be at least 0");
|
||||
}
|
||||
|
||||
for (Forecast forecast : forecasts) {
|
||||
if (forecast.getValidTime().compareTo(startTime.plusHours(hourOffset)) >= 0) {
|
||||
return forecast;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Forecast> iterator() {
|
||||
return forecasts.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(@Nullable Consumer<? super Forecast> action) {
|
||||
if (action == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
for (Forecast f : forecasts) {
|
||||
action.accept(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Forecast> spliterator() {
|
||||
return forecasts.spliterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="smhi" 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>SMHI Binding</name>
|
||||
<description>Binding for getting weather forecasts from the Swedish Meteorological and Hydrological Institute (SMHI)</description>
|
||||
<author>Anders Alfredsson</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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-description uri="thing-type:smhi:forecast">
|
||||
<parameter name="latitude" type="decimal" required="true">
|
||||
<label>Latitude</label>
|
||||
<description>Latitude for the forecast</description>
|
||||
</parameter>
|
||||
<parameter name="longitude" type="decimal" required="true">
|
||||
<label>Longitude</label>
|
||||
<description>Longitude for the forecast</description>
|
||||
</parameter>
|
||||
<parameter name="hourlyForecasts" type="integer" multiple="true">
|
||||
<label>Hourly Forecasts</label>
|
||||
<description>The hourly forecasts to display</description>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="0">00: Current hour</option>
|
||||
<option value="1">01: Next hour</option>
|
||||
<option value="2">02: 2 hours from now</option>
|
||||
<option value="3">03: 3 hours from now</option>
|
||||
<option value="4">04: 4 hours from now</option>
|
||||
<option value="5">05: 5 hours from now</option>
|
||||
<option value="6">06: 6 hours from now</option>
|
||||
<option value="7">07: 7 hours from now</option>
|
||||
<option value="8">08: 8 hours from now</option>
|
||||
<option value="9">09: 9 hours from now</option>
|
||||
<option value="10">10: 10 hours from now</option>
|
||||
<option value="11">11: 11 hours from now</option>
|
||||
<option value="12">12: 12 hours from now</option>
|
||||
<option value="13">13: 13 hours from now</option>
|
||||
<option value="14">14: 14 hours from now</option>
|
||||
<option value="15">15: 15 hours from now</option>
|
||||
<option value="16">16: 16 hours from now</option>
|
||||
<option value="17">17: 17 hours from now</option>
|
||||
<option value="18">18: 18 hours from now</option>
|
||||
<option value="19">19: 19 hours from now</option>
|
||||
<option value="20">20: 20 hours from now</option>
|
||||
<option value="21">21: 21 hours from now</option>
|
||||
<option value="22">22: 22 hours from now</option>
|
||||
<option value="23">23: 23 hours from now</option>
|
||||
<option value="24">24: 24 hours from now</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="dailyForecasts" type="integer" multiple="true">
|
||||
<label>Daily Forecasts</label>
|
||||
<description>The daily forecasts to display</description>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="0">00: Today</option>
|
||||
<option value="1">01: Tomorrow</option>
|
||||
<option value="2">02: 2 days from now</option>
|
||||
<option value="3">03: 3 days from now</option>
|
||||
<option value="4">04: 4 days from now</option>
|
||||
<option value="5">05: 5 days from now</option>
|
||||
<option value="6">06: 6 days from now</option>
|
||||
<option value="7">07: 7 days from now</option>
|
||||
<option value="8">08: 8 days from now</option>
|
||||
<option value="9">09: 9 days from now</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="smhi"
|
||||
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-type id="msl">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Air Pressure</label>
|
||||
<description>Air pressure in hPa</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="t">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="vis" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Visibility</label>
|
||||
<description>Horizontal visibility</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="wd">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Wind Direction</label>
|
||||
<description>Wind direction</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="ws">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Speed</label>
|
||||
<description>Wind speed</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="r">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Relative Humidity</label>
|
||||
<description>Relative humidity in percent</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="tstm" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Thunder Probability</label>
|
||||
<description>Probability of thunder in percent</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="tcc_mean">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Total Cloud Cover</label>
|
||||
<description>Mean value of total cloud cover in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="lcc_mean" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Low Level Cloud Cover</label>
|
||||
<description>Mean value of low level cloud cover (0-2500 m) in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="mcc_mean" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Medium Level Cloud Cover</label>
|
||||
<description>Mean value of medium level cloud cover (2500-6000 m) in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="hcc_mean" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>High Level Cloud Cover</label>
|
||||
<description>Mean value of high level cloud cover (> 6000 m) in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="gust">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Gust Speed</label>
|
||||
<description>Wind gust speed</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="pmin">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Minimum Precipitation</label>
|
||||
<description>Minimum precipitation intensity</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="pmax">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Maximum Precipitation</label>
|
||||
<description>Maximum precipitation intensity</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="pmean" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Mean Precipitation</label>
|
||||
<description>Mean precipitation intensity</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="pmedian" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Median Precipitation</label>
|
||||
<description>Median precipitation intensity</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="pcat">
|
||||
<item-type>Number</item-type>
|
||||
<label>Precipitation Category</label>
|
||||
<description>Type of precipitation</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">No precipitation</option>
|
||||
<option value="1">Snow</option>
|
||||
<option value="2">Snow and rain</option>
|
||||
<option value="3">Rain</option>
|
||||
<option value="4">Drizzle</option>
|
||||
<option value="5">Freezing rain</option>
|
||||
<option value="6">Freezing drizzle</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="spp" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Frozen Precipitation</label>
|
||||
<description>Percent of precipitation in frozen form</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="wsymb2">
|
||||
<item-type>Number</item-type>
|
||||
<label>Weather Condition</label>
|
||||
<description>Short description of the weather conditions</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="1">Clear sky</option>
|
||||
<option value="2">Nearly clear sky</option>
|
||||
<option value="3">Variable cloudiness</option>
|
||||
<option value="4">Halfclear sky</option>
|
||||
<option value="5">Cloudy sky</option>
|
||||
<option value="6">Overcast</option>
|
||||
<option value="7">Fog</option>
|
||||
<option value="8">Light rain showers</option>
|
||||
<option value="9">Moderate rain showers</option>
|
||||
<option value="10">Heavy rain showers</option>
|
||||
<option value="11">Thunderstorm</option>
|
||||
<option value="12">Light sleet showers</option>
|
||||
<option value="13">Moderate sleet showers</option>
|
||||
<option value="14">Heavy sleet showers</option>
|
||||
<option value="15">Light snow showers</option>
|
||||
<option value="16">Moderate snow showers</option>
|
||||
<option value="17">Heavy snow showers</option>
|
||||
<option value="18">Light rain</option>
|
||||
<option value="19">Moderate rain</option>
|
||||
<option value="20">Heavy rain</option>
|
||||
<option value="21">Thunder</option>
|
||||
<option value="22">Light sleet</option>
|
||||
<option value="23">Moderate sleet</option>
|
||||
<option value="24">Heavy sleet</option>
|
||||
<option value="25">Light snowfall</option>
|
||||
<option value="26">Moderate snowfall</option>
|
||||
<option value="27">Heavy snowfall</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-group-type id="hourlyForecast">
|
||||
<label>Hourly Forecast</label>
|
||||
<description>Hourly forecast for the specified offset</description>
|
||||
<channels>
|
||||
<channel id="t" typeId="t"/>
|
||||
<channel id="wd" typeId="wd"/>
|
||||
<channel id="ws" typeId="ws"/>
|
||||
<channel id="gust" typeId="gust"/>
|
||||
<channel id="pmin" typeId="pmin"/>
|
||||
<channel id="pmax" typeId="pmax"/>
|
||||
<channel id="pcat" typeId="pcat"/>
|
||||
<channel id="msl" typeId="msl"/>
|
||||
<channel id="r" typeId="r"/>
|
||||
<channel id="tcc_mean" typeId="tcc_mean"/>
|
||||
<channel id="wsymb2" typeId="wsymb2"/>
|
||||
<channel id="vis" typeId="vis"/>
|
||||
<channel id="tstm" typeId="tstm"/>
|
||||
<channel id="spp" typeId="spp"/>
|
||||
<channel id="lcc_mean" typeId="lcc_mean"/>
|
||||
<channel id="mcc_mean" typeId="mcc_mean"/>
|
||||
<channel id="hcc_mean" typeId="hcc_mean"/>
|
||||
<channel id="pmean" typeId="pmean"/>
|
||||
<channel id="pmedian" typeId="pmedian"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="dailyForecast">
|
||||
<label>Daily Forecast</label>
|
||||
<description>Forecast at noon for the specified offset</description>
|
||||
<channels>
|
||||
<channel id="t" typeId="t"/>
|
||||
<channel id="wd" typeId="wd"/>
|
||||
<channel id="ws" typeId="ws"/>
|
||||
<channel id="gust" typeId="gust"/>
|
||||
<channel id="pmin" typeId="pmin"/>
|
||||
<channel id="pmax" typeId="pmax"/>
|
||||
<channel id="pcat" typeId="pcat"/>
|
||||
<channel id="msl" typeId="msl"/>
|
||||
<channel id="r" typeId="r"/>
|
||||
<channel id="tcc_mean" typeId="tcc_mean"/>
|
||||
<channel id="wsymb2" typeId="wsymb2"/>
|
||||
<channel id="vis" typeId="vis"/>
|
||||
<channel id="tstm" typeId="tstm"/>
|
||||
<channel id="spp" typeId="spp"/>
|
||||
<channel id="lcc_mean" typeId="lcc_mean"/>
|
||||
<channel id="mcc_mean" typeId="mcc_mean"/>
|
||||
<channel id="hcc_mean" typeId="hcc_mean"/>
|
||||
<channel id="pmean" typeId="pmean"/>
|
||||
<channel id="pmedian" typeId="pmedian"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="smhi"
|
||||
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">
|
||||
|
||||
<thing-type id="forecast">
|
||||
<label>SMHI Weather Forecast</label>
|
||||
<description>Gets weather forecasts from SMHI</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="hour_0" typeId="hourlyForecast">
|
||||
<label>Current Hour</label>
|
||||
<description>Forecast for the current hour</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_1" typeId="hourlyForecast">
|
||||
<label>Next Hour</label>
|
||||
<description>Forecast for the next hour</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_2" typeId="hourlyForecast">
|
||||
<label>2 Hours from Now</label>
|
||||
<description>Forecast for 2 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_3" typeId="hourlyForecast">
|
||||
<label>3 Hours from Now</label>
|
||||
<description>Forecast for 3 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_4" typeId="hourlyForecast">
|
||||
<label>4 Hours from Now</label>
|
||||
<description>Forecast for 4 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_5" typeId="hourlyForecast">
|
||||
<label>5 Hours from Now</label>
|
||||
<description>Forecast for 5 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_6" typeId="hourlyForecast">
|
||||
<label>6 Hours from Now</label>
|
||||
<description>Forecast for 6 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_7" typeId="hourlyForecast">
|
||||
<label>7 Hours from Now</label>
|
||||
<description>Forecast for 7 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_8" typeId="hourlyForecast">
|
||||
<label>8 Hours from Now</label>
|
||||
<description>Forecast for 8 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_9" typeId="hourlyForecast">
|
||||
<label>9 Hours from Now</label>
|
||||
<description>Forecast for 9 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_10" typeId="hourlyForecast">
|
||||
<label>10 Hours from Now</label>
|
||||
<description>Forecast for 10 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_11" typeId="hourlyForecast">
|
||||
<label>11 Hours from Now</label>
|
||||
<description>Forecast for 11 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_12" typeId="hourlyForecast">
|
||||
<label>12 Hours from Now</label>
|
||||
<description>Forecast for 12 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_13" typeId="hourlyForecast">
|
||||
<label>13 Hours from Now</label>
|
||||
<description>Forecast for 13 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_14" typeId="hourlyForecast">
|
||||
<label>14 Hours from Now</label>
|
||||
<description>Forecast for 14 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_15" typeId="hourlyForecast">
|
||||
<label>15 Hours from Now</label>
|
||||
<description>Forecast for 15 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_16" typeId="hourlyForecast">
|
||||
<label>16 Hours from Now</label>
|
||||
<description>Forecast for 16 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_17" typeId="hourlyForecast">
|
||||
<label>17 Hours from Now</label>
|
||||
<description>Forecast for 17 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_18" typeId="hourlyForecast">
|
||||
<label>18 Hours from Now</label>
|
||||
<description>Forecast for 18 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_19" typeId="hourlyForecast">
|
||||
<label>19 Hours from Now</label>
|
||||
<description>Forecast for 19 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_20" typeId="hourlyForecast">
|
||||
<label>20 Hours from Now</label>
|
||||
<description>Forecast for 20 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_21" typeId="hourlyForecast">
|
||||
<label>21 Hours from Now</label>
|
||||
<description>Forecast for 21 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_22" typeId="hourlyForecast">
|
||||
<label>22 Hours from Now</label>
|
||||
<description>Forecast for 22 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_23" typeId="hourlyForecast">
|
||||
<label>23 Hours from Now</label>
|
||||
<description>Forecast for 23 hours from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="hour_24" typeId="hourlyForecast">
|
||||
<label>24 Hours from Now</label>
|
||||
<description>Forecast for 24 hours from now</description>
|
||||
</channel-group>
|
||||
|
||||
<channel-group id="day_0" typeId="dailyForecast">
|
||||
<label>Today</label>
|
||||
<description>Forecast for today</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_1" typeId="dailyForecast">
|
||||
<label>Tomorrow</label>
|
||||
<description>Forecast for tomorrow</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_2" typeId="dailyForecast">
|
||||
<label>2 Days from Now</label>
|
||||
<description>Forecast for 2 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_3" typeId="dailyForecast">
|
||||
<label>3 Days from Now</label>
|
||||
<description>Forecast for 3 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_4" typeId="dailyForecast">
|
||||
<label>4 Days from Now</label>
|
||||
<description>Forecast for 4 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_5" typeId="dailyForecast">
|
||||
<label>5 Days from Now</label>
|
||||
<description>Forecast for 5 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_6" typeId="dailyForecast">
|
||||
<label>6 Days from Now</label>
|
||||
<description>Forecast for 6 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_7" typeId="dailyForecast">
|
||||
<label>7 Days from Now</label>
|
||||
<description>Forecast for 7 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_8" typeId="dailyForecast">
|
||||
<label>8 Days from Now</label>
|
||||
<description>Forecast for 8 days from now</description>
|
||||
</channel-group>
|
||||
<channel-group id="day_9" typeId="dailyForecast">
|
||||
<label>9 Days from Now</label>
|
||||
<description>Forecast for 9 days from now</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:smhi:forecast"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user