]> git.basschouten.com Git - openhab-addons.git/blob
9a7da2063b2d0b4b6653d0951d586691cc0addb8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.List;
21 import java.util.Set;
22
23 import javax.measure.Unit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.openweathermap.internal.config.OpenWeatherMapLocationConfiguration;
28 import org.openhab.binding.openweathermap.internal.connection.OpenWeatherMapConnection;
29 import org.openhab.core.i18n.CommunicationException;
30 import org.openhab.core.i18n.ConfigurationException;
31 import org.openhab.core.i18n.TimeZoneProvider;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.PointType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.RawType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelGroupUID;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingStatusInfo;
45 import org.openhab.core.thing.ThingTypeUID;
46 import org.openhab.core.thing.binding.BaseThingHandler;
47 import org.openhab.core.thing.binding.ThingHandlerCallback;
48 import org.openhab.core.thing.binding.builder.ChannelBuilder;
49 import org.openhab.core.thing.type.ChannelGroupTypeUID;
50 import org.openhab.core.thing.type.ChannelKind;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.openhab.core.types.State;
54 import org.openhab.core.types.UnDefType;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * The {@link AbstractOpenWeatherMapHandler} is responsible for handling commands, which are sent to one of the
60  * channels.
61  *
62  * @author Christoph Weitkamp - Initial contribution
63  */
64 @NonNullByDefault
65 public abstract class AbstractOpenWeatherMapHandler extends BaseThingHandler {
66
67     private final Logger logger = LoggerFactory.getLogger(AbstractOpenWeatherMapHandler.class);
68
69     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_WEATHER_AND_FORECAST,
70             THING_TYPE_UVINDEX, THING_TYPE_AIR_POLLUTION, THING_TYPE_ONECALL_WEATHER_AND_FORECAST,
71             THING_TYPE_ONECALL_HISTORY);
72
73     private final TimeZoneProvider timeZoneProvider;
74
75     // keeps track of the parsed location
76     protected @Nullable PointType location;
77
78     public AbstractOpenWeatherMapHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
79         super(thing);
80         this.timeZoneProvider = timeZoneProvider;
81     }
82
83     @Override
84     public void initialize() {
85         OpenWeatherMapLocationConfiguration config = getConfigAs(OpenWeatherMapLocationConfiguration.class);
86
87         boolean configValid = true;
88         if (config.location == null || config.location.trim().isEmpty()) {
89             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
90                     "@text/offline.conf-error-missing-location");
91             configValid = false;
92         }
93
94         try {
95             location = new PointType(config.location);
96         } catch (IllegalArgumentException e) {
97             logger.warn("Error parsing 'location' parameter: {}", e.getMessage());
98             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
99                     "@text/offline.conf-error-parsing-location");
100             location = null;
101             configValid = false;
102         }
103
104         if (configValid) {
105             updateStatus(ThingStatus.UNKNOWN);
106         }
107     }
108
109     @Override
110     public void handleCommand(ChannelUID channelUID, Command command) {
111         if (command instanceof RefreshType) {
112             updateChannel(channelUID);
113         } else {
114             logger.debug("The OpenWeatherMap binding is a read-only binding and cannot handle command '{}'.", command);
115         }
116     }
117
118     @Override
119     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
120         if (ThingStatus.ONLINE.equals(bridgeStatusInfo.getStatus())
121                 && ThingStatusDetail.BRIDGE_OFFLINE.equals(getThing().getStatusInfo().getStatusDetail())) {
122             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
123         } else if (ThingStatus.OFFLINE.equals(bridgeStatusInfo.getStatus())
124                 && !ThingStatus.OFFLINE.equals(getThing().getStatus())) {
125             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
126         }
127     }
128
129     /**
130      * Updates OpenWeatherMap data for this location.
131      *
132      * @param connection {@link OpenWeatherMapConnection} instance
133      */
134     public void updateData(OpenWeatherMapConnection connection) {
135         try {
136             if (requestData(connection)) {
137                 updateChannels();
138                 updateStatus(ThingStatus.ONLINE);
139             }
140         } catch (CommunicationException e) {
141             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getRawMessage());
142         } catch (ConfigurationException e) {
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
144         }
145     }
146
147     /**
148      * Requests the data from OpenWeatherMap API.
149      *
150      * @param connection {@link OpenWeatherMapConnection} instance
151      * @return true, if the request for the OpenWeatherMap data was successful
152      * @throws CommunicationException if there is a problem retrieving the data
153      * @throws ConfigurationException if there is a configuration error
154      */
155     protected abstract boolean requestData(OpenWeatherMapConnection connection)
156             throws CommunicationException, ConfigurationException;
157
158     /**
159      * Updates all channels of this handler from the latest OpenWeatherMap data retrieved.
160      */
161     private void updateChannels() {
162         for (Channel channel : getThing().getChannels()) {
163             ChannelUID channelUID = channel.getUID();
164             if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
165                     && isLinked(channelUID)) {
166                 updateChannel(channelUID);
167             }
168         }
169     }
170
171     /**
172      * Updates the channel with the given UID from the latest OpenWeatherMap data retrieved.
173      *
174      * @param channelUID UID of the channel
175      */
176     protected abstract void updateChannel(ChannelUID channelUID);
177
178     protected State getDateTimeTypeState(@Nullable Integer value) {
179         return (value == null) ? UnDefType.UNDEF
180                 : new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(value.longValue()),
181                         timeZoneProvider.getTimeZone()));
182     }
183
184     protected State getDecimalTypeState(@Nullable Double value) {
185         return (value == null) ? UnDefType.UNDEF : new DecimalType(value);
186     }
187
188     protected State getPointTypeState(@Nullable Double latitude, @Nullable Double longitude) {
189         return ((latitude == null) || (longitude == null)) ? UnDefType.UNDEF
190                 : new PointType(new DecimalType(latitude), new DecimalType(longitude));
191     }
192
193     protected State getRawTypeState(@Nullable RawType image) {
194         return (image == null) ? UnDefType.UNDEF : image;
195     }
196
197     protected State getStringTypeState(@Nullable String value) {
198         return (value == null) ? UnDefType.UNDEF : new StringType(value);
199     }
200
201     protected State getQuantityTypeState(@Nullable Number value, Unit<?> unit) {
202         return (value == null) ? UnDefType.UNDEF : new QuantityType<>(value, unit);
203     }
204
205     protected List<Channel> createChannelsForGroup(String channelGroupId, ChannelGroupTypeUID channelGroupTypeUID) {
206         logger.debug("Building channel group '{}' for thing '{}' and GroupType '{}'.", channelGroupId,
207                 getThing().getUID(), channelGroupTypeUID);
208         List<Channel> channels = new ArrayList<>();
209         ThingHandlerCallback callback = getCallback();
210         if (callback != null) {
211             for (ChannelBuilder channelBuilder : callback.createChannelBuilders(
212                     new ChannelGroupUID(getThing().getUID(), channelGroupId), channelGroupTypeUID)) {
213                 Channel newChannel = channelBuilder.build(),
214                         existingChannel = getThing().getChannel(newChannel.getUID().getId());
215                 if (existingChannel != null) {
216                     logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
217                             getThing().getUID(), existingChannel.getUID(), newChannel.getUID());
218                     continue;
219                 }
220                 channels.add(newChannel);
221             }
222         }
223         logger.debug("Built channels: {}", channels);
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 }