]> git.basschouten.com Git - openhab-addons.git/blob
f0622f345697b13f270c4af532f2f212874aa635
[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.openuv.internal.handler;
14
15 import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
16
17 import java.time.ZoneId;
18 import java.time.ZonedDateTime;
19 import java.time.temporal.ChronoUnit;
20 import java.util.Map;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import javax.measure.quantity.Angle;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.openuv.internal.config.ReportConfiguration;
29 import org.openhab.binding.openuv.internal.config.SafeExposureConfiguration;
30 import org.openhab.binding.openuv.internal.json.OpenUVResult;
31 import org.openhab.core.library.types.DateTimeType;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.HSBType;
34 import org.openhab.core.library.types.PointType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.unit.SmartHomeUnits;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.thing.type.ChannelTypeUID;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.UnDefType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * The {@link OpenUVReportHandler} is responsible for handling commands, which are
55  * sent to one of the channels.
56  *
57  * @author Gaël L'hopital - Initial contribution
58  */
59 @NonNullByDefault
60 public class OpenUVReportHandler extends BaseThingHandler {
61     private static final DecimalType ALERT_GREEN = DecimalType.ZERO;
62     private static final DecimalType ALERT_YELLOW = new DecimalType(1);
63     private static final DecimalType ALERT_ORANGE = new DecimalType(2);
64     private static final DecimalType ALERT_RED = new DecimalType(3);
65     private static final DecimalType ALERT_PURPLE = new DecimalType(4);
66     private static final State ALERT_UNDEF = HSBType.fromRGB(179, 179, 179);
67
68     private static final Map<State, State> ALERT_COLORS = Map.of(ALERT_GREEN, HSBType.fromRGB(85, 139, 47),
69             ALERT_YELLOW, HSBType.fromRGB(249, 168, 37), ALERT_ORANGE, HSBType.fromRGB(239, 108, 0), ALERT_RED,
70             HSBType.fromRGB(183, 28, 28), ALERT_PURPLE, HSBType.fromRGB(106, 27, 154));
71
72     private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
73
74     private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
75     private @Nullable ScheduledFuture<?> refreshJob;
76     private @Nullable ScheduledFuture<?> uvMaxJob;
77     private boolean suspendUpdates = false;
78
79     public OpenUVReportHandler(Thing thing) {
80         super(thing);
81     }
82
83     @Override
84     public void initialize() {
85         logger.debug("Initializing OpenUV handler.");
86
87         ReportConfiguration config = getConfigAs(ReportConfiguration.class);
88
89         if (config.refresh < 3) {
90             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
91                     "Parameter 'refresh' must be higher than 3 minutes to stay in free API plan");
92         } else {
93             Bridge bridge = getBridge();
94             if (bridge == null) {
95                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid bridge");
96             } else {
97                 bridgeHandler = (OpenUVBridgeHandler) bridge.getHandler();
98                 updateStatus(ThingStatus.UNKNOWN);
99                 startAutomaticRefresh();
100             }
101         }
102     }
103
104     /**
105      * Start the job screening UV Max reached
106      *
107      * @param openUVData
108      */
109     private void scheduleUVMaxEvent(OpenUVResult openUVData) {
110         ScheduledFuture<?> job = this.uvMaxJob;
111         if ((job == null || job.isCancelled())) {
112             State uvMaxTime = openUVData.getUVMaxTime();
113             if (uvMaxTime != UnDefType.NULL) {
114                 ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
115                 long timeDiff = ChronoUnit.MINUTES.between(ZonedDateTime.now(ZoneId.systemDefault()), uvMaxZdt);
116                 if (timeDiff > 0) {
117                     logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
118                     uvMaxJob = scheduler.schedule(() -> {
119                         triggerChannel(UV_MAX_EVENT);
120                         uvMaxJob = null;
121                     }, timeDiff, TimeUnit.MINUTES);
122                 }
123             }
124         }
125     }
126
127     /**
128      * Start the job refreshing the data
129      */
130     private void startAutomaticRefresh() {
131         ScheduledFuture<?> job = this.refreshJob;
132         if (job == null || job.isCancelled()) {
133             ReportConfiguration config = getConfigAs(ReportConfiguration.class);
134             refreshJob = scheduler.scheduleWithFixedDelay(() -> {
135                 if (!suspendUpdates) {
136                     updateChannels(config);
137                 }
138             }, 0, config.refresh, TimeUnit.MINUTES);
139         }
140     }
141
142     private void updateChannels(ReportConfiguration config) {
143         ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
144         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
145             PointType location = new PointType(config.location);
146             OpenUVResult openUVData = bridgeHandler.getUVData(location.getLatitude().toString(),
147                     location.getLongitude().toString(), location.getAltitude().toString());
148             if (openUVData != null) {
149                 scheduleUVMaxEvent(openUVData);
150                 getThing().getChannels().forEach(channel -> {
151                     updateChannel(channel.getUID(), openUVData);
152                 });
153                 updateStatus(ThingStatus.ONLINE);
154             } else {
155                 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
156                         bridgeStatusInfo.getDescription());
157             }
158         }
159     }
160
161     @Override
162     public void dispose() {
163         logger.debug("Disposing the OpenUV handler.");
164         ScheduledFuture<?> refresh = this.refreshJob;
165         if (refresh != null && !refresh.isCancelled()) {
166             refresh.cancel(true);
167         }
168         refreshJob = null;
169
170         ScheduledFuture<?> uxMax = this.uvMaxJob;
171         if (uxMax != null && !uxMax.isCancelled()) {
172             uxMax.cancel(true);
173         }
174         uvMaxJob = null;
175     }
176
177     @SuppressWarnings("unchecked")
178     @Override
179     public void handleCommand(ChannelUID channelUID, Command command) {
180         if (command instanceof RefreshType) {
181             scheduler.execute(() -> {
182                 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
183                 updateChannels(config);
184             });
185         } else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
186             QuantityType<?> qtty = (QuantityType<?>) command;
187             if ("°".equals(qtty.getUnit().toString())) {
188                 suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0;
189             } else {
190                 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
191             }
192         } else {
193             logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
194         }
195     }
196
197     /**
198      * Update the channel from the last OpenUV data retrieved
199      *
200      * @param channelUID the id identifying the channel to be updated
201      * @param openUVData
202      *
203      */
204     private void updateChannel(ChannelUID channelUID, OpenUVResult openUVData) {
205         Channel channel = getThing().getChannel(channelUID.getId());
206         if (channel != null && isLinked(channelUID)) {
207             ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
208             if (channelTypeUID != null) {
209                 switch (channelTypeUID.getId()) {
210                     case UV_INDEX:
211                         updateState(channelUID, asDecimalType(openUVData.getUv()));
212                         break;
213                     case ALERT_LEVEL:
214                         updateState(channelUID, asAlertLevel(openUVData.getUv()));
215                         break;
216                     case UV_COLOR:
217                         updateState(channelUID,
218                                 ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF));
219                         break;
220                     case UV_MAX:
221                         updateState(channelUID, asDecimalType(openUVData.getUvMax()));
222                         break;
223                     case OZONE:
224                         updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
225                         break;
226                     case OZONE_TIME:
227                         updateState(channelUID, openUVData.getOzoneTime());
228                         break;
229                     case UV_MAX_TIME:
230                         updateState(channelUID, openUVData.getUVMaxTime());
231                         break;
232                     case UV_TIME:
233                         updateState(channelUID, openUVData.getUVTime());
234                         break;
235                     case SAFE_EXPOSURE:
236                         SafeExposureConfiguration configuration = channel.getConfiguration()
237                                 .as(SafeExposureConfiguration.class);
238                         if (configuration.index != -1) {
239                             updateState(channelUID,
240                                     openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
241                         }
242                         break;
243                 }
244             }
245         }
246     }
247
248     private State asDecimalType(int uv) {
249         if (uv >= 1) {
250             return new DecimalType(uv);
251         }
252         return UnDefType.NULL;
253     }
254
255     private State asAlertLevel(int uv) {
256         if (uv >= 11) {
257             return ALERT_PURPLE;
258         } else if (uv >= 8) {
259             return ALERT_RED;
260         } else if (uv >= 6) {
261             return ALERT_ORANGE;
262         } else if (uv >= 3) {
263             return ALERT_YELLOW;
264         } else if (uv >= 1) {
265             return ALERT_GREEN;
266         }
267         return UnDefType.NULL;
268     }
269 }