]> git.basschouten.com Git - openhab-addons.git/blob
24b2becf0201716f1d1b1bf74255c612bfa922ac
[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
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()));
75
76     private final TimeZoneProvider timeZoneProvider;
77
78     // keeps track of the parsed location
79     protected @Nullable PointType location;
80
81     public AbstractOpenWeatherMapHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
82         super(thing);
83         this.timeZoneProvider = timeZoneProvider;
84     }
85
86     @Override
87     public void initialize() {
88         OpenWeatherMapLocationConfiguration config = getConfigAs(OpenWeatherMapLocationConfiguration.class);
89
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");
94             configValid = false;
95         }
96
97         try {
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");
103             location = null;
104             configValid = false;
105         }
106
107         if (configValid) {
108             updateStatus(ThingStatus.UNKNOWN);
109         }
110     }
111
112     @Override
113     public void handleCommand(ChannelUID channelUID, Command command) {
114         if (command instanceof RefreshType) {
115             updateChannel(channelUID);
116         } else {
117             logger.debug("The OpenWeatherMap binding is a read-only binding and cannot handle command '{}'.", command);
118         }
119     }
120
121     @Override
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);
129         }
130     }
131
132     /**
133      * Updates OpenWeatherMap data for this location.
134      *
135      * @param connection {@link OpenWeatherMapConnection} instance
136      */
137     public void updateData(OpenWeatherMapConnection connection) {
138         try {
139             if (requestData(connection)) {
140                 updateChannels();
141                 updateStatus(ThingStatus.ONLINE);
142             }
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());
147         }
148     }
149
150     /**
151      * Requests the data from OpenWeatherMap API.
152      *
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
157      */
158     protected abstract boolean requestData(OpenWeatherMapConnection connection)
159             throws OpenWeatherMapCommunicationException, OpenWeatherMapConfigurationException;
160
161     /**
162      * Updates all channels of this handler from the latest OpenWeatherMap data retrieved.
163      */
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);
170             }
171         }
172     }
173
174     /**
175      * Updates the channel with the given UID from the latest OpenWeatherMap data retrieved.
176      *
177      * @param channelUID UID of the channel
178      */
179     protected abstract void updateChannel(ChannelUID channelUID);
180
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()));
185     }
186
187     protected State getDecimalTypeState(@Nullable Double value) {
188         return (value == null) ? UnDefType.UNDEF : new DecimalType(value);
189     }
190
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));
194     }
195
196     protected State getRawTypeState(@Nullable RawType image) {
197         return (image == null) ? UnDefType.UNDEF : image;
198     }
199
200     protected State getStringTypeState(@Nullable String value) {
201         return (value == null) ? UnDefType.UNDEF : new StringType(value);
202     }
203
204     protected State getQuantityTypeState(@Nullable Number value, Unit<?> unit) {
205         return (value == null) ? UnDefType.UNDEF : new QuantityType<>(value, unit);
206     }
207
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());
221                     continue;
222                 }
223                 channels.add(newChannel);
224             }
225         }
226         logger.debug("Built channels: {}", channels);
227         return channels;
228     }
229
230     protected List<Channel> removeChannelsOfGroup(String channelGroupId) {
231         logger.debug("Removing channel group '{}' from thing '{}'.", channelGroupId, getThing().getUID());
232         return getThing().getChannelsOfGroup(channelGroupId);
233     }
234 }