Code review corrections

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2024-12-12 12:46:58 +01:00
parent 02bf3c1334
commit 080f7a5172
12 changed files with 111 additions and 58 deletions

View File

@ -61,7 +61,8 @@ Please check that proposed value is correct according to the place.
| aq-bulletin-tomorrow | pm10-max | Number:Density | R | Maximum level of PM 10 concentration | | aq-bulletin-tomorrow | pm10-max | Number:Density | R | Maximum level of PM 10 concentration |
| aq-bulletin-tomorrow | pm25-min | Number:Density | R | Minimum level of PM 2.5 concentration | | aq-bulletin-tomorrow | pm25-min | Number:Density | R | Minimum level of PM 2.5 concentration |
| aq-bulletin-tomorrow | pm25-max | Number:Density | R | Maximum level of PM 2.5 concentration | | aq-bulletin-tomorrow | pm25-max | Number:Density | R | Maximum level of PM 2.5 concentration |
| daily | message | String | R | Today's daily general information ||| daily | tomorrow | String | R | Tomorrow's daily general information | | daily | message | String | R | Today's daily general information |
| daily | tomorrow | String | R | Tomorrow's daily general information |
### `location` Thing Channels ### `location` Thing Channels
@ -150,19 +151,75 @@ This binding has its own IconProvider and makes available the following list of
| oh:airparif:willow | Yes | ![](doc/images/willow.svg) | | oh:airparif:willow | Yes | ![](doc/images/willow.svg) |
| oh:airparif:wormwood | Yes | ![](doc/images/wormwood.svg) | | oh:airparif:wormwood | Yes | ![](doc/images/wormwood.svg) |
## Full Examplee ## Full Examplee
### Thing Configurationn ### Thing Configurationn
```jav ```java
Bridge airparif:api:local "AirParif" [ apikey="xxxxx-dddd-cccc-4321-zzzzzzzzzzzzz" ] { Bridge airparif:api:local "AirParif" [ apikey="xxxxx-dddd-cccc-4321-zzzzzzzzzzzzz" ] {
location yvelines "Yvelines" [ department="78", location="52.639,1.8284" ] location 78 "Yvelines" [ department="78", location="52.639,1.8284" ]
}a }
``` ```
### Item Configurationn ### Item Configurationn
```java ```java
Example item configuration goes here. String AirParifPollensComment "Situation" {channel="airparif:api:local:pollens#comment"}
DateTime AirParifPollensBeginValidity "Begin validity" {channel="airparif:api:local:pollens#begin-validity"}
DateTime AirParifPollensEndValidity "End validity" {channel="airparif:api:local:pollens#end-validity"}
String AirParifAqBulletinComment "Message" {channel="airparif:api:local:aq-bulletin#comment"}
Number:Density AirParifAqBulletinNo2Min "No2 min" {channel="airparif:api:local:aq-bulletin#no2-min"}
Number:Density AirParifAqBulletinNo2Max "No2 max" {channel="airparif:api:local:aq-bulletin#no2-max"}
Number:Density AirParifAqBulletinO3Min "O3 min" {channel="airparif:api:local:aq-bulletin#o3-min"}
Number:Density AirParifAqBulletinO3Max "O3 max" {channel="airparif:api:local:aq-bulletin#o3-max"}
Number:Density AirParifAqBulletinPm10Min "Pm 10 min" {channel="airparif:api:local:aq-bulletin#pm10-min"}
Number:Density AirParifAqBulletinPm10Max "Pm 10 max" {channel="airparif:api:local:aq-bulletin#pm10-max"}
Number:Density AirParifAqBulletinPm25Min "Pm 2.5 min" {channel="airparif:api:local:aq-bulletin#pm25-min"}
Number:Density AirParifAqBulletinPm25Max "Pm 2.5 max" {channel="airparif:api:local:aq-bulletin#pm25-max"}
String AirParifAqBulletinTomorrowComment "Message" {channel="airparif:api:local:aq-bulletin-tomorrow#comment"}
Number:Density AirParifAqBulletinTomorrowNo2Min "No2 min" {channel="airparif:api:local:aq-bulletin-tomorrow#no2-min"}
Number:Density AirParifAqBulletinTomorrowNo2Max "No2 max" {channel="airparif:api:local:aq-bulletin-tomorrow#no2-max"}
Number:Density AirParifAqBulletinTomorrowO3Min "O3 min" {channel="airparif:api:local:aq-bulletin-tomorrow#o3-min"}
Number:Density AirParifAqBulletinTomorrowO3Max "O3 max" {channel="airparif:api:local:aq-bulletin-tomorrow#o3-max"}
Number:Density AirParifAqBulletinTomorrowPm10Min "Pm 10 min" {channel="airparif:api:local:aq-bulletin-tomorrow#pm10-min"}
Number:Density AirParifAqBulletinTomorrowPm10Max "Pm 10 max" {channel="airparif:api:local:aq-bulletin-tomorrow#pm10-max"}
Number:Density AirParifAqBulletinTomorrowPm25Min "Pm 2.5 min" {channel="airparif:api:local:aq-bulletin-tomorrow#pm25-min"}
Number:Density AirParifAqBulletinTomorrowPm25Max "Pm 2.5 max" {channel="airparif:api:local:aq-bulletin-tomorrow#pm25-max"}
String AirParifDailyMessage "Message" {channel="airparif:api:local:daily#message"}
String AirParifDailyTomorrow "Tomorrow" {channel="airparif:api:local:daily#tomorrow"}
Number Yvelines_Pollens_Cypress "Cypress" {channel="airparif:location:local:78:pollens#cypress"}
Number Yvelines_Pollens_Hazel "Hazel level" {channel="airparif:location:local:78:pollens#hazel"}
Number Yvelines_Pollens_Alder "Alder" {channel="airparif:location:local:78:pollens#alder"}
Number Yvelines_Pollens_Poplar "Poplar" {channel="airparif:location:local:78:pollens#poplar"}
Number Yvelines_Pollens_Willow "Willow" {channel="airparif:location:local:78:pollens#willow"}
Number Yvelines_Pollens_Ash "Ash" {channel="airparif:location:local:78:pollens#ash"}
Number Yvelines_Pollens_Hornbeam "Hornbeam" {channel="airparif:location:local:78:pollens#hornbeam"}
Number Yvelines_Pollens_Birch "Birch level" {channel="airparif:location:local:78:pollens#birch"}
Number Yvelines_Pollens_Plane "Plane" {channel="airparif:location:local:78:pollens#plane"}
Number Yvelines_Pollens_Oak "Oak" {channel="airparif:location:local:78:pollens#oak"}
Number Yvelines_Pollens_Olive "Olive" {channel="airparif:location:local:78:pollens#olive"}
Number Yvelines_Pollens_Linden "Linden" {channel="airparif:location:local:78:pollens#linden"}
Number Yvelines_Pollens_Chestnut "Chestnut" {channel="airparif:location:local:78:pollens#chestnut"}
Number Yvelines_Pollens_Rumex "Rumex" {channel="airparif:location:local:78:pollens#rumex"}
Number Yvelines_Pollens_Grasses "Grasses" {channel="airparif:location:local:78:pollens#grasses"}
Number Yvelines_Pollens_Plantain "Plantain" {channel="airparif:location:local:78:pollens#plantain"}
Number Yvelines_Pollens_Urticaceae "Urticacea" {channel="airparif:location:local:78:pollens#urticaceae"}
Number Yvelines_Pollens_Wormwood "Wormwood" {channel="airparif:location:local:78:pollens#wormwood"}
Number Yvelines_Pollens_Ragweed "Ragweed" {channel="airparif:location:local:78:pollens#ragweed"}
String Yvelines_Indice_Message "Message" {channel="airparif:location:local:78:indice#message"}
DateTime Yvelines_Indice_Timestamp "Timestamp" {channel="airparif:location:local:78:indice#timestamp"}
Number Yvelines_Indice_Alert "Index" {channel="airparif:location:local:78:indice#alert"}
String Yvelines_O3_Message "Message" {channel="airparif:location:local:78:o3#message"}
Number:Density Yvelines_O3_Value "Concentration" {channel="airparif:location:local:78:o3#value"}
Number Yvelines_O3_Alert "Alert level" {channel="airparif:location:local:78:o3#alert"}
String Yvelines_No2_Message "Message" {channel="airparif:location:local:78:no2#message"}
Number:Density Yvelines_No2_Value "Concentration" {channel="airparif:location:local:78:no2#value"}
Number Yvelines_No2_Alert "Alert level" {channel="airparif:location:local:78:no2#alert"}
String Yvelines_Pm25_Message "Message" {channel="airparif:location:local:78:pm25#message"}
Number:Density Yvelines_Pm25_Value "Concentration" {channel="airparif:location:local:78:pm25#value"}
Number Yvelines_Pm25_Alert "Alert level" {channel="airparif:location:local:78:pm25#alert"}
String Yvelines_Pm10_Message "Message" {channel="airparif:location:local:78:pm10#message"}
Number:Density Yvelines_Pm10_Value "Concentration" {channel="airparif:location:local:78:pm10#value"}
Number Yvelines_Pm10_Alert "Alert level" {channel="airparif:location:local:78:pm10#alert"}
`` ``

View File

@ -16,7 +16,6 @@ import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*;
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.eclipse.jetty.client.HttpClient;
import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer;
import org.openhab.binding.airparif.internal.handler.AirParifBridgeHandler; import org.openhab.binding.airparif.internal.handler.AirParifBridgeHandler;
import org.openhab.binding.airparif.internal.handler.LocationHandler; import org.openhab.binding.airparif.internal.handler.LocationHandler;
@ -40,12 +39,12 @@ import org.osgi.service.component.annotations.Reference;
@Component(configurationPid = "binding.airparif", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.airparif", service = ThingHandlerFactory.class)
public class AirParifHandlerFactory extends BaseThingHandlerFactory { public class AirParifHandlerFactory extends BaseThingHandlerFactory {
private final AirParifDeserializer deserializer; private final AirParifDeserializer deserializer;
private final HttpClient httpClient; private final HttpClientFactory httpClientFactory;
@Activate @Activate
public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory, public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference AirParifDeserializer deserializer) { final @Reference AirParifDeserializer deserializer) {
this.httpClient = httpClientFactory.getCommonHttpClient(); this.httpClientFactory = httpClientFactory;
this.deserializer = deserializer; this.deserializer = deserializer;
} }
@ -59,7 +58,7 @@ public class AirParifHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
return APIBRIDGE_THING_TYPE.equals(thingTypeUID) return APIBRIDGE_THING_TYPE.equals(thingTypeUID)
? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer) ? new AirParifBridgeHandler((Bridge) thing, httpClientFactory.getCommonHttpClient(), deserializer)
: LOCATION_THING_TYPE.equals(thingTypeUID) ? new LocationHandler(thing) : null; : LOCATION_THING_TYPE.equals(thingTypeUID) ? new LocationHandler(thing) : null;
} }
} }

View File

@ -41,10 +41,6 @@ public class AirParifApi {
private static final UriBuilder POLLENS_BUILDER = AIRPARIF_BUILDER.clone().path("pollens"); private static final UriBuilder POLLENS_BUILDER = AIRPARIF_BUILDER.clone().path("pollens");
public static final URI POLLENS_URI = POLLENS_BUILDER.clone().path("bulletin").build(); public static final URI POLLENS_URI = POLLENS_BUILDER.clone().path("bulletin").build();
// Poor interest, only returns highest risk level for the dept.
// public static final UriBuilder POLLENS_DEPT_BUILDER = POLLENS_BUILDER.clone().path("departement");
// public static final URI PREV_COLORS_URI = INDICES_BUILDER.clone().path("couleurs").build();
public enum Scope { public enum Scope {
@SerializedName("Cartes et résultats Hor'Air") @SerializedName("Cartes et résultats Hor'Air")
MAPS, MAPS,

View File

@ -13,9 +13,9 @@
package org.openhab.binding.airparif.internal.api; package org.openhab.binding.airparif.internal.api;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -54,7 +54,7 @@ public class AirParifDto {
} }
public record KeyInfo(// public record KeyInfo(//
ZonedDateTime expiration, // Instant expiration, //
@SerializedName("droits") Set<Scope> scopes) { @SerializedName("droits") Set<Scope> scopes) {
} }
@ -135,33 +135,33 @@ public class AirParifDto {
private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris"); private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris");
public List<Pollens> data = List.of(); public List<Pollens> data = List.of();
private @Nullable ZonedDateTime beginValidity; private @Nullable Instant beginValidity;
private @Nullable ZonedDateTime endValidity; private @Nullable Instant endValidity;
public Optional<Pollens> getData() { public Optional<Pollens> getData() {
return Optional.ofNullable(data.isEmpty() ? null : data.get(0)); return Optional.ofNullable(data.isEmpty() ? null : data.get(0));
} }
private Set<ZonedDateTime> getValidities() { private Set<Instant> getValidities() {
Set<ZonedDateTime> validities = new TreeSet<>(); Set<Instant> validities = new TreeSet<>();
getData().ifPresent(pollens -> { getData().ifPresent(pollens -> {
Matcher matcher = PATTERN.matcher(pollens.periode); Matcher matcher = PATTERN.matcher(pollens.periode);
while (matcher.find()) { while (matcher.find()) {
validities.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); validities.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE).toInstant());
} }
}); });
return validities; return validities;
} }
public Optional<ZonedDateTime> getBeginValidity() { public Optional<Instant> getBeginValidity() {
if (beginValidity == null) { if (beginValidity == null) {
beginValidity = getValidities().iterator().next(); beginValidity = getValidities().iterator().next();
} }
return Optional.ofNullable(beginValidity); return Optional.ofNullable(beginValidity);
} }
public Optional<ZonedDateTime> getEndValidity() { public Optional<Instant> getEndValidity() {
if (endValidity == null) { if (endValidity == null) {
endValidity = getValidities().stream().reduce((prev, next) -> next).orElse(null); endValidity = getValidities().stream().reduce((prev, next) -> next).orElse(null);
} }
@ -170,7 +170,7 @@ public class AirParifDto {
public Duration getValidityDuration() { public Duration getValidityDuration() {
return Objects.requireNonNull(getEndValidity().map(end -> { return Objects.requireNonNull(getEndValidity().map(end -> {
Duration duration = Duration.between(ZonedDateTime.now().withZoneSameInstant(end.getZone()), end); Duration duration = Duration.between(Instant.now(), end);
return duration.isNegative() ? Duration.ZERO : duration; return duration.isNegative() ? Duration.ZERO : duration;
}).orElse(Duration.ZERO)); }).orElse(Duration.ZERO));
} }
@ -197,7 +197,7 @@ public class AirParifDto {
public record Concentration(// public record Concentration(//
@SerializedName("polluant") Pollutant pollutant, // @SerializedName("polluant") Pollutant pollutant, //
ZonedDateTime date, // Instant date, //
@SerializedName("valeurs") double[] values, // @SerializedName("valeurs") double[] values, //
@Nullable Message message) { @Nullable Message message) {
@ -224,7 +224,7 @@ public class AirParifDto {
} }
public record Route(// public record Route(//
@SerializedName("dateRequise") ZonedDateTime requestedDate, // @SerializedName("dateRequise") Instant requestedDate, //
double[][] longlats, // double[][] longlats, //
@SerializedName("resultats") List<Concentration> concentrations, // @SerializedName("resultats") List<Concentration> concentrations, //
@Nullable Message[] messages) { @Nullable Message[] messages) {

View File

@ -12,18 +12,16 @@
*/ */
package org.openhab.binding.airparif.internal.deserialization; package org.openhab.binding.airparif.internal.deserialization;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZonedDateTime;
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.airparif.internal.AirParifException; import org.openhab.binding.airparif.internal.AirParifException;
import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration;
import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.api.PollenAlertLevel;
import org.openhab.core.i18n.TimeZoneProvider;
import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -42,7 +40,7 @@ public class AirParifDeserializer {
private final Gson gson; private final Gson gson;
@Activate @Activate
public AirParifDeserializer(final @Reference TimeZoneProvider timeZoneProvider) { public AirParifDeserializer() {
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
.registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer()) .registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer())
.registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory())
@ -50,10 +48,10 @@ public class AirParifDeserializer {
.registerTypeAdapter(LocalDate.class, .registerTypeAdapter(LocalDate.class,
(JsonDeserializer<LocalDate>) (json, type, context) -> LocalDate (JsonDeserializer<LocalDate>) (json, type, context) -> LocalDate
.parse(json.getAsJsonPrimitive().getAsString())) .parse(json.getAsJsonPrimitive().getAsString()))
.registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer<ZonedDateTime>) (json, type, context) -> { .registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (json, type, context) -> {
String string = json.getAsJsonPrimitive().getAsString(); String string = json.getAsJsonPrimitive().getAsString();
string += string.contains("+") ? "" : "Z"; string += string.contains("+") ? "" : "Z";
return ZonedDateTime.parse(string).withZoneSameInstant(timeZoneProvider.getTimeZone()); return Instant.parse(string);
}).create(); }).create();
} }

View File

@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration;
import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.api.Pollutant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
@ -32,6 +34,7 @@ import com.google.gson.JsonSyntaxException;
*/ */
@NonNullByDefault @NonNullByDefault
class PollutantConcentrationDeserializer implements JsonDeserializer<PollutantConcentration> { class PollutantConcentrationDeserializer implements JsonDeserializer<PollutantConcentration> {
private final Logger logger = LoggerFactory.getLogger(PollutantConcentrationDeserializer.class);
@Override @Override
public @Nullable PollutantConcentration deserialize(JsonElement json, Type clazz, public @Nullable PollutantConcentration deserialize(JsonElement json, Type clazz,
@ -44,7 +47,7 @@ class PollutantConcentrationDeserializer implements JsonDeserializer<PollutantCo
try { try {
result = new PollutantConcentration(pollutant, array.get(1).getAsInt(), array.get(2).getAsInt()); result = new PollutantConcentration(pollutant, array.get(1).getAsInt(), array.get(2).getAsInt());
} catch (JsonSyntaxException ignore) { } catch (JsonSyntaxException ignore) {
// result will remain null logger.debug("Error deserializing PollutantConcentration: {}", json.toString());
} }
} }
return result; return result;

View File

@ -64,7 +64,7 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi
LocationProvider localLocation = locationProvider; LocationProvider localLocation = locationProvider;
PointType location = localLocation != null ? localLocation.getLocation() : null; PointType location = localLocation != null ? localLocation.getLocation() : null;
if (location == null) { if (location == null) {
logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results"); logger.warn("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
return; return;
} }
@ -82,8 +82,6 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi
.withProperty(LocationConfiguration.LOCATION, serverLocation.toFullString())// .withProperty(LocationConfiguration.LOCATION, serverLocation.toFullString())//
.withRepresentationProperty(LocationConfiguration.DEPARTMENT) // .withRepresentationProperty(LocationConfiguration.DEPARTMENT) //
.withBridge(bridgeUID).build())); .withBridge(bridgeUID).build()));
} else {
logger.info("No department could be discovered matching server location");
} }
} }
} }

View File

@ -117,6 +117,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU
"@text/offline.config-error-unknown-apikey"); "@text/offline.config-error-unknown-apikey");
return; return;
} }
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(this::initiateConnexion); scheduler.execute(this::initiateConnexion);
} }
@ -196,7 +197,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU
thing.setProperty("api-version", version.version()); thing.setProperty("api-version", version.version());
thing.setProperty("key-expiration", keyInfo.expiration().toString()); thing.setProperty("key-expiration", keyInfo.expiration().toString());
thing.setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(","))); thing.setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(",")));
logger.info("The api key is valid until {}", keyInfo.expiration().toString()); logger.debug("The api key is valid until {}", keyInfo.expiration().toString());
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
ThingUID thingUID = thing.getUID(); ThingUID thingUID = thing.getUID();
@ -258,10 +259,8 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU
}); });
}); });
ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1);
logger.debug("Rescheduling daily air quality bulletin job tomorrow morning"); logger.debug("Rescheduling daily air quality bulletin job tomorrow morning");
schedule(AQ_JOB, () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), schedule(AQ_JOB, () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), untilTomorrowMorning());
Duration.between(ZonedDateTime.now(), tomorrowMorning));
} }
private void updateEpisode(ChannelGroupUID dailyGroupUID) { private void updateEpisode(ChannelGroupUID dailyGroupUID) {
@ -278,9 +277,12 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU
updateState(new ChannelUID(dailyGroupUID, CHANNEL_MESSAGE), new StringType(episode.message().fr())); updateState(new ChannelUID(dailyGroupUID, CHANNEL_MESSAGE), new StringType(episode.message().fr()));
updateState(new ChannelUID(dailyGroupUID, CHANNEL_TOMORROW), new StringType(episode.message().fr())); updateState(new ChannelUID(dailyGroupUID, CHANNEL_TOMORROW), new StringType(episode.message().fr()));
ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), untilTomorrowMorning());
schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), }
Duration.between(ZonedDateTime.now(), tomorrowMorning));
private Duration untilTomorrowMorning() {
return Duration.between(ZonedDateTime.now(),
ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1));
} }
public @Nullable Route getConcentrations(String location) { public @Nullable Route getConcentrations(String location) {

View File

@ -62,12 +62,12 @@ public interface HandlerUtils {
default void schedule(String jobName, Runnable job, Duration duration) { default void schedule(String jobName, Runnable job, Duration duration) {
ScheduledFuture<?> result = getJobs().remove(jobName); ScheduledFuture<?> result = getJobs().remove(jobName);
String operation = "Scheduling";
getLogger().debug("{} {} in {}", result != null ? "Rescheduled" : "Scheduling", jobName, duration);
if (result != null) { if (result != null) {
operation = "Rescheduled";
cancelFuture(result); cancelFuture(result);
} }
getLogger().info("{} {} in {}", operation, jobName, duration);
getJobs().put(jobName, getScheduler().schedule(job, duration.getSeconds(), TimeUnit.SECONDS)); getJobs().put(jobName, getScheduler().schedule(job, duration.getSeconds(), TimeUnit.SECONDS));
} }

View File

@ -11,10 +11,10 @@ thing-type.airparif.api.group.aq-bulletin.label = Today's Air Quality Bulletin
thing-type.airparif.api.group.aq-bulletin-tomorrow.label = Tomorrow's Air Quality Bulletin thing-type.airparif.api.group.aq-bulletin-tomorrow.label = Tomorrow's Air Quality Bulletin
thing-type.airparif.location.label = Department Report thing-type.airparif.location.label = Department Report
thing-type.airparif.location.description = AirParif air quality report for the given location thing-type.airparif.location.description = AirParif air quality report for the given location
thing-type.airparif.location.group.no2.label = NO2 Concentration Information thing-type.airparif.location.group.no2.label = NO2 Concentration
thing-type.airparif.location.group.o3.label = Ozone Concentration Information thing-type.airparif.location.group.o3.label = Ozone Concentration
thing-type.airparif.location.group.pm10.label = PM10 Concentration Information thing-type.airparif.location.group.pm10.label = PM10 Concentration
thing-type.airparif.location.group.pm25.label = PM2.5 Concentration Information thing-type.airparif.location.group.pm25.label = PM2.5 Concentration
# thing types config # thing types config
@ -60,12 +60,12 @@ channel-group-type.airparif.bridge-pollens.channel.comment.label = Situation
channel-group-type.airparif.bridge-pollens.channel.comment.description = Current pollens situation channel-group-type.airparif.bridge-pollens.channel.comment.description = Current pollens situation
channel-group-type.airparif.bridge-pollens.channel.end-validity.label = End Validity channel-group-type.airparif.bridge-pollens.channel.end-validity.label = End Validity
channel-group-type.airparif.bridge-pollens.channel.end-validity.description = Bulletin validity end channel-group-type.airparif.bridge-pollens.channel.end-validity.description = Bulletin validity end
channel-group-type.airparif.daily.label = Daily information for the region channel-group-type.airparif.daily.label = Daily Region Information
channel-group-type.airparif.daily.channel.message.label = Message channel-group-type.airparif.daily.channel.message.label = Message
channel-group-type.airparif.daily.channel.message.description = Today's daily general information channel-group-type.airparif.daily.channel.message.description = Today's daily general information
channel-group-type.airparif.daily.channel.tomorrow.label = Tomorrow channel-group-type.airparif.daily.channel.tomorrow.label = Tomorrow
channel-group-type.airparif.daily.channel.tomorrow.description = Tomorrow's daily general information channel-group-type.airparif.daily.channel.tomorrow.description = Tomorrow's daily general information
channel-group-type.airparif.dept-pollens.label = Pollen information for the department channel-group-type.airparif.dept-pollens.label = Department Pollen Information
channel-group-type.airparif.pollutant-mpc.label = Pollutant Concentration Information channel-group-type.airparif.pollutant-mpc.label = Pollutant Concentration Information
channel-group-type.airparif.pollutant-mpc.channel.alert.label = Alert Level channel-group-type.airparif.pollutant-mpc.channel.alert.label = Alert Level
channel-group-type.airparif.pollutant-mpc.channel.alert.description = Alert Level associated to pollutant concentration channel-group-type.airparif.pollutant-mpc.channel.alert.description = Alert Level associated to pollutant concentration

View File

@ -101,7 +101,7 @@
</channel-group-type> </channel-group-type>
<channel-group-type id="daily"> <channel-group-type id="daily">
<label>Daily information for the region</label> <label>Daily Region Information</label>
<channels> <channels>
<channel id="message" typeId="comment"> <channel id="message" typeId="comment">
<label>Message</label> <label>Message</label>
@ -115,7 +115,7 @@
</channel-group-type> </channel-group-type>
<channel-group-type id="dept-pollens"> <channel-group-type id="dept-pollens">
<label>Pollen information for the department</label> <label>Department Pollen Information</label>
<channels> <channels>
<channel id="cypress" typeId="cypress-level"/> <channel id="cypress" typeId="cypress-level"/>
<channel id="hazel" typeId="hazel-level"/> <channel id="hazel" typeId="hazel-level"/>

View File

@ -16,16 +16,16 @@
<channel-group id="pollens" typeId="dept-pollens"/> <channel-group id="pollens" typeId="dept-pollens"/>
<channel-group id="indice" typeId="pollutant-ndx"/> <channel-group id="indice" typeId="pollutant-ndx"/>
<channel-group id="o3" typeId="pollutant-mpc"> <channel-group id="o3" typeId="pollutant-mpc">
<label>Ozone Concentration Information</label> <label>Ozone Concentration</label>
</channel-group> </channel-group>
<channel-group id="no2" typeId="pollutant-mpc"> <channel-group id="no2" typeId="pollutant-mpc">
<label>NO2 Concentration Information</label> <label>NO2 Concentration</label>
</channel-group> </channel-group>
<channel-group id="pm25" typeId="pollutant-mpc"> <channel-group id="pm25" typeId="pollutant-mpc">
<label>PM2.5 Concentration Information</label> <label>PM2.5 Concentration</label>
</channel-group> </channel-group>
<channel-group id="pm10" typeId="pollutant-mpc"> <channel-group id="pm10" typeId="pollutant-mpc">
<label>PM10 Concentration Information</label> <label>PM10 Concentration</label>
</channel-group> </channel-group>
</channel-groups> </channel-groups>