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;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import javax.measure.quantity.Angle;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.openuv.internal.config.ReportConfiguration;
28 import org.openhab.binding.openuv.internal.config.SafeExposureConfiguration;
29 import org.openhab.binding.openuv.internal.json.OpenUVResult;
30 import org.openhab.core.library.types.DateTimeType;
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.SmartHomeUnits;
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;
52 * The {@link OpenUVReportHandler} is responsible for handling commands, which are
53 * sent to one of the channels.
55 * @author Gaël L'hopital - Initial contribution
58 public class OpenUVReportHandler extends BaseThingHandler {
59 private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
61 private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
62 private @Nullable ScheduledFuture<?> refreshJob;
63 private @Nullable ScheduledFuture<?> uvMaxJob;
64 private boolean suspendUpdates = false;
66 public OpenUVReportHandler(Thing thing) {
71 public void initialize() {
72 logger.debug("Initializing OpenUV handler.");
74 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
76 if (config.refresh < 3) {
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
78 "Parameter 'refresh' must be higher than 3 minutes to stay in free API plan");
80 Bridge bridge = getBridge();
82 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid bridge");
84 bridgeHandler = (OpenUVBridgeHandler) bridge.getHandler();
85 updateStatus(ThingStatus.UNKNOWN);
86 startAutomaticRefresh();
92 * Start the job screening UV Max reached
96 private void scheduleUVMaxEvent(OpenUVResult openUVData) {
97 ScheduledFuture<?> job = this.uvMaxJob;
98 if ((job == null || job.isCancelled())) {
99 State uvMaxTime = openUVData.getUVMaxTime();
100 if (uvMaxTime != UnDefType.NULL) {
101 ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
102 long timeDiff = ChronoUnit.MINUTES.between(ZonedDateTime.now(ZoneId.systemDefault()), uvMaxZdt);
104 logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
105 uvMaxJob = scheduler.schedule(() -> {
106 triggerChannel(UV_MAX_EVENT);
108 }, timeDiff, TimeUnit.MINUTES);
115 * Start the job refreshing the data
117 private void startAutomaticRefresh() {
118 ScheduledFuture<?> job = this.refreshJob;
119 if (job == null || job.isCancelled()) {
120 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
121 refreshJob = scheduler.scheduleWithFixedDelay(() -> {
122 if (!suspendUpdates) {
123 updateChannels(config);
125 }, 0, config.refresh, TimeUnit.MINUTES);
129 private void updateChannels(ReportConfiguration config) {
130 ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
131 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
132 PointType location = new PointType(config.location);
133 OpenUVResult openUVData = bridgeHandler.getUVData(location.getLatitude().toString(),
134 location.getLongitude().toString(), location.getAltitude().toString());
135 if (openUVData != null) {
136 scheduleUVMaxEvent(openUVData);
137 getThing().getChannels().forEach(channel -> {
138 updateChannel(channel.getUID(), openUVData);
140 updateStatus(ThingStatus.ONLINE);
142 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
143 bridgeStatusInfo.getDescription());
149 public void dispose() {
150 logger.debug("Disposing the OpenUV handler.");
151 ScheduledFuture<?> refresh = this.refreshJob;
152 if (refresh != null && !refresh.isCancelled()) {
153 refresh.cancel(true);
157 ScheduledFuture<?> uxMax = this.uvMaxJob;
158 if (uxMax != null && !uxMax.isCancelled()) {
164 @SuppressWarnings("unchecked")
166 public void handleCommand(ChannelUID channelUID, Command command) {
167 if (command instanceof RefreshType) {
168 scheduler.execute(() -> {
169 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
170 updateChannels(config);
172 } else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
173 QuantityType<?> qtty = (QuantityType<?>) command;
174 if ("°".equals(qtty.getUnit().toString())) {
175 suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0;
177 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
180 logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
185 * Update the channel from the last OpenUV data retrieved
187 * @param channelUID the id identifying the channel to be updated
191 private void updateChannel(ChannelUID channelUID, OpenUVResult openUVData) {
192 Channel channel = getThing().getChannel(channelUID.getId());
193 if (channel != null && isLinked(channelUID)) {
194 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
195 if (channelTypeUID != null) {
196 switch (channelTypeUID.getId()) {
198 updateState(channelUID, openUVData.getUv());
201 updateState(channelUID, getAsHSB(openUVData.getUv().intValue()));
204 updateState(channelUID, openUVData.getUvMax());
207 updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
210 updateState(channelUID, openUVData.getOzoneTime());
213 updateState(channelUID, openUVData.getUVMaxTime());
216 updateState(channelUID, openUVData.getUVTime());
219 SafeExposureConfiguration configuration = channel.getConfiguration()
220 .as(SafeExposureConfiguration.class);
221 if (configuration.index != -1) {
222 updateState(channelUID,
223 openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
231 private State getAsHSB(int uv) {
233 return HSBType.fromRGB(106, 27, 154);
234 } else if (uv >= 8) {
235 return HSBType.fromRGB(183, 28, 28);
236 } else if (uv >= 6) {
237 return HSBType.fromRGB(239, 108, 0);
238 } else if (uv >= 3) {
239 return HSBType.fromRGB(249, 168, 37);
241 return HSBType.fromRGB(85, 139, 47);