mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Add time series support for forecasts (#17543)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
2a1fb95946
commit
04b9c33706
@ -28,13 +28,13 @@ The binding automatically discovers weather stations and forecasts for nearby pl
|
|||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
### `observation` thing configuration
|
### `observation` Thing Configuration
|
||||||
|
|
||||||
| Parameter | Type | Required | Description | Example |
|
| 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 |
|
| `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 |
|
| 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 and forecast things provide slightly different details on weather.
|
||||||
|
|
||||||
### `observation` thing channels
|
### `observation` Thing Channels
|
||||||
|
|
||||||
Observation channels are grouped in single group, `current`.
|
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`.
|
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:
|
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
|
- `forecastHours01`: Forecasted weather for 1 hours from now
|
||||||
- `forecastHours02`: Forecasted weather for 2 hours from now
|
- `forecastHours02`: Forecasted weather for 2 hours from now
|
||||||
- etc.
|
- etc.
|
||||||
|
@ -44,6 +44,7 @@ import org.openhab.core.thing.ThingStatusDetail;
|
|||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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) {
|
protected void updateStateIfLinked(ChannelUID channelUID, @Nullable BigDecimal value, @Nullable Unit<?> unit) {
|
||||||
if (isLinked(channelUID)) {
|
if (isLinked(channelUID)) {
|
||||||
if (value == null) {
|
updateState(channelUID, getState(value, unit));
|
||||||
updateState(channelUID, UnDefType.UNDEF);
|
}
|
||||||
} else if (unit == null) {
|
}
|
||||||
updateState(channelUID, new DecimalType(value));
|
|
||||||
} else {
|
/**
|
||||||
updateState(channelUID, new QuantityType<>(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,9 @@ public class BindingConstants {
|
|||||||
public static final ThingTypeUID THING_TYPE_FORECAST = new ThingTypeUID(BINDING_ID, "forecast");
|
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");
|
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
|
// List of all Channel ids
|
||||||
public static final String CHANNEL_TIME = "time";
|
public static final String CHANNEL_TIME = "time";
|
||||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||||
|
@ -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.binding.fmiweather.internal.client.ForecastRequest.*;
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||||
import static org.openhab.core.library.unit.Units.*;
|
import static org.openhab.core.library.unit.Units.*;
|
||||||
|
import static org.openhab.core.types.TimeSeries.Policy.REPLACE;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Instant;
|
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.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.types.TimeSeries;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -140,29 +142,8 @@ public class ForecastWeatherHandler extends AbstractWeatherHandler {
|
|||||||
properties.put(PROP_LATITUDE, location.latitude.toPlainString());
|
properties.put(PROP_LATITUDE, location.latitude.toPlainString());
|
||||||
properties.put(PROP_LONGITUDE, location.longitude.toPlainString());
|
properties.put(PROP_LONGITUDE, location.longitude.toPlainString());
|
||||||
updateProperties(properties);
|
updateProperties(properties);
|
||||||
for (Channel channel : getThing().getChannels()) {
|
updateHourlyChannels(response, location);
|
||||||
ChannelUID channelUID = channel.getUID();
|
updateTimeSeriesChannels(response, location);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
} catch (FMIUnexpectedResponseException e) {
|
} catch (FMIUnexpectedResponseException e) {
|
||||||
// Unexpected (possibly bug) issue with response
|
// 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) {
|
private static int getHours(ChannelUID uid) {
|
||||||
String groupId = uid.getGroupId();
|
String groupId = uid.getGroupId();
|
||||||
if (groupId == null) {
|
if (groupId == null) {
|
||||||
|
@ -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.label = FMI Weather Forecast
|
||||||
thing-type.fmiweather.forecast.description = Finnish Meteorological Institute (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.label = 1 Hours Forecast
|
||||||
thing-type.fmiweather.forecast.group.forecastHours01.description = This is the weather forecast in 1 hours.
|
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
|
thing-type.fmiweather.forecast.group.forecastHours02.label = 2 Hours Forecast
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
<description>Finnish Meteorological Institute (FMI) weather forecast</description>
|
<description>Finnish Meteorological Institute (FMI) weather forecast</description>
|
||||||
|
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
<channel-group id="forecast" typeId="group-forecast">
|
||||||
|
<label>Forecast</label>
|
||||||
|
<description>This is the weather forecast</description>
|
||||||
|
</channel-group>
|
||||||
<channel-group id="forecastNow" typeId="group-forecast">
|
<channel-group id="forecastNow" typeId="group-forecast">
|
||||||
<label>Forecast for the Current Time</label>
|
<label>Forecast for the Current Time</label>
|
||||||
<description>This is the weather forecast for the current time</description>
|
<description>This is the weather forecast for the current time</description>
|
||||||
@ -234,6 +238,10 @@
|
|||||||
</channel-group>
|
</channel-group>
|
||||||
</channel-groups>
|
</channel-groups>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<property name="thingTypeVersion">1</property>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="location" type="text" required="true">
|
<parameter name="location" type="text" required="true">
|
||||||
<label>Location</label>
|
<label>Location</label>
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
<?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>
|
Loading…
Reference in New Issue
Block a user