mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[fmiweather] Add support for edited Scandinavia forecast (#17555)
* Add support for edited Scandinavia forecast Resolves #17548 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
6ec28a8ea2
commit
170444bfc0
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
This binding integrates to [the Finnish Meteorological Institute (FMI) Open Data API](https://en.ilmatieteenlaitos.fi/open-data).
|
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).
|
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.
|
||||||
|
|
||||||
![example of things](doc/images/fmi-example-things.png)
|
![example of things](doc/images/fmi-example-things.png)
|
||||||
|
|
||||||
@ -36,9 +40,10 @@ The binding automatically discovers weather stations and forecasts for nearby pl
|
|||||||
|
|
||||||
### `forecast` Thing Configuration
|
### `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 |
|
| `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
|
## Channels
|
||||||
|
|
||||||
|
@ -138,9 +138,6 @@ public abstract class AbstractWeatherHandler extends BaseThingHandler {
|
|||||||
if (retry < RETRIES) {
|
if (retry < RETRIES) {
|
||||||
try {
|
try {
|
||||||
response = client.query(getRequest(), TIMEOUT_MILLIS);
|
response = client.query(getRequest(), TIMEOUT_MILLIS);
|
||||||
} catch (FMIUnexpectedResponseException e) {
|
|
||||||
handleError(e, retry);
|
|
||||||
return;
|
|
||||||
} catch (FMIResponseException e) {
|
} catch (FMIResponseException e) {
|
||||||
handleError(e, retry);
|
handleError(e, retry);
|
||||||
return;
|
return;
|
||||||
|
@ -37,6 +37,7 @@ import org.openhab.binding.fmiweather.internal.client.LatLon;
|
|||||||
import org.openhab.binding.fmiweather.internal.client.Location;
|
import org.openhab.binding.fmiweather.internal.client.Location;
|
||||||
import org.openhab.binding.fmiweather.internal.client.Request;
|
import org.openhab.binding.fmiweather.internal.client.Request;
|
||||||
import org.openhab.binding.fmiweather.internal.client.exception.FMIUnexpectedResponseException;
|
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.Channel;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
@ -82,6 +83,7 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @NonNullByDefault({}) LatLon location;
|
private @NonNullByDefault({}) LatLon location;
|
||||||
|
private String query = "";
|
||||||
|
|
||||||
public ForecastWeatherHandler(Thing thing) {
|
public ForecastWeatherHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
@ -91,27 +93,23 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
try {
|
ForecastConfiguration config = getConfigAs(ForecastConfiguration.class);
|
||||||
Object location = getConfig().get(BindingConstants.LOCATION);
|
String location = config.location;
|
||||||
if (location == null) {
|
if (location.isBlank()) {
|
||||||
logger.debug("Location not set for thing {} -- aborting initialization.", getThing().getUID());
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "location parameter not set");
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
return;
|
||||||
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) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
|
|
||||||
"location parameter should be in format LATITUDE,LONGITUDE. Error details: %s", e.getMessage()));
|
|
||||||
}
|
}
|
||||||
|
String[] split = location.split(",");
|
||||||
|
if (split.length != 2) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
|
||||||
|
"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
|
||||||
@ -123,7 +121,7 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler {
|
|||||||
@Override
|
@Override
|
||||||
protected Request getRequest() {
|
protected Request getRequest() {
|
||||||
long now = Instant.now().getEpochSecond();
|
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),
|
ceilToEvenMinutes(now + TimeUnit.HOURS.toSeconds(FORECAST_HORIZON_HOURS), QUERY_RESOLUTION_MINUTES),
|
||||||
QUERY_RESOLUTION_MINUTES);
|
QUERY_RESOLUTION_MINUTES);
|
||||||
}
|
}
|
||||||
|
@ -133,10 +133,12 @@ public class Client {
|
|||||||
throws FMIExceptionReportException, FMIUnexpectedResponseException, FMIIOException {
|
throws FMIExceptionReportException, FMIUnexpectedResponseException, FMIIOException {
|
||||||
try {
|
try {
|
||||||
String url = request.toUrl();
|
String url = request.toUrl();
|
||||||
|
logger.trace("GET request for {}", url);
|
||||||
String responseText = HttpUtil.executeUrl("GET", url, timeoutMillis);
|
String responseText = HttpUtil.executeUrl("GET", url, timeoutMillis);
|
||||||
if (responseText == null) {
|
if (responseText == null) {
|
||||||
throw new FMIIOException(String.format("HTTP error with %s", request.toUrl()));
|
throw new FMIIOException(String.format("HTTP error with %s", request.toUrl()));
|
||||||
}
|
}
|
||||||
|
logger.trace("Response content: '{}'", responseText);
|
||||||
FMIResponse response = parseMultiPointCoverageXml(responseText);
|
FMIResponse response = parseMultiPointCoverageXml(responseText);
|
||||||
logger.debug("Request {} translated to url {}. Response: {}", request, url, response);
|
logger.debug("Request {} translated to url {}. Response: {}", request, url, response);
|
||||||
return response;
|
return response;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.fmiweather.internal.client;
|
package org.openhab.binding.fmiweather.internal.client;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.fmiweather.internal.config.ForecastConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request for weather forecasts
|
* Request for weather forecasts
|
||||||
@ -23,7 +24,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class ForecastRequest extends Request {
|
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
|
// For description of variables: http://opendata.fmi.fi/meta?observableProperty=forecast
|
||||||
public static final String PARAM_TEMPERATURE = "Temperature";
|
public static final String PARAM_TEMPERATURE = "Temperature";
|
||||||
@ -39,7 +41,12 @@ public class ForecastRequest extends Request {
|
|||||||
PARAM_WIND_SPEED, PARAM_WIND_GUST, PARAM_PRESSURE, PARAM_PRECIPITATION_1H, PARAM_TOTAL_CLOUD_COVER,
|
PARAM_WIND_SPEED, PARAM_WIND_GUST, PARAM_PRESSURE, PARAM_PRECIPITATION_1H, PARAM_TOTAL_CLOUD_COVER,
|
||||||
PARAM_WEATHER_SYMBOL };
|
PARAM_WEATHER_SYMBOL };
|
||||||
|
|
||||||
public ForecastRequest(QueryParameter location, long startEpoch, long endEpoch, long timestepMinutes) {
|
public ForecastRequest(QueryParameter location, String query, long startEpoch, long endEpoch,
|
||||||
super(STORED_QUERY_ID, location, startEpoch, endEpoch, timestepMinutes, PARAMETERS);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
@ -118,6 +118,10 @@ thing-type.fmiweather.observation.description = Finnish Meteorological Institute
|
|||||||
|
|
||||||
thing-type.config.fmiweather.forecast.location.label = Location
|
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.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.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.
|
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.
|
||||||
|
|
||||||
|
@ -247,6 +247,15 @@
|
|||||||
<label>Location</label>
|
<label>Location</label>
|
||||||
<description>Location of weather in geographical coordinates (latitude,longitude).</description>
|
<description>Location of weather in geographical coordinates (latitude,longitude).</description>
|
||||||
</parameter>
|
</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>
|
</config-description>
|
||||||
|
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
@ -49,9 +49,9 @@ public class FMIRequestTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testForecastRequestToUrl() {
|
public void testForecastRequestToUrlHarmonie() {
|
||||||
ForecastRequest request = new ForecastRequest(new LatLon(new BigDecimal("9"), new BigDecimal("8")), 1552215664L,
|
ForecastRequest request = new ForecastRequest(new LatLon(new BigDecimal("9"), new BigDecimal("8")), "harmonie",
|
||||||
1552215665L, 61);
|
1552215664L, 1552215665L, 61);
|
||||||
assertThat(request.toUrl(),
|
assertThat(request.toUrl(),
|
||||||
is("""
|
is("""
|
||||||
https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::forecast::harmonie::surface::point::multipointcoverage\
|
https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::forecast::harmonie::surface::point::multipointcoverage\
|
||||||
@ -60,6 +60,18 @@ public class FMIRequestTest {
|
|||||||
"""));
|
"""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void testCustomLocation() {
|
public void testCustomLocation() {
|
||||||
QueryParameter location = new QueryParameter() {
|
QueryParameter location = new QueryParameter() {
|
||||||
|
Loading…
Reference in New Issue
Block a user