| Channel ID | Item Type | Description |
|--------------|---------------------|-------------------------------------------------|
| UVIndex | Number | UV Index |
-| UVColor | Color | Color associated to given UV Index. |
+| Alert | Number | Alert level associated to given UV Index |
+| UVColor | Color | Color associated to given alert level. |
| UVMax | Number | Max UV Index for the day (at solar noon) |
| UVMaxTime | DateTime | Max UV Index datetime (solar noon) |
| Ozone | Number:ArealDensity | Ozone level in du (Dobson Units) from OMI data |
*/
@NonNullByDefault
public class SafeExposureConfiguration {
- public int index = -1;
+ public String index = "II";
}
*/
@NonNullByDefault
public class OpenUVBridgeHandler extends BaseBridgeHandler {
- private final Logger logger = LoggerFactory.getLogger(OpenUVBridgeHandler.class);
-
private static final String QUERY_URL = "https://api.openuv.io/api/v1/uv?lat=%s&lng=%s&alt=%s";
-
private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30);
+ private final Logger logger = LoggerFactory.getLogger(OpenUVBridgeHandler.class);
private final Properties header = new Properties();
private final Gson gson;
-
private final LocationProvider locationProvider;
+
private @Nullable ScheduledFuture<?> reconnectJob;
public OpenUVBridgeHandler(Bridge bridge, LocationProvider locationProvider, Gson gson) {
if (config.apikey.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'apikey' must be configured.");
- } else {
- header.put("x-access-token", config.apikey);
- initiateConnexion();
+ return;
}
+ header.put("x-access-token", config.apikey);
+ initiateConnexion();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
initiateConnexion();
- } else {
- logger.debug("The OpenUV bridge only handles Refresh command and not '{}'", command);
+ return;
}
+ logger.debug("The OpenUV bridge only handles Refresh command and not '{}'", command);
}
private void initiateConnexion() {
- // Check if the provided api key is valid for use with the OpenUV service
+ // Just checking if the provided api key is a valid one by making a fake call
getUVData("0", "0", "0");
}
String jsonData = HttpUtil.executeUrl("GET", String.format(QUERY_URL, latitude, longitude, altitude),
header, null, null, REQUEST_TIMEOUT_MS);
OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class);
- if (uvResponse.getError() == null) {
- updateStatus(ThingStatus.ONLINE);
- return uvResponse.getResult();
- } else {
- throw new OpenUVException(uvResponse.getError());
+ if (uvResponse != null) {
+ String error = uvResponse.getError();
+ if (error == null) {
+ updateStatus(ThingStatus.ONLINE);
+ return uvResponse.getResult();
+ }
+ throw new OpenUVException(error);
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
reconnectJob = scheduler.schedule(this::initiateConnexion,
Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
- } else if (e.isApiKeyError()) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
+ updateStatus(ThingStatus.OFFLINE,
+ e.isApiKeyError() ? ThingStatusDetail.CONFIGURATION_ERROR : ThingStatusDetail.NONE,
+ e.getMessage());
}
}
return null;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-import javax.measure.quantity.Angle;
-
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.config.ReportConfiguration;
uvMaxJob = null;
}
- @SuppressWarnings("unchecked")
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
});
} else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
QuantityType<?> qtty = (QuantityType<?>) command;
- if ("°".equals(qtty.getUnit().toString())) {
- suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0;
+ if (qtty.getUnit() == SmartHomeUnits.DEGREE_ANGLE) {
+ suspendUpdates = qtty.doubleValue() < 0;
} else {
logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
}
if (channelTypeUID != null) {
switch (channelTypeUID.getId()) {
case UV_INDEX:
- updateState(channelUID, asDecimalType(openUVData.getUv()));
+ updateState(channelUID, new DecimalType(openUVData.getUv()));
break;
case ALERT_LEVEL:
updateState(channelUID, asAlertLevel(openUVData.getUv()));
ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF));
break;
case UV_MAX:
- updateState(channelUID, asDecimalType(openUVData.getUvMax()));
+ updateState(channelUID, new DecimalType(openUVData.getUvMax()));
break;
case OZONE:
updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
case SAFE_EXPOSURE:
SafeExposureConfiguration configuration = channel.getConfiguration()
.as(SafeExposureConfiguration.class);
- if (configuration.index != -1) {
- updateState(channelUID,
- openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
- }
+ updateState(channelUID, openUVData.getSafeExposureTime(configuration.index));
break;
}
}
}
}
- private State asDecimalType(int uv) {
- if (uv >= 1) {
- return new DecimalType(uv);
- }
- return UnDefType.NULL;
- }
-
- private State asAlertLevel(int uv) {
+ private State asAlertLevel(double uv) {
if (uv >= 11) {
return ALERT_PURPLE;
} else if (uv >= 8) {
return ALERT_ORANGE;
} else if (uv >= 3) {
return ALERT_YELLOW;
- } else if (uv >= 1) {
+ } else if (uv > 0) {
return ALERT_GREEN;
}
return UnDefType.NULL;
*/
package org.openhab.binding.openuv.internal.json;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
/**
* The {@link OpenUVResponse} is the Java class used to map the JSON
* response to the OpenUV request.
*
* @author Gaël L'hopital - Initial contribution
*/
+@NonNullByDefault
public class OpenUVResponse {
- private String error;
- private OpenUVResult result;
+ private @Nullable String error;
+ private @Nullable OpenUVResult result;
- public OpenUVResult getResult() {
+ public @Nullable OpenUVResult getResult() {
return result;
}
- public String getError() {
+ public @Nullable String getError() {
return error;
}
}
*/
package org.openhab.binding.openuv.internal.json;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.annotations.SerializedName;
/**
* The {@link OpenUVResult} is responsible for storing
*/
@NonNullByDefault
public class OpenUVResult {
- private final ZonedDateTime DEFAULT_ZDT = ZonedDateTime.of(LocalDateTime.MIN, ZoneId.systemDefault());
+ private final Logger logger = LoggerFactory.getLogger(OpenUVResult.class);
+
+ public enum FitzpatrickType {
+ @SerializedName("st1")
+ I, // Fitzpatrick Skin Type I
+ @SerializedName("st2")
+ II, // Fitzpatrick Skin Type II
+ @SerializedName("st3")
+ III, // Fitzpatrick Skin Type III
+ @SerializedName("st4")
+ IV, // Fitzpatrick Skin Type IV
+ @SerializedName("st5")
+ V, // Fitzpatrick Skin Type V
+ @SerializedName("st6")
+ VI;// Fitzpatrick Skin Type VI
+ }
+
private double uv;
- private ZonedDateTime uvTime = DEFAULT_ZDT;
+ private @Nullable ZonedDateTime uvTime;
private double uvMax;
- private ZonedDateTime uvMaxTime = DEFAULT_ZDT;
+ private @Nullable ZonedDateTime uvMaxTime;
private double ozone;
- private ZonedDateTime ozoneTime = DEFAULT_ZDT;
- private SafeExposureTime safeExposureTime = new SafeExposureTime();
+ private @Nullable ZonedDateTime ozoneTime;
+ private Map<FitzpatrickType, @Nullable Integer> safeExposureTime = new HashMap<>();
- public int getUv() {
- return (int) uv;
+ public double getUv() {
+ return uv;
}
- public int getUvMax() {
- return (int) uvMax;
+ public double getUvMax() {
+ return uvMax;
}
public double getOzone() {
}
public State getUVTime() {
- return uvTime != DEFAULT_ZDT ? new DateTimeType(uvTime.withZoneSameInstant(ZoneId.systemDefault()))
- : UnDefType.NULL;
+ ZonedDateTime value = uvTime;
+ return value != null ? new DateTimeType(value) : UnDefType.NULL;
}
public State getUVMaxTime() {
- return uvMaxTime != DEFAULT_ZDT ? new DateTimeType(uvMaxTime.withZoneSameInstant(ZoneId.systemDefault()))
- : UnDefType.NULL;
+ ZonedDateTime value = uvMaxTime;
+ return value != null ? new DateTimeType(value) : UnDefType.NULL;
}
public State getOzoneTime() {
- return ozoneTime != DEFAULT_ZDT ? new DateTimeType(ozoneTime.withZoneSameInstant(ZoneId.systemDefault()))
- : UnDefType.NULL;
+ ZonedDateTime value = ozoneTime;
+ return value != null ? new DateTimeType(value) : UnDefType.NULL;
}
- public SafeExposureTime getSafeExposureTime() {
- return safeExposureTime;
+ public State getSafeExposureTime(String index) {
+ try {
+ FitzpatrickType value = FitzpatrickType.valueOf(index);
+ Integer duration = safeExposureTime.get(value);
+ if (duration != null) {
+ return new QuantityType<>(duration, SmartHomeUnits.MINUTE);
+ }
+ } catch (IllegalArgumentException e) {
+ logger.warn("Unexpected Fitzpatrick index value '{}' : {}", index, e.getMessage());
+ }
+ return UnDefType.NULL;
}
}
+++ /dev/null
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openuv.internal.json;
-
-import java.math.BigInteger;
-
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.SmartHomeUnits;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-/**
- * Wrapper type around values reported by OpenUV safe exposure time.
- *
- * @author Gaël L'hopital - Initial contribution
- */
-public class SafeExposureTime {
- public @Nullable BigInteger st1;
- public @Nullable BigInteger st2;
- public @Nullable BigInteger st3;
- public @Nullable BigInteger st4;
- public @Nullable BigInteger st5;
- public @Nullable BigInteger st6;
-
- public State getSafeExposure(int index) {
- BigInteger result;
- switch (index) {
- case 1:
- result = st1;
- break;
- case 2:
- result = st2;
- break;
- case 3:
- result = st3;
- break;
- case 4:
- result = st4;
- break;
- case 5:
- result = st5;
- break;
- case 6:
- result = st6;
- break;
- default:
- result = null;
- }
- return (result != null) ? new QuantityType<>(result, SmartHomeUnits.MINUTE) : UnDefType.NULL;
- }
-}
--- /dev/null
+# binding
+binding.openuv.name = Extension OpenUV
+binding.openuv.description = Service de prévision globale de l'indice UV en temps réel.
+
+# thing types
+thing-type.openuv.openuvapi.label = Bridge OpenUV
+thing-type.openuv.openuvapi.description = Passerelle vers le service du projet OpenUV. Pour recevoir des données vous devez créer votre compte à l'adresse https://www.openuv.io/auth/google et obtenir votre clef API.
+
+thing-type.openuv.uvreport.label = Rapport UV
+thing-type.openuv.uvreport.description = Fournit diverses information pour un emplacement donnée.
<item-type>Number</item-type>
<label>UV Index</label>
<description>UV Index</description>
- <state readOnly="true" pattern="%d/16" min="0" max="16"/>
+ <state readOnly="true" pattern="%.0f/16" min="0" max="16"/>
</channel-type>
<channel-type id="UVMax" advanced="true">
<item-type>Number</item-type>
<label>UV Max</label>
<description>Max UV Index for the day (at solar noon)</description>
- <state readOnly="true" pattern="%d/16" min="0" max="16"/>
+ <state readOnly="true" pattern="%.0f/16" min="0" max="16"/>
</channel-type>
<channel-type id="Ozone">
<channel-type id="UVTime" advanced="true">
<item-type>DateTime</item-type>
- <label>UV Time</label>
- <description>UV Index timestamp.</description>
+ <label>Report Timestamp</label>
+ <description>UV Report timestamp.</description>
<category>time</category>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="UVColor" advanced="true">
<item-type>Color</item-type>
- <label>UV Alert Color</label>
+ <label>Alert Color</label>
<description>Color associated to given UV Index alert level.</description>
+ <category>rgb</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="SafeExposure" advanced="false">
<item-type>Number:Time</item-type>
<label>Safe Exposure</label>
- <description>Safe exposure time for Fitzpatrick Skin Types</description>
+ <description>Safe exposure duration for Fitzpatrick Skin Types.</description>
+ <category>time</category>
<state readOnly="true" pattern="%d %unit%"/>
<config-description>
- <parameter name="index" type="integer">
+ <parameter name="index" type="text">
<label>Skin Type</label>
<description>Fitzpatrick Skin Type.</description>
<options>
- <option value="1">I – White</option>
- <option value="2">II – White</option>
- <option value="3">III – Light brown</option>
- <option value="4">IV – Moderate brown</option>
- <option value="5">V – Dark brown</option>
- <option value="6">VI – Black</option>
+ <option value="I">Pale</option>
+ <option value="II">White</option>
+ <option value="III">Light brown</option>
+ <option value="IV">Moderate brown</option>
+ <option value="V">Dark brown</option>
+ <option value="VI">Black</option>
</options>
- <default>2</default>
+ <default>II</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="elevation">
<item-type>Number:Angle</item-type>
<label>Elevation</label>
- <description>The elevation of the sun</description>
+ <description>The elevation of the sun (should FOLLOW appropriate item).</description>
+ <category>niveau</category>
<state pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="Alert">
<item-type>Number</item-type>
<label>UV Alert</label>
+ <category>alarm</category>
<state readOnly="true">
<options>
<option value="0">Low</option>