]> git.basschouten.com Git - openhab-addons.git/blob
8d3ccde87d737d9a3dfb883c0a8e5283f6b6e83a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.openweathermap.internal.handler;
14
15 import static org.openhab.binding.openweathermap.internal.OpenWeatherMapBindingConstants.*;
16
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;
22 import java.util.Set;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import javax.measure.Unit;
27
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;
60
61 /**
62  * The {@link AbstractOpenWeatherMapHandler} is responsible for handling commands, which are sent to one of the
63  * channels.
64  *
65  * @author Christoph Weitkamp - Initial contribution
66  */
67 @NonNullByDefault
68 public abstract class AbstractOpenWeatherMapHandler extends BaseThingHandler {
69
70     private final Logger logger = LoggerFactory.getLogger(AbstractOpenWeatherMapHandler.class);
71
72     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(
73             Stream.of(THING_TYPE_WEATHER_AND_FORECAST, THING_TYPE_UVINDEX).collect(Collectors.toSet()));
74
75     private final TimeZoneProvider timeZoneProvider;
76
77     // keeps track of the parsed location
78     protected @Nullable PointType location;
79
80     public AbstractOpenWeatherMapHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
81         super(thing);
82         this.timeZoneProvider = timeZoneProvider;
83     }
84
85     @Override
86     public void initialize() {
87         OpenWeatherMapLocationConfiguration config = getConfigAs(OpenWeatherMapLocationConfiguration.class);
88
89         boolean configValid = true;
90         if (config.location == null || config.location.trim().isEmpty()) {
91             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
92                     "@text/offline.conf-error-missing-location");
93             configValid = false;
94         }
95
96         try {
97             location = new PointType(config.location);
98         } catch (IllegalArgumentException e) {
99             logger.warn("Error parsing 'location' parameter: {}", e.getMessage());
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
101                     "@text/offline.conf-error-parsing-location");
102             location = null;
103             configValid = false;
104         }
105
106         if (configValid) {
107             updateStatus(ThingStatus.UNKNOWN);
108         }
109     }
110
111     @Override
112     public void handleCommand(ChannelUID channelUID, Command command) {
113         if (command instanceof RefreshType) {
114             updateChannel(channelUID);
115         } else {
116             logger.debug("The OpenWeatherMap binding is a read-only binding and cannot handle command '{}'.", command);
117         }
118     }
119
120     @Override
121     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
122         if (ThingStatus.ONLINE.equals(bridgeStatusInfo.getStatus())
123                 && ThingStatusDetail.BRIDGE_OFFLINE.equals(getThing().getStatusInfo().getStatusDetail())) {
124             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
125         } else if (ThingStatus.OFFLINE.equals(bridgeStatusInfo.getStatus())
126                 && !ThingStatus.OFFLINE.equals(getThing().getStatus())) {
127             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
128         }
129     }
130
131     /**
132      * Updates OpenWeatherMap data for this location.
133      *
134      * @param connection {@link OpenWeatherMapConnection} instance
135      */
136     public void updateData(OpenWeatherMapConnection connection) {
137         try {
138             if (requestData(connection)) {
139                 updateChannels();
140                 updateStatus(ThingStatus.ONLINE);
141             }
142         } catch (OpenWeatherMapCommunicationException e) {
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
144         } catch (OpenWeatherMapConfigurationException e) {
145             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
146         }
147     }
148
149     /**
150      * Requests the data from OpenWeatherMap API.
151      *
152      * @param connection {@link OpenWeatherMapConnection} instance
153      * @return true, if the request for the OpenWeatherMap data was successful
154      * @throws OpenWeatherMapCommunicationException
155      * @throws OpenWeatherMapConfigurationException
156      */
157     protected abstract boolean requestData(OpenWeatherMapConnection connection)
158             throws OpenWeatherMapCommunicationException, OpenWeatherMapConfigurationException;
159
160     /**
161      * Updates all channels of this handler from the latest OpenWeatherMap data retrieved.
162      */
163     private void updateChannels() {
164         for (Channel channel : getThing().getChannels()) {
165             ChannelUID channelUID = channel.getUID();
166             if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
167                     && isLinked(channelUID)) {
168                 updateChannel(channelUID);
169             }
170         }
171     }
172
173     /**
174      * Updates the channel with the given UID from the latest OpenWeatherMap data retrieved.
175      *
176      * @param channelUID UID of the channel
177      */
178     protected abstract void updateChannel(ChannelUID channelUID);
179
180     protected State getDateTimeTypeState(@Nullable Integer value) {
181         return (value == null) ? UnDefType.UNDEF
182                 : new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(value.longValue()),
183                         timeZoneProvider.getTimeZone()));
184     }
185
186     protected State getDecimalTypeState(@Nullable Double value) {
187         return (value == null) ? UnDefType.UNDEF : new DecimalType(value);
188     }
189
190     protected State getPointTypeState(@Nullable Double latitude, @Nullable Double longitude) {
191         return ((latitude == null) || (longitude == null)) ? UnDefType.UNDEF
192                 : new PointType(new DecimalType(latitude), new DecimalType(longitude));
193     }
194
195     protected State getRawTypeState(@Nullable RawType image) {
196         return (image == null) ? UnDefType.UNDEF : image;
197     }
198
199     protected State getStringTypeState(@Nullable String value) {
200         return (value == null) ? UnDefType.UNDEF : new StringType(value);
201     }
202
203     protected State getQuantityTypeState(@Nullable Number value, Unit<?> unit) {
204         return (value == null) ? UnDefType.UNDEF : new QuantityType<>(value, unit);
205     }
206
207     protected List<Channel> createChannelsForGroup(String channelGroupId, ChannelGroupTypeUID channelGroupTypeUID) {
208         logger.debug("Building channel group '{}' for thing '{}'.", channelGroupId, getThing().getUID());
209         List<Channel> channels = new ArrayList<>();
210         ThingHandlerCallback callback = getCallback();
211         if (callback != null) {
212             for (ChannelBuilder channelBuilder : callback.createChannelBuilders(
213                     new ChannelGroupUID(getThing().getUID(), channelGroupId), channelGroupTypeUID)) {
214                 Channel newChannel = channelBuilder.build(),
215                         existingChannel = getThing().getChannel(newChannel.getUID().getId());
216                 if (existingChannel != null) {
217                     logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
218                             getThing().getUID(), existingChannel.getUID(), newChannel.getUID());
219                     continue;
220                 }
221                 channels.add(newChannel);
222             }
223         }
224         return channels;
225     }
226
227     protected List<Channel> removeChannelsOfGroup(String channelGroupId) {
228         logger.debug("Removing channel group '{}' from thing '{}'.", channelGroupId, getThing().getUID());
229         return getThing().getChannelsOfGroup(channelGroupId);
230     }
231 }