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.*;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapAirPollutionConfiguration;
25 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
26 import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonAirPollutionData;
27 import org.openhab.core.i18n.CommunicationException;
28 import org.openhab.core.i18n.ConfigurationException;
29 import org.openhab.core.i18n.TimeZoneProvider;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.unit.Units;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.builder.ThingBuilder;
38 import org.openhab.core.types.State;
39 import org.openhab.core.types.UnDefType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.JsonSyntaxException;
46 * The {@link OpenWeatherMapAirPollutionHandler} is responsible for handling commands, which are sent to one of the
49 * @author Christoph Weitkamp - Initial contribution
52 public class OpenWeatherMapAirPollutionHandler extends AbstractOpenWeatherMapHandler {
54 private final Logger logger = LoggerFactory.getLogger(OpenWeatherMapAirPollutionHandler.class);
56 private static final String CHANNEL_GROUP_HOURLY_FORECAST_PREFIX = "forecastHours";
57 private static final Pattern CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN = Pattern
58 .compile(CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + "([0-9]*)");
60 // keeps track of the parsed count
61 private int forecastHours = 0;
63 private @Nullable OpenWeatherMapJsonAirPollutionData airPollutionData;
64 private @Nullable OpenWeatherMapJsonAirPollutionData airPollutionForecastData;
66 public OpenWeatherMapAirPollutionHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
67 super(thing, timeZoneProvider);
71 public void initialize() {
73 logger.debug("Initialize OpenWeatherMapAirPollutionHandler handler '{}'.", getThing().getUID());
74 OpenWeatherMapAirPollutionConfiguration config = getConfigAs(OpenWeatherMapAirPollutionConfiguration.class);
76 boolean configValid = true;
77 int newForecastHours = config.forecastHours;
78 if (newForecastHours < 0 || newForecastHours > 120) {
79 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
80 "@text/offline.conf-error-not-supported-air-pollution-number-of-hours");
85 logger.debug("Rebuilding thing '{}'.", getThing().getUID());
86 List<Channel> toBeAddedChannels = new ArrayList<>();
87 List<Channel> toBeRemovedChannels = new ArrayList<>();
88 if (forecastHours != newForecastHours) {
89 logger.debug("Rebuilding air pollution channel groups.");
90 if (forecastHours > newForecastHours) {
91 for (int i = newForecastHours + 1; i <= forecastHours; i++) {
92 toBeRemovedChannels.addAll(removeChannelsOfGroup(
93 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i)));
96 for (int i = forecastHours + 1; i <= newForecastHours; i++) {
97 toBeAddedChannels.addAll(createChannelsForGroup(
98 CHANNEL_GROUP_HOURLY_FORECAST_PREFIX + ((i < 10) ? "0" : "") + Integer.toString(i),
99 CHANNEL_GROUP_TYPE_AIR_POLLUTION_FORECAST));
102 forecastHours = newForecastHours;
104 ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
105 for (Channel channel : toBeAddedChannels) {
106 builder.withChannel(channel);
108 updateThing(builder.build());
113 protected boolean requestData(OpenWeatherMapConnection connection)
114 throws CommunicationException, ConfigurationException {
115 logger.debug("Update air pollution data of thing '{}'.", getThing().getUID());
117 airPollutionData = connection.getAirPollutionData(location);
118 if (forecastHours > 0) {
119 airPollutionForecastData = connection.getAirPollutionForecastData(location);
122 } catch (JsonSyntaxException e) {
123 logger.debug("JsonSyntaxException occurred during execution: {}", e.getMessage(), e);
129 protected void updateChannel(ChannelUID channelUID) {
130 switch (channelUID.getGroupId()) {
131 case CHANNEL_GROUP_CURRENT_AIR_POLLUTION:
132 updateCurrentAirPollutionChannel(channelUID);
135 Matcher m = CHANNEL_GROUP_HOURLY_FORECAST_PREFIX_PATTERN.matcher(channelUID.getGroupId());
137 if (m.find() && (i = Integer.parseInt(m.group(1))) > 0 && i <= 120) {
138 updateHourlyForecastChannel(channelUID, i);
145 * Update the channel from the last OpenWeatherMap data retrieved.
147 * @param channelUID the id identifying the channel to be updated
149 private void updateCurrentAirPollutionChannel(ChannelUID channelUID) {
150 String channelId = channelUID.getIdWithoutGroup();
151 String channelGroupId = channelUID.getGroupId();
152 OpenWeatherMapJsonAirPollutionData localAirPollutionData = airPollutionData;
153 if (localAirPollutionData != null && !localAirPollutionData.list.isEmpty()) {
154 org.openhab.binding.openweathermap.internal.dto.airpollution.List currentData = localAirPollutionData.list
156 State state = UnDefType.UNDEF;
158 case CHANNEL_TIME_STAMP:
159 state = getDateTimeTypeState(currentData.dt);
161 case CHANNEL_AIR_QUALITY_INDEX:
162 state = new DecimalType(currentData.airQualityIndex.index);
164 case CHANNEL_PARTICULATE_MATTER_2_5:
165 state = getQuantityTypeState(currentData.measurements.particulateMatter2dot5,
166 Units.MICROGRAM_PER_CUBICMETRE);
168 case CHANNEL_PARTICULATE_MATTER_10:
169 state = getQuantityTypeState(currentData.measurements.particulateMatter10,
170 Units.MICROGRAM_PER_CUBICMETRE);
172 case CHANNEL_CARBON_MONOXIDE:
173 state = getQuantityTypeState(currentData.measurements.carbonMonoxide,
174 Units.MICROGRAM_PER_CUBICMETRE);
176 case CHANNEL_NITROGEN_MONOXIDE:
177 state = getQuantityTypeState(currentData.measurements.nitrogenMonoxide,
178 Units.MICROGRAM_PER_CUBICMETRE);
180 case CHANNEL_NITROGEN_DIOXIDE:
181 state = getQuantityTypeState(currentData.measurements.nitrogenDioxide,
182 Units.MICROGRAM_PER_CUBICMETRE);
185 state = getQuantityTypeState(currentData.measurements.ozone, Units.MICROGRAM_PER_CUBICMETRE);
187 case CHANNEL_SULPHUR_DIOXIDE:
188 state = getQuantityTypeState(currentData.measurements.sulphurDioxide,
189 Units.MICROGRAM_PER_CUBICMETRE);
191 case CHANNEL_AMMONIA:
192 state = getQuantityTypeState(currentData.measurements.ammonia, Units.MICROGRAM_PER_CUBICMETRE);
195 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
196 updateState(channelUID, state);
198 logger.debug("No air pollution data available to update channel '{}' of group '{}'.", channelId,
204 * Update the channel from the last OpenWeatherMap data retrieved.
206 * @param channelUID the id identifying the channel to be updated
209 private void updateHourlyForecastChannel(ChannelUID channelUID, int count) {
210 String channelId = channelUID.getIdWithoutGroup();
211 String channelGroupId = channelUID.getGroupId();
212 OpenWeatherMapJsonAirPollutionData localAirPollutionForecastData = airPollutionForecastData;
213 if (localAirPollutionForecastData != null && localAirPollutionForecastData.list.size() >= count) {
214 org.openhab.binding.openweathermap.internal.dto.airpollution.List forecastData = localAirPollutionForecastData.list
216 State state = UnDefType.UNDEF;
218 case CHANNEL_TIME_STAMP:
219 state = getDateTimeTypeState(forecastData.dt);
221 case CHANNEL_AIR_QUALITY_INDEX:
222 state = new DecimalType(forecastData.airQualityIndex.index);
224 case CHANNEL_PARTICULATE_MATTER_2_5:
225 state = getQuantityTypeState(forecastData.measurements.particulateMatter2dot5,
226 Units.MICROGRAM_PER_CUBICMETRE);
228 case CHANNEL_PARTICULATE_MATTER_10:
229 state = getQuantityTypeState(forecastData.measurements.particulateMatter10,
230 Units.MICROGRAM_PER_CUBICMETRE);
232 case CHANNEL_CARBON_MONOXIDE:
233 state = getQuantityTypeState(forecastData.measurements.carbonMonoxide,
234 Units.MICROGRAM_PER_CUBICMETRE);
236 case CHANNEL_NITROGEN_MONOXIDE:
237 state = getQuantityTypeState(forecastData.measurements.nitrogenMonoxide,
238 Units.MICROGRAM_PER_CUBICMETRE);
240 case CHANNEL_NITROGEN_DIOXIDE:
241 state = getQuantityTypeState(forecastData.measurements.nitrogenDioxide,
242 Units.MICROGRAM_PER_CUBICMETRE);
245 state = getQuantityTypeState(forecastData.measurements.ozone, Units.MICROGRAM_PER_CUBICMETRE);
247 case CHANNEL_SULPHUR_DIOXIDE:
248 state = getQuantityTypeState(forecastData.measurements.sulphurDioxide,
249 Units.MICROGRAM_PER_CUBICMETRE);
251 case CHANNEL_AMMONIA:
252 state = getQuantityTypeState(forecastData.measurements.ammonia, Units.MICROGRAM_PER_CUBICMETRE);
255 logger.debug("Update channel '{}' of group '{}' with new state '{}'.", channelId, channelGroupId, state);
256 updateState(channelUID, state);
258 logger.debug("No air pollution data available to update channel '{}' of group '{}'.", channelId,