]> git.basschouten.com Git - openhab-addons.git/blob
6049aaf71ec532da19b6d2c43cfc88f40e828347
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.openweathermap.internal.handler;
14
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
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapOneCallConfiguration;
28 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
29 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapOneCallAPIData;
30 import org.openhab.binding.openweathermap.internal.dto.forecast.daily.FeelsLikeTemp;
31 import org.openhab.binding.openweathermap.internal.dto.forecast.daily.Temp;
32 import org.openhab.binding.openweathermap.internal.dto.onecall.Alert;
33 import org.openhab.binding.openweathermap.internal.dto.onecall.Precipitation;
34 import org.openhab.core.i18n.CommunicationException;
35 import org.openhab.core.i18n.ConfigurationException;
36 import org.openhab.core.i18n.TimeZoneProvider;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.binding.builder.ThingBuilder;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.google.gson.JsonSyntaxException;
50
51 /**
52  * The {@link OpenWeatherMapOneCallHandler} is responsible for handling commands, which are sent to one of
53  * the channels.
54  *
55  * @author Wolfgang Klimt - Initial contribution
56  * @author Christoph Weitkamp - Added weather alerts
57  */
58 @NonNullByDefault
59 public class OpenWeatherMapOneCallHandler extends AbstractOpenWeatherMapHandler {
60
61     private final Logger logger = LoggerFactory.getLogger(OpenWeatherMapOneCallHandler.class);
62
63     private static final String CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX = "forecastMinutes";
64     private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "forecastHours";
65     private static final String CHANNEL_GROUP_DAILY_FORECAST_PREFIX = "forecastDay";
66     private static final String CHANNEL_GROUP_ALERTS_PREFIX = "alerts";
67     private static final Pattern CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN = Pattern
68             .compile(CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + "([0-9]*)");
69     private static final Pattern CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN = Pattern
70             .compile(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + "([0-9]*)");
71     private static final Pattern CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX_PATTERN = Pattern
72             .compile(CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + "([0-9]*)");
73     private static final Pattern CHANNEL_GROUP_ALERTS_PREFIX_PATTERN = Pattern
74             .compile(CHANNEL_GROUP_ALERTS_PREFIX + "([0-9]*)");
75
76     private @Nullable OpenWeatherMapOneCallAPIData weatherData;
77
78     private int forecastMinutes = 60;
79     private int forecastHours = 24;
80     private int forecastDays = 8;
81     private int numberOfAlerts = 0;
82
83     public OpenWeatherMapOneCallHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
84         super(thing, timeZoneProvider);
85     }
86
87     @Override
88     public void initialize() {
89         super.initialize();
90         logger.debug("Initialize OpenWeatherMapOneCallHandler handler '{}'.", getThing().getUID());
91         OpenWeatherMapOneCallConfiguration config = getConfigAs(OpenWeatherMapOneCallConfiguration.class);
92
93         boolean configValid = true;
94         int newForecastMinutes = config.forecastMinutes;
95         if (newForecastMinutes < 0 || newForecastMinutes > 60) {
96             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
97                     "@text/offline.conf-error-not-supported-onecall-number-of-minutes");
98             configValid = false;
99         }
100         int newForecastHours = config.forecastHours;
101         if (newForecastHours < 0 || newForecastHours > 48) {
102             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
103                     "@text/offline.conf-error-not-supported-onecall-number-of-hours");
104             configValid = false;
105         }
106         int newForecastDays = config.forecastDays;
107         if (newForecastDays < 0 || newForecastDays > 8) {
108             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
109                     "@text/offline.conf-error-not-supported-onecall-number-of-days");
110             configValid = false;
111         }
112         int newNumberOfAlerts = config.numberOfAlerts;
113         if (newNumberOfAlerts < 0) {
114             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
115                     "@text/offline.conf-error-not-supported-onecall-number-of-alerts");
116             configValid = false;
117         }
118
119         if (configValid) {
120             logger.debug("Rebuilding thing '{}'.", getThing().getUID());
121             List<Channel> toBeAddedChannels = new ArrayList<>();
122             List<Channel> toBeRemovedChannels = new ArrayList<>();
123             toBeAddedChannels
124                     .addAll(createChannelsForGroup(CHANNEL_GROUP_ONECALL_CURRENT, CHANNEL_GROUP_TYPE_ONECALL_CURRENT));
125             if (forecastMinutes != newForecastMinutes) {
126                 logger.debug("forecastMinutes changed from {} to {}. Rebuilding minutely forecast channel groups.",
127                         forecastMinutes, newForecastMinutes);
128                 if (forecastMinutes > newForecastMinutes) {
129                     for (int i = newForecastMinutes + 1; i <= forecastMinutes; i++) {
130                         toBeRemovedChannels.addAll(removeChannelsOfGroup(
131                                 CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
132                     }
133                 } else {
134                     for (int i = forecastMinutes + 1; i <= newForecastMinutes; i++) {
135                         toBeAddedChannels.addAll(createChannelsForGroup(
136                                 CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
137                                 CHANNEL_GROUP_TYPE_ONECALL_MINUTELY_FORECAST));
138                     }
139                 }
140                 forecastMinutes = newForecastMinutes;
141             }
142             if (forecastHours != newForecastHours) {
143                 logger.debug("ForecastHours changed from {} to {}. Rebuilding hourly forecast channel groups.",
144                         forecastHours, newForecastHours);
145                 if (forecastHours > newForecastHours) {
146                     for (int i = newForecastHours + 1; i <= forecastHours; i++) {
147                         toBeRemovedChannels.addAll(removeChannelsOfGroup(
148                                 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
149                     }
150                 } else {
151                     for (int i = forecastHours + 1; i <= newForecastHours; i++) {
152                         toBeAddedChannels.addAll(createChannelsForGroup(
153                                 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
154                                 CHANNEL_GROUP_TYPE_ONECALL_HOURLY_FORECAST));
155                     }
156                 }
157                 forecastHours = newForecastHours;
158             }
159             if (forecastDays != newForecastDays) {
160                 logger.debug("ForecastDays changed from {} to {}. Rebuilding daily forecast channel groups.",
161                         forecastDays, newForecastDays);
162                 if (forecastDays > newForecastDays) {
163                     if (newForecastDays < 1) {
164                         toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TODAY));
165                     }
166                     if (newForecastDays < 2) {
167                         toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TOMORROW));
168                     }
169                     for (int i = newForecastDays; i < forecastDays; ++i) {
170                         toBeRemovedChannels.addAll(
171                                 removeChannelsOfGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i)));
172                     }
173                 } else {
174                     if (forecastDays == 0 && newForecastDays > 0) {
175                         toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TODAY,
176                                 CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
177                     }
178                     if (forecastDays <= 1 && newForecastDays > 1) {
179                         toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TOMORROW,
180                                 CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
181                     }
182                     for (int i = Math.max(forecastDays, 2); i < newForecastDays; ++i) {
183                         toBeAddedChannels.addAll(
184                                 createChannelsForGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i),
185                                         CHANNEL_GROUP_TYPE_ONECALL_DAILY_FORECAST));
186                     }
187                 }
188                 forecastDays = newForecastDays;
189                 if (numberOfAlerts != newNumberOfAlerts) {
190                     logger.debug("Rebuilding alerts channel groups.");
191                     if (numberOfAlerts > newNumberOfAlerts) {
192                         for (int i = newNumberOfAlerts + 1; i <= numberOfAlerts; ++i) {
193                             toBeRemovedChannels
194                                     .addAll(removeChannelsOfGroup(CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i)));
195                         }
196                     } else {
197                         for (int i = numberOfAlerts + 1; i <= newNumberOfAlerts; ++i) {
198                             toBeAddedChannels
199                                     .addAll(createChannelsForGroup(CHANNEL_GROUP_ALERTS_PREFIX + Integer.toString(i),
200                                             CHANNEL_GROUP_TYPE_ONECALL_ALERTS));
201                         }
202                     }
203                     numberOfAlerts = newNumberOfAlerts;
204                 }
205             }
206             logger.debug("toBeRemovedChannels: {}. toBeAddedChannels: {}", toBeRemovedChannels, toBeAddedChannels);
207             ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
208             for (Channel channel : toBeAddedChannels) {
209                 builder.withChannel(channel);
210             }
211             updateThing(builder.build());
212         }
213     }
214
215     @Override
216     protected boolean requestData(OpenWeatherMapConnection connection)
217             throws CommunicationException, ConfigurationException {
218         logger.debug("Update weather and forecast data of thing '{}'.", getThing().getUID());
219         try {
220             weatherData = connection.getOneCallAPIData(location, forecastMinutes == 0, forecastHours == 0,
221                     forecastDays == 0, numberOfAlerts == 0);
222             return true;
223         } catch (JsonSyntaxException e) {
224             logger.debug("JsonSyntaxException occurred during execution: {}", e.getMessage(), e);
225             return false;
226         }
227     }
228
229     @Override
230     protected void updateChannel(ChannelUID channelUID) {
231         String channelGroupId = channelUID.getGroupId();
232         logger.debug("OneCallHandler: updateChannel {}, groupID {}", channelUID, channelGroupId);
233         switch (channelGroupId) {
234             case CHANNEL_GROUP_ONECALL_CURRENT:
235                 updateCurrentChannel(channelUID);
236                 break;
237             case CHANNEL_GROUP_ONECALL_TODAY:
238                 updateDailyForecastChannel(channelUID, 0);
239                 break;
240             case CHANNEL_GROUP_ONECALL_TOMORROW:
241                 updateDailyForecastChannel(channelUID, 1);
242                 break;
243             default:
244                 int i;
245                 Matcher hourlyForecastMatcher = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
246                 if (hourlyForecastMatcher.find() && (i = Integer.parseInt(hourlyForecastMatcher.group(1))) >= 1
247                         && i <= 48) {
248                     updateHourlyForecastChannel(channelUID, (i - 1));
249                     break;
250                 }
251                 Matcher dailyForecastMatcher = CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
252                 if (dailyForecastMatcher.find() && (i = Integer.parseInt(dailyForecastMatcher.group(1))) >= 1
253                         && i <= 7) {
254                     updateDailyForecastChannel(channelUID, i);
255                     break;
256                 }
257                 Matcher minutelyForecastMatcher = CHANNEL_GROUP_MINUTELY_FORECAST_PREFIX_PATTERN
258                         .matcher(channelGroupId);
259                 if (minutelyForecastMatcher.find() && (i = Integer.parseInt(minutelyForecastMatcher.group(1))) >= 1
260                         && i <= 60) {
261                     updateMinutelyForecastChannel(channelUID, i - 1);
262                     break;
263                 }
264                 Matcher alertsMatcher = CHANNEL_GROUP_ALERTS_PREFIX_PATTERN.matcher(channelGroupId);
265                 if (alertsMatcher.find() && (i = Integer.parseInt(alertsMatcher.group(1))) >= 1) {
266                     updateAlertsChannel(channelUID, i - 1);
267                     break;
268                 }
269                 break;
270         }
271     }
272
273     /**
274      * Update the channel from the last OpenWeatherMap data retrieved.
275      *
276      * @param channelUID the id identifying the channel to be updated
277      */
278     private void updateCurrentChannel(ChannelUID channelUID) {
279         String channelId = channelUID.getIdWithoutGroup();
280         String channelGroupId = channelUID.getGroupId();
281         OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
282         if (localWeatherData != null) {
283             State state = UnDefType.UNDEF;
284             switch (channelId) {
285                 case CHANNEL_STATION_LOCATION:
286                     state = getPointTypeState(localWeatherData.getLat(), localWeatherData.getLon());
287                     break;
288                 case CHANNEL_TIME_STAMP:
289                     state = getDateTimeTypeState(localWeatherData.getCurrent().getDt());
290                     break;
291                 case CHANNEL_SUNRISE:
292                     state = getDateTimeTypeState(localWeatherData.getCurrent().getSunrise());
293                     break;
294                 case CHANNEL_SUNSET:
295                     state = getDateTimeTypeState(localWeatherData.getCurrent().getSunset());
296                     break;
297                 case CHANNEL_CONDITION:
298                     if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
299                         state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getDescription());
300                     }
301                     break;
302                 case CHANNEL_CONDITION_ID:
303                     if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
304                         state = getStringTypeState(
305                                 Integer.toString(localWeatherData.getCurrent().getWeather().get(0).getId()));
306                     }
307                     break;
308                 case CHANNEL_CONDITION_ICON:
309                     if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
310                         state = getRawTypeState(OpenWeatherMapConnection
311                                 .getWeatherIcon(localWeatherData.getCurrent().getWeather().get(0).getIcon()));
312                     }
313                     break;
314                 case CHANNEL_CONDITION_ICON_ID:
315                     if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
316                         state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getIcon());
317                     }
318                     break;
319                 case CHANNEL_TEMPERATURE:
320                     state = getQuantityTypeState(localWeatherData.getCurrent().getTemp(), CELSIUS);
321                     break;
322                 case CHANNEL_APPARENT_TEMPERATURE:
323                     state = getQuantityTypeState(localWeatherData.getCurrent().getFeelsLike(), CELSIUS);
324                     break;
325                 case CHANNEL_PRESSURE:
326                     state = getQuantityTypeState(localWeatherData.getCurrent().getPressure(), HECTO(PASCAL));
327                     break;
328                 case CHANNEL_HUMIDITY:
329                     state = getQuantityTypeState(localWeatherData.getCurrent().getHumidity(), PERCENT);
330                     break;
331                 case CHANNEL_DEW_POINT:
332                     state = getQuantityTypeState(localWeatherData.getCurrent().getDewPoint(), CELSIUS);
333                     break;
334                 case CHANNEL_WIND_SPEED:
335                     state = getQuantityTypeState(localWeatherData.getCurrent().getWindSpeed(), METRE_PER_SECOND);
336                     break;
337                 case CHANNEL_WIND_DIRECTION:
338                     state = getQuantityTypeState(localWeatherData.getCurrent().getWindDeg(), DEGREE_ANGLE);
339                     break;
340                 case CHANNEL_GUST_SPEED:
341                     state = getQuantityTypeState(localWeatherData.getCurrent().getWindGust(), METRE_PER_SECOND);
342                     break;
343                 case CHANNEL_CLOUDINESS:
344                     state = getQuantityTypeState(localWeatherData.getCurrent().getClouds(), PERCENT);
345                     break;
346                 case CHANNEL_UVINDEX:
347                     state = getDecimalTypeState(localWeatherData.getCurrent().getUvi());
348                     break;
349                 case CHANNEL_RAIN:
350                     Precipitation rain = localWeatherData.getCurrent().getRain();
351                     state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
352                     break;
353                 case CHANNEL_SNOW:
354                     Precipitation snow = localWeatherData.getCurrent().getSnow();
355                     state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
356                     break;
357                 case CHANNEL_VISIBILITY:
358                     State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
359                             .toUnit(KILO(METRE));
360                     state = (tempstate == null ? state : tempstate);
361                     break;
362                 default:
363                     // This should not happen
364                     logger.warn("Unknown channel id {} in onecall current weather data", channelId);
365                     break;
366             }
367             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
368             updateState(channelUID, state);
369         } else {
370             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
371         }
372     }
373
374     /**
375      * Update the channel from the last OpenWeatherMap data retrieved.
376      *
377      * @param channelUID the id identifying the channel to be updated
378      * @param count the index of the minutely data referenced by the channel (minute 1 is count 0)
379      */
380     private void updateMinutelyForecastChannel(ChannelUID channelUID, int count) {
381         String channelId = channelUID.getIdWithoutGroup();
382         String channelGroupId = channelUID.getGroupId();
383         OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
384         if (forecastMinutes == 0) {
385             logger.warn(
386                     "Can't update channel group {} because forecastMinutes is set to '0'. Please adjust config accordingly",
387                     channelGroupId);
388             return;
389         }
390         if (localWeatherData != null && localWeatherData.getMinutely() != null
391                 && localWeatherData.getMinutely().size() > count) {
392             org.openhab.binding.openweathermap.internal.dto.onecall.Minutely forecastData = localWeatherData
393                     .getMinutely().get(count);
394             State state = UnDefType.UNDEF;
395             switch (channelId) {
396                 case CHANNEL_TIME_STAMP:
397                     state = getDateTimeTypeState(forecastData.getDt());
398                     break;
399                 case CHANNEL_PRECIPITATION:
400                     double precipitation = forecastData.getPrecipitation();
401                     state = getQuantityTypeState(precipitation, MILLI(METRE));
402                     break;
403                 default:
404                     // This should not happen
405                     logger.warn("Unknown channel id {} in onecall minutely weather data", channelId);
406                     break;
407             }
408             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
409             updateState(channelUID, state);
410         } else {
411             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
412         }
413     }
414
415     /**
416      * Update the channel from the last OpenWeatherMap data retrieved.
417      *
418      * @param channelUID the id identifying the channel to be updated
419      * @param count the index of the hourly data referenced by the channel (hour 1 is count 0)
420      */
421     private void updateHourlyForecastChannel(ChannelUID channelUID, int count) {
422         String channelId = channelUID.getIdWithoutGroup();
423         String channelGroupId = channelUID.getGroupId();
424         if (forecastHours == 0) {
425             logger.warn(
426                     "Can't update channel group {} because forecastHours is set to '0'. Please adjust config accordingly",
427                     channelGroupId);
428             return;
429         }
430         OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
431         if (localWeatherData != null && localWeatherData.getHourly().size() > count) {
432             org.openhab.binding.openweathermap.internal.dto.onecall.Hourly forecastData = localWeatherData.getHourly()
433                     .get(count);
434             State state = UnDefType.UNDEF;
435             switch (channelId) {
436                 case CHANNEL_TIME_STAMP:
437                     state = getDateTimeTypeState(forecastData.getDt());
438                     break;
439                 case CHANNEL_CONDITION:
440                     if (!forecastData.getWeather().isEmpty()) {
441                         state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
442                     }
443                     break;
444                 case CHANNEL_CONDITION_ID:
445                     if (!forecastData.getWeather().isEmpty()) {
446                         state = getStringTypeState(Integer.toString(forecastData.getWeather().get(0).getId()));
447                     }
448                     break;
449                 case CHANNEL_CONDITION_ICON:
450                     if (!forecastData.getWeather().isEmpty()) {
451                         state = getRawTypeState(
452                                 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
453                     }
454                     break;
455                 case CHANNEL_CONDITION_ICON_ID:
456                     if (!forecastData.getWeather().isEmpty()) {
457                         state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
458                     }
459                     break;
460                 case CHANNEL_TEMPERATURE:
461                     state = getQuantityTypeState(forecastData.getTemp(), CELSIUS);
462                     break;
463                 case CHANNEL_APPARENT_TEMPERATURE:
464                     state = getQuantityTypeState(forecastData.getFeelsLike(), CELSIUS);
465                     break;
466                 case CHANNEL_PRESSURE:
467                     state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
468                     break;
469                 case CHANNEL_HUMIDITY:
470                     state = getQuantityTypeState(forecastData.getHumidity(), PERCENT);
471                     break;
472                 case CHANNEL_DEW_POINT:
473                     state = getQuantityTypeState(forecastData.getDewPoint(), CELSIUS);
474                     break;
475                 case CHANNEL_WIND_SPEED:
476                     state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
477                     break;
478                 case CHANNEL_WIND_DIRECTION:
479                     state = getQuantityTypeState(forecastData.getWindDeg(), DEGREE_ANGLE);
480                     break;
481                 case CHANNEL_GUST_SPEED:
482                     state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
483                     break;
484                 case CHANNEL_CLOUDINESS:
485                     state = getQuantityTypeState(forecastData.getClouds(), PERCENT);
486                     break;
487                 case CHANNEL_VISIBILITY:
488                     State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
489                             .toUnit(KILO(METRE));
490                     state = (tempstate == null ? state : tempstate);
491                 case CHANNEL_PRECIP_PROBABILITY:
492                     state = getQuantityTypeState(forecastData.getPop() * 100.0, PERCENT);
493                     break;
494                 case CHANNEL_RAIN:
495                     Precipitation rain = forecastData.getRain();
496                     state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
497                     break;
498                 case CHANNEL_SNOW:
499                     Precipitation snow = forecastData.getSnow();
500                     state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
501                     break;
502                 default:
503                     // This should not happen
504                     logger.warn("Unknown channel id {} in onecall hourly weather data", channelId);
505                     break;
506             }
507             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
508             updateState(channelUID, state);
509         } else {
510             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
511         }
512     }
513
514     /**
515      * Update the channel from the last OpenWeatherMap data retrieved.
516      *
517      * @param channelUID the id identifying the channel to be updated
518      * @param count the index of the daily data referenced by the channel (today is count 0)
519      */
520     private void updateDailyForecastChannel(ChannelUID channelUID, int count) {
521         String channelId = channelUID.getIdWithoutGroup();
522         String channelGroupId = channelUID.getGroupId();
523         if (forecastDays == 0) {
524             logger.warn(
525                     "Can't update channel group {} because forecastDays is set to '0'. Please adjust config accordingly",
526                     channelGroupId);
527             return;
528         }
529         OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
530         if (localWeatherData != null && localWeatherData.getDaily().size() > count) {
531             org.openhab.binding.openweathermap.internal.dto.onecall.Daily forecastData = localWeatherData.getDaily()
532                     .get(count);
533             State state = UnDefType.UNDEF;
534             Temp temp;
535             FeelsLikeTemp feelsLike;
536             switch (channelId) {
537                 case CHANNEL_TIME_STAMP:
538                     state = getDateTimeTypeState(forecastData.getDt());
539                     break;
540                 case CHANNEL_SUNRISE:
541                     state = getDateTimeTypeState(forecastData.getSunrise());
542                     break;
543                 case CHANNEL_SUNSET:
544                     state = getDateTimeTypeState(forecastData.getSunset());
545                     break;
546                 case CHANNEL_CONDITION:
547                     if (!forecastData.getWeather().isEmpty()) {
548                         state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
549                     }
550                     break;
551                 case CHANNEL_CONDITION_ID:
552                     if (!forecastData.getWeather().isEmpty()) {
553                         state = getStringTypeState(Integer.toString(forecastData.getWeather().get(0).getId()));
554                     }
555                     break;
556                 case CHANNEL_CONDITION_ICON:
557                     if (!forecastData.getWeather().isEmpty()) {
558                         state = getRawTypeState(
559                                 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
560                     }
561                     break;
562                 case CHANNEL_CONDITION_ICON_ID:
563                     if (!forecastData.getWeather().isEmpty()) {
564                         state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
565                     }
566                     break;
567                 case CHANNEL_MIN_TEMPERATURE:
568                     temp = forecastData.getTemp();
569                     if (temp != null) {
570                         state = getQuantityTypeState(temp.getMin(), CELSIUS);
571                     }
572                     break;
573                 case CHANNEL_MAX_TEMPERATURE:
574                     temp = forecastData.getTemp();
575                     if (temp != null) {
576                         state = getQuantityTypeState(temp.getMax(), CELSIUS);
577                     }
578                     break;
579                 case CHANNEL_MORNING_TEMPERATURE:
580                     temp = forecastData.getTemp();
581                     if (temp != null) {
582                         state = getQuantityTypeState(temp.getMorn(), CELSIUS);
583                     }
584                     break;
585                 case CHANNEL_DAY_TEMPERATURE:
586                     temp = forecastData.getTemp();
587                     if (temp != null) {
588                         state = getQuantityTypeState(temp.getDay(), CELSIUS);
589                     }
590                     break;
591                 case CHANNEL_EVENING_TEMPERATURE:
592                     temp = forecastData.getTemp();
593                     if (temp != null) {
594                         state = getQuantityTypeState(temp.getEve(), CELSIUS);
595                     }
596                     break;
597                 case CHANNEL_NIGHT_TEMPERATURE:
598                     temp = forecastData.getTemp();
599                     if (temp != null) {
600                         state = getQuantityTypeState(temp.getNight(), CELSIUS);
601                     }
602                     break;
603
604                 case CHANNEL_APPARENT_DAY:
605                     feelsLike = forecastData.getFeelsLike();
606                     if (feelsLike != null) {
607                         state = getQuantityTypeState(feelsLike.getDay(), CELSIUS);
608                     }
609                     break;
610                 case CHANNEL_APPARENT_MORNING:
611                     feelsLike = forecastData.getFeelsLike();
612                     if (feelsLike != null) {
613                         state = getQuantityTypeState(feelsLike.getMorn(), CELSIUS);
614                     }
615                     break;
616                 case CHANNEL_APPARENT_EVENING:
617                     feelsLike = forecastData.getFeelsLike();
618                     if (feelsLike != null) {
619                         state = getQuantityTypeState(feelsLike.getEve(), CELSIUS);
620                     }
621                     break;
622                 case CHANNEL_APPARENT_NIGHT:
623                     feelsLike = forecastData.getFeelsLike();
624                     if (feelsLike != null) {
625                         state = getQuantityTypeState(feelsLike.getNight(), CELSIUS);
626                     }
627                     break;
628                 case CHANNEL_PRESSURE:
629                     state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
630                     break;
631                 case CHANNEL_HUMIDITY:
632                     state = getQuantityTypeState(forecastData.getHumidity(), PERCENT);
633                     break;
634                 case CHANNEL_WIND_SPEED:
635                     state = getQuantityTypeState(forecastData.getWindSpeed(), METRE_PER_SECOND);
636                     break;
637                 case CHANNEL_WIND_DIRECTION:
638                     state = getQuantityTypeState(forecastData.getWindDeg(), DEGREE_ANGLE);
639                     break;
640                 case CHANNEL_GUST_SPEED:
641                     state = getQuantityTypeState(forecastData.getWindGust(), METRE_PER_SECOND);
642                     break;
643                 case CHANNEL_CLOUDINESS:
644                     state = getQuantityTypeState(forecastData.getClouds(), PERCENT);
645                     break;
646                 case CHANNEL_DEW_POINT:
647                     state = getQuantityTypeState(forecastData.getDewPoint(), CELSIUS);
648                     break;
649                 case CHANNEL_UVINDEX:
650                     state = getDecimalTypeState(forecastData.getUvi());
651                     break;
652                 case CHANNEL_VISIBILITY:
653                     State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
654                             .toUnit(KILO(METRE));
655                     state = (tempstate == null ? state : tempstate);
656                 case CHANNEL_PRECIP_PROBABILITY:
657                     state = getQuantityTypeState(forecastData.getPop() * 100.0, PERCENT);
658                     break;
659                 case CHANNEL_RAIN:
660                     state = getQuantityTypeState(forecastData.getRain(), MILLI(METRE));
661                     break;
662                 case CHANNEL_SNOW:
663                     state = getQuantityTypeState(forecastData.getSnow(), MILLI(METRE));
664                     break;
665                 default:
666                     // This should not happen
667                     logger.warn("Unknown channel id {} in onecall daily weather data", channelId);
668                     break;
669             }
670             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
671             updateState(channelUID, state);
672         } else {
673             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
674         }
675     }
676
677     /**
678      * Update the channel from the last OpenWeaterhMap data retrieved.
679      *
680      * @param channelUID the id identifying the channel to be updated
681      * @param count the index of the alert data referenced by the channel (alert 1 is count 0)
682      */
683     private void updateAlertsChannel(ChannelUID channelUID, int count) {
684         String channelId = channelUID.getIdWithoutGroup();
685         String channelGroupId = channelUID.getGroupId();
686         OpenWeatherMapOneCallAPIData localWeatherData = weatherData;
687         List<Alert> alerts = localWeatherData != null ? localWeatherData.alerts : null;
688         State state = UnDefType.UNDEF;
689         if (alerts != null && alerts.size() > count) {
690             Alert alert = alerts.get(count);
691             switch (channelId) {
692                 case CHANNEL_ALERT_EVENT:
693                     state = getStringTypeState(alert.event);
694                     break;
695                 case CHANNEL_ALERT_DESCRIPTION:
696                     state = getStringTypeState(alert.description);
697                     break;
698                 case CHANNEL_ALERT_ONSET:
699                     state = getDateTimeTypeState(alert.start);
700                     break;
701                 case CHANNEL_ALERT_EXPIRES:
702                     state = getDateTimeTypeState(alert.end);
703                     break;
704                 case CHANNEL_ALERT_SOURCE:
705                     state = getStringTypeState(alert.senderName);
706                     break;
707             }
708             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
709         } else {
710             logger.debug("No data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
711         }
712         updateState(channelUID, state);
713     }
714 }