[OpenUV] Issue when UV index < 1 (#9198)

* [OpenUV] Correcting incorrect behaviour when UV < 1
and some code enhancements
* Correcting SAT findings
* Initiating bundle localization in French

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2020-12-04 02:31:18 +01:00 committed by GitHub
parent b27ddbe9fc
commit 48dcb27a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 119 additions and 143 deletions

View File

@ -52,7 +52,8 @@ The OpenUV Report thing that is retrieved has these channels:
| Channel ID | Item Type | Description | | Channel ID | Item Type | Description |
|--------------|---------------------|-------------------------------------------------| |--------------|---------------------|-------------------------------------------------|
| UVIndex | Number | UV Index | | 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) | | UVMax | Number | Max UV Index for the day (at solar noon) |
| UVMaxTime | DateTime | Max UV Index datetime (solar noon) | | UVMaxTime | DateTime | Max UV Index datetime (solar noon) |
| Ozone | Number:ArealDensity | Ozone level in du (Dobson Units) from OMI data | | Ozone | Number:ArealDensity | Ozone level in du (Dobson Units) from OMI data |

View File

@ -22,5 +22,5 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public class SafeExposureConfiguration { public class SafeExposureConfiguration {
public int index = -1; public String index = "II";
} }

View File

@ -54,16 +54,14 @@ import com.google.gson.Gson;
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenUVBridgeHandler extends BaseBridgeHandler { 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 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 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 Properties header = new Properties();
private final Gson gson; private final Gson gson;
private final LocationProvider locationProvider; private final LocationProvider locationProvider;
private @Nullable ScheduledFuture<?> reconnectJob; private @Nullable ScheduledFuture<?> reconnectJob;
public OpenUVBridgeHandler(Bridge bridge, LocationProvider locationProvider, Gson gson) { public OpenUVBridgeHandler(Bridge bridge, LocationProvider locationProvider, Gson gson) {
@ -79,10 +77,10 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
if (config.apikey.isEmpty()) { if (config.apikey.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'apikey' must be configured."); "Parameter 'apikey' must be configured.");
} else { return;
header.put("x-access-token", config.apikey);
initiateConnexion();
} }
header.put("x-access-token", config.apikey);
initiateConnexion();
} }
@Override @Override
@ -98,13 +96,13 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
initiateConnexion(); initiateConnexion();
} else { return;
logger.debug("The OpenUV bridge only handles Refresh command and not '{}'", command);
} }
logger.debug("The OpenUV bridge only handles Refresh command and not '{}'", command);
} }
private void initiateConnexion() { 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"); getUVData("0", "0", "0");
} }
@ -113,11 +111,13 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
String jsonData = HttpUtil.executeUrl("GET", String.format(QUERY_URL, latitude, longitude, altitude), String jsonData = HttpUtil.executeUrl("GET", String.format(QUERY_URL, latitude, longitude, altitude),
header, null, null, REQUEST_TIMEOUT_MS); header, null, null, REQUEST_TIMEOUT_MS);
OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class); OpenUVResponse uvResponse = gson.fromJson(jsonData, OpenUVResponse.class);
if (uvResponse.getError() == null) { if (uvResponse != null) {
updateStatus(ThingStatus.ONLINE); String error = uvResponse.getError();
return uvResponse.getResult(); if (error == null) {
} else { updateStatus(ThingStatus.ONLINE);
throw new OpenUVException(uvResponse.getError()); return uvResponse.getResult();
}
throw new OpenUVException(error);
} }
} catch (IOException e) { } catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
@ -133,10 +133,10 @@ public class OpenUVBridgeHandler extends BaseBridgeHandler {
reconnectJob = scheduler.schedule(this::initiateConnexion, reconnectJob = scheduler.schedule(this::initiateConnexion,
Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES); Duration.between(LocalDateTime.now(), tomorrowMidnight).toMinutes(), TimeUnit.MINUTES);
} else if (e.isApiKeyError()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage()); updateStatus(ThingStatus.OFFLINE,
e.isApiKeyError() ? ThingStatusDetail.CONFIGURATION_ERROR : ThingStatusDetail.NONE,
e.getMessage());
} }
} }
return null; return null;

View File

@ -21,8 +21,6 @@ import java.util.Map;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Angle;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openuv.internal.config.ReportConfiguration; import org.openhab.binding.openuv.internal.config.ReportConfiguration;
@ -174,7 +172,6 @@ public class OpenUVReportHandler extends BaseThingHandler {
uvMaxJob = null; uvMaxJob = null;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
@ -184,8 +181,8 @@ public class OpenUVReportHandler extends BaseThingHandler {
}); });
} else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) { } else if (ELEVATION.equals(channelUID.getId()) && command instanceof QuantityType) {
QuantityType<?> qtty = (QuantityType<?>) command; QuantityType<?> qtty = (QuantityType<?>) command;
if ("°".equals(qtty.getUnit().toString())) { if (qtty.getUnit() == SmartHomeUnits.DEGREE_ANGLE) {
suspendUpdates = ((QuantityType<Angle>) qtty).doubleValue() < 0; suspendUpdates = qtty.doubleValue() < 0;
} else { } else {
logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command); logger.info("The OpenUV Report handles Sun Elevation of Number:Angle type, {} does not fit.", command);
} }
@ -208,7 +205,7 @@ public class OpenUVReportHandler extends BaseThingHandler {
if (channelTypeUID != null) { if (channelTypeUID != null) {
switch (channelTypeUID.getId()) { switch (channelTypeUID.getId()) {
case UV_INDEX: case UV_INDEX:
updateState(channelUID, asDecimalType(openUVData.getUv())); updateState(channelUID, new DecimalType(openUVData.getUv()));
break; break;
case ALERT_LEVEL: case ALERT_LEVEL:
updateState(channelUID, asAlertLevel(openUVData.getUv())); updateState(channelUID, asAlertLevel(openUVData.getUv()));
@ -218,7 +215,7 @@ public class OpenUVReportHandler extends BaseThingHandler {
ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF)); ALERT_COLORS.getOrDefault(asAlertLevel(openUVData.getUv()), ALERT_UNDEF));
break; break;
case UV_MAX: case UV_MAX:
updateState(channelUID, asDecimalType(openUVData.getUvMax())); updateState(channelUID, new DecimalType(openUVData.getUvMax()));
break; break;
case OZONE: case OZONE:
updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT)); updateState(channelUID, new QuantityType<>(openUVData.getOzone(), SmartHomeUnits.DOBSON_UNIT));
@ -235,24 +232,14 @@ public class OpenUVReportHandler extends BaseThingHandler {
case SAFE_EXPOSURE: case SAFE_EXPOSURE:
SafeExposureConfiguration configuration = channel.getConfiguration() SafeExposureConfiguration configuration = channel.getConfiguration()
.as(SafeExposureConfiguration.class); .as(SafeExposureConfiguration.class);
if (configuration.index != -1) { updateState(channelUID, openUVData.getSafeExposureTime(configuration.index));
updateState(channelUID,
openUVData.getSafeExposureTime().getSafeExposure(configuration.index));
}
break; break;
} }
} }
} }
} }
private State asDecimalType(int uv) { private State asAlertLevel(double uv) {
if (uv >= 1) {
return new DecimalType(uv);
}
return UnDefType.NULL;
}
private State asAlertLevel(int uv) {
if (uv >= 11) { if (uv >= 11) {
return ALERT_PURPLE; return ALERT_PURPLE;
} else if (uv >= 8) { } else if (uv >= 8) {
@ -261,7 +248,7 @@ public class OpenUVReportHandler extends BaseThingHandler {
return ALERT_ORANGE; return ALERT_ORANGE;
} else if (uv >= 3) { } else if (uv >= 3) {
return ALERT_YELLOW; return ALERT_YELLOW;
} else if (uv >= 1) { } else if (uv > 0) {
return ALERT_GREEN; return ALERT_GREEN;
} }
return UnDefType.NULL; return UnDefType.NULL;

View File

@ -12,21 +12,25 @@
*/ */
package org.openhab.binding.openuv.internal.json; 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 * The {@link OpenUVResponse} is the Java class used to map the JSON
* response to the OpenUV request. * response to the OpenUV request.
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault
public class OpenUVResponse { public class OpenUVResponse {
private String error; private @Nullable String error;
private OpenUVResult result; private @Nullable OpenUVResult result;
public OpenUVResult getResult() { public @Nullable OpenUVResult getResult() {
return result; return result;
} }
public String getError() { public @Nullable String getError() {
return error; return error;
} }
} }

View File

@ -12,14 +12,21 @@
*/ */
package org.openhab.binding.openuv.internal.json; package org.openhab.binding.openuv.internal.json;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.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.State;
import org.openhab.core.types.UnDefType; 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 * The {@link OpenUVResult} is responsible for storing
@ -29,21 +36,37 @@ import org.openhab.core.types.UnDefType;
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenUVResult { public class OpenUVResult {
private final ZonedDateTime DEFAULT_ZDT = ZonedDateTime.of(LocalDateTime.MIN, ZoneId.systemDefault()); private final Logger logger = LoggerFactory.getLogger(OpenUVResult.class);
private double uv;
private ZonedDateTime uvTime = DEFAULT_ZDT;
private double uvMax;
private ZonedDateTime uvMaxTime = DEFAULT_ZDT;
private double ozone;
private ZonedDateTime ozoneTime = DEFAULT_ZDT;
private SafeExposureTime safeExposureTime = new SafeExposureTime();
public int getUv() { public enum FitzpatrickType {
return (int) uv; @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
} }
public int getUvMax() { private double uv;
return (int) uvMax; private @Nullable ZonedDateTime uvTime;
private double uvMax;
private @Nullable ZonedDateTime uvMaxTime;
private double ozone;
private @Nullable ZonedDateTime ozoneTime;
private Map<FitzpatrickType, @Nullable Integer> safeExposureTime = new HashMap<>();
public double getUv() {
return uv;
}
public double getUvMax() {
return uvMax;
} }
public double getOzone() { public double getOzone() {
@ -51,21 +74,30 @@ public class OpenUVResult {
} }
public State getUVTime() { public State getUVTime() {
return uvTime != DEFAULT_ZDT ? new DateTimeType(uvTime.withZoneSameInstant(ZoneId.systemDefault())) ZonedDateTime value = uvTime;
: UnDefType.NULL; return value != null ? new DateTimeType(value) : UnDefType.NULL;
} }
public State getUVMaxTime() { public State getUVMaxTime() {
return uvMaxTime != DEFAULT_ZDT ? new DateTimeType(uvMaxTime.withZoneSameInstant(ZoneId.systemDefault())) ZonedDateTime value = uvMaxTime;
: UnDefType.NULL; return value != null ? new DateTimeType(value) : UnDefType.NULL;
} }
public State getOzoneTime() { public State getOzoneTime() {
return ozoneTime != DEFAULT_ZDT ? new DateTimeType(ozoneTime.withZoneSameInstant(ZoneId.systemDefault())) ZonedDateTime value = ozoneTime;
: UnDefType.NULL; return value != null ? new DateTimeType(value) : UnDefType.NULL;
} }
public SafeExposureTime getSafeExposureTime() { public State getSafeExposureTime(String index) {
return safeExposureTime; 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;
} }
} }

View File

@ -1,62 +0,0 @@
/**
* 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;
}
}

View File

@ -0,0 +1,10 @@
# 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.

View File

@ -67,14 +67,14 @@
<item-type>Number</item-type> <item-type>Number</item-type>
<label>UV Index</label> <label>UV Index</label>
<description>UV Index</description> <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>
<channel-type id="UVMax" advanced="true"> <channel-type id="UVMax" advanced="true">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>UV Max</label> <label>UV Max</label>
<description>Max UV Index for the day (at solar noon)</description> <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>
<channel-type id="Ozone"> <channel-type id="Ozone">
@ -102,37 +102,39 @@
<channel-type id="UVTime" advanced="true"> <channel-type id="UVTime" advanced="true">
<item-type>DateTime</item-type> <item-type>DateTime</item-type>
<label>UV Time</label> <label>Report Timestamp</label>
<description>UV Index timestamp.</description> <description>UV Report timestamp.</description>
<category>time</category> <category>time</category>
<state readOnly="true" pattern="%1$tF %1$tR"/> <state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type> </channel-type>
<channel-type id="UVColor" advanced="true"> <channel-type id="UVColor" advanced="true">
<item-type>Color</item-type> <item-type>Color</item-type>
<label>UV Alert Color</label> <label>Alert Color</label>
<description>Color associated to given UV Index alert level.</description> <description>Color associated to given UV Index alert level.</description>
<category>rgb</category>
<state readOnly="true"/> <state readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="SafeExposure" advanced="false"> <channel-type id="SafeExposure" advanced="false">
<item-type>Number:Time</item-type> <item-type>Number:Time</item-type>
<label>Safe Exposure</label> <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%"/> <state readOnly="true" pattern="%d %unit%"/>
<config-description> <config-description>
<parameter name="index" type="integer"> <parameter name="index" type="text">
<label>Skin Type</label> <label>Skin Type</label>
<description>Fitzpatrick Skin Type.</description> <description>Fitzpatrick Skin Type.</description>
<options> <options>
<option value="1">I White</option> <option value="I">Pale</option>
<option value="2">II White</option> <option value="II">White</option>
<option value="3">III Light brown</option> <option value="III">Light brown</option>
<option value="4">IV Moderate brown</option> <option value="IV">Moderate brown</option>
<option value="5">V Dark brown</option> <option value="V">Dark brown</option>
<option value="6">VI Black</option> <option value="VI">Black</option>
</options> </options>
<default>2</default> <default>II</default>
</parameter> </parameter>
</config-description> </config-description>
</channel-type> </channel-type>
@ -140,7 +142,8 @@
<channel-type id="elevation"> <channel-type id="elevation">
<item-type>Number:Angle</item-type> <item-type>Number:Angle</item-type>
<label>Elevation</label> <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%"/> <state pattern="%.2f %unit%"/>
</channel-type> </channel-type>
@ -154,6 +157,7 @@
<channel-type id="Alert"> <channel-type id="Alert">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>UV Alert</label> <label>UV Alert</label>
<category>alarm</category>
<state readOnly="true"> <state readOnly="true">
<options> <options>
<option value="0">Low</option> <option value="0">Low</option>