2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.openuv.internal.handler;
15 import static org.openhab.binding.openuv.internal.OpenUVBindingConstants.*;
17 import java.time.ZoneId;
18 import java.time.ZonedDateTime;
19 import java.time.temporal.ChronoUnit;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import javax.measure.quantity.Angle;
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;
54 * The {@link OpenUVReportHandler} is responsible for handling commands, which are
55 * sent to one of the channels.
57 * @author Gaël L'hopital - Initial contribution
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);
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));
72 private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
74 private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
75 private @Nullable ScheduledFuture<?> refreshJob;
76 private @Nullable ScheduledFuture<?> uvMaxJob;
77 private boolean suspendUpdates = false;
79 public OpenUVReportHandler(Thing thing) {
84 public void initialize() {
85 logger.debug("Initializing OpenUV handler.");
87 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
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");
93 Bridge bridge = getBridge();
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid bridge");
97 bridgeHandler = (OpenUVBridgeHandler) bridge.getHandler();
98 updateStatus(ThingStatus.UNKNOWN);
99 startAutomaticRefresh();
105 * Start the job screening UV Max reached
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);
117 logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
118 uvMaxJob = scheduler.schedule(() -> {
119 triggerChannel(UV_MAX_EVENT);
121 }, timeDiff, TimeUnit.MINUTES);
128 * Start the job refreshing the data
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);
138 }, 0, config.refresh, TimeUnit.MINUTES);
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);
153 updateStatus(ThingStatus.ONLINE);
155 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
156 bridgeStatusInfo.getDescription());
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);
170 ScheduledFuture<?> uxMax = this.uvMaxJob;
171 if (uxMax != null && !uxMax.isCancelled()) {
177 @SuppressWarnings("unchecked")
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);
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;
190 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
193 logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
198 * Update the channel from the last OpenUV data retrieved
200 * @param channelUID the id identifying the channel to be updated
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()) {
211 updateState(channelUID, asDecimalType(openUVData.getUv()));
214 updateState(channelUID, asAlertLevel(openUVData.getUv()));
217 updateState(channelUID,
218 ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF));
221 updateState(channelUID, asDecimalType(openUVData.getUvMax()));
224 updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
227 updateState(channelUID, openUVData.getOzoneTime());
230 updateState(channelUID, openUVData.getUVMaxTime());
233 updateState(channelUID, openUVData.getUVTime());
236 SafeExposureConfiguration configuration = channel.getConfiguration()
237 .as(SafeExposureConfiguration.class);
238 if (configuration.index != -1) {
239 updateState(channelUID,
240 openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
248 private State asDecimalType(int uv) {
250 return new DecimalType(uv);
252 return UnDefType.NULL;
255 private State asAlertLevel(int uv) {
258 } else if (uv >= 8) {
260 } else if (uv >= 6) {
262 } else if (uv >= 3) {
264 } else if (uv >= 1) {
267 return UnDefType.NULL;