added migrated 2.x add-ons

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,144 @@
# Smhi Binding
This binding gets hourly and daily forecast from SMHI - the Swedish Meteorological and Hydrological Institute.
It can get forecasts for the nordic countries (Sweden, Norway, Denmark and Finland).
## Supported Things
The binding support only one thing-type: forecast.
The thing can be configured to get hourly forecasts for up to 24 hours, and daily forecasts for up to 10 days.
## Discovery
This binding does not support automatic discovery.
## Thing Configuration
The forecast thing needs to be configured with the latitude and longitude for the location of the forecast.
You can also choose for which hours and which days you would like to get forecasts.
| Parameter | Description | Required |
|-----------|-------------|----------|
| Latitude | Latitude of the forecast | Yes |
| Longitute | Longitude of the forecast | Yes |
| Hourly forecasts | The hourly forecasts to display | No |
| Daily forecasts | The daily forecasts to display | No |
## Channels
The channels are the same for all forecasts:
#### Basic channels
| channel | type | description |
|----------|--------|------------------------------|
| Temperature | Number:Temperature | Temperature in Celsius |
| Wind direction | Number:Angle | Wind direction in degrees |
| Wind Speed | Number:Speed | Wind speed in m/s |
| Wind gust speed | Number:Speed | Wind gust speed in m/s |
| Minimum precipitation | Number:Speed | Minimum precipitation intensity in mm/h |
| Maximum precipitation | Number:Speed | Maximum precipitation intensity in mm/h |
| Precipitation category* | Number | Type of precipitation |
| Air pressure | Number:Pressure | Air pressure in hPa |
| Relative humidity | Number:Dimensionless | Relative humidity in percent |
| Total cloud cover | Number:Dimensionless | Mean value of total cloud cover in percent |
| Weather condition** | Number | Short description of the weather conditions |
#### Advanced channels
| channel | type | description |
|----------|--------|------------------------------|
| Visibility | Number:Length | Horizontal visibility in km |
| Thunder probability | Number:Dimensionless | Probability of thunder in percent |
| Frozen precipitation | Number:Dimensionless | Percent of precipitation in frozen form (will be set to UNDEF if there's no precipitation) |
| Low level cloud cover | Number:Dimensionless | Mean value of low level cloud cover (0-2500 m) in percent |
| Medium level cloud cover | Number:Dimensionless | Mean value of medium level cloud cover (2500-6000 m) in percent |
| High level cloud cover | Number:Dimensionless | Mean value of high level cloud cover (> 6000 m) in percent |
| Mean precipitation | Number:Speed | Mean precipitation intensity in mm/h |
| Median precipitation | Number:Speed | Median precipitation intensity in mm/h |
\* The precipitation category can have a value from 0-6, representing different types of precipitaion:
| Value | Meaning |
|-------|---------|
| 0 | No precipitation|
| 1 | Snow |
| 2 | Snow and rain |
| 3 | Rain |
| 4 | Drizzle |
| 5 | Freezing rain |
| 6 | Freezing drizzle |
\** The weather condition channel can take values from 1-27, each corresponding to a different weather condition:
| Value | Condition |
|-------|-----------|
| 1 | Clear sky |
| 2 | Nearly clear sky |
| 3 | Variable cloudiness |
| 4 | Halfclear sky |
| 5 | Cloudy sky |
| 6 | Overcast |
| 7 | Fog |
| 8 | Light rain showers |
| 9 | Moderate rain showers |
| 10 | Heavy rain showers |
| 11 | Thunderstorm |
| 12 | Light sleet showers |
| 13 | Moderate sleet showers |
| 14 | Heavy sleet showers |
| 15 | Light snow showers |
| 16 | Moderate snow showers |
| 17 | Heavy snow showers |
| 18 | Light rain |
| 19 | Moderate rain |
| 20 | Heavy rain |
| 21 | Thunder |
| 22 | Light sleet |
| 23 | Moderate sleet |
| 24 | Heavy sleet |
| 25 | Light snowfall |
| 26 | Moderate snowfall |
| 27 | Heavy snowfall |
## Full Example
demo.things
```
Thing smhi:forecast:demoforecast "Demo forecast" [ latitude=57.997072, longitude=15.990068, hourlyForecasts=0,1,2, dailyForecasts=0,1 ]
```
demo.items
```
Number:Temperature Smhi_Temperature_Now "Current temperature [%.1f °C]" {channel="smhi:forecast:demoforecast:hour_0#t"}
Number:Speed Smhi_Min_Precipitation_Now "Current precipitation (min) [%.1f mm/h]" {channel="smhi:forecast:demoforecast:hour_0#pmin"}
Number:Temperature Smhi_Temperature_1hour "Temperature next hour [%.1f °C]" {channel="smhi:forecast:demoforecast:hour_1#t"}
Number:Speed Smhi_Min_Precipitation_1hour "Precipitaion next hour (min) [%.1f mm/h]" {channel="smhi:forecast:demoforecast:hour_1#pmin"}
Number:Temperature Smhi_Temperature_Tomorrow "Temperature tomorrow [%.1f °C]" {channel="smhi:forecast:demoforecast:day_1#t"}
Number:Speed Smhi_Min_Precipitation_Tomorrow "Precipitaion tomorrow (min) [%.1f mm/h]" {channel="smhi:forecast:demoforecast:hour_1#pmin"}
```
demo.sitemap
```
sitemap demo label="Smhi" {
Frame label="Current weather" {
Text item=Smhi_Temperature_Now
Text item=Smhi_Min_Precipitation_Now
}
Frame label="Weather next hour" {
Text item=Smhi_Temperature_1hour
Text item=Smhi_Min_Precipitation_1hour
}
Frame label="Weather tomorrow" {
Text item=Smhi_Temperature_Tomorrow
Text item=Smhi_Min_Precipitation_Tomorrow
}
}
```

View File

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

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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);
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

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