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.openhab.binding.openuv.internal.ReportConfiguration;
27 import org.openhab.binding.openuv.internal.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.HSBType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.unit.SmartHomeUnits;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.Channel;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.type.ChannelTypeUID;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link OpenUVReportHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Gaël L'hopital - Initial contribution
56 public class OpenUVReportHandler extends BaseThingHandler {
57 private static final int DEFAULT_REFRESH_PERIOD = 30;
59 private final Logger logger = LoggerFactory.getLogger(OpenUVReportHandler.class);
61 private @NonNullByDefault({}) OpenUVBridgeHandler bridgeHandler;
62 private @NonNullByDefault({}) ScheduledFuture<?> refreshJob;
63 private @NonNullByDefault({}) 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 != null && 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 if ((uvMaxJob == null || uvMaxJob.isCancelled())) {
98 State uvMaxTime = openUVData.getUVMaxTime();
99 if (uvMaxTime != UnDefType.NULL) {
100 ZonedDateTime uvMaxZdt = ((DateTimeType) uvMaxTime).getZonedDateTime();
101 long timeDiff = ChronoUnit.MINUTES.between(ZonedDateTime.now(ZoneId.systemDefault()), uvMaxZdt);
103 logger.debug("Scheduling {} in {} minutes", UV_MAX_EVENT, timeDiff);
104 uvMaxJob = scheduler.schedule(() -> {
105 triggerChannel(UV_MAX_EVENT);
107 }, timeDiff, TimeUnit.MINUTES);
114 * Start the job refreshing the data
116 private void startAutomaticRefresh() {
117 if (refreshJob == null || refreshJob.isCancelled()) {
118 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
119 int delay = (config.refresh != null) ? config.refresh.intValue() : DEFAULT_REFRESH_PERIOD;
120 refreshJob = scheduler.scheduleWithFixedDelay(() -> {
121 if (!suspendUpdates) {
122 updateChannels(config);
124 }, 0, delay, TimeUnit.MINUTES);
128 private void updateChannels(ReportConfiguration config) {
129 ThingStatusInfo bridgeStatusInfo = bridgeHandler.getThing().getStatusInfo();
130 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
131 OpenUVResult openUVData = bridgeHandler.getUVData(config.getLatitude(), config.getLongitude(),
132 config.getAltitude());
133 if (openUVData != null) {
134 scheduleUVMaxEvent(openUVData);
135 getThing().getChannels().forEach(channel -> {
136 updateChannel(channel.getUID(), openUVData);
138 updateStatus(ThingStatus.ONLINE);
140 updateStatus(ThingStatus.OFFLINE, bridgeStatusInfo.getStatusDetail(),
141 bridgeStatusInfo.getDescription());
147 public void dispose() {
148 logger.debug("Disposing the OpenUV handler.");
150 if (refreshJob != null && !refreshJob.isCancelled()) {
151 refreshJob.cancel(true);
155 if (uvMaxJob != null && !uvMaxJob.isCancelled()) {
156 uvMaxJob.cancel(true);
161 @SuppressWarnings("unchecked")
163 public void handleCommand(ChannelUID channelUID, Command command) {
164 if (command instanceof RefreshType) {
165 scheduler.execute(() -> {
166 ReportConfiguration config = getConfigAs(ReportConfiguration.class);
167 updateChannels(config);
169 } else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
170 QuantityType<?> qtty = (QuantityType<?>) command;
171 if ("°".equals(qtty.getUnit().toString())) {
172 suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0;
174 logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
177 logger.info("The OpenUV Report Thing handles Refresh or Sun Elevation command and not '{}'", command);
182 * Update the channel from the last OpenUV data retrieved
184 * @param channelUID the id identifying the channel to be updated
188 private void updateChannel(ChannelUID channelUID, OpenUVResult openUVData) {
189 Channel channel = getThing().getChannel(channelUID.getId());
190 if (channel != null && isLinked(channelUID)) {
191 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
192 if (channelTypeUID != null) {
193 switch (channelTypeUID.getId()) {
195 updateState(channelUID, openUVData.getUv());
198 updateState(channelUID, getAsHSB(openUVData.getUv().intValue()));
201 updateState(channelUID, openUVData.getUvMax());
204 updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
207 updateState(channelUID, openUVData.getOzoneTime());
210 updateState(channelUID, openUVData.getUVMaxTime());
213 updateState(channelUID, openUVData.getUVTime());
216 SafeExposureConfiguration configuration = channel.getConfiguration()
217 .as(SafeExposureConfiguration.class);
218 if (configuration.index != -1) {
219 updateState(channelUID,
220 openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
228 private State getAsHSB(int uv) {
230 return HSBType.fromRGB(106, 27, 154);
231 } else if (uv >= 8) {
232 return HSBType.fromRGB(183, 28, 28);
233 } else if (uv >= 6) {
234 return HSBType.fromRGB(239, 108, 0);
235 } else if (uv >= 3) {
236 return HSBType.fromRGB(249, 168, 37);
238 return HSBType.fromRGB(85, 139, 47);