]> git.basschouten.com Git - openhab-addons.git/blob
bba5e46b0177a303db864700ce83040e56bbaa56
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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 import java.util.stream.Collectors;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpResponseException;
29 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapWeatherAndForecastConfiguration;
30 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
31 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonDailyForecastData;
32 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonHourlyForecastData;
33 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonWeatherData;
34 import org.openhab.binding.openweathermap.internal.dto.base.Precipitation;
35 import org.openhab.binding.openweathermap.internal.dto.forecast.daily.FeelsLikeTemp;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.i18n.CommunicationException;
38 import org.openhab.core.i18n.ConfigurationException;
39 import org.openhab.core.i18n.TimeZoneProvider;
40 import org.openhab.core.library.types.QuantityType;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.binding.builder.ThingBuilder;
47 import org.openhab.core.types.State;
48 import org.openhab.core.types.UnDefType;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import com.google.gson.JsonSyntaxException;
53
54 /**
55  * The {@link OpenWeatherMapWeatherAndForecastHandler} is responsible for handling commands, which are sent to one of
56  * the channels.
57  *
58  * @author Christoph Weitkamp - Initial contribution
59  */
60 @NonNullByDefault
61 public class OpenWeatherMapWeatherAndForecastHandler extends AbstractOpenWeatherMapHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(OpenWeatherMapWeatherAndForecastHandler.class);
64
65     private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "forecastHours";
66     private static final String CHANNEL_GROUP_DAILY_FORECAST_PREFIX = "forecastDay";
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
72     // keeps track of the parsed counts
73     private int forecastHours = 24;
74     private int forecastDays = 6;
75
76     private @Nullable OpenWeatherMapJsonWeatherData weatherData;
77     private @Nullable OpenWeatherMapJsonHourlyForecastData hourlyForecastData;
78     private @Nullable OpenWeatherMapJsonDailyForecastData dailyForecastData;
79
80     public OpenWeatherMapWeatherAndForecastHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
81         super(thing, timeZoneProvider);
82     }
83
84     @Override
85     public void initialize() {
86         super.initialize();
87         logger.debug("Initialize OpenWeatherMapWeatherAndForecastHandler handler '{}'.", getThing().getUID());
88         OpenWeatherMapWeatherAndForecastConfiguration config = getConfigAs(
89                 OpenWeatherMapWeatherAndForecastConfiguration.class);
90
91         boolean configValid = true;
92         int newForecastHours = config.forecastHours;
93         if (newForecastHours < 0 || newForecastHours > 120 || newForecastHours % 3 != 0) {
94             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
95                     "@text/offline.conf-error-not-supported-number-of-hours");
96             configValid = false;
97         }
98         int newForecastDays = config.forecastDays;
99         if (newForecastDays < 0 || newForecastDays > 16) {
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
101                     "@text/offline.conf-error-not-supported-number-of-days");
102             configValid = false;
103         }
104
105         if (configValid) {
106             logger.debug("Rebuilding thing '{}'.", getThing().getUID());
107             List<Channel> toBeAddedChannels = new ArrayList<>();
108             List<Channel> toBeRemovedChannels = new ArrayList<>();
109             if (forecastHours != newForecastHours) {
110                 logger.debug("Rebuilding hourly forecast channel groups.");
111                 if (forecastHours > newForecastHours) {
112                     for (int i = newForecastHours + 3; i <= forecastHours; i += 3) {
113                         toBeRemovedChannels.addAll(removeChannelsOfGroup(
114                                 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
115                     }
116                 } else {
117                     for (int i = forecastHours + 3; i <= newForecastHours; i += 3) {
118                         toBeAddedChannels.addAll(createChannelsForGroup(
119                                 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
120                                 CHANNEL_GROUP_TYPE_HOURLY_FORECAST));
121                     }
122                 }
123                 forecastHours = newForecastHours;
124             }
125             if (forecastDays != newForecastDays) {
126                 logger.debug("Rebuilding daily forecast channel groups.");
127                 if (forecastDays > newForecastDays) {
128                     if (newForecastDays < 1) {
129                         toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TODAY));
130                     }
131                     if (newForecastDays < 2) {
132                         toBeRemovedChannels.addAll(removeChannelsOfGroup(CHANNEL_GROUP_FORECAST_TOMORROW));
133                     }
134                     for (int i = newForecastDays; i < forecastDays; ++i) {
135                         toBeRemovedChannels.addAll(
136                                 removeChannelsOfGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i)));
137                     }
138                 } else {
139                     if (forecastDays == 0 && newForecastDays > 0) {
140                         toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TODAY,
141                                 CHANNEL_GROUP_TYPE_DAILY_FORECAST));
142                     }
143                     if (forecastDays <= 1 && newForecastDays > 1) {
144                         toBeAddedChannels.addAll(createChannelsForGroup(CHANNEL_GROUP_FORECAST_TOMORROW,
145                                 CHANNEL_GROUP_TYPE_DAILY_FORECAST));
146                     }
147                     for (int i = (forecastDays < 2) ? 2 : forecastDays; i < newForecastDays; ++i) {
148                         toBeAddedChannels.addAll(
149                                 createChannelsForGroup(CHANNEL_GROUP_DAILY_FORECAST_PREFIX + Integer.toString(i),
150                                         CHANNEL_GROUP_TYPE_DAILY_FORECAST));
151                     }
152                 }
153                 forecastDays = newForecastDays;
154             }
155             ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
156             for (Channel channel : toBeAddedChannels) {
157                 builder.withChannel(channel);
158             }
159             updateThing(builder.build());
160         }
161     }
162
163     @Override
164     protected boolean requestData(OpenWeatherMapConnection connection)
165             throws CommunicationException, ConfigurationException {
166         logger.debug("Update weather and forecast data of thing '{}'.", getThing().getUID());
167         try {
168             weatherData = connection.getWeatherData(location);
169             if (forecastHours > 0) {
170                 hourlyForecastData = connection.getHourlyForecastData(location, forecastHours / 3);
171             }
172             if (forecastDays > 0) {
173                 try {
174                     dailyForecastData = connection.getDailyForecastData(location, forecastDays);
175                 } catch (ConfigurationException e) {
176                     if (e.getCause() instanceof HttpResponseException) {
177                         forecastDays = 0;
178                         Configuration editConfig = editConfiguration();
179                         editConfig.put(CONFIG_FORECAST_DAYS, 0);
180                         updateConfiguration(editConfig);
181                         logger.debug("Removing daily forecast channel groups.");
182                         List<Channel> channels = getThing().getChannels().stream()
183                                 .filter(c -> CHANNEL_GROUP_FORECAST_TODAY.equals(c.getUID().getGroupId())
184                                         || CHANNEL_GROUP_FORECAST_TOMORROW.equals(c.getUID().getGroupId())
185                                         || c.getUID().getGroupId().startsWith(CHANNEL_GROUP_DAILY_FORECAST_PREFIX))
186                                 .collect(Collectors.toList());
187                         updateThing(editThing().withoutChannels(channels).build());
188                     } else {
189                         throw e;
190                     }
191                 }
192             }
193             return true;
194         } catch (JsonSyntaxException e) {
195             logger.debug("JsonSyntaxException occurred during execution: {}", e.getMessage(), e);
196             return false;
197         }
198     }
199
200     @Override
201     protected void updateChannel(ChannelUID channelUID) {
202         String channelGroupId = channelUID.getGroupId();
203         switch (channelGroupId) {
204             case CHANNEL_GROUP_STATION:
205             case CHANNEL_GROUP_CURRENT_WEATHER:
206                 updateCurrentChannel(channelUID);
207                 break;
208             case CHANNEL_GROUP_FORECAST_TODAY:
209                 updateDailyForecastChannel(channelUID, 0);
210                 break;
211             case CHANNEL_GROUP_FORECAST_TOMORROW:
212                 updateDailyForecastChannel(channelUID, 1);
213                 break;
214             default:
215                 int i;
216                 Matcher hourlyForecastMatcher = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
217                 if (hourlyForecastMatcher.find() && (i = Integer.parseInt(hourlyForecastMatcher.group(1))) >= 3
218                         && i <= 120) {
219                     updateHourlyForecastChannel(channelUID, (i / 3) - 1);
220                     break;
221                 }
222                 Matcher dailyForecastMatcher = CHANNEL_GROUP_DAILY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
223                 if (dailyForecastMatcher.find() && (i = Integer.parseInt(dailyForecastMatcher.group(1))) > 1
224                         && i <= 16) {
225                     updateDailyForecastChannel(channelUID, i);
226                     break;
227                 }
228                 break;
229         }
230     }
231
232     /**
233      * Update the channel from the last OpenWeatherMap data retrieved.
234      *
235      * @param channelUID the id identifying the channel to be updated
236      */
237     private void updateCurrentChannel(ChannelUID channelUID) {
238         String channelId = channelUID.getIdWithoutGroup();
239         String channelGroupId = channelUID.getGroupId();
240         OpenWeatherMapJsonWeatherData localWeatherData = weatherData;
241         if (localWeatherData != null) {
242             State state = UnDefType.UNDEF;
243             switch (channelId) {
244                 case CHANNEL_STATION_ID:
245                     state = getStringTypeState(localWeatherData.getId().toString());
246                     break;
247                 case CHANNEL_STATION_NAME:
248                     state = getStringTypeState(localWeatherData.getName());
249                     break;
250                 case CHANNEL_STATION_LOCATION:
251                     state = getPointTypeState(localWeatherData.getCoord().getLat(),
252                             localWeatherData.getCoord().getLon());
253                     break;
254                 case CHANNEL_TIME_STAMP:
255                     state = getDateTimeTypeState(localWeatherData.getDt());
256                     break;
257                 case CHANNEL_CONDITION:
258                     if (!localWeatherData.getWeather().isEmpty()) {
259                         state = getStringTypeState(localWeatherData.getWeather().get(0).getDescription());
260                     }
261                     break;
262                 case CHANNEL_CONDITION_ID:
263                     if (!localWeatherData.getWeather().isEmpty()) {
264                         state = getStringTypeState(localWeatherData.getWeather().get(0).getId().toString());
265                     }
266                     break;
267                 case CHANNEL_CONDITION_ICON:
268                     if (!localWeatherData.getWeather().isEmpty()) {
269                         state = getRawTypeState(OpenWeatherMapConnection
270                                 .getWeatherIcon(localWeatherData.getWeather().get(0).getIcon()));
271                     }
272                     break;
273                 case CHANNEL_CONDITION_ICON_ID:
274                     if (!localWeatherData.getWeather().isEmpty()) {
275                         state = getStringTypeState(localWeatherData.getWeather().get(0).getIcon());
276                     }
277                     break;
278                 case CHANNEL_TEMPERATURE:
279                     state = getQuantityTypeState(localWeatherData.getMain().getTemp(), CELSIUS);
280                     break;
281                 case CHANNEL_APPARENT_TEMPERATURE:
282                     state = getQuantityTypeState(localWeatherData.getMain().getFeelsLikeTemp(), CELSIUS);
283                     break;
284                 case CHANNEL_PRESSURE:
285                     state = getQuantityTypeState(localWeatherData.getMain().getPressure(), HECTO(PASCAL));
286                     break;
287                 case CHANNEL_HUMIDITY:
288                     state = getQuantityTypeState(localWeatherData.getMain().getHumidity(), PERCENT);
289                     break;
290                 case CHANNEL_WIND_SPEED:
291                     state = getQuantityTypeState(localWeatherData.getWind().getSpeed(), METRE_PER_SECOND);
292                     break;
293                 case CHANNEL_WIND_DIRECTION:
294                     state = getQuantityTypeState(localWeatherData.getWind().getDeg(), DEGREE_ANGLE);
295                     break;
296                 case CHANNEL_GUST_SPEED:
297                     state = getQuantityTypeState(localWeatherData.getWind().getGust(), METRE_PER_SECOND);
298                     break;
299                 case CHANNEL_CLOUDINESS:
300                     state = getQuantityTypeState(localWeatherData.getClouds().getAll(), PERCENT);
301                     break;
302                 case CHANNEL_RAIN:
303                     Precipitation rain = localWeatherData.getRain();
304                     state = getQuantityTypeState(rain == null ? 0 : rain.getVolume(), MILLI(METRE));
305                     break;
306                 case CHANNEL_SNOW:
307                     Precipitation snow = localWeatherData.getSnow();
308                     state = getQuantityTypeState(snow == null ? 0 : snow.getVolume(), MILLI(METRE));
309                     break;
310                 case CHANNEL_VISIBILITY:
311                     Integer localVisibility = localWeatherData.getVisibility();
312                     state = localVisibility == null ? UnDefType.UNDEF
313                             : new QuantityType<>(localVisibility, METRE).toUnit(KILO(METRE));
314                     if (state == null) {
315                         logger.debug("State conversion failed, cannot update state.");
316                         return;
317                     }
318                     break;
319             }
320             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
321             updateState(channelUID, state);
322         } else {
323             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
324         }
325     }
326
327     /**
328      * Update the channel from the last OpenWeatherMap data retrieved.
329      *
330      * @param channelUID the id identifying the channel to be updated
331      * @param count
332      */
333     private void updateHourlyForecastChannel(ChannelUID channelUID, int count) {
334         String channelId = channelUID.getIdWithoutGroup();
335         String channelGroupId = channelUID.getGroupId();
336         OpenWeatherMapJsonHourlyForecastData localHourlyForecastData = hourlyForecastData;
337         if (localHourlyForecastData != null && localHourlyForecastData.getList().size() > count) {
338             org.openhab.binding.openweathermap.internal.dto.forecast.hourly.List forecastData = localHourlyForecastData
339                     .getList().get(count);
340             State state = UnDefType.UNDEF;
341             switch (channelId) {
342                 case CHANNEL_TIME_STAMP:
343                     state = getDateTimeTypeState(forecastData.getDt());
344                     break;
345                 case CHANNEL_CONDITION:
346                     if (!forecastData.getWeather().isEmpty()) {
347                         state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
348                     }
349                     break;
350                 case CHANNEL_CONDITION_ID:
351                     if (!forecastData.getWeather().isEmpty()) {
352                         state = getStringTypeState(forecastData.getWeather().get(0).getId().toString());
353                     }
354                     break;
355                 case CHANNEL_CONDITION_ICON:
356                     if (!forecastData.getWeather().isEmpty()) {
357                         state = getRawTypeState(
358                                 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
359                     }
360                     break;
361                 case CHANNEL_CONDITION_ICON_ID:
362                     if (!forecastData.getWeather().isEmpty()) {
363                         state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
364                     }
365                     break;
366                 case CHANNEL_TEMPERATURE:
367                     state = getQuantityTypeState(forecastData.getMain().getTemp(), CELSIUS);
368                     break;
369                 case CHANNEL_APPARENT_TEMPERATURE:
370                     state = getQuantityTypeState(forecastData.getMain().getFeelsLikeTemp(), CELSIUS);
371                     break;
372                 case CHANNEL_MIN_TEMPERATURE:
373                     state = getQuantityTypeState(forecastData.getMain().getTempMin(), CELSIUS);
374                     break;
375                 case CHANNEL_MAX_TEMPERATURE:
376                     state = getQuantityTypeState(forecastData.getMain().getTempMax(), CELSIUS);
377                     break;
378                 case CHANNEL_PRESSURE:
379                     state = getQuantityTypeState(forecastData.getMain().getPressure(), HECTO(PASCAL));
380                     break;
381                 case CHANNEL_HUMIDITY:
382                     state = getQuantityTypeState(forecastData.getMain().getHumidity(), PERCENT);
383                     break;
384                 case CHANNEL_WIND_SPEED:
385                     state = getQuantityTypeState(forecastData.getWind().getSpeed(), METRE_PER_SECOND);
386                     break;
387                 case CHANNEL_WIND_DIRECTION:
388                     state = getQuantityTypeState(forecastData.getWind().getDeg(), DEGREE_ANGLE);
389                     break;
390                 case CHANNEL_GUST_SPEED:
391                     state = getQuantityTypeState(forecastData.getWind().getGust(), METRE_PER_SECOND);
392                     break;
393                 case CHANNEL_CLOUDINESS:
394                     state = getQuantityTypeState(forecastData.getClouds().getAll(), PERCENT);
395                     break;
396                 case CHANNEL_RAIN:
397                     Precipitation rain = forecastData.getRain();
398                     state = getQuantityTypeState(rain == null ? 0 : rain.getVolume(), MILLI(METRE));
399                     break;
400                 case CHANNEL_SNOW:
401                     Precipitation snow = forecastData.getSnow();
402                     state = getQuantityTypeState(snow == null ? 0 : snow.getVolume(), MILLI(METRE));
403                     break;
404             }
405             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
406             updateState(channelUID, state);
407         } else {
408             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
409         }
410     }
411
412     /**
413      * Update the channel from the last OpenWeatherMap data retrieved.
414      *
415      * @param channelUID the id identifying the channel to be updated
416      * @param count
417      */
418     private void updateDailyForecastChannel(ChannelUID channelUID, int count) {
419         String channelId = channelUID.getIdWithoutGroup();
420         String channelGroupId = channelUID.getGroupId();
421         OpenWeatherMapJsonDailyForecastData localDailyForecastData = dailyForecastData;
422         if (localDailyForecastData != null && localDailyForecastData.getList().size() > count) {
423             org.openhab.binding.openweathermap.internal.dto.forecast.daily.List forecastData = localDailyForecastData
424                     .getList().get(count);
425             State state = UnDefType.UNDEF;
426             switch (channelId) {
427                 case CHANNEL_TIME_STAMP:
428                     state = getDateTimeTypeState(forecastData.getDt());
429                     break;
430                 case CHANNEL_SUNRISE:
431                     state = getDateTimeTypeState(forecastData.getSunrise());
432                     break;
433                 case CHANNEL_SUNSET:
434                     state = getDateTimeTypeState(forecastData.getSunset());
435                     break;
436                 case CHANNEL_CONDITION:
437                     if (!forecastData.getWeather().isEmpty()) {
438                         state = getStringTypeState(forecastData.getWeather().get(0).getDescription());
439                     }
440                     break;
441                 case CHANNEL_CONDITION_ID:
442                     if (!forecastData.getWeather().isEmpty()) {
443                         state = getStringTypeState(forecastData.getWeather().get(0).getId().toString());
444                     }
445                     break;
446                 case CHANNEL_CONDITION_ICON:
447                     if (!forecastData.getWeather().isEmpty()) {
448                         state = getRawTypeState(
449                                 OpenWeatherMapConnection.getWeatherIcon(forecastData.getWeather().get(0).getIcon()));
450                     }
451                     break;
452                 case CHANNEL_CONDITION_ICON_ID:
453                     if (!forecastData.getWeather().isEmpty()) {
454                         state = getStringTypeState(forecastData.getWeather().get(0).getIcon());
455                     }
456                     break;
457                 case CHANNEL_MIN_TEMPERATURE:
458                     state = getQuantityTypeState(forecastData.getTemp().getMin(), CELSIUS);
459                     break;
460                 case CHANNEL_MAX_TEMPERATURE:
461                     state = getQuantityTypeState(forecastData.getTemp().getMax(), CELSIUS);
462                     break;
463                 case CHANNEL_APPARENT_TEMPERATURE:
464                     FeelsLikeTemp feelsLikeTemp = forecastData.getFeelsLike();
465                     if (feelsLikeTemp != null) {
466                         state = getQuantityTypeState(feelsLikeTemp.getDay(), CELSIUS);
467                     }
468                     break;
469                 case CHANNEL_PRESSURE:
470                     state = getQuantityTypeState(forecastData.getPressure(), HECTO(PASCAL));
471                     break;
472                 case CHANNEL_HUMIDITY:
473                     state = getQuantityTypeState(forecastData.getHumidity(), PERCENT);
474                     break;
475                 case CHANNEL_WIND_SPEED:
476                     state = getQuantityTypeState(forecastData.getSpeed(), METRE_PER_SECOND);
477                     break;
478                 case CHANNEL_WIND_DIRECTION:
479                     state = getQuantityTypeState(forecastData.getDeg(), DEGREE_ANGLE);
480                     break;
481                 case CHANNEL_GUST_SPEED:
482                     state = getQuantityTypeState(forecastData.getGust(), METRE_PER_SECOND);
483                     break;
484                 case CHANNEL_CLOUDINESS:
485                     state = getQuantityTypeState(forecastData.getClouds(), PERCENT);
486                     break;
487                 case CHANNEL_RAIN:
488                     Double rain = forecastData.getRain();
489                     state = getQuantityTypeState(rain == null ? 0 : rain, MILLI(METRE));
490                     break;
491                 case CHANNEL_SNOW:
492                     Double snow = forecastData.getSnow();
493                     state = getQuantityTypeState(snow == null ? 0 : snow, MILLI(METRE));
494                     break;
495                 case CHANNEL_PRECIP_PROBABILITY:
496                     Double probability = forecastData.getPop();
497                     state = getQuantityTypeState(probability == null ? 0 : probability * 100.0, PERCENT);
498                     break;
499             }
500             logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
501             updateState(channelUID, state);
502         } else {
503             logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
504         }
505     }
506 }