]> git.basschouten.com Git - openhab-addons.git/blob
a0aaac8be5436732edf02b241f8e1f734d34a948
[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.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().stream().filter(channel -> isLinked(channel.getUID().getId()))
149                         .forEach(channel -> updateState(channel.getUID(), getState(channel, openUVData)));
150                 updateStatus(ThingStatus.ONLINE);
151             } else {
152                 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
153                         bridgeStatusInfo.getDescription());
154             }
155         }
156     }
157
158     @Override
159     public void dispose() {
160         logger.debug("Disposing the OpenUV handler.");
161         ScheduledFuture<?> refresh = refreshJob;
162         if (refresh != null && !refresh.isCancelled()) {
163             refresh.cancel(true);
164         }
165         refreshJob = null;
166
167         ScheduledFuture<?> uxMax = uvMaxJob;
168         if (uxMax != null && !uxMax.isCancelled()) {
169             uxMax.cancel(true);
170         }
171         uvMaxJob = null;
172     }
173
174     @Override
175     public void handleCommand(ChannelUID channelUID, Command command) {
176         if (command instanceof RefreshType) {
177             scheduler.execute(() -> {
178                 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
179                 updateChannels(config);
180             });
181         } else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
182             QuantityType<?> qtty = (QuantityType<?>) command;
183             if (Units.DEGREE_ANGLE.equals(qtty.getUnit())) {
184                 suspendUpdates = qtty.doubleValue() < 0;
185             } else {
186                 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
187             }
188         } else {
189             logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
190         }
191     }
192
193     private State getState(Channel channel, OpenUVResult openUVData) {
194         ChannelUID uid = channel.getUID();
195         switch (uid.getId()) {
196             case UV_INDEX:
197                 return new DecimalType(openUVData.getUv());
198             case ALERT_LEVEL:
199                 return asAlertLevel(openUVData.getUv());
200             case UV_COLOR:
201                 return ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF);
202             case UV_MAX:
203                 return new DecimalType(openUVData.getUvMax());
204             case OZONE:
205                 return new QuantityType<>(openUVData.getOzone(), Units.DOBSON_UNIT);
206             case OZONE_TIME:
207                 return openUVData.getOzoneTime();
208             case UV_MAX_TIME:
209                 return openUVData.getUVMaxTime();
210             case UV_TIME:
211                 return openUVData.getUVTime();
212         }
213         ChannelTypeUID channelType = channel.getChannelTypeUID();
214         if (channelType != null && SAFE_EXPOSURE.equals(channelType.getId())) {
215             SafeExposureConfiguration configuration = channel.getConfiguration().as(SafeExposureConfiguration.class);
216             return openUVData.getSafeExposureTime(configuration.index);
217         }
218         return UnDefType.NULL;
219     }
220
221     private State asAlertLevel(double uv) {
222         if (uv >= 11) {
223             return ALERT_PURPLE;
224         } else if (uv >= 8) {
225             return ALERT_RED;
226         } else if (uv >= 6) {
227             return ALERT_ORANGE;
228         } else if (uv >= 3) {
229             return ALERT_YELLOW;
230         } else if (uv > 0) {
231             return ALERT_GREEN;
232         }
233         return UnDefType.NULL;
234     }
235 }