## 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 |
| ---------- | ---- | -------- | ---------------------------------------------------------------------------------------------------- | --------------------------------- |
Observation and forecast things provide slightly different details on weather.
-### `observation` thing channels
+### `observation` Thing Channels
Observation channels are grouped in single group, `current`.
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.
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;
*/
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);
}
}
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;
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;
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
}
}
+ 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) {
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="fmiweather:forecast">
+
+ <instruction-set targetVersion="1">
+ <add-channel id="time" groupIds="forecast">
+ <type>fmiweather:forecast-time-channel</type>
+ </add-channel>
+ <add-channel id="temperature" groupIds="forecast">
+ <type>fmiweather:temperature-channel</type>
+ </add-channel>
+ <add-channel id="humidity" groupIds="forecast">
+ <type>fmiweather:humidity-channel</type>
+ </add-channel>
+ <add-channel id="wind-direction" groupIds="forecast">
+ <type>fmiweather:wind-direction-channel</type>
+ </add-channel>
+ <add-channel id="wind-speed" groupIds="forecast">
+ <type>fmiweather:wind-speed-channel</type>
+ </add-channel>
+ <add-channel id="wind-gust" groupIds="forecast">
+ <type>fmiweather:wind-gust-channel</type>
+ </add-channel>
+ <add-channel id="pressure" groupIds="forecast">
+ <type>fmiweather:pressure-channel</type>
+ </add-channel>
+ <add-channel id="precipitation-intensity" groupIds="forecast">
+ <type>fmiweather:precipitation-intensity-channel</type>
+ </add-channel>
+ <add-channel id="total-cloud-cover" groupIds="forecast">
+ <type>fmiweather:total-cloud-cover-channel</type>
+ </add-channel>
+ <add-channel id="weather-id" groupIds="forecast">
+ <type>fmiweather:weather-id-channel</type>
+ </add-channel>
+ </instruction-set>
+
+ </thing-type>
+
+</update:update-descriptions>