diff --git a/bundles/org.openhab.binding.fmiweather/README.md b/bundles/org.openhab.binding.fmiweather/README.md index 0b604981a4b..be0724a8784 100644 --- a/bundles/org.openhab.binding.fmiweather/README.md +++ b/bundles/org.openhab.binding.fmiweather/README.md @@ -28,13 +28,13 @@ The binding automatically discovers weather stations and forecasts for nearby pl ## Thing Configuration -### `observation` thing configuration +### `observation` Thing Configuration | Parameter | Type | Required | Description | Example | | --------- | ---- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | `fmisid` | text | ✓ | FMI Station ID. You can FMISID of see all weathers stations at [FMI web site](https://en.ilmatieteenlaitos.fi/observation-stations?p_p_id=stationlistingportlet_WAR_fmiwwwweatherportlets&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-4&p_p_col_count=1&_stationlistingportlet_WAR_fmiwwwweatherportlets_stationGroup=WEATHER#station-listing) | `"852678"` for Espoo Nuuksio station | -### `forecast` thing configuration +### `forecast` Thing Configuration | Parameter | Type | Required | Description | Example | | ---------- | ---- | -------- | ---------------------------------------------------------------------------------------------------- | --------------------------------- | @@ -44,7 +44,7 @@ The binding automatically discovers weather stations and forecasts for nearby pl Observation and forecast things provide slightly different details on weather. -### `observation` thing channels +### `observation` Thing Channels Observation channels are grouped in single group, `current`. @@ -67,11 +67,12 @@ You can check the exact observation time by using the `time` channel. To refer to certain channel, use the normal convention `THING_ID:GROUP_ID#CHANNEL_ID`, e.g. `fmiweather:observation:station_874863_Espoo_Tapiola:current#temperature`. -### `forecast` thing channels +### `forecast` Thing Channels Forecast has multiple channel groups, one for each forecasted time. The groups are named as follows: -- `forecastNow`: Forecasted weather for the current time +- `forecast`: Forecasted weather (with time series support) +- `forecastNow`: Forecasted weather for the current time (deprecated, please use `forecast` instead) - `forecastHours01`: Forecasted weather for 1 hours from now - `forecastHours02`: Forecasted weather for 2 hours from now - etc. diff --git a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/AbstractWeatherHandler.java b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/AbstractWeatherHandler.java index eb20e2cfc37..0aaf824e399 100644 --- a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/AbstractWeatherHandler.java +++ b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/AbstractWeatherHandler.java @@ -44,6 +44,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; 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; @@ -210,13 +211,24 @@ public abstract class AbstractWeatherHandler extends BaseThingHandler { */ protected void updateStateIfLinked(ChannelUID channelUID, @Nullable BigDecimal value, @Nullable Unit unit) { if (isLinked(channelUID)) { - if (value == null) { - updateState(channelUID, UnDefType.UNDEF); - } else if (unit == null) { - updateState(channelUID, new DecimalType(value)); - } else { - updateState(channelUID, new QuantityType<>(value, unit)); - } + updateState(channelUID, getState(value, unit)); + } + } + + /** + * Return QuantityType or DecimalType channel state + * + * @param value value to update + * @param unit unit associated with the value + * @return UNDEF state when value is null, otherwise QuantityType or DecimalType + */ + protected State getState(@Nullable BigDecimal value, @Nullable Unit unit) { + if (value == null) { + return UnDefType.UNDEF; + } else if (unit == null) { + return new DecimalType(value); + } else { + return new QuantityType<>(value, unit); } } diff --git a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/BindingConstants.java b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/BindingConstants.java index 4791d09a701..624089e7bb7 100644 --- a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/BindingConstants.java +++ b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/BindingConstants.java @@ -35,6 +35,9 @@ public class BindingConstants { public static final ThingTypeUID THING_TYPE_FORECAST = new ThingTypeUID(BINDING_ID, "forecast"); public static final ThingUID UID_LOCAL_FORECAST = new ThingUID(BINDING_ID, "forecast", "local"); + // List of all static Channel Group IDs + public static final String CHANNEL_GROUP_FORECAST = "forecast"; + // List of all Channel ids public static final String CHANNEL_TIME = "time"; public static final String CHANNEL_TEMPERATURE = "temperature"; diff --git a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/ForecastWeatherHandler.java b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/ForecastWeatherHandler.java index 3aee7ede982..03e78ef778b 100644 --- a/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/ForecastWeatherHandler.java +++ b/bundles/org.openhab.binding.fmiweather/src/main/java/org/openhab/binding/fmiweather/internal/ForecastWeatherHandler.java @@ -16,6 +16,7 @@ import static org.openhab.binding.fmiweather.internal.BindingConstants.*; import static org.openhab.binding.fmiweather.internal.client.ForecastRequest.*; import static org.openhab.core.library.unit.SIUnits.CELSIUS; import static org.openhab.core.library.unit.Units.*; +import static org.openhab.core.types.TimeSeries.Policy.REPLACE; import java.math.BigDecimal; import java.time.Instant; @@ -41,6 +42,7 @@ 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.types.TimeSeries; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -140,29 +142,8 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler { properties.put(PROP_LATITUDE, location.latitude.toPlainString()); properties.put(PROP_LONGITUDE, location.longitude.toPlainString()); updateProperties(properties); - for (Channel channel : getThing().getChannels()) { - ChannelUID channelUID = channel.getUID(); - int hours = getHours(channelUID); - int timeIndex = getTimeIndex(hours); - if (channelUID.getIdWithoutGroup().equals(CHANNEL_TIME)) { - // All parameters and locations should share the same timestamps. We use temperature to figure out - // timestamp for the group of channels - String field = ForecastRequest.PARAM_TEMPERATURE; - Data data = unwrap(response.getData(location, field), - "Field %s not present for location %s in response. Bug?", field, location); - updateEpochSecondStateIfLinked(channelUID, data.timestampsEpochSecs[timeIndex]); - } else { - String field = getDataField(channelUID); - Unit unit = getUnit(channelUID); - if (field == null) { - logger.error("Channel {} not handled. Bug?", channelUID.getId()); - continue; - } - Data data = unwrap(response.getData(location, field), - "Field %s not present for location %s in response. Bug?", field, location); - updateStateIfLinked(channelUID, data.values[timeIndex], unit); - } - } + updateHourlyChannels(response, location); + updateTimeSeriesChannels(response, location); updateStatus(ThingStatus.ONLINE); } catch (FMIUnexpectedResponseException e) { // Unexpected (possibly bug) issue with response @@ -172,6 +153,71 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler { } } + private void updateHourlyChannels(FMIResponse response, Location location) throws FMIUnexpectedResponseException { + for (Channel channel : getThing().getChannels()) { + ChannelUID channelUID = channel.getUID(); + if (CHANNEL_GROUP_FORECAST.equals(channelUID.getGroupId())) { + // Skip time series group + continue; + } + int hours = getHours(channelUID); + int timeIndex = getTimeIndex(hours); + if (channelUID.getIdWithoutGroup().equals(CHANNEL_TIME)) { + // All parameters and locations should share the same timestamps. We use temperature to figure out + // timestamp for the group of channels + final String field = ForecastRequest.PARAM_TEMPERATURE; + Data data = unwrap(response.getData(location, field), + "Field %s not present for location %s in response. Bug?", field, location); + updateEpochSecondStateIfLinked(channelUID, data.timestampsEpochSecs[timeIndex]); + } else { + String field = getDataField(channelUID); + Unit unit = getUnit(channelUID); + if (field == null) { + logger.error("Channel {} not handled. Bug?", channelUID.getId()); + continue; + } + Data data = unwrap(response.getData(location, field), + "Field %s not present for location %s in response. Bug?", field, location); + updateStateIfLinked(channelUID, data.values[timeIndex], unit); + } + } + } + + private void updateTimeSeriesChannels(FMIResponse response, Location location) + throws FMIUnexpectedResponseException { + for (Channel channel : getThing().getChannelsOfGroup(CHANNEL_GROUP_FORECAST)) { + ChannelUID channelUID = channel.getUID(); + if (CHANNEL_TIME.equals(channelUID.getIdWithoutGroup())) { + // All parameters and locations should share the same timestamps. We use temperature to figure out + // timestamp for the group of channels + final String field = ForecastRequest.PARAM_TEMPERATURE; + Data data = unwrap(response.getData(location, field), + "Field %s not present for location %s in response. Bug?", field, location); + updateEpochSecondStateIfLinked(channelUID, data.timestampsEpochSecs[0]); + continue; + } + String field = getDataField(channelUID); + Unit unit = getUnit(channelUID); + if (field == null) { + logger.error("Channel {} not handled. Bug?", channelUID.getId()); + continue; + } + Data data = unwrap(response.getData(location, field), + "Field %s not present for location %s in response. Bug?", field, location); + if (data.values.length != data.timestampsEpochSecs.length) { + logger.warn("Number of values ({}) doesn't match number of timestamps ({})", data.values.length, + data.timestampsEpochSecs.length); + continue; + } + updateStateIfLinked(channelUID, data.values[0], unit); + TimeSeries timeSeries = new TimeSeries(REPLACE); + for (int i = 0; i < data.values.length; i++) { + timeSeries.add(Instant.ofEpochSecond(data.timestampsEpochSecs[i]), getState(data.values[i], unit)); + } + sendTimeSeries(channelUID, timeSeries); + } + } + private static int getHours(ChannelUID uid) { String groupId = uid.getGroupId(); if (groupId == null) { diff --git a/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/i18n/fmiweather.properties b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/i18n/fmiweather.properties index bcc28a52cf5..f231e99d1a8 100644 --- a/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/i18n/fmiweather.properties +++ b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/i18n/fmiweather.properties @@ -7,6 +7,8 @@ addon.fmiweather.description = This is the binding for Finnish Meteorological In thing-type.fmiweather.forecast.label = FMI Weather Forecast thing-type.fmiweather.forecast.description = Finnish Meteorological Institute (FMI) weather forecast +thing-type.fmiweather.forecast.group.forecast.label = Forecast +thing-type.fmiweather.forecast.group.forecast.description = This is the weather forecast thing-type.fmiweather.forecast.group.forecastHours01.label = 1 Hours Forecast thing-type.fmiweather.forecast.group.forecastHours01.description = This is the weather forecast in 1 hours. thing-type.fmiweather.forecast.group.forecastHours02.label = 2 Hours Forecast diff --git a/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/thing/thing-types.xml index 5f7c64966f9..7ce3e14f82b 100644 --- a/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/thing/thing-types.xml @@ -28,6 +28,10 @@ Finnish Meteorological Institute (FMI) weather forecast + + + This is the weather forecast + This is the weather forecast for the current time @@ -234,6 +238,10 @@ + + 1 + + diff --git a/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 00000000000..6c0e1a58f11 --- /dev/null +++ b/bundles/org.openhab.binding.fmiweather/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,43 @@ + + + + + + + + fmiweather:forecast-time-channel + + + fmiweather:temperature-channel + + + fmiweather:humidity-channel + + + fmiweather:wind-direction-channel + + + fmiweather:wind-speed-channel + + + fmiweather:wind-gust-channel + + + fmiweather:pressure-channel + + + fmiweather:precipitation-intensity-channel + + + fmiweather:total-cloud-cover-channel + + + fmiweather:weather-id-channel + + + + + +