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.*;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapOneCallHistoryConfiguration;
26 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
27 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapOneCallHistAPIData;
28 import org.openhab.binding.openweathermap.internal.dto.onecall.Precipitation;
29 import org.openhab.binding.openweathermap.internal.dto.onecallhist.Hourly;
30 import org.openhab.core.i18n.CommunicationException;
31 import org.openhab.core.i18n.ConfigurationException;
32 import org.openhab.core.i18n.TimeZoneProvider;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.google.gson.JsonSyntaxException;
45 * The {@link OpenWeatherMapOneCallHistoryHandler} is responsible for handling commands, which are sent to one of
48 * @author Wolfgang Klimt - Initial contribution
51 public class OpenWeatherMapOneCallHistoryHandler extends AbstractOpenWeatherMapHandler {
53 private final Logger logger = LoggerFactory.getLogger(OpenWeatherMapOneCallHistoryHandler.class);
55 private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "historyHours";
56 private static final Pattern CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN = Pattern
57 .compile(CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + "([0-9]*)");
59 private @Nullable OpenWeatherMapOneCallHistAPIData weatherData;
61 // the relative day in history.
64 public OpenWeatherMapOneCallHistoryHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
65 super(thing, timeZoneProvider);
69 public void initialize() {
71 OpenWeatherMapOneCallHistoryConfiguration config = getConfigAs(OpenWeatherMapOneCallHistoryConfiguration.class);
72 if (config.historyDay <= 0) {
73 logger.warn("historyDay value of {} is not supported", config.historyDay);
77 * As of now, only 5 days in history are supported by the one call API. As this may change in the future,
78 * we allow any value here and only log a warning if the value exceeds 5.
80 if (config.historyDay > 5) {
81 logger.warn("History configuration of {} days may cause errors. You have been warned :-)",
84 day = config.historyDay;
85 logger.debug("Initialize OpenWeatherMapOneCallHistoryHandler handler '{}' with historyDay {}.",
86 getThing().getUID(), day);
87 updateStatus(ThingStatus.ONLINE);
91 protected boolean requestData(OpenWeatherMapConnection connection)
92 throws CommunicationException, ConfigurationException {
93 logger.debug("Update weather and forecast data of thing '{}'.", getThing().getUID());
95 weatherData = connection.getOneCallHistAPIData(location, day);
97 } catch (JsonSyntaxException e) {
98 logger.debug("JsonSyntaxException occurred during execution: {}", e.getMessage(), e);
104 protected void updateChannel(ChannelUID channelUID) {
105 String channelGroupId = channelUID.getGroupId();
106 switch (channelGroupId) {
107 case CHANNEL_GROUP_ONECALL_HISTORY:
108 updateHistoryCurrentChannel(channelUID);
113 Matcher hourlyForecastMatcher = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelGroupId);
114 if (hourlyForecastMatcher.find() && (i = Integer.parseInt(hourlyForecastMatcher.group(1))) >= 1
116 updateHourlyHistoryChannel(channelUID, i - 1);
124 * Update the channel from the last OpenWeatherMap data retrieved.
126 * @param channelUID the id identifying the channel to be updated
128 private void updateHistoryCurrentChannel(ChannelUID channelUID) {
129 String channelId = channelUID.getIdWithoutGroup();
130 String channelGroupId = channelUID.getGroupId();
131 OpenWeatherMapOneCallHistAPIData localWeatherData = weatherData;
132 if (localWeatherData != null) {
133 State state = UnDefType.UNDEF;
135 case CHANNEL_STATION_LOCATION:
136 state = getPointTypeState(localWeatherData.getLat(), localWeatherData.getLon());
138 case CHANNEL_TIME_STAMP:
139 state = getDateTimeTypeState(localWeatherData.getCurrent().getDt());
141 case CHANNEL_SUNRISE:
142 state = getDateTimeTypeState(localWeatherData.getCurrent().getSunrise());
145 state = getDateTimeTypeState(localWeatherData.getCurrent().getSunset());
147 case CHANNEL_CONDITION:
148 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
149 state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getDescription());
152 case CHANNEL_CONDITION_ID:
153 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
154 state = getStringTypeState(
155 Integer.toString(localWeatherData.getCurrent().getWeather().get(0).getId()));
158 case CHANNEL_CONDITION_ICON:
159 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
160 state = getRawTypeState(OpenWeatherMapConnection
161 .getWeatherIcon(localWeatherData.getCurrent().getWeather().get(0).getIcon()));
164 case CHANNEL_CONDITION_ICON_ID:
165 if (!localWeatherData.getCurrent().getWeather().isEmpty()) {
166 state = getStringTypeState(localWeatherData.getCurrent().getWeather().get(0).getIcon());
169 case CHANNEL_TEMPERATURE:
170 state = getQuantityTypeState(localWeatherData.getCurrent().getTemp(), CELSIUS);
172 case CHANNEL_APPARENT_TEMPERATURE:
173 state = getQuantityTypeState(localWeatherData.getCurrent().getFeelsLike(), CELSIUS);
175 case CHANNEL_PRESSURE:
176 state = getQuantityTypeState(localWeatherData.getCurrent().getPressure(), HECTO(PASCAL));
178 case CHANNEL_HUMIDITY:
179 state = getQuantityTypeState(localWeatherData.getCurrent().getHumidity(), PERCENT);
181 case CHANNEL_DEW_POINT:
182 state = getQuantityTypeState(localWeatherData.getCurrent().getDewPoint(), CELSIUS);
184 case CHANNEL_WIND_SPEED:
185 state = getQuantityTypeState(localWeatherData.getCurrent().getWindSpeed(), METRE_PER_SECOND);
187 case CHANNEL_WIND_DIRECTION:
188 state = getQuantityTypeState(localWeatherData.getCurrent().getWindDeg(), DEGREE_ANGLE);
190 case CHANNEL_GUST_SPEED:
191 state = getQuantityTypeState(localWeatherData.getCurrent().getWindGust(), METRE_PER_SECOND);
193 case CHANNEL_CLOUDINESS:
194 state = getQuantityTypeState(localWeatherData.getCurrent().getClouds(), PERCENT);
196 case CHANNEL_UVINDEX:
197 state = getDecimalTypeState(localWeatherData.getCurrent().getUvi());
200 Precipitation rain = localWeatherData.getCurrent().getRain();
201 state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
204 Precipitation snow = localWeatherData.getCurrent().getSnow();
205 state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
207 case CHANNEL_VISIBILITY:
209 State tempstate = new QuantityType<>(localWeatherData.getCurrent().getVisibility(), METRE)
210 .toUnit(KILO(METRE));
211 state = (tempstate == null ? state : tempstate);
214 // This should not happen
215 logger.warn("Unknown channel id {} in onecall current weather data", channelId);
218 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
219 updateState(channelUID, state);
221 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);
226 * Update the channel from the last OpenWeatherMap data retrieved.
228 * @param channelUID the id identifying the channel to be updated
229 * @param count the number of the hour referenced by the channel
231 private void updateHourlyHistoryChannel(ChannelUID channelUID, int count) {
232 String channelId = channelUID.getIdWithoutGroup();
233 String channelGroupId = channelUID.getGroupId();
234 logger.debug("Updating hourly history data for channel {}, group {}, count {}", channelId, channelGroupId,
236 OpenWeatherMapOneCallHistAPIData localWeatherData = weatherData;
237 if (localWeatherData != null && localWeatherData.getHourly().size() > count) {
238 Hourly historyData = localWeatherData.getHourly().get(count);
239 State state = UnDefType.UNDEF;
241 case CHANNEL_TIME_STAMP:
242 state = getDateTimeTypeState(historyData.getDt());
244 case CHANNEL_CONDITION:
245 if (!historyData.getWeather().isEmpty()) {
246 state = getStringTypeState(historyData.getWeather().get(0).getDescription());
249 case CHANNEL_CONDITION_ID:
250 if (!historyData.getWeather().isEmpty()) {
251 state = getStringTypeState(Integer.toString(historyData.getWeather().get(0).getId()));
254 case CHANNEL_CONDITION_ICON:
255 if (!historyData.getWeather().isEmpty()) {
256 state = getRawTypeState(
257 OpenWeatherMapConnection.getWeatherIcon(historyData.getWeather().get(0).getIcon()));
260 case CHANNEL_CONDITION_ICON_ID:
261 if (!historyData.getWeather().isEmpty()) {
262 state = getStringTypeState(historyData.getWeather().get(0).getIcon());
265 case CHANNEL_TEMPERATURE:
266 state = getQuantityTypeState(historyData.getTemp(), CELSIUS);
268 case CHANNEL_APPARENT_TEMPERATURE:
269 state = getQuantityTypeState(historyData.getFeelsLike(), CELSIUS);
271 case CHANNEL_PRESSURE:
272 state = getQuantityTypeState(historyData.getPressure(), HECTO(PASCAL));
274 case CHANNEL_HUMIDITY:
275 state = getQuantityTypeState(historyData.getHumidity(), PERCENT);
277 case CHANNEL_DEW_POINT:
278 state = getQuantityTypeState(historyData.getDewPoint(), CELSIUS);
280 case CHANNEL_WIND_SPEED:
281 state = getQuantityTypeState(historyData.getWindSpeed(), METRE_PER_SECOND);
283 case CHANNEL_WIND_DIRECTION:
284 state = getQuantityTypeState(historyData.getWindDeg(), DEGREE_ANGLE);
286 case CHANNEL_GUST_SPEED:
287 state = getQuantityTypeState(historyData.getWindGust(), METRE_PER_SECOND);
289 case CHANNEL_CLOUDINESS:
290 state = getQuantityTypeState(historyData.getClouds(), PERCENT);
292 case CHANNEL_VISIBILITY:
294 State tempstate = new QuantityType<>(historyData.getVisibility(), METRE).toUnit(KILO(METRE));
295 state = (tempstate == null ? state : tempstate);
297 Precipitation rain = historyData.getRain();
298 state = getQuantityTypeState(rain == null ? 0 : rain.get1h(), MILLI(METRE));
301 Precipitation snow = historyData.getSnow();
302 state = getQuantityTypeState(snow == null ? 0 : snow.get1h(), MILLI(METRE));
305 // This should not happen
306 logger.warn("Unknown channel id {} in onecall hourly weather data", channelId);
309 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
310 updateState(channelUID, state);
312 logger.debug("No weather data available to update channel '{}' of group '{}'.", channelId, channelGroupId);