This binding integrates to [the Finnish Meteorological Institute (FMI) Open Data API](https://en.ilmatieteenlaitos.fi/open-data).
-Binding provides access to weather observations from FMI weather stations and [HARMONIE weather forecast model](https://en.ilmatieteenlaitos.fi/weather-forecast-models) forecasts.
+The binding provides access to weather observations from FMI weather stations and FMI weather forecasts.
Forecast covers "northern Europe" (Finland, Baltics, Scandinavia, some parts of surrounding countries), see [coverage map in the documentation](https://en.ilmatieteenlaitos.fi/weather-forecast-models).
+The binding supports two different forecast queries:
+
+- [HARMONIE weather forecast model](https://en.ilmatieteenlaitos.fi/weather-forecast-models), which is one of the weather models that meteorologists use in their work.
+- An edited query providing the official FMI forecast, which is often more accurate since it's edited by meteorologists who combine several different weather models and their experience to produce the official forecast.

### `forecast` Thing Configuration
-| Parameter | Type | Required | Description | Example |
-| ---------- | ---- | -------- | ---------------------------------------------------------------------------------------------------- | --------------------------------- |
+| Parameter | Type | Required | Description | Example |
+| ---------- | ---- | -------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `location` | text | ✓ | Latitude longitude location for the forecast. The parameter is given in format `LATITUDE,LONGITUDE`. | `"60.192059, 24.945831"` for Helsinki |
+| `query` | text | | Stored query for official FMI forecast, either `harmonie` or `edited`. | |
## Channels
if (retry < RETRIES) {
try {
response = client.query(getRequest(), TIMEOUT_MILLIS);
- } catch (FMIUnexpectedResponseException e) {
- handleError(e, retry);
- return;
} catch (FMIResponseException e) {
handleError(e, retry);
return;
import org.openhab.binding.fmiweather.internal.client.Location;
import org.openhab.binding.fmiweather.internal.client.Request;
import org.openhab.binding.fmiweather.internal.client.exception.FMIUnexpectedResponseException;
+import org.openhab.binding.fmiweather.internal.config.ForecastConfiguration;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
}
private @NonNullByDefault({}) LatLon location;
+ private String query = "";
public ForecastWeatherHandler(Thing thing) {
super(thing);
@Override
public void initialize() {
- try {
- Object location = getConfig().get(BindingConstants.LOCATION);
- if (location == null) {
- logger.debug("Location not set for thing {} -- aborting initialization.", getThing().getUID());
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- String.format("location parameter not set"));
- return;
- }
- String latlon = location.toString();
- String[] split = latlon.split(",");
- if (split.length != 2) {
- throw new NumberFormatException(String.format(
- "Expecting location parameter to have latitude and longitude separated by comma (LATITUDE,LONGITUDE). Found %d values instead.",
- split.length));
- }
- this.location = new LatLon(new BigDecimal(split[0].trim()), new BigDecimal(split[1].trim()));
- super.initialize();
- } catch (NumberFormatException e) {
+ ForecastConfiguration config = getConfigAs(ForecastConfiguration.class);
+ String location = config.location;
+ if (location.isBlank()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "location parameter not set");
+ return;
+ }
+ String[] split = location.split(",");
+ if (split.length != 2) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
- "location parameter should be in format LATITUDE,LONGITUDE. Error details: %s", e.getMessage()));
+ "location parameter should have latitude and longitude separated by comma (LATITUDE,LONGITUDE). Found %d values instead",
+ split.length));
+ return;
}
+ this.location = new LatLon(new BigDecimal(split[0].trim()), new BigDecimal(split[1].trim()));
+ query = config.query;
+
+ super.initialize();
}
@Override
@Override
protected Request getRequest() {
long now = Instant.now().getEpochSecond();
- return new ForecastRequest(location, floorToEvenMinutes(now, QUERY_RESOLUTION_MINUTES),
+ return new ForecastRequest(location, query, floorToEvenMinutes(now, QUERY_RESOLUTION_MINUTES),
ceilToEvenMinutes(now + TimeUnit.HOURS.toSeconds(FORECAST_HORIZON_HOURS), QUERY_RESOLUTION_MINUTES),
QUERY_RESOLUTION_MINUTES);
}
throws FMIExceptionReportException, FMIUnexpectedResponseException, FMIIOException {
try {
String url = request.toUrl();
+ logger.trace("GET request for {}", url);
String responseText = HttpUtil.executeUrl("GET", url, timeoutMillis);
if (responseText == null) {
throw new FMIIOException(String.format("HTTP error with %s", request.toUrl()));
}
+ logger.trace("Response content: '{}'", responseText);
FMIResponse response = parseMultiPointCoverageXml(responseText);
logger.debug("Request {} translated to url {}. Response: {}", request, url, response);
return response;
package org.openhab.binding.fmiweather.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.fmiweather.internal.config.ForecastConfiguration;
/**
* Request for weather forecasts
@NonNullByDefault
public class ForecastRequest extends Request {
- public static final String STORED_QUERY_ID = "fmi::forecast::harmonie::surface::point::multipointcoverage";
+ public static final String STORED_QUERY_ID_HARMONIE = "fmi::forecast::harmonie::surface::point::multipointcoverage";
+ public static final String STORED_QUERY_ID_EDITED = "fmi::forecast::edited::weather::scandinavia::point::multipointcoverage";
// For description of variables: http://opendata.fmi.fi/meta?observableProperty=forecast
public static final String PARAM_TEMPERATURE = "Temperature";
PARAM_WIND_SPEED, PARAM_WIND_GUST, PARAM_PRESSURE, PARAM_PRECIPITATION_1H, PARAM_TOTAL_CLOUD_COVER,
PARAM_WEATHER_SYMBOL };
- public ForecastRequest(QueryParameter location, long startEpoch, long endEpoch, long timestepMinutes) {
- super(STORED_QUERY_ID, location, startEpoch, endEpoch, timestepMinutes, PARAMETERS);
+ public ForecastRequest(QueryParameter location, String query, long startEpoch, long endEpoch,
+ long timestepMinutes) {
+ super(switch (query) {
+ case ForecastConfiguration.QUERY_HARMONIE -> STORED_QUERY_ID_HARMONIE;
+ case ForecastConfiguration.QUERY_EDITED -> STORED_QUERY_ID_EDITED;
+ default -> throw new IllegalArgumentException("Invalid query parameter '%s'".formatted(query));
+ }, location, startEpoch, endEpoch, timestepMinutes, PARAMETERS);
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 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.fmiweather.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ForecastConfiguration} class contains fields mapping Thing configuration parameters.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class ForecastConfiguration {
+ public static final String QUERY_HARMONIE = "harmonie";
+ public static final String QUERY_EDITED = "edited";
+
+ public String location = "";
+ public String query = QUERY_HARMONIE;
+}
thing-type.config.fmiweather.forecast.location.label = Location
thing-type.config.fmiweather.forecast.location.description = Location of weather in geographical coordinates (latitude,longitude).
+thing-type.config.fmiweather.forecast.query.label = Stored Query
+thing-type.config.fmiweather.forecast.query.description = Stored query for official FMI forecast
+thing-type.config.fmiweather.forecast.query.option.harmonie = Harmonie Surface Point Weather Forecast
+thing-type.config.fmiweather.forecast.query.option.edited = Forecast for Scandinavia (edited by a forecaster)
thing-type.config.fmiweather.observation.fmisid.label = FMISID of the Weather Station
thing-type.config.fmiweather.observation.fmisid.description = Station ID (FMISID) of the weather observation station <br /> <br />See https://en.ilmatieteenlaitos.fi/observation-stations for a list of observation stations. Select 'Weather' station for widest set of observations.
<label>Location</label>
<description>Location of weather in geographical coordinates (latitude,longitude).</description>
</parameter>
+ <parameter name="query" type="text" required="false">
+ <label>Stored Query</label>
+ <description>Stored query for official FMI forecast</description>
+ <default>harmonie</default>
+ <options>
+ <option value="harmonie">Harmonie Surface Point Weather Forecast</option>
+ <option value="edited">Forecast for Scandinavia (edited by a forecaster)</option>
+ </options>
+ </parameter>
</config-description>
</thing-type>
}
@Test
- public void testForecastRequestToUrl() {
- ForecastRequest request = new ForecastRequest(new LatLon(new BigDecimal("9"), new BigDecimal("8")), 1552215664L,
- 1552215665L, 61);
+ public void testForecastRequestToUrlHarmonie() {
+ ForecastRequest request = new ForecastRequest(new LatLon(new BigDecimal("9"), new BigDecimal("8")), "harmonie",
+ 1552215664L, 1552215665L, 61);
assertThat(request.toUrl(),
is("""
https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::forecast::harmonie::surface::point::multipointcoverage\
"""));
}
+ @Test
+ public void testForecastRequestToUrlEdited() {
+ ForecastRequest request = new ForecastRequest(new LatLon(new BigDecimal("9"), new BigDecimal("8")), "edited",
+ 1552215664L, 1552215665L, 61);
+ assertThat(request.toUrl(),
+ is("""
+ https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::forecast::edited::weather::scandinavia::point::multipointcoverage\
+ &starttime=2019-03-10T11:01:04Z&endtime=2019-03-10T11:01:05Z×tep=61&latlon=9,8\
+ ¶meters=Temperature,Humidity,WindDirection,WindSpeedMS,WindGust,Pressure,Precipitation1h,TotalCloudCover,WeatherSymbol3\
+ """));
+ }
+
@Test
public void testCustomLocation() {
QueryParameter location = new QueryParameter() {