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