2 * Copyright (c) 2010-2021 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.time.Instant;
18 import java.time.ZonedDateTime;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
26 import javax.measure.Unit;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapLocationConfiguration;
31 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapCommunicationException;
32 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConfigurationException;
33 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
34 import org.openhab.core.i18n.TimeZoneProvider;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.PointType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.RawType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelGroupUID;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.ThingHandlerCallback;
51 import org.openhab.core.thing.binding.builder.ChannelBuilder;
52 import org.openhab.core.thing.type.ChannelGroupTypeUID;
53 import org.openhab.core.thing.type.ChannelKind;
54 import org.openhab.core.types.Command;
55 import org.openhab.core.types.RefreshType;
56 import org.openhab.core.types.State;
57 import org.openhab.core.types.UnDefType;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * The {@link AbstractOpenWeatherMapHandler} is responsible for handling commands, which are sent to one of the
65 * @author Christoph Weitkamp - Initial contribution
68 public abstract class AbstractOpenWeatherMapHandler extends BaseThingHandler {
70 private final Logger logger = LoggerFactory.getLogger(AbstractOpenWeatherMapHandler.class);
72 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
73 .unmodifiableSet(Stream.of(THING_TYPE_WEATHER_AND_FORECAST, THING_TYPE_UVINDEX,
74 THING_TYPE_ONECALL_WEATHER_AND_FORECAST, THING_TYPE_ONECALL_HISTORY).collect(Collectors.toSet()));
76 private final TimeZoneProvider timeZoneProvider;
78 // keeps track of the parsed location
79 protected @Nullable PointType location;
81 public AbstractOpenWeatherMapHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
83 this.timeZoneProvider = timeZoneProvider;
87 public void initialize() {
88 OpenWeatherMapLocationConfiguration config = getConfigAs(OpenWeatherMapLocationConfiguration.class);
90 boolean configValid = true;
91 if (config.location == null || config.location.trim().isEmpty()) {
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
93 "@text/offline.conf-error-missing-location");
98 location = new PointType(config.location);
99 } catch (IllegalArgumentException e) {
100 logger.warn("Error parsing 'location' parameter: {}", e.getMessage());
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
102 "@text/offline.conf-error-parsing-location");
108 updateStatus(ThingStatus.UNKNOWN);
113 public void handleCommand(ChannelUID channelUID, Command command) {
114 if (command instanceof RefreshType) {
115 updateChannel(channelUID);
117 logger.debug("The OpenWeatherMap binding is a read-only binding and cannot handle command '{}'.", command);
122 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
123 if (ThingStatus.ONLINE.equals(bridgeStatusInfo.getStatus())
124 && ThingStatusDetail.BRIDGE_OFFLINE.equals(getThing().getStatusInfo().getStatusDetail())) {
125 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
126 } else if (ThingStatus.OFFLINE.equals(bridgeStatusInfo.getStatus())
127 && !ThingStatus.OFFLINE.equals(getThing().getStatus())) {
128 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
133 * Updates OpenWeatherMap data for this location.
135 * @param connection {@link OpenWeatherMapConnection} instance
137 public void updateData(OpenWeatherMapConnection connection) {
139 if (requestData(connection)) {
141 updateStatus(ThingStatus.ONLINE);
143 } catch (OpenWeatherMapCommunicationException e) {
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
145 } catch (OpenWeatherMapConfigurationException e) {
146 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
151 * Requests the data from OpenWeatherMap API.
153 * @param connection {@link OpenWeatherMapConnection} instance
154 * @return true, if the request for the OpenWeatherMap data was successful
155 * @throws OpenWeatherMapCommunicationException if there is a problem retrieving the data
156 * @throws OpenWeatherMapConfigurationException if there is a configuration error
158 protected abstract boolean requestData(OpenWeatherMapConnection connection)
159 throws OpenWeatherMapCommunicationException, OpenWeatherMapConfigurationException;
162 * Updates all channels of this handler from the latest OpenWeatherMap data retrieved.
164 private void updateChannels() {
165 for (Channel channel : getThing().getChannels()) {
166 ChannelUID channelUID = channel.getUID();
167 if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
168 && isLinked(channelUID)) {
169 updateChannel(channelUID);
175 * Updates the channel with the given UID from the latest OpenWeatherMap data retrieved.
177 * @param channelUID UID of the channel
179 protected abstract void updateChannel(ChannelUID channelUID);
181 protected State getDateTimeTypeState(@Nullable Integer value) {
182 return (value == null) ? UnDefType.UNDEF
183 : new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(value.longValue()),
184 timeZoneProvider.getTimeZone()));
187 protected State getDecimalTypeState(@Nullable Double value) {
188 return (value == null) ? UnDefType.UNDEF : new DecimalType(value);
191 protected State getPointTypeState(@Nullable Double latitude, @Nullable Double longitude) {
192 return ((latitude == null) || (longitude == null)) ? UnDefType.UNDEF
193 : new PointType(new DecimalType(latitude), new DecimalType(longitude));
196 protected State getRawTypeState(@Nullable RawType image) {
197 return (image == null) ? UnDefType.UNDEF : image;
200 protected State getStringTypeState(@Nullable String value) {
201 return (value == null) ? UnDefType.UNDEF : new StringType(value);
204 protected State getQuantityTypeState(@Nullable Number value, Unit<?> unit) {
205 return (value == null) ? UnDefType.UNDEF : new QuantityType<>(value, unit);
208 protected List<Channel> createChannelsForGroup(String channelGroupId, ChannelGroupTypeUID channelGroupTypeUID) {
209 logger.debug("Building channel group '{}' for thing '{}' and GroupType '{}'.", channelGroupId,
210 getThing().getUID(), channelGroupTypeUID);
211 List<Channel> channels = new ArrayList<>();
212 ThingHandlerCallback callback = getCallback();
213 if (callback != null) {
214 for (ChannelBuilder channelBuilder : callback.createChannelBuilders(
215 new ChannelGroupUID(getThing().getUID(), channelGroupId), channelGroupTypeUID)) {
216 Channel newChannel = channelBuilder.build(),
217 existingChannel = getThing().getChannel(newChannel.getUID().getId());
218 if (existingChannel != null) {
219 logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
220 getThing().getUID(), existingChannel.getUID(), newChannel.getUID());
223 channels.add(newChannel);
226 logger.debug("Built channels: {}", channels);
230 protected List<Channel> removeChannelsOfGroup(String channelGroupId) {
231 logger.debug("Removing channel group '{}' from thing '{}'.", channelGroupId, getThing().getUID());
232 return getThing().getChannelsOfGroup(channelGroupId);