2 * Copyright (c) 2010-2020 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.weathercompany.internal.handler;
15 import static org.openhab.binding.weathercompany.internal.WeatherCompanyBindingConstants.*;
17 import java.util.Objects;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.TimeUnit;
21 import org.apache.commons.lang.StringUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.openhab.binding.weathercompany.internal.config.WeatherCompanyObservationsConfig;
26 import org.openhab.binding.weathercompany.internal.model.PwsObservationsDTO;
27 import org.openhab.binding.weathercompany.internal.model.PwsObservationsDTO.Observations;
28 import org.openhab.core.i18n.LocaleProvider;
29 import org.openhab.core.i18n.TimeZoneProvider;
30 import org.openhab.core.i18n.UnitProvider;
31 import org.openhab.core.library.unit.ImperialUnits;
32 import org.openhab.core.library.unit.SmartHomeUnits;
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.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.JsonSyntaxException;
46 * The {@link WeatherCompanyObservationsHandler} is responsible for pulling Personal
47 * Weather Station (PWS) observations from the Weather Company API.
49 * API documentation is located here
50 * - https://docs.google.com/document/d/1eKCnKXI9xnoMGRRzOL1xPCBihNV2rOet08qpE_gArAY/edit
52 * @author Mark Hilbush - Initial contribution
55 public class WeatherCompanyObservationsHandler extends WeatherCompanyAbstractHandler {
56 private static final String BASE_PWS_URL = "https://api.weather.com/v2/pws/observations/current";
58 private final Logger logger = LoggerFactory.getLogger(WeatherCompanyObservationsHandler.class);
60 private int refreshIntervalSeconds;
62 private @Nullable Future<?> refreshObservationsJob;
64 private final Runnable refreshRunnable = new Runnable() {
67 refreshPwsObservations();
71 public WeatherCompanyObservationsHandler(Thing thing, TimeZoneProvider timeZoneProvider, HttpClient httpClient,
72 UnitProvider unitProvider, LocaleProvider localeProvider) {
73 super(thing, timeZoneProvider, httpClient, unitProvider);
77 public void initialize() {
78 logger.debug("Initializing observations handler with configuration: {}",
79 getConfigAs(WeatherCompanyObservationsConfig.class).toString());
81 refreshIntervalSeconds = getConfigAs(WeatherCompanyObservationsConfig.class).refreshInterval * 60;
82 weatherDataCache.clear();
84 updateStatus(isBridgeOnline() ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
88 public void dispose() {
90 updateStatus(ThingStatus.OFFLINE);
94 public void handleCommand(ChannelUID channelUID, Command command) {
95 if (command.equals(RefreshType.REFRESH)) {
96 State state = weatherDataCache.get(channelUID.getId());
98 updateChannel(channelUID.getId(), state);
104 * Build the URL for requesting the PWS current observations
106 private @Nullable String buildPwsUrl() {
107 if (StringUtils.isEmpty(getConfigAs(WeatherCompanyObservationsConfig.class).pwsStationId)) {
110 String apiKey = getApiKey();
111 StringBuilder sb = new StringBuilder(BASE_PWS_URL);
112 // Set to use Imperial units. UoM will convert to the other units
113 sb.append("?units=e");
114 // Get temperatures with one decimal point precision
115 sb.append("&numericPrecision=decimal");
116 // Set response type as JSON
117 sb.append("&format=json");
118 // Set PWS station Id from config
119 sb.append("&stationId=").append(getConfigAs(WeatherCompanyObservationsConfig.class).pwsStationId);
120 // Set API key from config
121 sb.append("&apiKey=").append(apiKey);
122 String url = sb.toString();
123 logger.debug("PWS observations URL is {}", url.replace(apiKey, REPLACE_API_KEY));
127 private synchronized void refreshPwsObservations() {
128 if (!isBridgeOnline()) {
129 // If bridge is not online, API has not been validated yet
130 logger.debug("Handler: Can't refresh PWS observations because bridge is not online");
133 logger.debug("Handler: Requesting PWS observations from The Weather Company API");
134 String response = executeApiRequest(buildPwsUrl());
135 if (response == null) {
139 logger.debug("Handler: Parsing PWS observations response: {}", response);
140 PwsObservationsDTO pwsObservations = Objects
141 .requireNonNull(gson.fromJson(response, PwsObservationsDTO.class));
142 logger.debug("Handler: Successfully parsed PWS observations response object");
143 updateStatus(ThingStatus.ONLINE);
144 updatePwsObservations(pwsObservations);
145 } catch (JsonSyntaxException e) {
146 logger.debug("Handler: Error parsing pws observations response object: {}", e.getMessage(), e);
147 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error parsing PWS observations");
152 private void updatePwsObservations(PwsObservationsDTO pwsObservations) {
153 if (pwsObservations.observations.length == 0) {
154 logger.debug("Handler: PWS observation object contains no observations!");
157 Observations obs = pwsObservations.observations[0];
158 logger.debug("Handler: Processing observations from station {} at {}", obs.stationID, obs.obsTimeLocal);
159 updateChannel(CH_PWS_TEMP, undefOrQuantity(obs.imperial.temp, ImperialUnits.FAHRENHEIT));
160 updateChannel(CH_PWS_TEMP_HEAT_INDEX, undefOrQuantity(obs.imperial.heatIndex, ImperialUnits.FAHRENHEIT));
161 updateChannel(CH_PWS_TEMP_WIND_CHILL, undefOrQuantity(obs.imperial.windChill, ImperialUnits.FAHRENHEIT));
162 updateChannel(CH_PWS_TEMP_DEW_POINT, undefOrQuantity(obs.imperial.dewpt, ImperialUnits.FAHRENHEIT));
163 updateChannel(CH_PWS_HUMIDITY, undefOrQuantity(obs.humidity, SmartHomeUnits.PERCENT));
164 updateChannel(CH_PWS_PRESSURE, undefOrQuantity(obs.imperial.pressure, ImperialUnits.INCH_OF_MERCURY));
165 updateChannel(CH_PWS_PRECIPTATION_RATE,
166 undefOrQuantity(obs.imperial.precipRate, SmartHomeUnits.INCHES_PER_HOUR));
167 updateChannel(CH_PWS_PRECIPITATION_TOTAL, undefOrQuantity(obs.imperial.precipTotal, ImperialUnits.INCH));
168 updateChannel(CH_PWS_WIND_SPEED, undefOrQuantity(obs.imperial.windSpeed, ImperialUnits.MILES_PER_HOUR));
169 updateChannel(CH_PWS_WIND_GUST, undefOrQuantity(obs.imperial.windGust, ImperialUnits.MILES_PER_HOUR));
170 updateChannel(CH_PWS_WIND_DIRECTION, undefOrQuantity(obs.winddir, SmartHomeUnits.DEGREE_ANGLE));
171 updateChannel(CH_PWS_SOLAR_RADIATION, undefOrQuantity(obs.solarRadiation, SmartHomeUnits.IRRADIANCE));
172 updateChannel(CH_PWS_UV, undefOrDecimal(obs.uv));
173 updateChannel(CH_PWS_OBSERVATION_TIME_LOCAL, undefOrDate(obs.obsTimeUtc));
174 updateChannel(CH_PWS_NEIGHBORHOOD, undefOrString(obs.neighborhood));
175 updateChannel(CH_PWS_STATION_ID, undefOrString(obs.stationID));
176 updateChannel(CH_PWS_COUNTRY, undefOrString(obs.country));
177 updateChannel(CH_PWS_LOCATION, undefOrPoint(obs.lat, obs.lon));
178 updateChannel(CH_PWS_ELEVATION, undefOrQuantity(obs.imperial.elev, ImperialUnits.FOOT));
179 updateChannel(CH_PWS_QC_STATUS, undefOrDecimal(obs.qcStatus));
180 updateChannel(CH_PWS_SOFTWARE_TYPE, undefOrString(obs.softwareType));
184 * The refresh job updates the PWS current observations
185 * on the refresh interval set in the thing config
187 private void scheduleRefreshJob() {
188 logger.debug("Handler: Scheduling observations refresh job in {} seconds", REFRESH_JOB_INITIAL_DELAY_SECONDS);
190 refreshObservationsJob = scheduler.scheduleWithFixedDelay(refreshRunnable, REFRESH_JOB_INITIAL_DELAY_SECONDS,
191 refreshIntervalSeconds, TimeUnit.SECONDS);
194 private void cancelRefreshJob() {
195 if (refreshObservationsJob != null) {
196 refreshObservationsJob.cancel(true);
197 logger.debug("Handler: Canceling observations refresh job");