2 * Copyright (c) 2010-2023 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.weatherunderground.internal.handler;
15 import java.io.IOException;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.weatherunderground.internal.WeatherUndergroundBindingConstants;
22 import org.openhab.binding.weatherunderground.internal.json.WeatherUndergroundJsonData;
23 import org.openhab.core.config.core.Configuration;
24 import org.openhab.core.io.net.http.HttpUtil;
25 import org.openhab.core.thing.Bridge;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.openhab.core.thing.binding.BaseBridgeHandler;
30 import org.openhab.core.types.Command;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.Gson;
35 import com.google.gson.JsonSyntaxException;
38 * The {@link WeatherUndergroundBridgeHandler} is responsible for handling the
39 * bridge things created to use the Weather Underground Service. This way, the
40 * API key may be entered only once.
42 * @author Theo Giovanna - Initial Contribution
43 * @author Laurent Garnier - refactor bridge/thing handling
46 public class WeatherUndergroundBridgeHandler extends BaseBridgeHandler {
48 private final Logger logger = LoggerFactory.getLogger(WeatherUndergroundBridgeHandler.class);
49 private final Gson gson;
50 private static final String URL = "http://api.wunderground.com/api/%APIKEY%/";
51 public static final int FETCH_TIMEOUT_MS = 30000;
54 private ScheduledFuture<?> controlApiKeyJob;
56 private String apikey = "";
58 public WeatherUndergroundBridgeHandler(Bridge bridge) {
64 public void initialize() {
65 logger.debug("Initializing weatherunderground bridge handler.");
66 Configuration config = getThing().getConfiguration();
68 // Check if an api key has been provided during the bridge creation
69 Object configApiKey = config.get(WeatherUndergroundBindingConstants.APIKEY);
70 if (configApiKey == null || !(configApiKey instanceof String) || ((String) configApiKey).trim().isEmpty()) {
71 logger.debug("Setting thing '{}' to OFFLINE: Parameter 'apikey' must be configured.", getThing().getUID());
72 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
73 "@text/offline.conf-error-missing-apikey");
75 apikey = ((String) configApiKey).trim();
76 updateStatus(ThingStatus.UNKNOWN);
77 startControlApiKeyJob();
82 * Start the job controlling the API key
84 private void startControlApiKeyJob() {
85 if (controlApiKeyJob == null || controlApiKeyJob.isCancelled()) {
86 Runnable runnable = new Runnable() {
89 WeatherUndergroundJsonData result = null;
90 String errorDetail = null;
92 String statusDescr = null;
93 boolean resultOk = false;
95 // Check if the provided api key is valid for use with the weatherunderground service
97 String urlStr = URL.replace("%APIKEY%", getApikey());
98 // Run the HTTP request and get the JSON response from Weather Underground
99 String response = null;
101 response = HttpUtil.executeUrl("GET", urlStr, FETCH_TIMEOUT_MS);
102 logger.debug("apiResponse = {}", response);
103 } catch (IllegalArgumentException e) {
104 // Catch Illegal character in path at index XX: http://api.wunderground.com/...
105 error = "Error creating URI";
106 errorDetail = e.getMessage();
107 statusDescr = "@text/offline.uri-error";
109 // Map the JSON response to an object
110 result = gson.fromJson(response, WeatherUndergroundJsonData.class);
111 if (result.getResponse() == null) {
112 error = "Error in Weather Underground response";
113 errorDetail = "missing response sub-object";
114 statusDescr = "@text/offline.comm-error-response";
115 } else if (result.getResponse().getErrorDescription() != null) {
116 if ("keynotfound".equals(result.getResponse().getErrorType())) {
117 error = "API key has to be fixed";
118 errorDetail = result.getResponse().getErrorDescription();
119 statusDescr = "@text/offline.comm-error-invalid-api-key";
120 } else if ("invalidquery".equals(result.getResponse().getErrorType())) {
121 // The API key provided is valid
124 error = "Error in Weather Underground response";
125 errorDetail = result.getResponse().getErrorDescription();
126 statusDescr = "@text/offline.comm-error-response";
131 } catch (IOException e) {
132 error = "Error running Weather Underground request";
133 errorDetail = e.getMessage();
134 statusDescr = "@text/offline.comm-error-running-request";
135 } catch (JsonSyntaxException e) {
136 error = "Error parsing Weather Underground response";
137 errorDetail = e.getMessage();
138 statusDescr = "@text/offline.comm-error-parsing-response";
141 // Update the thing status
143 updateStatus(ThingStatus.ONLINE);
145 logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", getThing().getUID(), error,
147 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, statusDescr);
151 controlApiKeyJob = scheduler.schedule(runnable, 1, TimeUnit.SECONDS);
156 public void dispose() {
157 logger.debug("Disposing weatherunderground bridge handler.");
159 if (controlApiKeyJob != null && !controlApiKeyJob.isCancelled()) {
160 controlApiKeyJob.cancel(true);
161 controlApiKeyJob = null;
166 public void handleCommand(ChannelUID channelUID, Command command) {
170 public String getApikey() {