2 * Copyright (c) 2010-2022 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 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;
51 * The {@link OpenUVReportHandler} is responsible for handling commands, which are
52 * sent to one of the channels.
54 * @author Gaƫl L'hopital - Initial contribution
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);
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));
69 private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
71 private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
72 private @Nullable ScheduledFuture<?> refreshJob;
73 private @Nullable ScheduledFuture<?> uvMaxJob;
74 private boolean suspendUpdates = false;
76 public OpenUVReportHandler(Thing thing) {
81 public void initialize() {
82 logger.debug("Initializing OpenUV handler.");
84 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
86 if (config.refresh < 3) {
87 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
88 "@text/offline.config-error-invalid-refresh");
90 Bridge bridge = getBridge();
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
94 bridgeHandler = (OpenUVBridgeHandler) bridge.getHandler();
95 updateStatus(ThingStatus.UNKNOWN);
96 startAutomaticRefresh();
102 * Start the job screening UV Max reached
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);
114 logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
115 uvMaxJob = scheduler.schedule(() -> {
116 triggerChannel(UV_MAX_EVENT);
118 }, timeDiff, TimeUnit.MINUTES);
125 * Start the job refreshing the data
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);
135 }, 0, config.refresh, TimeUnit.MINUTES);
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);
151 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
152 bridgeStatusInfo.getDescription());
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);
166 ScheduledFuture<?> uxMax = uvMaxJob;
167 if (uxMax != null && !uxMax.isCancelled()) {
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);
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;
185 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
188 logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
192 private State getState(Channel channel, OpenUVResult openUVData) {
193 ChannelUID uid = channel.getUID();
194 switch (uid.getId()) {
196 return new DecimalType(openUVData.getUv());
198 return asAlertLevel(openUVData.getUv());
200 return ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF);
202 return new DecimalType(openUVData.getUvMax());
204 return new QuantityType<>(openUVData.getOzone(), Units.DOBSON_UNIT);
206 return openUVData.getOzoneTime();
208 return openUVData.getUVMaxTime();
210 return openUVData.getUVTime();
212 SafeExposureConfiguration configuration = channel.getConfiguration()
213 .as(SafeExposureConfiguration.class);
214 return openUVData.getSafeExposureTime(configuration.index);
216 return UnDefType.NULL;
219 private State asAlertLevel(double uv) {
222 } else if (uv >= 8) {
224 } else if (uv >= 6) {
226 } else if (uv >= 3) {
231 return UnDefType.NULL;