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