Skip to content

Commit ee6ebad

Browse files
authored
[smhi] Use parameter API endpoint (#20642)
* [smhi] Use parameter API endpoint Fixes #20592 Signed-off-by: Anders Alfredsson <andersb86@gmail.com>
1 parent bfa9e38 commit ee6ebad

17 files changed

Lines changed: 2522 additions & 6626 deletions

File tree

bundles/org.openhab.binding.smhi/src/main/java/org/openhab/binding/smhi/internal/Forecast.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020

2121
import org.eclipse.jdt.annotation.NonNullByDefault;
22+
import org.openhab.binding.smhi.provider.ParameterMetadata;
2223
import org.openhab.core.types.State;
2324

2425
/**
@@ -51,14 +52,14 @@ public Map<String, BigDecimal> getParameters() {
5152
}
5253

5354
public BigDecimal getParameter(String parameter) {
54-
// TODO: Remove after 6.0 release
55+
// TODO: Remove after last 2026 release
5556
parameter = PMP3G_BACKWARD_COMP.getOrDefault(parameter, parameter);
5657

5758
return parameters.getOrDefault(parameter, DEFAULT_MISSING_VALUE);
5859
}
5960

60-
public State getParameterAsState(String parameter) {
61-
return Util.getParameterAsState(parameter, getParameter(parameter));
61+
public State getParameterAsState(ParameterMetadata metadata) {
62+
return Util.getParameterAsState(metadata, getParameter(metadata.name()));
6263
}
6364

6465
@Override

bundles/org.openhab.binding.smhi/src/main/java/org/openhab/binding/smhi/internal/ForecastAggregator.java

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@
1212
*/
1313
package org.openhab.binding.smhi.internal;
1414

15-
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.DEFAULT_MISSING_VALUE;
16-
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.PRECIPITATION_TOTAL;
15+
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
1716

1817
import java.math.BigDecimal;
19-
import java.time.ZonedDateTime;
2018
import java.time.temporal.ChronoUnit;
2119
import java.util.List;
2220

2321
import org.eclipse.jdt.annotation.NonNullByDefault;
22+
import org.eclipse.jdt.annotation.Nullable;
23+
import org.openhab.binding.smhi.provider.ParameterMetadata;
2424
import org.openhab.core.types.State;
25+
import org.openhab.core.types.UnDefType;
2526

2627
/**
2728
* @author Anders Alfredsson - Initial contribution
@@ -33,31 +34,39 @@ public class ForecastAggregator {
3334
*
3435
* @param timeSeries
3536
* @param dayOffset
36-
* @param parameter
37+
* @param metadata
3738
* @return
3839
*/
39-
public static State max(SmhiTimeSeries timeSeries, int dayOffset, String parameter) {
40-
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
41-
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter))
42-
.filter(p -> !DEFAULT_MISSING_VALUE.equals(p)).max(BigDecimal::compareTo)
43-
.map(value -> Util.getParameterAsState(parameter, value))
44-
.orElseGet(() -> Util.getParameterAsState(parameter, DEFAULT_MISSING_VALUE));
40+
public static State max(SmhiTimeSeries timeSeries, int dayOffset, @Nullable ParameterMetadata metadata) {
41+
if (metadata == null) {
42+
return UnDefType.UNDEF;
43+
}
44+
45+
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset, false);
46+
return dayForecasts.stream().map(forecast -> forecast.getParameter(metadata.name()))
47+
.filter(p -> !metadata.missingValue().equals(p)).max(BigDecimal::compareTo)
48+
.map(value -> Util.getParameterAsState(metadata, value))
49+
.orElseGet(() -> Util.getParameterAsState(metadata, metadata.missingValue()));
4550
}
4651

4752
/**
4853
* Get the minimum value for the specified parameter for the n:th day after the forecast's reference time
4954
*
5055
* @param timeSeries
5156
* @param dayOffset
52-
* @param parameter
57+
* @param metadata
5358
* @return
5459
*/
55-
public static State min(SmhiTimeSeries timeSeries, int dayOffset, String parameter) {
56-
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
57-
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter))
58-
.filter(p -> !DEFAULT_MISSING_VALUE.equals(p)).min(BigDecimal::compareTo)
59-
.map(value -> Util.getParameterAsState(parameter, value))
60-
.orElseGet(() -> Util.getParameterAsState(parameter, DEFAULT_MISSING_VALUE));
60+
public static State min(SmhiTimeSeries timeSeries, int dayOffset, @Nullable ParameterMetadata metadata) {
61+
if (metadata == null) {
62+
return UnDefType.UNDEF;
63+
}
64+
65+
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset, false);
66+
return dayForecasts.stream().map(forecast -> forecast.getParameter(metadata.name()))
67+
.filter(p -> !metadata.missingValue().equals(p)).min(BigDecimal::compareTo)
68+
.map(value -> Util.getParameterAsState(metadata, value))
69+
.orElseGet(() -> Util.getParameterAsState(metadata, metadata.missingValue()));
6170
}
6271

6372
/**
@@ -66,23 +75,26 @@ public static State min(SmhiTimeSeries timeSeries, int dayOffset, String paramet
6675
*
6776
* @param timeSeries
6877
* @param dayOffset
69-
* @param parameter
78+
* @param baseMetadata
79+
* @param totalMetadata
7080
* @return
7181
*/
72-
public static State total(SmhiTimeSeries timeSeries, int dayOffset, String parameter) {
73-
ZonedDateTime start = timeSeries.getReferenceTime().plusDays(dayOffset).withHour(0);
74-
ZonedDateTime end = start.plusDays(1);
75-
List<Forecast> dayForecasts = timeSeries
76-
.filter(forecast -> forecast.getTime().isAfter(start) && !forecast.getTime().isAfter(end));
82+
public static State total(SmhiTimeSeries timeSeries, int dayOffset, @Nullable ParameterMetadata baseMetadata,
83+
@Nullable ParameterMetadata totalMetadata) {
84+
if (baseMetadata == null || totalMetadata == null) {
85+
return UnDefType.UNDEF;
86+
}
87+
88+
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset, true);
7789
if (dayForecasts.size() == 1) {
78-
return dayForecasts.getFirst().getParameterAsState(parameter);
90+
return dayForecasts.getFirst().getParameterAsState(baseMetadata);
7991
}
8092
return dayForecasts.stream().map(forecast -> {
8193
BigDecimal hours = BigDecimal
8294
.valueOf(forecast.getIntervalStartTime().until(forecast.getTime(), ChronoUnit.HOURS));
83-
return forecast.getParameter(parameter).multiply(hours);
84-
}).reduce(BigDecimal::add).map(value -> Util.getParameterAsState(PRECIPITATION_TOTAL, value))
85-
.orElseGet(() -> Util.getParameterAsState(PRECIPITATION_TOTAL, DEFAULT_MISSING_VALUE));
95+
return forecast.getParameter(baseMetadata.name()).multiply(hours);
96+
}).reduce(BigDecimal::add).map(value -> Util.getParameterAsState(totalMetadata, value))
97+
.orElseGet(() -> Util.getParameterAsState(totalMetadata, DEFAULT_MISSING_VALUE));
8698
}
8799

88100
/**
@@ -92,13 +104,17 @@ public static State total(SmhiTimeSeries timeSeries, int dayOffset, String param
92104
*
93105
* @param timeSeries
94106
* @param dayOffset
95-
* @param parameter
107+
* @param metadata
96108
* @return
97109
*/
98-
public static State noonOrFirst(SmhiTimeSeries timeSeries, int dayOffset, String parameter) {
99-
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
110+
public static State noonOrFirst(SmhiTimeSeries timeSeries, int dayOffset, @Nullable ParameterMetadata metadata) {
111+
if (metadata == null) {
112+
return UnDefType.UNDEF;
113+
}
114+
115+
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset, false);
100116
return dayForecasts.stream().filter(forecast -> forecast.getTime().getHour() >= 12).findFirst()
101-
.map(f -> f.getParameterAsState(parameter))
102-
.orElseGet(() -> Util.getParameterAsState(parameter, DEFAULT_MISSING_VALUE));
117+
.map(f -> f.getParameterAsState(metadata))
118+
.orElseGet(() -> Util.getParameterAsState(metadata, metadata.missingValue()));
103119
}
104120
}

bundles/org.openhab.binding.smhi/src/main/java/org/openhab/binding/smhi/internal/Parser.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.stream.StreamSupport;
2222

2323
import org.eclipse.jdt.annotation.NonNullByDefault;
24+
import org.openhab.binding.smhi.provider.ParameterMetadata;
2425

2526
import com.google.gson.JsonArray;
2627
import com.google.gson.JsonObject;
@@ -96,4 +97,22 @@ private static Forecast parseForecast(JsonObject object) {
9697

9798
return new Forecast(time, intervalStartTime, parameters);
9899
}
100+
101+
public static List<ParameterMetadata> parseParameterMetadata(String json) {
102+
JsonObject object = JsonParser.parseString(json).getAsJsonObject();
103+
JsonArray parameters = object.getAsJsonArray("parameter");
104+
return StreamSupport.stream(parameters.spliterator(), false)
105+
.map(jsonElement -> parseParameterMetadata(jsonElement.getAsJsonObject())).toList();
106+
}
107+
108+
private static ParameterMetadata parseParameterMetadata(JsonObject jsonObject) {
109+
String name = jsonObject.get("name").getAsString();
110+
String shortName = jsonObject.get("shortName").getAsString();
111+
String description = jsonObject.get("description").getAsString();
112+
String levelType = jsonObject.get("levelType").getAsString();
113+
BigDecimal level = jsonObject.get("level").getAsBigDecimal();
114+
String unit = jsonObject.get("unit").getAsString();
115+
BigDecimal missingValue = jsonObject.get("missingValue").getAsBigDecimal();
116+
return new ParameterMetadata(name, shortName, description, levelType, level, unit, missingValue);
117+
}
99118
}

bundles/org.openhab.binding.smhi/src/main/java/org/openhab/binding/smhi/internal/SmhiBindingConstants.java

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@
1515
import java.math.BigDecimal;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.Set;
19+
20+
import javax.measure.MetricPrefix;
21+
import javax.measure.Unit;
1822

1923
import org.eclipse.jdt.annotation.NonNullByDefault;
24+
import org.openhab.binding.smhi.provider.ParameterMetadata;
25+
import org.openhab.core.library.unit.SIUnits;
26+
import org.openhab.core.library.unit.Units;
27+
import org.openhab.core.semantics.SemanticTag;
28+
import org.openhab.core.semantics.model.DefaultSemanticTags;
2029
import org.openhab.core.thing.ThingTypeUID;
2130

2231
/**
@@ -66,28 +75,52 @@ public class SmhiBindingConstants {
6675
public static final String WIND_MIN = "wind_speed_min";
6776
public static final String PRECIPITATION_TOTAL = "precipitation_amount_total";
6877

69-
public static final List<String> HOURLY_CHANNELS = List.of(PRESSURE, TEMPERATURE, CLOUD_BASE_ALTITUDE,
70-
CLOUD_TOP_ALTITUDE, VISIBILITY, WIND_DIRECTION, WIND_SPEED, RELATIVE_HUMIDITY, THUNDER_PROBABILITY,
71-
TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER, HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN,
72-
PRECIPITATION_MAX, PRECIPITATION_MEAN, PRECIPITATION_MEDIAN, PRECIPITATION_PROBABILITY, PERCENT_FROZEN,
73-
FROZEN_PROBABILITY, PRECIPITATION_CATEGORY, WEATHER_SYMBOL);
78+
public static final Set<String> STANDARD_CHANNELS = Set.of(PRESSURE, TEMPERATURE, TEMPERATURE_MAX, TEMPERATURE_MIN,
79+
WIND_DIRECTION, WIND_SPEED, WIND_MAX, WIND_MIN, RELATIVE_HUMIDITY, TOTAL_CLOUD_COVER, GUST,
80+
PRECIPITATION_MIN, PRECIPITATION_MAX, PRECIPITATION_TOTAL, PRECIPITATION_PROBABILITY,
81+
PRECIPITATION_CATEGORY, WEATHER_SYMBOL);
7482

75-
public static final List<String> DAILY_CHANNELS = List.of(PRESSURE, TEMPERATURE, CLOUD_BASE_ALTITUDE,
76-
CLOUD_TOP_ALTITUDE, TEMPERATURE_MAX, TEMPERATURE_MIN, VISIBILITY, WIND_DIRECTION, WIND_SPEED, WIND_MAX,
77-
WIND_MIN, RELATIVE_HUMIDITY, THUNDER_PROBABILITY, TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER,
78-
HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN, PRECIPITATION_MAX, PRECIPITATION_TOTAL, PRECIPITATION_MEAN,
79-
PRECIPITATION_MEDIAN, PRECIPITATION_PROBABILITY, PERCENT_FROZEN, FROZEN_PROBABILITY, PRECIPITATION_CATEGORY,
80-
WEATHER_SYMBOL);
83+
public static final Set<String> AGGREGATE_CHANNELS = Set.of(TEMPERATURE_MAX, TEMPERATURE_MIN, WIND_MAX, WIND_MIN,
84+
PRECIPITATION_TOTAL);
8185

8286
public static final String BASE_URL = "https://opendata-download-metfcst.smhi.se/api/category/snow1g/version/1/";
8387
public static final String CREATED_TIME_URL = BASE_URL + "createdtime.json";
8488
public static final String POINT_FORECAST_URL = BASE_URL + "geotype/point/lon/%.6f/lat/%.6f/data.json";
89+
public static final String PARAMETER_METADATA_URL = BASE_URL + "parameter.json";
8590

8691
public static final BigDecimal OCTAS_TO_PERCENT = BigDecimal.valueOf(12.5);
8792
public static final BigDecimal FRACTION_TO_PERCENT = BigDecimal.valueOf(100);
8893
public static final BigDecimal DEFAULT_MISSING_VALUE = BigDecimal.valueOf(9999);
8994

90-
// TODO: Remove for 6.0 release
95+
public static final List<ParameterMetadata> AGGREGATE_CHANNELS_METADATA = List.of(
96+
new ParameterMetadata(TEMPERATURE_MAX, "", "Highest temperature of the day", "", BigDecimal.ZERO, "Cel",
97+
DEFAULT_MISSING_VALUE),
98+
new ParameterMetadata(TEMPERATURE_MIN, "", "Lowest temperature of the day", "", BigDecimal.ZERO, "Cel",
99+
DEFAULT_MISSING_VALUE),
100+
new ParameterMetadata(WIND_MAX, "", "Highest wind speed of the day", "", BigDecimal.ZERO, "m/s",
101+
DEFAULT_MISSING_VALUE),
102+
new ParameterMetadata(WIND_MIN, "", "Lowest wind speed of the day", "", BigDecimal.ZERO, "m/s",
103+
DEFAULT_MISSING_VALUE),
104+
new ParameterMetadata(PRECIPITATION_TOTAL, "", "Total amount of precipitation during the day", "",
105+
BigDecimal.ZERO, "mm", DEFAULT_MISSING_VALUE));
106+
107+
public static final Map<String, Unit<?>> UNIT_MAP = Map.ofEntries(Map.entry("Cel", SIUnits.CELSIUS),
108+
Map.entry("degree", Units.DEGREE_ANGLE), Map.entry("m/s", Units.METRE_PER_SECOND),
109+
Map.entry("m s**-1", Units.METRE_PER_SECOND), Map.entry("percent", Units.PERCENT),
110+
Map.entry("hPa", MetricPrefix.HECTO(SIUnits.PASCAL)), Map.entry("km", MetricPrefix.KILO(SIUnits.METRE)),
111+
Map.entry("fraction", Units.PERCENT), Map.entry("octas", Units.PERCENT), Map.entry("m", SIUnits.METRE),
112+
Map.entry("kg/m2", Units.MILLIMETRE_PER_HOUR), Map.entry("%", Units.PERCENT),
113+
Map.entry("mm", MetricPrefix.MILLI(SIUnits.METRE)));
114+
115+
public static final Map<Unit<?>, SemanticTag> SEMANTIC_PROPERTIES = Map.ofEntries(
116+
Map.entry(MetricPrefix.HECTO(SIUnits.PASCAL), DefaultSemanticTags.Property.PRESSURE),
117+
Map.entry(SIUnits.CELSIUS, DefaultSemanticTags.Property.TEMPERATURE),
118+
Map.entry(Units.DEGREE_ANGLE, DefaultSemanticTags.Property.WIND),
119+
Map.entry(Units.METRE_PER_SECOND, DefaultSemanticTags.Property.WIND),
120+
Map.entry(Units.MILLIMETRE_PER_HOUR, DefaultSemanticTags.Property.PRECIPITATION),
121+
Map.entry(MetricPrefix.MILLI(SIUnits.METRE), DefaultSemanticTags.Property.PRECIPITATION));
122+
123+
// TODO: Remove after last 2026 release
91124
public static final String PMP3G_PRESSURE = "msl";
92125
public static final String PMP3G_TEMPERATURE = "t";
93126
public static final String PMP3G_VISIBILITY = "vis";

bundles/org.openhab.binding.smhi/src/main/java/org/openhab/binding/smhi/internal/SmhiConnector.java

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@
1212
*/
1313
package org.openhab.binding.smhi.internal;
1414

15+
import static org.eclipse.jetty.http.HttpStatus.*;
1516
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
1617

1718
import java.time.ZonedDateTime;
1819
import java.time.format.DateTimeParseException;
20+
import java.util.List;
1921
import java.util.Locale;
2022
import java.util.concurrent.ExecutionException;
23+
import java.util.concurrent.TimeUnit;
2124
import java.util.concurrent.TimeoutException;
2225

2326
import org.eclipse.jdt.annotation.NonNullByDefault;
2427
import org.eclipse.jetty.client.HttpClient;
2528
import org.eclipse.jetty.client.api.ContentResponse;
2629
import org.eclipse.jetty.client.api.Request;
30+
import org.openhab.binding.smhi.provider.ParameterMetadata;
2731
import org.slf4j.Logger;
2832
import org.slf4j.LoggerFactory;
2933

@@ -54,16 +58,9 @@ public SmhiConnector(HttpClient httpClient) {
5458
*/
5559
public ZonedDateTime getCreatedTime() throws SmhiException {
5660
logger.debug("Fetching reference time");
57-
Request req = httpClient.newRequest(CREATED_TIME_URL);
58-
req.accept(ACCEPT);
59-
ContentResponse resp;
60-
try {
61-
resp = req.send();
62-
} catch (InterruptedException | TimeoutException | ExecutionException e) {
63-
throw new SmhiException(e);
64-
}
65-
logger.debug("Received response with status {} - {}", resp.getStatus(), resp.getReason());
66-
if (resp.getStatus() == 200) {
61+
ContentResponse resp = makeRequest(CREATED_TIME_URL);
62+
63+
if (resp.getStatus() == OK_200) {
6764
return Parser.parseCreatedTime(resp.getContentAsString());
6865
} else {
6966
throw new SmhiException(resp.getReason());
@@ -80,27 +77,51 @@ public ZonedDateTime getCreatedTime() throws SmhiException {
8077
public SmhiTimeSeries getForecast(double lat, double lon) throws SmhiException, PointOutOfBoundsException {
8178
logger.debug("Fetching new forecast");
8279
String url = String.format(Locale.ROOT, POINT_FORECAST_URL, lon, lat);
83-
Request req = httpClient.newRequest(url);
84-
req.accept(ACCEPT);
85-
ContentResponse resp;
86-
try {
87-
resp = req.send();
88-
} catch (InterruptedException | TimeoutException | ExecutionException e) {
89-
throw new SmhiException(e);
90-
}
91-
logger.debug("Received response with status {} - {}", resp.getStatus(), resp.getReason());
80+
ContentResponse resp = makeRequest(url);
81+
9282
switch (resp.getStatus()) {
93-
case 200:
83+
case OK_200:
9484
try {
9585
return Parser.parseTimeSeries(resp.getContentAsString());
9686
} catch (JsonParseException | DateTimeParseException e) {
9787
throw new SmhiException(e);
9888
}
99-
case 400:
100-
case 404:
89+
case BAD_REQUEST_400:
90+
case NOT_FOUND_404:
10191
throw new PointOutOfBoundsException();
10292
default:
10393
throw new SmhiException(resp.getReason());
10494
}
10595
}
96+
97+
public List<ParameterMetadata> getParameterMetadata() throws SmhiException {
98+
logger.debug("Fetching parameter metadata");
99+
ContentResponse resp = makeRequest(PARAMETER_METADATA_URL);
100+
101+
if (resp.getStatus() == OK_200) {
102+
try {
103+
return Parser.parseParameterMetadata(resp.getContentAsString());
104+
} catch (JsonParseException | DateTimeParseException e) {
105+
throw new SmhiException(e);
106+
}
107+
}
108+
throw new SmhiException(resp.getReason());
109+
}
110+
111+
private ContentResponse makeRequest(String url) throws SmhiException {
112+
Request req = httpClient.newRequest(url);
113+
req.timeout(5, TimeUnit.SECONDS);
114+
req.accept(ACCEPT);
115+
ContentResponse resp;
116+
try {
117+
resp = req.send();
118+
} catch (InterruptedException e) {
119+
Thread.currentThread().interrupt();
120+
throw new SmhiException(e);
121+
} catch (TimeoutException | ExecutionException e) {
122+
throw new SmhiException(e);
123+
}
124+
logger.debug("Received response with status {} - {}", resp.getStatus(), resp.getReason());
125+
return resp;
126+
}
106127
}

0 commit comments

Comments
 (0)