]> git.basschouten.com Git - openhab-addons.git/blob
8694e26a6a9232202555fb58dc1f96f807138396
[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.buienradar.internal;
14
15 import static java.util.concurrent.TimeUnit.MINUTES;
16
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.time.ZonedDateTime;
20 import java.time.temporal.ChronoUnit;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.locks.Lock;
27 import java.util.concurrent.locks.ReentrantLock;
28
29 import org.apache.commons.lang.StringUtils;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.buienradar.internal.buienradarapi.BuienradarPredictionAPI;
32 import org.openhab.binding.buienradar.internal.buienradarapi.Prediction;
33 import org.openhab.binding.buienradar.internal.buienradarapi.PredictionAPI;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.PointType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.unit.Units;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.types.Command;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link BuienradarHandler} is responsible for handling commands, which are
49  * sent to one of the channels.
50  *
51  * @author Edwin de Jong - Initial contribution
52  */
53 @NonNullByDefault
54 public class BuienradarHandler extends BaseThingHandler {
55
56     private final Logger logger = LoggerFactory.getLogger(BuienradarHandler.class);
57
58     private final PredictionAPI client = new BuienradarPredictionAPI();
59
60     private @NonNullByDefault({}) ScheduledFuture<?> listenableFuture;
61
62     /**
63      * Prevents race-condition access to listenableFuture.
64      */
65     private final Lock listenableFutureLock = new ReentrantLock();
66
67     private @NonNullByDefault({}) PointType location;
68
69     private @NonNullByDefault({}) BuienradarConfiguration config;
70
71     public BuienradarHandler(Thing thing) {
72         super(thing);
73     }
74
75     @Override
76     public void handleCommand(ChannelUID channelUID, Command command) {
77     }
78
79     @SuppressWarnings("null")
80     @Override
81     public void initialize() {
82         this.config = getConfigAs(BuienradarConfiguration.class);
83
84         boolean configValid = true;
85         if (StringUtils.trimToNull(config.location) == null) {
86             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
87                     "@text/offline.conf-error-missing-location");
88             configValid = false;
89         }
90
91         try {
92             location = new PointType(config.location);
93         } catch (IllegalArgumentException e) {
94             location = null;
95             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
96                     "@text/offline.conf-error-parsing-location");
97             configValid = false;
98         }
99
100         if (configValid) {
101             updateStatus(ThingStatus.UNKNOWN);
102         }
103         try {
104             listenableFutureLock.lock();
105             if (listenableFuture == null || listenableFuture.isCancelled()) {
106                 listenableFuture = scheduler.scheduleWithFixedDelay(() -> refresh(), 0L, config.refreshIntervalMinutes,
107                         MINUTES);
108             }
109         } finally {
110             listenableFutureLock.unlock();
111         }
112     }
113
114     private void refresh() {
115         refresh(config.retries, ZonedDateTime.now().plusMinutes(config.refreshIntervalMinutes),
116                 config.exponentialBackoffRetryBaseInSeconds);
117     }
118
119     private void refresh(int tries, ZonedDateTime nextRefresh, int retryInSeconds) {
120         if (nextRefresh.isBefore(ZonedDateTime.now())) {
121             // The next refresh is already running, stop retries.
122             return;
123         }
124         if (tries <= 0) {
125             // We are out of tries, stop retrying.
126             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
127             return;
128         }
129         try {
130             @SuppressWarnings("null")
131             final Optional<List<Prediction>> predictionsOpt = client.getPredictions(location);
132             if (!predictionsOpt.isPresent()) {
133                 // Did not get a result, retry the retrieval.
134                 logger.warn("Did not get a result from buienradar. Retrying. {} tries remaining, waiting {} seconds.",
135                         tries, retryInSeconds);
136                 scheduler.schedule(() -> refresh(tries - 1, nextRefresh, retryInSeconds * 2), retryInSeconds,
137                         TimeUnit.SECONDS);
138                 return;
139             }
140             final List<Prediction> predictions = predictionsOpt.get();
141             if (!predictions.isEmpty()) {
142                 final ZonedDateTime actual = predictions.get(0).getActualDateTime();
143                 updateState(BuienradarBindingConstants.ACTUAL_DATETIME, new DateTimeType(actual));
144             }
145             for (final Prediction prediction : predictions) {
146                 final BigDecimal intensity = prediction.getIntensity();
147
148                 final long minutesFromNow = prediction.getActualDateTime().until(prediction.getDateTimeOfPrediction(),
149                         ChronoUnit.MINUTES);
150                 logger.debug("Forecast for {} at {} made at {} is {}", minutesFromNow,
151                         prediction.getDateTimeOfPrediction(), prediction.getActualDateTime(), intensity);
152                 if (minutesFromNow >= 0 && minutesFromNow <= 115) {
153                     final String label = String.format(Locale.ENGLISH, "forecast_%d", minutesFromNow);
154
155                     updateState(label, new QuantityType<>(intensity, Units.MILLIMETRE_PER_HOUR));
156                 }
157             }
158
159             updateStatus(ThingStatus.ONLINE);
160         } catch (IOException e) {
161             logger.warn("Cannot retrieve predictions", e);
162             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
163                     String.format("Could not reach buienradar: %s", e.getMessage()));
164         }
165     }
166
167     @SuppressWarnings("null")
168     @Override
169     public void dispose() {
170         try {
171             listenableFutureLock.lock();
172             if (listenableFuture != null && !listenableFuture.isCancelled()) {
173                 listenableFuture.cancel(true);
174                 listenableFuture = null;
175             }
176         } finally {
177             listenableFutureLock.unlock();
178         }
179     }
180 }