2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.openweathermap.internal.handler;
15 import static org.openhab.binding.openweathermap.internal.OpenWeatherMapBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
17 import static org.openhab.core.library.unit.SIUnits.*;
18 import static org.openhab.core.library.unit.Units.*;
19 import static org.openhab.core.types.TimeSeries.Policy.REPLACE;
21 import java.time.Instant;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapOneCallConfiguration;
30 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
31 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapOneCallAPIData;
32 import org.openhab.binding.openweathermap.internal.dto.forecast.daily.FeelsLikeTemp;
33 import org.openhab.binding.openweathermap.internal.dto.forecast.daily.Temp;
34 import org.openhab.binding.openweathermap.internal.dto.onecall.Alert;
35 import org.openhab.binding.openweathermap.internal.dto.onecall.Daily;
36 import org.openhab.binding.openweathermap.internal.dto.onecall.Hourly;
37 import org.openhab.binding.openweathermap.internal.dto.onecall.Precipitation;
38 import org.openhab.core.i18n.CommunicationException;
39 import org.openhab.core.i18n.ConfigurationException;
40 import org.openhab.core.i18n.TimeZoneProvider;
41 import org.openhab.core.library.types.QuantityType;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.binding.builder.ThingBuilder;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.TimeSeries;
50 import org.openhab.core.types.UnDefType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 import com.google.gson.JsonSyntaxException;
57 * The {@link OpenWeatherMapOneCallHandler} is responsible for handling commands, which are sent to one of
60 * @author Wolfgang Klimt - Initial contribution
61 * @author Christoph Weitkamp - Added weather alerts
62 * @author Florian Hotze - Added support for persisting forecasts
65 public class OpenWeatherMapOneCallHandler extends AbstractOpenWeatherMapHandler {
67 private final Logger logger = LoggerFactory.getLogger(OpenWeatherMapOneCallHandler.class);
69 private static final String CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX = "forecastMinutes";
70 private static final String CHANNEL_GROUP_MINUTELY_TIMESERIES_PREFIX = "forecastMinutely";
71 private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "forecastHours";
72 private static final String CHANNEL_GROUP_HOURLY_TIMESERIES_PREFIX = "forecastHourly";
73 private static final String CHANNEL_GROUP_DAILY_FORECAST_PREFIX = "forecastDay";
74 private static final String CHANNEL_GROUP_DAILY_TIMESERIES_PREFIX = "forecastDaily";
75 private static final String CHANNEL_GROUP_ALERTS_PREFIX = "alerts";
76 private static final Pattern CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN = Pattern
77 .compile(CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + "([0-9]*)");
78 private static final Pattern CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN = Pattern
79 .compile(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + "([0-9]*)");
80 private static final Pattern CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX_PATTERN = Pattern
81 .compile(CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + "([0-9]*)");
82 private static final Pattern CHANNEL_GROUP_ALERTS_PREFIX_PATTERN = Pattern
83 .compile(CHANNEL_GROUP_ALERTS_PREFIX + "([0-9]*)");
85 private @Nullable OpenWeatherMapOneCallAPIData weatherData;
87 // forecastMinutes, -Hours and -Days determine the number of channel groups to create for each type
88 private int forecastMinutes = 60;
89 private int forecastHours = 48;
90 private int forecastDays = 8;
91 private int numberOfAlerts = 0;
93 public OpenWeatherMapOneCallHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
94 super(thing, timeZoneProvider);
98 public void initialize() {
100 logger.debug("Initialize OpenWeatherMapOneCallHandler handler '{}'.", getThing().getUID());
101 OpenWeatherMapOneCallConfiguration config = getConfigAs(OpenWeatherMapOneCallConfiguration.class);
103 boolean configValid = true;
104 int newForecastMinutes = config.forecastMinutes;
105 if (newForecastMinutes < 0 || newForecastMinutes > 60) {
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
107 "@text/offline.conf-error-not-supported-onecall-number-of-minutes");
110 int newForecastHours = config.forecastHours;
111 if (newForecastHours < 0 || newForecastHours > 48) {
112 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
113 "@text/offline.conf-error-not-supported-onecall-number-of-hours");
116 int newForecastDays = config.forecastDays;
117 if (newForecastDays < 0 || newForecastDays > 8) {
118 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
119 "@text/offline.conf-error-not-supported-onecall-number-of-days");
122 int newNumberOfAlerts = config.numberOfAlerts;
123 if (newNumberOfAlerts < 0) {
124 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
125 "@text/offline.conf-error-not-supported-onecall-number-of-alerts");
130 logger.debug("Rebuilding thing '{}'.", getThing().getUID());
131 List<Channel> toBeAddedChannels = new ArrayList<>();
132 List<Channel> toBeRemovedChannels = new ArrayList<>();
134 .addAll(createChannelsForGroup(CHANNEL_GROUP_ONECALL_CURRENT, CHANNEL_GROUP_TYPE_ONECALL_CURRENT));
135 if (forecastMinutes != newForecastMinutes) {
136 logger.debug("forecastMinutes changed from {} to {}. Rebuilding minutely forecast channel groups.",
137 forecastMinutes, newForecastMinutes);
138 if (forecastMinutes > newForecastMinutes) {
139 for (int i = newForecastMinutes + 1; i <= forecastMinutes; i++) {
140 toBeRemovedChannels.addAll(removeChannelsOfGroup(
141 CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
144 for (int i = forecastMinutes + 1; i <= newForecastMinutes; i++) {
145 toBeAddedChannels.addAll(createChannelsForGroup(
146 CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
147 CHANNEL_GROUP_TYPE_ONECALL_MINUTELY_FORECAST));
150 forecastMinutes = newForecastMinutes;
152 if (forecastHours != newForecastHours) {
153 logger.debug("ForecastHours changed from {} to {}. Rebuilding hourly forecast channel groups.",
154 forecastHours, newForecastHours);
155 if (forecastHours > newForecastHours) {
156 for (int i = newForecastHours + 1; i <= forecastHours; i++) {
157 toBeRemovedChannels.addAll(removeChannelsOfGroup(
158 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
161 for (int i = forecastHours + 1; i <= newForecastHours; i++) {
162 toBeAddedChannels.addAll(createChannelsForGroup(
163 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
164 CHANNEL_GROUP_TYPE_ONECALL_HOURLY_FORECAST));
167 forecastHours = newForecastHours;
169 if (forecastDays != newForecastDays) {
170 logger.debug("ForecastDays changed from {} to {}. Rebuilding daily forecast channel groups.",
171 forecastDays, newForecastDays);
172 if (forecastDays > newForecastDays) {
173 if (newForecastDays < 1) {
174 toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TODAY));
176 if (newForecastDays < 2) {
177 toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TOMORROW));
179 for (int i = newForecastDays; i < forecastDays; ++i) {
180 toBeRemovedChannels.addAll(
181 removeChannelsOfGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i)));
184 if (forecastDays == 0 && newForecastDays > 0) {
185 toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TODAY,
186 CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
188 if (forecastDays <= 1 && newForecastDays > 1) {
189 toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TOMORROW,
190 CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
192 for (int i = Math.max(forecastDays, 2); i < newForecastDays; ++i) {
193 toBeAddedChannels.addAll(
194 createChannelsForGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i),
195 CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
198 forecastDays = newForecastDays;
199 if (numberOfAlerts != newNumberOfAlerts) {
200 logger.debug("Rebuilding alerts channel groups.");
201 if (numberOfAlerts > newNumberOfAlerts) {
202 for (int i = newNumberOfAlerts + 1; i <= numberOfAlerts; ++i) {
204 .addAll(removeChannelsOfGroup(CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i)));
207 for (int i = numberOfAlerts + 1; i <= newNumberOfAlerts; ++i) {
209 .addAll(createChannelsForGroup(CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i),
210 CHANNEL_GROUP_TYPE_ONECALL_ALERTS));
213 numberOfAlerts = newNumberOfAlerts;
216 logger.debug("toBeRemovedChannels: {}. toBeAddedChannels: {}", toBeRemovedChannels, toBeAddedChannels);
217 ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
218 for (Channel channel : toBeAddedChannels) {
219 builder.withChannel(channel);
221 updateThing(builder.build());
226 protected boolean requestData(OpenWeatherMapConnection connection)
227 throws CommunicationException, ConfigurationException {
228 logger.debug("Update weather and forecast data of thing '{}'.", getThing().getUID());
230 // Include minutely, hourly and daily data as this is needed for the time series channels
231 weatherData = connection.getOneCallAPIData(location, false, false, false, numberOfAlerts == 0);
233 } catch (JsonSyntaxException e) {
234 logger.debug("JsonSyntaxException occurred during execution: {}", e.getMessage(), e);
240 protected void updateChannel(ChannelUID channelUID) {
241 String channelGroupId = channelUID.getGroupId();
242 logger.debug("OneCallHandler: updateChannel {}, groupID {}", channelUID, channelGroupId);
243 switch (channelGroupId) {
244 case CHANNEL_GROUP_ONECALL_CURRENT:
245 updateCurrentChannel(channelUID);
247 case CHANNEL_GROUP_ONECALL_TODAY:
248 updateDailyForecastChannel(channelUID, 0);
250 case CHANNEL_GROUP_ONECALL_TOMORROW:
251 updateDailyForecastChannel(channelUID, 1);
253 case CHANNEL_GROUP_MINUTELY_TIMESERIES_PREFIX:
254 updateMinutelyForecastTimeSeries(channelUID);
256 case CHANNEL_GROUP_HOURLY_TIMESERIES_PREFIX:
257 updateHourlyForecastTimeSeries(channelUID);
259 case CHANNEL_GROUP_DAILY_TIMESERIES_PREFIX:
260 updateDailyForecastTimeSeries(channelUID);
264 Matcher hourlyForecastMatcher = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
265 if (hourlyForecastMatcher.find() && (i = Integer.parseInt(hourlyForecastMatcher.group(1))) >= 1
267 updateHourlyForecastChannel(channelUID, (i - 1));
270 Matcher dailyForecastMatcher = CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
271 if (dailyForecastMatcher.find() && (i = Integer.parseInt(dailyForecastMatcher.group(1))) >= 1
273 updateDailyForecastChannel(channelUID, i);
276 Matcher minutelyForecastMatcher = CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX_PATTERN
277 .matcher(channelGroupId);
278 if (minutelyForecastMatcher.find() && (i = Integer.parseInt(minutelyForecastMatcher.group(1))) >= 1
280 updateMinutelyForecastChannel(channelUID, i - 1);
283 Matcher alertsMatcher = CHANNEL_GROUP_ALERTS_PREFIX_PATTERN.matcher(channelGroupId);
284 if (alertsMatcher.find() && (i = Integer.parseInt(alertsMatcher.group(1))) >= 1) {
285 updateAlertsChannel(channelUID, i - 1);
293 * Update the channel from the last OpenWeatherMap data retrieved.
295 * @param channelUID the id identifying the channel to be updated
297 private void updateCurrentChannel(ChannelUID channelUID) {
298 String channelId = channelUID.getIdWithoutGroup();
299 String channelGroupId = channelUID.getGroupId();
300 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
301 if (localWeatherData != null) {
302 State state = UnDefType.UNDEF;
304 case CHANNEL_STATION_LOCATION:
305 state = getPointTypeState(localWeatherData.getLat(), localWeatherData.getLon());
307 case CHANNEL_TIME_STAMP:
308 state = getDateTimeTypeState(localWeatherData.getCurrent().getDt());
310 case CHANNEL_SUNRISE:
311 state = getDateTimeTypeState(localWeatherData.getCurrent().getSunrise());
314 state = getDateTimeTypeState(localWeatherData.getCurrent().getSunset());
316 case CHANNEL_CONDITION:
317 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
318 state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getDescription());
321 case CHANNEL_CONDITION_ID:
322 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
323 state = getStringTypeState(
324 Integer.toString(localWeatherData.getCurrent().getWeather().get(0).getId()));
327 case CHANNEL_CONDITION_ICON:
328 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
329 state = getRawTypeState(OpenWeatherMapConnection
330 .getWeatherIcon(localWeatherData.getCurrent().getWeather().get(0).getIcon()));
333 case CHANNEL_CONDITION_ICON_ID:
334 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
335 state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getIcon());
338 case CHANNEL_TEMPERATURE:
339 state = getQuantityTypeState(localWeatherData.getCurrent().getTemp(), CELSIUS);
341 case CHANNEL_APPARENT_TEMPERATURE:
342 state = getQuantityTypeState(localWeatherData.getCurrent().getFeelsLike(), CELSIUS);
344 case CHANNEL_PRESSURE:
345 state = getQuantityTypeState(localWeatherData.getCurrent().getPressure(), HECTO(PASCAL));
347 case CHANNEL_HUMIDITY:
348 state = getQuantityTypeState(localWeatherData.getCurrent().getHumidity(), PERCENT);
350 case CHANNEL_DEW_POINT:
351 state = getQuantityTypeState(localWeatherData.getCurrent().getDewPoint(), CELSIUS);
353 case CHANNEL_WIND_SPEED:
354 state = getQuantityTypeState(localWeatherData.getCurrent().getWindSpeed(), METRE_PER_SECOND);
356 case CHANNEL_WIND_DIRECTION:
357 state = getQuantityTypeState(localWeatherData.getCurrent().getWindDeg(), DEGREE_ANGLE);
359 case CHANNEL_GUST_SPEED:
360 state = getQuantityTypeState(localWeatherData.getCurrent().getWindGust(), METRE_PER_SECOND);
362 case CHANNEL_CLOUDINESS:
363 state = getQuantityTypeState(localWeatherData.getCurrent().getClouds(), PERCENT);
365 case CHANNEL_UVINDEX:
366 state = getDecimalTypeState(localWeatherData.getCurrent().getUvi());
369 Precipitation rain = localWeatherData.getCurrent().getRain();
370 state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
373 Precipitation snow = localWeatherData.getCurrent().getSnow();
374 state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
376 case CHANNEL_VISIBILITY:
377 State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
378 .toUnit(KILO(METRE));
379 state = (tempstate == null ? state : tempstate);
382 // This should not happen
383 logger.warn("Unknown channel id {} in onecall current weather data", channelId);
386 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
387 updateState(channelUID, state);
389 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
394 * Update the channel from the last OpenWeatherMap data retrieved.
396 * @param channelUID the id identifying the channel to be updated
397 * @param count the index of the minutely data referenced by the channel (minute 1 is count 0)
399 private void updateMinutelyForecastChannel(ChannelUID channelUID, int count) {
400 String channelId = channelUID.getIdWithoutGroup();
401 String channelGroupId = channelUID.getGroupId();
402 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
403 if (forecastMinutes == 0) {
405 "Can't update channel group {} because forecastMinutes is set to '0'. Please adjust config accordingly",
409 if (localWeatherData != null && localWeatherData.getMinutely() != null
410 && localWeatherData.getMinutely().size() > count) {
411 org.openhab.binding.openweathermap.internal.dto.onecall.Minutely forecastData = localWeatherData
412 .getMinutely().get(count);
413 State state = UnDefType.UNDEF;
415 case CHANNEL_TIME_STAMP:
416 state = getDateTimeTypeState(forecastData.getDt());
418 case CHANNEL_PRECIPITATION:
419 double precipitation = forecastData.getPrecipitation();
420 state = getQuantityTypeState(precipitation, MILLI(METRE));
423 // This should not happen
424 logger.warn("Unknown channel id {} in onecall minutely weather data", channelId);
427 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
428 updateState(channelUID, state);
430 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
434 private void updateMinutelyForecastTimeSeries(ChannelUID channelUID) {
435 String channelId = channelUID.getIdWithoutGroup();
436 String channelGroupId = channelUID.getGroupId();
437 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
438 if (channelId.equals(CHANNEL_TIME_STAMP)) {
439 logger.debug("Channel `{}` of group '{}' is no supported time-series channel.", channelId, channelGroupId);
442 if (localWeatherData != null && !localWeatherData.getMinutely().isEmpty()) {
443 List<org.openhab.binding.openweathermap.internal.dto.onecall.Minutely> forecastData = localWeatherData
445 TimeSeries timeSeries = new TimeSeries(REPLACE);
446 forecastData.forEach((m) -> {
447 if (channelId.equals(CHANNEL_PRECIPITATION)) {
448 State state = UnDefType.UNDEF;
449 Instant timestamp = Instant.ofEpochSecond(m.getDt());
450 double precipitation = m.getPrecipitation();
451 state = getQuantityTypeState(precipitation, MILLI(METRE));
452 timeSeries.add(timestamp, state);
454 // This should not happen
455 logger.warn("Unknown channel id {} in onecall minutely weather data", channelId);
459 logger.debug("Update channel '{}' of group '{}' with new time-series '{}'.", channelId, channelGroupId,
461 sendTimeSeries(channelUID, timeSeries);
463 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
468 * Update the hourly forecast channel from the last OpenWeatherMap data retrieved.
470 * @param channelUID the id identifying the channel to be updated
471 * @param count the index of the hourly data referenced by the channel (hour 1 is count 0)
473 private void updateHourlyForecastChannel(ChannelUID channelUID, int count) {
474 String channelId = channelUID.getIdWithoutGroup();
475 String channelGroupId = channelUID.getGroupId();
476 if (forecastHours == 0) {
478 "Can't update channel group {} because forecastHours is set to '0'. Please adjust config accordingly",
482 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
483 if (localWeatherData != null && localWeatherData.getHourly().size() > count) {
484 org.openhab.binding.openweathermap.internal.dto.onecall.Hourly forecastData = localWeatherData.getHourly()
486 State state = getHourlyForecastState(channelId, forecastData, localWeatherData);
487 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
488 updateState(channelUID, state);
490 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
495 * Update the hourly forecast time series channel from the last OpenWeatherMap data retrieved.
497 * @param channelUID the id identifying the channel to be updated
499 private void updateHourlyForecastTimeSeries(ChannelUID channelUID) {
500 String channelId = channelUID.getIdWithoutGroup();
501 String channelGroupId = channelUID.getGroupId();
502 if (channelId.equals(CHANNEL_TIME_STAMP)) {
503 logger.debug("Channel `{}` of group '{}' is no supported time-series channel.", channelId, channelGroupId);
506 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
507 if (localWeatherData != null && !localWeatherData.getHourly().isEmpty()) {
508 List<org.openhab.binding.openweathermap.internal.dto.onecall.Hourly> forecastData = localWeatherData
510 TimeSeries timeSeries = new TimeSeries(REPLACE);
511 forecastData.forEach((h) -> {
512 Instant timestamp = Instant.ofEpochSecond(h.getDt());
513 State state = getHourlyForecastState(channelId, h, localWeatherData);
514 timeSeries.add(timestamp, state);
516 logger.debug("Update channel '{}' of group '{}' with new time-series '{}'.", channelId, channelGroupId,
518 sendTimeSeries(channelUID, timeSeries);
520 logger.debug("No weather data available to update channel '{}'.", channelId);
524 private State getHourlyForecastState(String channelId, Hourly forecastData,
525 OpenWeatherMapOneCallAPIData localWeatherData) {
526 State state = UnDefType.UNDEF;
528 case CHANNEL_TIME_STAMP:
529 state = getDateTimeTypeState(forecastData.getDt());
531 case CHANNEL_CONDITION:
532 if (!forecastData.getWeather().isEmpty()) {
533 state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
536 case CHANNEL_CONDITION_ID:
537 if (!forecastData.getWeather().isEmpty()) {
538 state = getStringTypeState(Integer.toString(forecastData.getWeather().get(0).getId()));
541 case CHANNEL_CONDITION_ICON:
542 if (!forecastData.getWeather().isEmpty()) {
543 state = getRawTypeState(
544 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
547 case CHANNEL_CONDITION_ICON_ID:
548 if (!forecastData.getWeather().isEmpty()) {
549 state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
552 case CHANNEL_TEMPERATURE:
553 state = getQuantityTypeState(forecastData.getTemp(), CELSIUS);
555 case CHANNEL_APPARENT_TEMPERATURE:
556 state = getQuantityTypeState(forecastData.getFeelsLike(), CELSIUS);
558 case CHANNEL_PRESSURE:
559 state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
561 case CHANNEL_HUMIDITY:
562 state = getQuantityTypeState(forecastData.getHumidity(), PERCENT);
564 case CHANNEL_DEW_POINT:
565 state = getQuantityTypeState(forecastData.getDewPoint(), CELSIUS);
567 case CHANNEL_WIND_SPEED:
568 state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
570 case CHANNEL_WIND_DIRECTION:
571 state = getQuantityTypeState(forecastData.getWindDeg(), DEGREE_ANGLE);
573 case CHANNEL_GUST_SPEED:
574 state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
576 case CHANNEL_CLOUDINESS:
577 state = getQuantityTypeState(forecastData.getClouds(), PERCENT);
579 case CHANNEL_VISIBILITY:
580 State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
581 .toUnit(KILO(METRE));
582 state = (tempstate == null ? state : tempstate);
583 case CHANNEL_PRECIP_PROBABILITY:
584 state = getQuantityTypeState(forecastData.getPop() * 100.0, PERCENT);
587 Precipitation rain = forecastData.getRain();
588 state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
591 Precipitation snow = forecastData.getSnow();
592 state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
595 // This should not happen
596 logger.warn("Unknown channel id {} in OneCall hourly weather data", channelId);
603 * Update the daily forecast channel from the last OpenWeatherMap data retrieved.
605 * @param channelUID the id identifying the channel to be updated
606 * @param count the index of the daily data referenced by the channel (today is count 0)
608 private void updateDailyForecastChannel(ChannelUID channelUID, int count) {
609 String channelId = channelUID.getIdWithoutGroup();
610 String channelGroupId = channelUID.getGroupId();
611 if (forecastDays == 0) {
613 "Can't update channel group {} because forecastDays is set to '0'. Please adjust config accordingly",
617 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
618 if (localWeatherData != null && localWeatherData.getDaily().size() > count) {
619 org.openhab.binding.openweathermap.internal.dto.onecall.Daily forecastData = localWeatherData.getDaily()
621 State state = getDailyForecastState(channelId, forecastData, localWeatherData);
622 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
623 updateState(channelUID, state);
625 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
629 private void updateDailyForecastTimeSeries(ChannelUID channelUID) {
630 String channelId = channelUID.getIdWithoutGroup();
631 String channelGroupId = channelUID.getGroupId();
632 if (channelId.equals(CHANNEL_TIME_STAMP)) {
633 logger.debug("Channel `{}` of group '{}' is no supported time-series channel.", channelId, channelGroupId);
636 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
637 if (localWeatherData != null && !localWeatherData.getDaily().isEmpty()) {
638 List<org.openhab.binding.openweathermap.internal.dto.onecall.Daily> forecastData = localWeatherData
640 TimeSeries timeSeries = new TimeSeries(REPLACE);
641 forecastData.forEach((d) -> {
642 Instant timestamp = Instant.ofEpochSecond(d.getDt());
643 State state = getDailyForecastState(channelId, d, localWeatherData);
644 timeSeries.add(timestamp, state);
646 logger.debug("Update channel '{}' of group '{}' with new time-series '{}'.", channelId, channelGroupId,
648 sendTimeSeries(channelUID, timeSeries);
650 logger.debug("No weather data available to update channel '{}'.", channelId);
654 private State getDailyForecastState(String channelId, Daily forecastData,
655 OpenWeatherMapOneCallAPIData localWeatherData) {
656 State state = UnDefType.UNDEF;
657 FeelsLikeTemp feelsLike;
660 case CHANNEL_TIME_STAMP:
661 state = getDateTimeTypeState(forecastData.getDt());
663 case CHANNEL_SUNRISE:
664 state = getDateTimeTypeState(forecastData.getSunrise());
667 state = getDateTimeTypeState(forecastData.getSunset());
669 case CHANNEL_MOONRISE:
670 state = getDateTimeTypeState(forecastData.getMoonrise());
672 case CHANNEL_MOONSET:
673 state = getDateTimeTypeState(forecastData.getMoonset());
675 case CHANNEL_MOON_PHASE:
676 state = getDecimalTypeState(forecastData.getMoonPhase());
678 case CHANNEL_CONDITION:
679 if (!forecastData.getWeather().isEmpty()) {
680 state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
683 case CHANNEL_CONDITION_ID:
684 if (!forecastData.getWeather().isEmpty()) {
685 state = getStringTypeState(Integer.toString(forecastData.getWeather().get(0).getId()));
688 case CHANNEL_CONDITION_ICON:
689 if (!forecastData.getWeather().isEmpty()) {
690 state = getRawTypeState(
691 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
694 case CHANNEL_CONDITION_ICON_ID:
695 if (!forecastData.getWeather().isEmpty()) {
696 state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
699 case CHANNEL_MIN_TEMPERATURE:
700 temp = forecastData.getTemp();
702 state = getQuantityTypeState(temp.getMin(), CELSIUS);
705 case CHANNEL_MAX_TEMPERATURE:
706 temp = forecastData.getTemp();
708 state = getQuantityTypeState(temp.getMax(), CELSIUS);
711 case CHANNEL_MORNING_TEMPERATURE:
712 temp = forecastData.getTemp();
714 state = getQuantityTypeState(temp.getMorn(), CELSIUS);
717 case CHANNEL_DAY_TEMPERATURE:
718 temp = forecastData.getTemp();
720 state = getQuantityTypeState(temp.getDay(), CELSIUS);
723 case CHANNEL_EVENING_TEMPERATURE:
724 temp = forecastData.getTemp();
726 state = getQuantityTypeState(temp.getEve(), CELSIUS);
729 case CHANNEL_NIGHT_TEMPERATURE:
730 temp = forecastData.getTemp();
732 state = getQuantityTypeState(temp.getNight(), CELSIUS);
736 case CHANNEL_APPARENT_DAY:
737 feelsLike = forecastData.getFeelsLike();
738 if (feelsLike != null) {
739 state = getQuantityTypeState(feelsLike.getDay(), CELSIUS);
742 case CHANNEL_APPARENT_MORNING:
743 feelsLike = forecastData.getFeelsLike();
744 if (feelsLike != null) {
745 state = getQuantityTypeState(feelsLike.getMorn(), CELSIUS);
748 case CHANNEL_APPARENT_EVENING:
749 feelsLike = forecastData.getFeelsLike();
750 if (feelsLike != null) {
751 state = getQuantityTypeState(feelsLike.getEve(), CELSIUS);
754 case CHANNEL_APPARENT_NIGHT:
755 feelsLike = forecastData.getFeelsLike();
756 if (feelsLike != null) {
757 state = getQuantityTypeState(feelsLike.getNight(), CELSIUS);
760 case CHANNEL_PRESSURE:
761 state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
763 case CHANNEL_HUMIDITY:
764 state = getQuantityTypeState(forecastData.getHumidity(), PERCENT);
766 case CHANNEL_WIND_SPEED:
767 state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
769 case CHANNEL_WIND_DIRECTION:
770 state = getQuantityTypeState(forecastData.getWindDeg(), DEGREE_ANGLE);
772 case CHANNEL_GUST_SPEED:
773 state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
775 case CHANNEL_CLOUDINESS:
776 state = getQuantityTypeState(forecastData.getClouds(), PERCENT);
778 case CHANNEL_DEW_POINT:
779 state = getQuantityTypeState(forecastData.getDewPoint(), CELSIUS);
781 case CHANNEL_UVINDEX:
782 state = getDecimalTypeState(forecastData.getUvi());
784 case CHANNEL_VISIBILITY:
785 State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
786 .toUnit(KILO(METRE));
787 state = (tempstate == null ? state : tempstate);
788 case CHANNEL_PRECIP_PROBABILITY:
789 state = getQuantityTypeState(forecastData.getPop() * 100.0, PERCENT);
792 state = getQuantityTypeState(forecastData.getRain(), MILLI(METRE));
795 state = getQuantityTypeState(forecastData.getSnow(), MILLI(METRE));
798 // This should not happen
799 logger.warn("Unknown channel id {} in OneCall daily weather data", channelId);
806 * Update the channel from the last OpenWeaterhMap data retrieved.
808 * @param channelUID the id identifying the channel to be updated
809 * @param count the index of the alert data referenced by the channel (alert 1 is count 0)
811 private void updateAlertsChannel(ChannelUID channelUID, int count) {
812 String channelId = channelUID.getIdWithoutGroup();
813 String channelGroupId = channelUID.getGroupId();
814 OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
815 List<Alert> alerts = localWeatherData != null ? localWeatherData.alerts : null;
816 State state = UnDefType.UNDEF;
817 if (alerts != null && alerts.size() > count) {
818 Alert alert = alerts.get(count);
820 case CHANNEL_ALERT_EVENT:
821 state = getStringTypeState(alert.getEvent());
823 case CHANNEL_ALERT_DESCRIPTION:
824 state = getStringTypeState(alert.getDescription());
826 case CHANNEL_ALERT_ONSET:
827 state = getDateTimeTypeState(alert.getStart());
829 case CHANNEL_ALERT_EXPIRES:
830 state = getDateTimeTypeState(alert.getEnd());
832 case CHANNEL_ALERT_SOURCE:
833 state = getStringTypeState(alert.getSenderName());
836 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
838 logger.debug("No data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
840 updateState(channelUID, state);