From 12654198344199ed1209f5fe114f3cc0ce92bd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Fri, 18 Oct 2024 13:41:10 +0200 Subject: [PATCH 01/17] Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.airparif/NOTICE | 13 + .../org.openhab.binding.airparif/README.md | 94 +++ .../org.openhab.binding.airparif/doc/info.txt | 29 + bundles/org.openhab.binding.airparif/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../internal/AirParifBindingConstants.java | 41 + .../airparif/internal/AirParifException.java | 33 + .../internal/AirParifHandlerFactory.java | 65 ++ .../internal/AirParifIconProvider.java | 99 +++ .../airparif/internal/api/AirParifApi.java | 126 +++ .../airparif/internal/api/AirParifDto.java | 127 +++ .../airparif/internal/api/ColorMap.java | 36 + .../internal/api/PollenAlertLevel.java | 43 + .../airparif/internal/api/Pollutant.java | 41 + .../internal/config/BridgeConfiguration.java | 25 + .../config/LocationConfiguration.java | 29 + .../internal/db/DepartmentDbService.java | 77 ++ .../deserialization/AirParifDeserializer.java | 75 ++ .../deserialization/ColorMapDeserializer.java | 42 + .../PollenAlertLevelDeserializer.java | 47 ++ .../PollutantConcentrationDeserializer.java | 52 ++ .../StrictEnumTypeAdapterFactory.java | 64 ++ .../discovery/AirParifDiscoveryService.java | 80 ++ .../handler/AirParifBridgeHandler.java | 176 ++++ .../internal/handler/LocationHandler.java | 78 ++ .../src/main/resources/OH-INF/addon/addon.xml | 12 + .../resources/OH-INF/i18n/airparif.properties | 14 + .../src/main/resources/OH-INF/thing/api.xml | 22 + .../main/resources/OH-INF/thing/channels.xml | 220 +++++ .../resources/OH-INF/thing/thing-types.xml | 27 + .../src/main/resources/db/departments.json | 770 ++++++++++++++++++ .../src/main/resources/icon/average.svg | 7 + .../src/main/resources/icon/bad.svg | 9 + .../src/main/resources/icon/degrated.svg | 7 + .../src/main/resources/icon/extremely-bad.svg | 6 + .../src/main/resources/icon/good.svg | 8 + .../src/main/resources/icon/pollen.svg | 4 + 39 files changed, 2630 insertions(+) create mode 100755 bundles/org.openhab.binding.airparif/NOTICE create mode 100755 bundles/org.openhab.binding.airparif/README.md create mode 100644 bundles/org.openhab.binding.airparif/doc/info.txt create mode 100755 bundles/org.openhab.binding.airparif/pom.xml create mode 100755 bundles/org.openhab.binding.airparif/src/main/feature/feature.xml create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/db/departments.json create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg diff --git a/CODEOWNERS b/CODEOWNERS index 0526f553154..d2c5193f1ac 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,6 +15,7 @@ /bundles/org.openhab.binding.adorne/ @theiding /bundles/org.openhab.binding.ahawastecollection/ @soenkekueper /bundles/org.openhab.binding.airgradient/ @austvik +/bundles/org.openhab.binding.airparif/ @clinique /bundles/org.openhab.binding.airq/ @aurelio1 @fwolter /bundles/org.openhab.binding.airquality/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.airvisualnode/ @3cky diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 170f8ce163d..754336edb1e 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -66,6 +66,11 @@ org.openhab.binding.airgradient ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.airparif + ${project.version} + org.openhab.addons.bundles org.openhab.binding.airq diff --git a/bundles/org.openhab.binding.airparif/NOTICE b/bundles/org.openhab.binding.airparif/NOTICE new file mode 100755 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.airparif/README.md b/bundles/org.openhab.binding.airparif/README.md new file mode 100755 index 00000000000..d7a6687bb29 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/README.md @@ -0,0 +1,94 @@ +# AirParif Binding + +_Give some details about what this binding is meant for - a protocol, system, specific device._ + +_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ +_You can place such resources into a `doc` folder next to this README.md._ + +_Put each sentence in a separate line to improve readability of diffs._ + +## Supported Things + +_Please describe the different supported things / devices including their ThingTypeUID within this section._ +_Which different types are supported, which models were tested etc.?_ +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +- `bridge`: Short description of the Bridge, if any +- `sample`: Short description of the Thing with the ThingTypeUID `sample` + +## Discovery + +_Describe the available auto-discovery features here._ +_Mention for what it works and what needs to be kept in mind when using it._ + +## Binding Configuration + +_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ +_In this section, you should link to this file and provide some information about the options._ +_The file could e.g. look like:_ + +``` +# Configuration for the AirParif Binding +# +# Default secret key for the pairing of the AirParif Thing. +# It has to be between 10-40 (alphanumeric) characters. +# This may be changed by the user for security reasons. +secret=openHABSecret +``` + +_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ + +_If your binding does not offer any generic configurations, you can remove this section completely._ + +## Thing Configuration + +_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ +_This should be mainly about its mandatory and optional configuration parameters._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +### `sample` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|---------------------------------------|---------|----------|----------| +| hostname | text | Hostname or IP address of the device | N/A | yes | no | +| password | text | Password to access the device | N/A | yes | no | +| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | + +## Channels + +_Here you should provide information about available channel types, what their meaning is and how they can be used._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +| Channel | Type | Read/Write | Description | +|---------|--------|------------|-----------------------------| +| control | Switch | RW | This is the control channel | + +## Full Example + +_Provide a full usage example based on textual configuration files._ +_*.things, *.items examples are mandatory as textual configuration is well used by many users._ +_*.sitemap examples are optional._ + +### Thing Configuration + +```java +Example thing configuration goes here. +``` +### Item Configuration + +```java +Example item configuration goes here. +``` + +### Sitemap Configuration + +```perl +Optional Sitemap configuration goes here. +Remove this section, if not needed. +``` + +## Any custom content here! + +_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.airparif/doc/info.txt b/bundles/org.openhab.binding.airparif/doc/info.txt new file mode 100644 index 00000000000..53c913750cc --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/info.txt @@ -0,0 +1,29 @@ + +Miguel Narvaez Miguel.Narvaez@airparif.fr via improvmx-mails.com +30 sept. 2024 10:06 (il y a 11 jours) +À gael@lhopital.org, api + +Bonjour, + +Veuillez trouver ci-dessous la clé API vous permettant d’accéder à nos API Indices de la qualité de l'air, Épisodes, Cartographie, Pollens : + +5a923300-e93d-2a0f-4321-96f115f19100 + +Cette clé sera active à partir de demain. + +Nous ouvrons ces API dans une démarche d’amélioration continue, n’hésitez pas à revenir vers nous pour des retours d’expérience et des propositions d’améliorations. + +La documentation des APIs se trouve à l’adresse suivante : +https://api.airparif.fr/docs + +Celle concernant les services Web de cartographie se trouve ici : +https://www.airparif.fr/doc_api_carto/ + +Les informations qui vous concernent sont destinées uniquement à Airparif. Votre adresse e-mail sera utilisée uniquement pour vous informer à propos des API. Vous disposez d'un droit d'accès, de modification, de rectification et de suppression de ces données (art. 34 de la loi "Informatique et Libertés"). + +Pour toute question concernant vos données personnelles ou nos API, nous vous invitons à nous contacter via notre formulaire en ligne : +https://www.airparif.asso.fr/contact + +Bonne journée ! + +Airparif \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/pom.xml b/bundles/org.openhab.binding.airparif/pom.xml new file mode 100755 index 00000000000..c38d9fba93e --- /dev/null +++ b/bundles/org.openhab.binding.airparif/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.3.0-SNAPSHOT + + + org.openhab.binding.airparif + + openHAB Add-ons :: Bundles :: AirParif Binding + + diff --git a/bundles/org.openhab.binding.airparif/src/main/feature/feature.xml b/bundles/org.openhab.binding.airparif/src/main/feature/feature.xml new file mode 100755 index 00000000000..d4a2170fcfa --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.airparif/${project.version} + + diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java new file mode 100755 index 00000000000..6ed929d4c87 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link AirParifBindingConstants} class defines common constants, which are used across the whole binding. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class AirParifBindingConstants { + public static final String BINDING_ID = "airparif"; + public static final String LOCAL = "local"; + + // List of Bridge Type UIDs + public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "api"); + + // List of Things Type UIDs + public static final ThingTypeUID LOCATION_THING_TYPE = new ThingTypeUID(BINDING_ID, "location"); + + // List of all Channel ids + public static final String CHANNEL_1 = "channel1"; + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(APIBRIDGE_THING_TYPE, + LOCATION_THING_TYPE); +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java new file mode 100755 index 00000000000..3d7f00492de --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * An exception that occurred while communicating with Air Parif API server or related processes. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class AirParifException extends Exception { + private static final long serialVersionUID = 4234683995736417341L; + + public AirParifException(String format, Object... args) { + super(format.formatted(args)); + } + + public AirParifException(Exception e, String format, Object... args) { + super(format.formatted(args), e); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java new file mode 100755 index 00000000000..ea9f49da607 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal; + +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +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.handler.AirParifBridgeHandler; +import org.openhab.binding.airparif.internal.handler.LocationHandler; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link AirParifHandlerFactory} is responsible for creating things and thing handlers. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.airparif", service = ThingHandlerFactory.class) +public class AirParifHandlerFactory extends BaseThingHandlerFactory { + private final AirParifDeserializer deserializer; + private final HttpClient httpClient; + + @Activate + public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory, + final @Reference AirParifDeserializer deserializer) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + this.deserializer = deserializer; + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + return APIBRIDGE_THING_TYPE.equals(thingTypeUID) + ? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer) + : LOCATION_THING_TYPE.equals(thingTypeUID) ? new LocationHandler(thing) : null; + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java new file mode 100755 index 00000000000..7a9007f2625 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal; + +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.BINDING_ID; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.ui.icon.IconProvider; +import org.openhab.core.ui.icon.IconSet; +import org.openhab.core.ui.icon.IconSet.Format; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AirParifIconProvider} is the class providing binding related icons. + * + * @author Gaël L'hopital - Initial contribution + */ +@Component(service = { IconProvider.class, AirParifIconProvider.class }) +@NonNullByDefault +public class AirParifIconProvider implements IconProvider { + private static final String DEFAULT_LABEL = "Air Parif Icons"; + private static final String DEFAULT_DESCRIPTION = "Icons illustrating air quality levels provided by AirParif"; + private static final List ICONS = List.of("average", "bad", "degrated", "extremely-bad", "good", "pollen"); + + private final Logger logger = LoggerFactory.getLogger(AirParifIconProvider.class); + private final TranslationProvider i18nProvider; + private final Bundle bundle; + + @Activate + public AirParifIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) { + this.i18nProvider = i18nProvider; + this.bundle = context.getBundle(); + } + + @Override + public Set getIconSets() { + return getIconSets(null); + } + + @Override + public Set getIconSets(@Nullable Locale locale) { + String label = getText("label", DEFAULT_LABEL, locale); + String description = getText("decription", DEFAULT_DESCRIPTION, locale); + + return Set.of(new IconSet(BINDING_ID, label, description, Set.of(Format.SVG))); + } + + private String getText(String entry, String defaultValue, @Nullable Locale locale) { + String text = locale == null ? null : i18nProvider.getText(bundle, "iconset." + entry, defaultValue, locale); + return text == null ? defaultValue : text; + } + + @Override + public @Nullable Integer hasIcon(String category, String iconSetId, Format format) { + return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) && ICONS.contains(category) ? 0 : null; + } + + @Override + public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) { + URL iconResource = bundle.getEntry("icon/%s.svg".formatted(category)); + + String result; + try (InputStream stream = iconResource.openStream()) { + result = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + logger.warn("Unable to load ressource '{}': {}", iconResource.getPath(), e.getMessage()); + result = ""; + } + + return result.isEmpty() ? null : new ByteArrayInputStream(result.getBytes()); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java new file mode 100644 index 00000000000..95f387064b3 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.api; + +import java.net.URI; +import java.util.EnumSet; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * {@link AirParifApi} class defines paths used to interact with server api + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirParifApi { + private static final UriBuilder AIRPARIF_BUILDER = UriBuilder.fromPath("/").scheme("https").host("api.airparif.fr"); + public static final URI VERSION_URI = AIRPARIF_BUILDER.clone().path("version").build(); + public static final URI KEY_INFO_URI = AIRPARIF_BUILDER.clone().path("key-info").build(); + public static final URI HORAIR_URI = AIRPARIF_BUILDER.clone().path("horair").path("itineraire").build(); + public static final URI EPISODES_URI = AIRPARIF_BUILDER.clone().path("episodes").path("en-cours-et-prevus").build(); + + private static final UriBuilder INDICES_BUILDER = AIRPARIF_BUILDER.clone().path("indices").path("prevision"); + public static final URI PREV_COLORS_URI = INDICES_BUILDER.clone().path("couleurs").build(); + public static final URI PREV_BULLETIN_URI = INDICES_BUILDER.clone().path("bulletin").build(); + + private static final UriBuilder POLLENS_BUILDER = AIRPARIF_BUILDER.clone().path("pollens"); + 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 enum Scope { + @SerializedName("Cartes et résultats Hor'Air") + MAPS, + @SerializedName("Pollens") + POLLENS, + @SerializedName("Épisodes") + EVENTS, + @SerializedName("Indices") + INDEXES, + UNKNOWN; + } + + public enum Appreciation { + GOOD("Bon"), + AVERAGE("Moyen"), + DEGRATED("Dégradé"), + BAD("Mauvais"), + REALLY_BAD("Très Mauvais"), + EXTREMELY_BAD("Extrêmement Mauvais"), + UNKNOWN(""); + + public final String apiName; + + Appreciation(String apiName) { + this.apiName = apiName; + } + + public static final EnumSet AS_SET = EnumSet.allOf(Appreciation.class); + } + + public enum Pollen { + @SerializedName("cypres") + CYPRESS("cypres"), + @SerializedName("noisetier") + HAZEL("noisetier"), + @SerializedName("aulne") + ALDER("aulne"), + @SerializedName("peuplier") + POPLAR("peuplier"), + @SerializedName("saule") + WILLOW("saule"), + @SerializedName("frene") + ASH("frene"), + @SerializedName("charme") + HORNBEAM("charme"), + @SerializedName("bouleau") + BIRCH("bouleau"), + @SerializedName("platane") + PLANE("platane"), + @SerializedName("chene") + OAK("chene"), + @SerializedName("olivier") + OLIVE("olivier"), + @SerializedName("tilleul") + LINDEN("tilleul"), + @SerializedName("chataignier") + CHESTNUT("chataignier"), + @SerializedName("rumex") + RUMEX("rumex"), + @SerializedName("graminees") + GRASSES("graminees"), + @SerializedName("plantain") + PLANTAIN("plantain"), + @SerializedName("urticacees") + URTICACEAE("urticacees"), + @SerializedName("armoises") + WORMWOOD("armoises"), + @SerializedName("ambroisies") + RAGWEED("ambroisies"); + + public final String apiName; + + Pollen(String apiName) { + this.apiName = apiName; + } + + public static final EnumSet AS_SET = EnumSet.allOf(Pollen.class); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java new file mode 100644 index 00000000000..bb2238f8692 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.api; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; +import org.openhab.binding.airparif.internal.api.AirParifApi.Scope; + +import com.google.gson.annotations.SerializedName; + +/** + * {@link AirParifDto} class defines DTO used to interact with server api + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirParifDto { + public record Version(// + String version) { + } + + public record KeyInfo(// + ZonedDateTime expiration, // + @SerializedName("droits") Set scopes) { + } + + private record Message(// + String fr, // + @Nullable String en) { + } + + public record PollutantConcentration(// + Pollutant pollutant, // + int min, // + int max) { + } + + public record PollutantEpisode(// + @SerializedName("nom") Pollutant pollutant, // + @SerializedName("niveau") String level) { + } + + public record DailyBulletin(// + @SerializedName("date") LocalDate previsionDate, // + @SerializedName("date_previ") LocalDate productionDate, // + @SerializedName("disponible") boolean available, // + Message bulletin, // + Set concentrations) { + public String dayDescription() { + return bulletin.fr; + } + } + + public record DailyEpisode(// + @SerializedName("actif") boolean active, // + @SerializedName("polluants") Set pollutants) { + } + + public record Bulletin( // + @SerializedName("jour") DailyBulletin today, // + @SerializedName("demain") DailyBulletin tomorrow) { + } + + public record Episode( // + @SerializedName("actif") boolean active, Message message, @SerializedName("jour") DailyEpisode today, // + @SerializedName("demain") DailyEpisode tomorrow) { + } + + public record Pollens(// + Pollen[] taxons, // + Map valeurs, // + String commentaire, // + String periode) { + + private static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yy"); + private static Pattern PATTERN = Pattern.compile("\\d{2}.\\d{2}.\\d{2}"); + + private static @Nullable LocalDate getValidity(String periode, boolean begin) { + Matcher matcher = PATTERN.matcher(periode); + if (matcher.find()) { + String extractedDate = matcher.group(); + if (begin) { + return LocalDate.parse(extractedDate, FORMATTER); + } + if (matcher.find()) { + extractedDate = matcher.group(); + return LocalDate.parse(extractedDate, FORMATTER); + } + } + return null; + } + + public @Nullable LocalDate beginValidity() { + return getValidity(periode, true); + } + + public @Nullable LocalDate endValidity() { + return getValidity(periode, false); + } + + } + + public record PollensResponse(ArrayList data) { + } + +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java new file mode 100644 index 00000000000..59aa08af91d --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.api; + +import java.util.HashMap; +import java.util.Objects; + +import org.openhab.binding.airparif.internal.api.AirParifApi.Appreciation; + +/** + * Class association between air quality appreciation and its color + * + * @author Gaël L'hopital - Initial contribution + */ +public class ColorMap extends HashMap { + private static final long serialVersionUID = -605462873565278453L; + + private static Appreciation fromApiName(String searched) { + return Objects.requireNonNull( + Appreciation.AS_SET.stream().filter(mt -> searched.equals(mt.apiName)).findFirst().orElse(Appreciation.UNKNOWN)); + } + + public String put(String key, String value) { + return super.put(fromApiName(key), value); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java new file mode 100644 index 00000000000..74b2517cded --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.api; + +import java.util.EnumSet; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum PollenAlertLevel { + @SerializedName("0") + NONE(0), + @SerializedName("1") + LOW(1), + @SerializedName("2") + AVERAGE(2), + @SerializedName("3") + HIGH(3), + UNKNOWN(-1); + + public static final EnumSet AS_SET = EnumSet.allOf(PollenAlertLevel.class); + + public final int riskLevel; + + PollenAlertLevel(int riskLevel) { + this.riskLevel = riskLevel; + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java new file mode 100644 index 00000000000..21a1163a90b --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.api; + +import java.util.EnumSet; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link Pollutant} enum lists all pollutants tracked by AirParif + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public enum Pollutant { + PM25, + PM10, + NO2, + O3, + UNKNOWN; + + public static final EnumSet AS_SET = EnumSet.allOf(Pollutant.class); + + public static Pollutant safeValueOf(String searched) { + try { + return Pollutant.valueOf(searched); + } catch (IllegalArgumentException e) { + return Pollutant.UNKNOWN; + } + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java new file mode 100755 index 00000000000..710581478df --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link BridgeConfiguration} is the class used to match the bridge configuration. + * + * @author Gaël L"hopital - Initial contribution + */ +@NonNullByDefault +public class BridgeConfiguration { + public String apikey = ""; +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java new file mode 100755 index 00000000000..0053cf0f9f5 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LocationConfiguration} is the class used to match the + * thing configuration. + * + * @author Gaël L"hopital - Initial contribution + */ +@NonNullByDefault +public class LocationConfiguration { + public static final String LOCATION = "location"; + + public int refresh = 10; + public String location = ""; +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java new file mode 100644 index 00000000000..b9278becd4f --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.db; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.PointType; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link DepartmentDbService} makes available a list of known French Metropolitan departments. + * + * @author Gaël L'hopital - Initial Contribution + */ + +@Component(service = DepartmentDbService.class) +@NonNullByDefault +public class DepartmentDbService { + private final Logger logger = LoggerFactory.getLogger(DepartmentDbService.class); + private final List departments = new ArrayList<>(); + + public record Department(String id, String name, double northestLat, double southestLat, double eastestLon, + double westestLon) { + } + + @Activate + public DepartmentDbService() { + try (InputStream is = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("/db/departments.json"); + Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + departments.addAll(Arrays.asList(gson.fromJson(reader, Department[].class))); + logger.debug("Successfully loaded {} departments", departments.size()); + } catch (IOException | JsonSyntaxException | JsonIOException e) { + logger.warn("Unable to load departments list: {}", e.getMessage()); + } + } + + public List getBounding(PointType location) { + double latitude = location.getLatitude().doubleValue(); + double longitude = location.getLongitude().doubleValue(); + return departments.stream().filter(dep -> dep.northestLat >= latitude && dep.southestLat <= latitude + && dep.westestLon <= longitude && dep.eastestLon >= longitude).toList(); + } + + public @Nullable Department getDept(String deptId) { + return departments.stream().filter(dep -> dep.id.equalsIgnoreCase(deptId)).findFirst().orElse(null); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java new file mode 100755 index 00000000000..a96b359ea00 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.deserialization; + +import java.time.LocalDate; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.AirParifException; +import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; +import org.openhab.binding.airparif.internal.api.ColorMap; +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.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link AirParifDeserializer} is responsible to instantiate suitable Gson (de)serializer + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +@Component(service = AirParifDeserializer.class) +public class AirParifDeserializer { + private final Gson gson; + + @Activate + public AirParifDeserializer(final @Reference TimeZoneProvider timeZoneProvider) { + gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer()) + .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) + .registerTypeAdapter(ColorMap.class, new ColorMapDeserializer()) + .registerTypeAdapter(PollutantConcentration.class, new PollutantConcentrationDeserializer()) + .registerTypeAdapter(LocalDate.class, + (JsonDeserializer) (json, type, context) -> LocalDate + .parse(json.getAsJsonPrimitive().getAsString())) + .registerTypeAdapter(ZonedDateTime.class, + (JsonDeserializer) (json, type, context) -> ZonedDateTime + .parse(json.getAsJsonPrimitive().getAsString() + "Z") + .withZoneSameInstant(timeZoneProvider.getTimeZone())) + .create(); + } + + public T deserialize(Class clazz, String json) throws AirParifException { + try { + @Nullable + T result = gson.fromJson(json, clazz); + if (result != null) { + return result; + } + throw new AirParifException("Deserialization of '%s' resulted in null value", json); + } catch (JsonSyntaxException e) { + throw new AirParifException(e, "Unexpected error deserializing '%s'", json); + } + } + +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java new file mode 100644 index 00000000000..c0804753b37 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.deserialization; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.ColorMap; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; + +/** + * Specialized deserializer for ColorMap class + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class ColorMapDeserializer implements JsonDeserializer { + + @Override + public @Nullable ColorMap deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) { + ColorMap result = new ColorMap(); + Set> entrySet = json.getAsJsonObject().entrySet(); + entrySet.forEach(entry -> result.put(entry.getKey(), entry.getValue().getAsString())); + return result; + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java new file mode 100644 index 00000000000..112796875e2 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.deserialization; + +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.PollenAlertLevel; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; + +/** + * Specialized deserializer for ColorMap class + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class PollenAlertLevelDeserializer implements JsonDeserializer { + + @Override + public @Nullable PollenAlertLevel deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) { + int level; + try { + level = json.getAsInt(); + } catch (JsonSyntaxException ignore) { + return PollenAlertLevel.UNKNOWN; + } + + return PollenAlertLevel.AS_SET.stream().filter(s -> s.riskLevel == level).findFirst() + .orElse(PollenAlertLevel.UNKNOWN); + + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java new file mode 100644 index 00000000000..d66045b6d77 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.deserialization; + +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; +import org.openhab.binding.airparif.internal.api.Pollutant; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; + +/** + * Specialized deserializer for ColorMap class + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class PollutantConcentrationDeserializer implements JsonDeserializer { + + @Override + public @Nullable PollutantConcentration deserialize(JsonElement json, Type clazz, + JsonDeserializationContext context) { + PollutantConcentration result = null; + JsonArray array = json.getAsJsonArray(); + + if (array.size() == 3) { + Pollutant pollutant = Pollutant.safeValueOf(array.get(0).getAsString()); + try { + result = new PollutantConcentration(pollutant, array.get(1).getAsInt(), array.get(2).getAsInt()); + } catch (JsonSyntaxException ignore) { + // result will remain null + } + } + return result; + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java new file mode 100755 index 00000000000..a68f243895e --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.deserialization; + +import java.io.IOException; +import java.io.StringReader; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Enforces a fallback to UNKNOWN when deserializing enum types, marked as @NonNull whereas they were valued + * to null if the appropriate value is absent. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +class StrictEnumTypeAdapterFactory implements TypeAdapterFactory { + + @Override + public @Nullable TypeAdapter create(@NonNullByDefault({}) Gson gson, + @NonNullByDefault({}) TypeToken type) { + return type.getRawType().isEnum() ? newStrictEnumAdapter(gson.getDelegateAdapter(this, type)) : null; + } + + private TypeAdapter newStrictEnumAdapter(@NonNullByDefault({}) TypeAdapter delegateAdapter) { + return new TypeAdapter<>() { + @Override + public void write(JsonWriter out, @Nullable T value) throws IOException { + delegateAdapter.write(out, value); + } + + @Override + public @Nullable T read(JsonReader in) throws IOException { + JsonReader delegateReader = new JsonReader( + new StringReader('"' + in.nextString().replace(",", "") + '"')); + @Nullable + T value = delegateAdapter.read(delegateReader); + delegateReader.close(); + if (value == null) { + value = delegateAdapter.read(new JsonReader(new StringReader("\"UNKNOWN\""))); + } + return value; + } + }; + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java new file mode 100755 index 00000000000..6df88a8a2d1 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.discovery; + +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; +import static org.openhab.binding.airparif.internal.config.LocationConfiguration.LOCATION; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.airparif.internal.handler.AirParifBridgeHandler; +import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.library.types.PointType; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ServiceScope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AirParifDiscoveryService} creates things based on the configured location. + * + * @author Gaël L'hopital - Initial Contribution + */ +@Component(scope = ServiceScope.PROTOTYPE, service = AirParifDiscoveryService.class) +@NonNullByDefault +public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryService { + private static final int DISCOVER_TIMEOUT_SECONDS = 2; + + private final Logger logger = LoggerFactory.getLogger(AirParifDiscoveryService.class); + + private @NonNullByDefault({}) LocationProvider locationProvider; + + public AirParifDiscoveryService() { + super(AirParifBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS); + } + + @Reference(unbind = "-") + public void bindTranslationProvider(TranslationProvider translationProvider) { + this.i18nProvider = translationProvider; + } + + @Reference(unbind = "-") + public void bindLocaleProvider(LocaleProvider localeProvider) { + this.localeProvider = localeProvider; + } + + @Reference(unbind = "-") + public void bindLocationProvider(LocationProvider locationProvider) { + this.locationProvider = locationProvider; + } + + @Override + protected void startScan() { + logger.debug("Starting AirParif discovery scan"); + if (locationProvider.getLocation() instanceof PointType location) { + ThingUID bridgeUID = thingHandler.getThing().getUID(); + thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(LOCATION_THING_TYPE, bridgeUID, LOCAL)) + .withLabel("@text/discovery.airparif.location.local.label") // + .withProperty(LOCATION, location.toString()) // + .withRepresentationProperty(LOCATION) // + .withBridge(bridgeUID).build()); + } else { + logger.debug("LocationProvider.getLocation() is not set, no discovery results can be provided"); + } + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java new file mode 100755 index 00000000000..bb2883ee288 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.handler; + +import static org.openhab.binding.airparif.internal.api.AirParifApi.*; + +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import javax.ws.rs.core.MediaType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpStatus.Code; +import org.openhab.binding.airparif.internal.AirParifException; +import org.openhab.binding.airparif.internal.api.AirParifDto.Bulletin; +import org.openhab.binding.airparif.internal.api.AirParifDto.Episode; +import org.openhab.binding.airparif.internal.api.AirParifDto.KeyInfo; +import org.openhab.binding.airparif.internal.api.AirParifDto.Pollens; +import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; +import org.openhab.binding.airparif.internal.api.AirParifDto.Version; +import org.openhab.binding.airparif.internal.api.ColorMap; +import org.openhab.binding.airparif.internal.config.BridgeConfiguration; +import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link AirParifBridgeHandler} is the handler for OpenUV API and connects it + * to the webservice. + * + * @author Gaël L'hopital - Initial contribution + * + */ +@NonNullByDefault +public class AirParifBridgeHandler extends BaseBridgeHandler { + private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private final Logger logger = LoggerFactory.getLogger(AirParifBridgeHandler.class); + private final AirParifDeserializer deserializer; + private final HttpClient httpClient; + + private BridgeConfiguration config = new BridgeConfiguration(); + + public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer) { + super(bridge); + this.deserializer = deserializer; + this.httpClient = httpClient; + } + + @Override + public void initialize() { + logger.debug("Initializing AirParif bridge handler."); + config = getConfigAs(BridgeConfiguration.class); + if (config.apikey.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.config-error-unknown-apikey"); + return; + } + initiateConnexion(); + } + + public synchronized String executeUri(URI uri) throws AirParifException { + logger.debug("executeUrl: {} ", uri); + + Request request = httpClient.newRequest(uri).method(HttpMethod.GET) + .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .header(HttpHeader.ACCEPT, MediaType.APPLICATION_JSON).header("X-Api-Key", config.apikey); + + try { + ContentResponse response = request.send(); + + Code statusCode = HttpStatus.getCode(response.getStatus()); + + if (statusCode == Code.OK) { + String content = new String(response.getContent(), DEFAULT_CHARSET); + logger.trace("executeUrl: {} returned {}", uri, content); + return content; + } else if (statusCode == Code.FORBIDDEN) { + throw new AirParifException("@text/offline.config-error-invalid-apikey"); + } + + throw new AirParifException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString()); + } catch (TimeoutException | ExecutionException e) { + throw new AirParifException(e, "Exception while calling %s", request.getURI()); + } catch (InterruptedException e) { + throw new AirParifException(e, "Execution interrupted: %s", e.getMessage()); + } + } + + public synchronized T executeUri(URI uri, Class clazz) throws AirParifException { + String content = executeUri(uri); + return deserializer.deserialize(clazz, content); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("The AirParif bridge does not handles commands"); + } + + private void initiateConnexion() { + Version version; + KeyInfo keyInfo; + + try { // This does validate communication with the server + version = executeUri(VERSION_URI, Version.class); + } catch (AirParifException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + return; + } + + try { // This validates the api key value + keyInfo = executeUri(KEY_INFO_URI, KeyInfo.class); + } catch (AirParifException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } + + getThing().setProperty("api-version", version.version()); + getThing().setProperty("key-expiration", keyInfo.expiration().toString()); + logger.info("The api key is valid until {}", keyInfo.expiration().toString()); + getThing().setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(","))); + updateStatus(ThingStatus.ONLINE); + + try { + ColorMap map = executeUri(PREV_COLORS_URI, ColorMap.class); + logger.info("The color map is {}", map.toString()); + + Bulletin bulletin = executeUri(PREV_BULLETIN_URI, Bulletin.class); + logger.info("The bulletin is {}", bulletin.today().dayDescription()); + + Episode episode = executeUri(EPISODES_URI, Episode.class); + logger.info("The bulletin is {}", episode); + + Pollens pollens = executeUri(POLLENS_URI, PollensResponse.class).data().get(0); + logger.info("The pollens are {}", pollens); + LocalDate begin = pollens.beginValidity(); + LocalDate end = pollens.endValidity(); + + String response = executeUri(POLLENS_DEPT_BUILDER.path("78").build()); + logger.info("The pollens 78 {}", response); + } catch (AirParifException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } + } + +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java new file mode 100755 index 00000000000..11450b1ecb2 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.handler; + +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.CHANNEL_1; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.config.LocationConfiguration; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LocationHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class LocationHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(LocationHandler.class); + + private @Nullable LocationConfiguration config; + + public LocationHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (CHANNEL_1.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + // TODO: handle data refresh + } + + // TODO: handle command + + // Note: if communication with thing fails for some reason, + // indicate that by setting the status with detail information: + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + // "Could not control device at IP address x.x.x.x"); + } + } + + @Override + public void initialize() { + config = getConfigAs(LocationConfiguration.class); + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + } +} diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml new file mode 100755 index 00000000000..74286c6bb4c --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,12 @@ + + + + binding + AirParif Binding + Air Quality data and forecasts provided by AirParif. + cloud + fr + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties new file mode 100755 index 00000000000..98191f8a6c2 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -0,0 +1,14 @@ + +# discovery result + +discovery.airparif.location.local.label = Air Quality Report + +# iconprovider + +iconset.label = AirParif Icons +iconset.description = Icons illustrating air quality measures provided by AirParif + +# thing status descriptions + +offline.config-error-unknown-apikey = Parameter 'apikey' must be configured +offline.config-error-invalid-apikey = Parameter 'apikey' is invalid \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml new file mode 100755 index 00000000000..86e17c866e2 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml @@ -0,0 +1,22 @@ + + + + + + + Bridge to the AirParif API Portal. In order to receive the data, you must register an account on + https://www.airparif.fr/contact and receive your API token. + + + + + Token used to access the service + password + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml new file mode 100755 index 00000000000..a7876a8ccec --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml @@ -0,0 +1,220 @@ + + + + + Number + + @text/alertLevelChannelDescription + error + + Alarm + + + + + + + + + + + + + + DateTime + + @text/timestampChannelDescription + time + + + + + String + + + + + + + + + + + + + + + Number + + Rain intensity level + oh:meteofrance:intensity + + + + + + + + + + + + Number + + Wind event alert level + oh:meteofrance:vent + + + + + + + + + + + + Number + + Storm alert level + oh:meteofrance:orage + + + + + + + + + + + + Number + + Flood alert level + oh:meteofrance:inondation + + + + + + + + + + + + Number + + Snow event alert level + oh:meteofrance:neige + + + + + + + + + + + + Number + + High temperature alert level + oh:meteofrance:canicule + + + + + + + + + + + + Number + + Negative temperature alert level + oh:meteofrance:grand-froid + + + + + + + + + + + + Number + + Avalanche alert level + oh:meteofrance:avalanches + + + + + + + + + + + + Number + + Submersion wave alert level + oh:meteofrance:vague-submersion + + + + + + + + + + + + Number + + Flood caused by rainfall alert level + oh:meteofrance:pluie-inondation + + + + + + + + + + + + String + + text + + + + + DateTime + + time + + + + + Image + + Pictogram associated with the alert level. + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100755 index 00000000000..90fdbc3463f --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,27 @@ + + + + + + + + + + AirParif air quality report for the given location + + + + + + + + location + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/db/departments.json b/bundles/org.openhab.binding.airparif/src/main/resources/db/departments.json new file mode 100644 index 00000000000..afa41c50c61 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/db/departments.json @@ -0,0 +1,770 @@ +[ + { + "id": "01", + "name": "Ain", + "northest_lat": 46.517199374492, + "southest_lat": 45.611235124481, + "eastest_lon": 6.1697363568789, + "westest_lon": 4.729097034294 + }, + { + "id": "02", + "name": "Aisne", + "northest_lat": 50.069271974234, + "southest_lat": 48.837795469902, + "eastest_lon": 4.2540638814402, + "westest_lon": 2.9624508927558 + }, + { + "id": "03", + "name": "Allier", + "northest_lat": 46.803872076205, + "southest_lat": 45.930727869968, + "eastest_lon": 4.0055701432229, + "westest_lon": 2.2804029533754 + }, + { + "id": "04", + "name": "Alpes-de-Haute-Provence", + "northest_lat": 44.659501345682, + "southest_lat": 43.668282105491, + "eastest_lon": 6.9668199032047, + "westest_lon": 5.4980104773442 + }, + { + "id": "05", + "name": "Hautes-Alpes", + "northest_lat": 45.12684420383, + "southest_lat": 44.186478874057, + "eastest_lon": 7.0771048243018, + "westest_lon": 5.4185330627929 + }, + { + "id": "06", + "name": "Alpes-Maritimes", + "northest_lat": 44.361051257141, + "southest_lat": 43.480065494401, + "eastest_lon": 7.7169378581589, + "westest_lon": 6.6363906079569 + }, + { + "id": "07", + "name": "Ardèche", + "northest_lat": 45.365674921417, + "southest_lat": 44.264783733394, + "eastest_lon": 4.8865943991285, + "westest_lon": 3.8615128126047 + }, + { + "id": "08", + "name": "Ardennes", + "northest_lat": 50.168317417174, + "southest_lat": 49.228510768771, + "eastest_lon": 5.3935393812988, + "westest_lon": 4.0252899216328 + }, + { + "id": "09", + "name": "Ariège", + "northest_lat": 43.315601482419, + "southest_lat": 42.572397819345, + "eastest_lon": 2.1758766074869, + "westest_lon": 0.82612266137771 + }, + { + "id": "10", + "name": "Aube", + "northest_lat": 48.716672523486, + "southest_lat": 47.924112631788, + "eastest_lon": 4.863174195777, + "westest_lon": 3.3883584814447 + }, + { + "id": "11", + "name": "Aude", + "northest_lat": 43.459927734352, + "southest_lat": 42.64890098195, + "eastest_lon": 3.2405623482295, + "westest_lon": 1.6884233932357 + }, + { + "id": "12", + "name": "Aveyron", + "northest_lat": 44.941219492647, + "southest_lat": 43.692056102371, + "eastest_lon": 3.4507554815828, + "westest_lon": 1.8396044963184 + }, + { + "id": "13", + "name": "Bouches-du-Rhône", + "northest_lat": 43.92406219253, + "southest_lat": 43.162545513212, + "eastest_lon": 5.8132476569219, + "westest_lon": 4.2302808850321 + }, + { + "id": "14", + "name": "Calvados", + "northest_lat": 49.429859840723, + "southest_lat": 48.752223582274, + "eastest_lon": 0.44627431224134, + "westest_lon": -1.1595014604014 + }, + { + "id": "15", + "name": "Cantal", + "northest_lat": 45.480702895801, + "southest_lat": 44.61552895784, + "eastest_lon": 3.3699091459722, + "westest_lon": 2.0629079799591 + }, + { + "id": "16", + "name": "Charente", + "northest_lat": 46.139591492141, + "southest_lat": 45.191628193392, + "eastest_lon": 0.9456207917489, + "westest_lon": -0.46177341555077 + }, + { + "id": "17", + "name": "Charente-Maritime", + "northest_lat": 46.371051399922, + "southest_lat": 45.088810634907, + "eastest_lon": 0.005823224821197, + "westest_lon": -1.5614800452621 + }, + { + "id": "18", + "name": "Cher", + "northest_lat": 47.628965936616, + "southest_lat": 46.420403547753, + "eastest_lon": 3.0793324170792, + "westest_lon": 1.7745852665449 + }, + { + "id": "19", + "name": "Corrèze", + "northest_lat": 45.763895555756, + "southest_lat": 44.923721627249, + "eastest_lon": 2.5283596411119, + "westest_lon": 1.2271245972559 + }, + { + "id": "21", + "name": "Côte-d'Or", + "northest_lat": 48.030241950581, + "southest_lat": 46.900857518168, + "eastest_lon": 5.5185372800929, + "westest_lon": 4.0660574486622 + }, + { + "id": "22", + "name": "Côtes-d'Armor", + "northest_lat": 48.867411825697, + "southest_lat": 48.035478415172, + "eastest_lon": -1.9089921410274, + "westest_lon": -3.663669588163 + }, + { + "id": "23", + "name": "Creuse", + "northest_lat": 46.45481310551, + "southest_lat": 45.664008285608, + "eastest_lon": 2.6107853057918, + "westest_lon": 1.3748978470741 + }, + { + "id": "24", + "name": "Dordogne", + "northest_lat": 45.714569962764, + "southest_lat": 44.57129825478, + "eastest_lon": 1.4482602497483, + "westest_lon": -0.041998525054412 + }, + { + "id": "25", + "name": "Doubs", + "northest_lat": 47.579897594928, + "southest_lat": 46.553996454277, + "eastest_lon": 7.0622006908671, + "westest_lon": 5.6987272452696 + }, + { + "id": "26", + "name": "Drôme", + "northest_lat": 45.344042230781, + "southest_lat": 44.115716677493, + "eastest_lon": 5.8294720463131, + "westest_lon": 4.6477668446587 + }, + { + "id": "27", + "name": "Eure", + "northest_lat": 49.485111873749, + "southest_lat": 48.666521479531, + "eastest_lon": 1.8026740663848, + "westest_lon": 0.29722451460974 + }, + { + "id": "28", + "name": "Eure-et-Loir", + "northest_lat": 48.941051842112, + "southest_lat": 47.953852019595, + "eastest_lon": 1.9940901445311, + "westest_lon": 0.76023175104941 + }, + { + "id": "29", + "name": "Finistère", + "northest_lat": 48.75230874743, + "southest_lat": 47.762638930067, + "eastest_lon": -3.3880788564101, + "westest_lon": -5.138001239929 + }, + { + "id": "30", + "name": "Gard", + "northest_lat": 44.459798467391, + "southest_lat": 43.460183661653, + "eastest_lon": 4.8455501032842, + "westest_lon": 3.2628340569911 + }, + { + "id": "31", + "name": "Haute-Garonne", + "northest_lat": 43.920240096152, + "southest_lat": 42.68989270234, + "eastest_lon": 2.0478554672695, + "westest_lon": 0.44199364903152 + }, + { + "id": "32", + "name": "Gers", + "northest_lat": 44.078224550392, + "southest_lat": 43.310884111508, + "eastest_lon": 1.2013345895525, + "westest_lon": -0.28211623210758 + }, + { + "id": "33", + "name": "Gironde", + "northest_lat": 45.574691325999, + "southest_lat": 44.193811119459, + "eastest_lon": 0.31506020240148, + "westest_lon": -1.2617334302552 + }, + { + "id": "34", + "name": "Hérault", + "northest_lat": 43.969527164685, + "southest_lat": 43.212804132866, + "eastest_lon": 4.1944474773799, + "westest_lon": 2.5399656073586 + }, + { + "id": "35", + "name": "Ille-et-Vilaine", + "northest_lat": 48.704854504824, + "southest_lat": 47.631356309182, + "eastest_lon": -1.0168893967587, + "westest_lon": -2.289084836122 + }, + { + "id": "36", + "name": "Indre", + "northest_lat": 47.276819032313, + "southest_lat": 46.347214822447, + "eastest_lon": 2.2043920861378, + "westest_lon": 0.86746898682573 + }, + { + "id": "37", + "name": "Indre-et-Loire", + "northest_lat": 47.709346222795, + "southest_lat": 46.737086924375, + "eastest_lon": 1.3653663291974, + "westest_lon": 0.053277684947378 + }, + { + "id": "38", + "name": "Isère", + "northest_lat": 45.883269928025, + "southest_lat": 44.696067584965, + "eastest_lon": 6.3588423781754, + "westest_lon": 4.7441167394752 + }, + { + "id": "39", + "name": "Jura", + "northest_lat": 47.305475313171, + "southest_lat": 46.260731935709, + "eastest_lon": 6.2033299339615, + "westest_lon": 5.2548827302617 + }, + { + "id": "40", + "name": "Landes", + "northest_lat": 44.532195517275, + "southest_lat": 43.487949116697, + "eastest_lon": 0.13672631290526, + "westest_lon": -1.524870110434 + }, + { + "id": "41", + "name": "Loir-et-Cher", + "northest_lat": 48.132548568904, + "southest_lat": 47.18622172903, + "eastest_lon": 2.2478931361182, + "westest_lon": 0.58052041667909 + }, + { + "id": "42", + "name": "Loire", + "northest_lat": 46.275936357353, + "southest_lat": 45.232177394436, + "eastest_lon": 4.7604638818845, + "westest_lon": 3.6906909501902 + }, + { + "id": "43", + "name": "Haute-Loire", + "northest_lat": 45.427582294546, + "southest_lat": 44.743866105932, + "eastest_lon": 4.489606977621, + "westest_lon": 3.0822533822787 + }, + { + "id": "44", + "name": "Loire-Atlantique", + "northest_lat": 47.833557723029, + "southest_lat": 46.860078088448, + "eastest_lon": -0.94643916329696, + "westest_lon": -2.5589448655806 + }, + { + "id": "45", + "name": "Loiret", + "northest_lat": 48.344598562828, + "southest_lat": 47.482968445146, + "eastest_lon": 3.1284487900515, + "westest_lon": 1.5129691249084 + }, + { + "id": "46", + "name": "Lot", + "northest_lat": 45.046275852749, + "southest_lat": 44.204018679795, + "eastest_lon": 2.2108934010391, + "westest_lon": 0.98177646477517 + }, + { + "id": "47", + "name": "Lot-et-Garonne", + "northest_lat": 44.764390535693, + "southest_lat": 43.973860568873, + "eastest_lon": 1.0779367166615, + "westest_lon": -0.14068987994571 + }, + { + "id": "48", + "name": "Lozère", + "northest_lat": 44.971408091786, + "southest_lat": 44.113818175271, + "eastest_lon": 3.9981617468281, + "westest_lon": 2.981675726654 + }, + { + "id": "49", + "name": "Maine-et-Loire", + "northest_lat": 47.809992506553, + "southest_lat": 46.969397597368, + "eastest_lon": 0.23453049018557, + "westest_lon": -1.3541992398083 + }, + { + "id": "50", + "name": "Manche", + "northest_lat": 49.725557927402, + "southest_lat": 48.458282754255, + "eastest_lon": -0.73732101904671, + "westest_lon": -1.9472733176655 + }, + { + "id": "51", + "name": "Marne", + "northest_lat": 49.406179032892, + "southest_lat": 48.516108006569, + "eastest_lon": 5.0379027924329, + "westest_lon": 3.398657955437 + }, + { + "id": "52", + "name": "Haute-Marne", + "northest_lat": 48.688711618117, + "southest_lat": 47.576950536437, + "eastest_lon": 5.8908642780035, + "westest_lon": 4.6268310932286 + }, + { + "id": "53", + "name": "Mayenne", + "northest_lat": 48.567994064435, + "southest_lat": 47.733379704738, + "eastest_lon": -0.049909790963035, + "westest_lon": -1.238247803597 + }, + { + "id": "54", + "name": "Meurthe-et-Moselle", + "northest_lat": 49.562644003065, + "southest_lat": 48.349889737943, + "eastest_lon": 7.1231636635608, + "westest_lon": 5.429907860027 + }, + { + "id": "55", + "name": "Meuse", + "northest_lat": 49.617086785829, + "southest_lat": 48.410687855212, + "eastest_lon": 5.8541770017029, + "westest_lon": 4.8885820531146 + }, + { + "id": "56", + "name": "Morbihan", + "northest_lat": 48.210884763611, + "southest_lat": 47.283069445657, + "eastest_lon": -2.0357552590146, + "westest_lon": -3.7321436369252 + }, + { + "id": "57", + "name": "Moselle", + "northest_lat": 49.510019040716, + "southest_lat": 48.52694525177, + "eastest_lon": 7.6352815933424, + "westest_lon": 5.8934039932125 + }, + { + "id": "58", + "name": "Nièvre", + "northest_lat": 47.587958865747, + "southest_lat": 46.651760006926, + "eastest_lon": 4.2306617272065, + "westest_lon": 2.8451871650071 + }, + { + "id": "59", + "name": "Nord", + "northest_lat": 51.08854370897, + "southest_lat": 49.969186662527, + "eastest_lon": 4.2279959931456, + "westest_lon": 2.0677049871716 + }, + { + "id": "60", + "name": "Oise", + "northest_lat": 49.758309270134, + "southest_lat": 49.060452516659, + "eastest_lon": 3.162641421643, + "westest_lon": 1.6895744511517 + }, + { + "id": "61", + "name": "Orne", + "northest_lat": 48.972557592954, + "southest_lat": 48.181599665308, + "eastest_lon": 0.9762713097259, + "westest_lon": -0.86036021134895 + }, + { + "id": "62", + "name": "Pas-de-Calais", + "northest_lat": 51.006501514321, + "southest_lat": 50.020975633738, + "eastest_lon": 3.1883563131291, + "westest_lon": 1.5577948179294 + }, + { + "id": "63", + "name": "Puy-de-Dôme", + "northest_lat": 46.255486133208, + "southest_lat": 45.287121578381, + "eastest_lon": 3.9844000097893, + "westest_lon": 2.388014020679 + }, + { + "id": "64", + "name": "Pyrénées-Atlantiques", + "northest_lat": 43.596401195938, + "southest_lat": 42.777515930774, + "eastest_lon": 0.02629551293813, + "westest_lon": -1.7908870919282 + }, + { + "id": "65", + "name": "Hautes-Pyrénées", + "northest_lat": 43.609311711216, + "southest_lat": 42.674921018438, + "eastest_lon": 0.64553925526757, + "westest_lon": -0.3270823405503 + }, + { + "id": "66", + "name": "Pyrénées-Orientales", + "northest_lat": 42.918339638726, + "southest_lat": 42.33364688988, + "eastest_lon": 3.1747892794105, + "westest_lon": 1.7256472450279 + }, + { + "id": "67", + "name": "Bas-Rhin", + "northest_lat": 49.077884925649, + "southest_lat": 48.120371112534, + "eastest_lon": 8.2303986615424, + "westest_lon": 6.9403717864006 + }, + { + "id": "68", + "name": "Haut-Rhin", + "northest_lat": 48.310471263573, + "southest_lat": 47.422198455938, + "eastest_lon": 7.6220859200517, + "westest_lon": 6.8428287756472 + }, + { + "id": "69", + "name": "Rhône", + "northest_lat": 46.303994122044, + "southest_lat": 45.45503324486, + "eastest_lon": 5.1592030475156, + "westest_lon": 4.243469905983 + }, + { + "id": "70", + "name": "Haute-Saône", + "northest_lat": 48.023714993889, + "southest_lat": 47.253139353829, + "eastest_lon": 6.8235333222471, + "westest_lon": 5.3727580571009 + }, + { + "id": "71", + "name": "Saône-et-Loire", + "northest_lat": 47.155410810959, + "southest_lat": 46.156946471815, + "eastest_lon": 5.4622983197648, + "westest_lon": 3.6225898833129 + }, + { + "id": "72", + "name": "Sarthe", + "northest_lat": 48.482954540393, + "southest_lat": 47.569104805534, + "eastest_lon": 0.91379809767445, + "westest_lon": -0.44786007819229 + }, + { + "id": "73", + "name": "Savoie", + "northest_lat": 45.93845768164, + "southest_lat": 45.051837207667, + "eastest_lon": 7.1842712160815, + "westest_lon": 5.6230208703548 + }, + { + "id": "74", + "name": "Haute-Savoie", + "northest_lat": 46.408081546332, + "southest_lat": 45.682198985672, + "eastest_lon": 7.0438913499404, + "westest_lon": 5.8074048290847 + }, + { + "id": "75", + "name": "Paris", + "northest_lat": 48.902007785215, + "southest_lat": 48.816314210034, + "eastest_lon": 2.4675819883673, + "westest_lon": 2.2242191058804 + }, + { + "id": "76", + "name": "Seine-Maritime", + "northest_lat": 50.070851596042, + "southest_lat": 49.252262168305, + "eastest_lon": 1.79022549105, + "westest_lon": 0.065609431053556 + }, + { + "id": "77", + "name": "Seine-et-Marne", + "northest_lat": 49.11755332218, + "southest_lat": 48.122204261229, + "eastest_lon": 3.555613758785, + "westest_lon": 2.3931765378081 + }, + { + "id": "78", + "name": "Yvelines", + "northest_lat": 49.08303659502, + "southest_lat": 48.440146719021, + "eastest_lon": 2.2265538842831, + "westest_lon": 1.4472851104304 + }, + { + "id": "79", + "name": "Deux-Sèvres", + "northest_lat": 47.108333434925, + "southest_lat": 45.969661749473, + "eastest_lon": 0.22035828616308, + "westest_lon": -0.89196408624284 + }, + { + "id": "80", + "name": "Somme", + "northest_lat": 50.366290636763, + "southest_lat": 49.571762327, + "eastest_lon": 3.2030417908111, + "westest_lon": 1.3796981484469 + }, + { + "id": "81", + "name": "Tarn", + "northest_lat": 44.200834436147, + "southest_lat": 43.383508824887, + "eastest_lon": 2.93545676901, + "westest_lon": 1.5439759556659 + }, + { + "id": "82", + "name": "Tarn-et-Garonne", + "northest_lat": 44.393331095059, + "southest_lat": 43.770780304363, + "eastest_lon": 1.9963637896774, + "westest_lon": 0.73810974125492 + }, + { + "id": "83", + "name": "Var", + "northest_lat": 43.806770970879, + "southest_lat": 42.982043008785, + "eastest_lon": 6.9337211159516, + "westest_lon": 5.6559638228901 + }, + { + "id": "84", + "name": "Vaucluse", + "northest_lat": 44.431367227183, + "southest_lat": 43.658685905188, + "eastest_lon": 5.7573377215236, + "westest_lon": 4.649227423465 + }, + { + "id": "85", + "name": "Vendée", + "northest_lat": 47.083893903306, + "southest_lat": 46.266566974958, + "eastest_lon": -0.53779518169029, + "westest_lon": -2.3987614706025 + }, + { + "id": "86", + "name": "Vienne", + "northest_lat": 47.175754285742, + "southest_lat": 46.049008552161, + "eastest_lon": 1.2126877519811, + "westest_lon": -0.1021158452812 + }, + { + "id": "87", + "name": "Haute-Vienne", + "northest_lat": 46.401546863371, + "southest_lat": 45.437356928582, + "eastest_lon": 1.9094484810021, + "westest_lon": 0.62974117909144 + }, + { + "id": "88", + "name": "Vosges", + "northest_lat": 48.513587820739, + "southest_lat": 47.813051201983, + "eastest_lon": 7.1982872111206, + "westest_lon": 5.3944755711529 + }, + { + "id": "89", + "name": "Yonne", + "northest_lat": 48.39970411104, + "southest_lat": 47.312769315752, + "eastest_lon": 4.3403007872795, + "westest_lon": 2.8487899744432 + }, + { + "id": "90", + "name": "Territoire de Belfort", + "northest_lat": 47.824784354742, + "southest_lat": 47.433371743667, + "eastest_lon": 7.1398015507652, + "westest_lon": 6.7576409592057 + }, + { + "id": "91", + "name": "Essonne", + "northest_lat": 48.776101996393, + "southest_lat": 48.284688606385, + "eastest_lon": 2.5853737107586, + "westest_lon": 1.9149199821626 + }, + { + "id": "92", + "name": "Hauts-de-Seine", + "northest_lat": 48.950965864655, + "southest_lat": 48.72948996497, + "eastest_lon": 2.3363529889891, + "westest_lon": 2.1458760215967 + }, + { + "id": "93", + "name": "Seine-Saint-Denis", + "northest_lat": 49.012397786393, + "southest_lat": 48.807437551952, + "eastest_lon": 2.6025997962059, + "westest_lon": 2.2882536989787 + }, + { + "id": "94", + "name": "Val-de-Marne", + "northest_lat": 48.861405371284, + "southest_lat": 48.688326690701, + "eastest_lon": 2.6136517425679, + "westest_lon": 2.3102224901101 + }, + { + "id": "95", + "name": "Val-d'Oise", + "northest_lat": 49.232197221792, + "southest_lat": 48.908679329899, + "eastest_lon": 2.5905283926735, + "westest_lon": 1.608798807603 + }, + { + "id": "2A", + "name": "Corse-du-Sud", + "northest_lat": 42.381404942274, + "southest_lat": 41.362164776515, + "eastest_lon": 9.4073217319481, + "westest_lon": 8.5401025407511 + }, + { + "id": "2B", + "name": "Haute-Corse", + "northest_lat": 43.011724041684, + "southest_lat": 41.832143660252, + "eastest_lon": 9.5592262719626, + "westest_lon": 8.5734085639674 + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg new file mode 100644 index 00000000000..f690930b938 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg new file mode 100644 index 00000000000..aff876d3508 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg new file mode 100644 index 00000000000..df239efc873 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg new file mode 100644 index 00000000000..bce1d6ef083 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg @@ -0,0 +1,6 @@ + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg new file mode 100644 index 00000000000..22a5d524ec6 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg new file mode 100644 index 00000000000..1d75a230199 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg @@ -0,0 +1,4 @@ + + + From 46582923666e8056a17dea33e48577fef71b7b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Thu, 24 Oct 2024 17:38:04 +0200 Subject: [PATCH 02/17] Progress done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- bundles/org.openhab.binding.airparif/NOTICE | 14 + .../internal/AirParifBindingConstants.java | 12 +- .../internal/AirParifHandlerFactory.java | 6 +- .../internal/AirParifIconProvider.java | 24 +- .../airparif/internal/api/AirParifDto.java | 81 ++- .../airparif/internal/api/ColorMap.java | 4 +- .../internal/api/PollenAlertLevel.java | 14 +- .../airparif/internal/api/Pollutant.java | 32 +- .../config/LocationConfiguration.java | 2 + .../internal/db/DepartmentDbService.java | 4 - .../deserialization/AirParifDeserializer.java | 15 +- .../PollenAlertLevelDeserializer.java | 3 +- .../discovery/AirParifDiscoveryService.java | 55 +- .../handler/AirParifBridgeHandler.java | 165 +++++- .../internal/handler/LocationHandler.java | 74 ++- .../src/main/resources/OH-INF/addon/addon.xml | 6 +- .../resources/OH-INF/i18n/airparif.properties | 225 +++++++- .../src/main/resources/OH-INF/thing/api.xml | 10 + .../resources/OH-INF/thing/channel-groups.xml | 106 ++++ .../main/resources/OH-INF/thing/channels.xml | 479 +++++++++++------- .../resources/OH-INF/thing/thing-types.xml | 48 +- .../src/main/resources/icon/alder.svg | 7 + .../src/main/resources/icon/ash.svg | 19 + .../src/main/resources/icon/birch.svg | 5 + .../src/main/resources/icon/chestnut.svg | 5 + .../src/main/resources/icon/cypress.svg | 14 + .../src/main/resources/icon/grasses.svg | 5 + .../src/main/resources/icon/hazel.svg | 17 + .../src/main/resources/icon/hornbeam.svg | 11 + .../src/main/resources/icon/linden.svg | 8 + .../src/main/resources/icon/oak.svg | 5 + .../src/main/resources/icon/olive.svg | 5 + .../src/main/resources/icon/plane.svg | 5 + .../src/main/resources/icon/plantain.svg | 11 + .../src/main/resources/icon/poplar.svg | 5 + .../src/main/resources/icon/ragweed.svg | 14 + .../src/main/resources/icon/rumex.svg | 5 + .../src/main/resources/icon/urticaceae.svg | 14 + .../src/main/resources/icon/willow.svg | 5 + .../src/main/resources/icon/wormwood.svg | 13 + 40 files changed, 1215 insertions(+), 337 deletions(-) create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg create mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg diff --git a/bundles/org.openhab.binding.airparif/NOTICE b/bundles/org.openhab.binding.airparif/NOTICE index 38d625e3492..a66ce2b105f 100755 --- a/bundles/org.openhab.binding.airparif/NOTICE +++ b/bundles/org.openhab.binding.airparif/NOTICE @@ -11,3 +11,17 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code https://github.com/openhab/openhab-addons + +== Credits +Icon set coming from the noon project: +Hazel, Ash : Imogen Oh +Birch, Oak : monkik +Cypress, Alder : Levi +Poplar, Rumex : Laymik +Willow : PizzaOter +Hornbeam, Linden : Cannavale +Olive : BnB Studio +Chestnut : Muhammad Fadli Rusady +Plantain : Lars Meiertoberens +Grasses : Neneng Yuliani Lestari +Ragweed, Wormwood : bsd studio diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java index 6ed929d4c87..b56b567303a 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -33,8 +33,18 @@ public class AirParifBindingConstants { // List of Things Type UIDs public static final ThingTypeUID LOCATION_THING_TYPE = new ThingTypeUID(BINDING_ID, "location"); + // Channel group ids + public static final String GROUP_POLLENS = "pollens"; + public static final String GROUP_DAILY = "daily"; + public static final String GROUP_AQ_BULLETIN = "aq-bulletin"; + public static final String GROUP_AQ_BULLETIN_TOMORROW = GROUP_AQ_BULLETIN + "-tomorrow"; + // List of all Channel ids - public static final String CHANNEL_1 = "channel1"; + public static final String CHANNEL_BEGIN_VALIDITY = "begin-validity"; + public static final String CHANNEL_END_VALIDITY = "end-validity"; + public static final String CHANNEL_COMMENT = "comment"; + public static final String CHANNEL_MESSAGE = "message"; + public static final String CHANNEL_TOMORROW = "tomorrow"; public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(APIBRIDGE_THING_TYPE, LOCATION_THING_TYPE); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java index ea9f49da607..ac814ef3a5c 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -41,12 +41,14 @@ import org.osgi.service.component.annotations.Reference; public class AirParifHandlerFactory extends BaseThingHandlerFactory { private final AirParifDeserializer deserializer; private final HttpClient httpClient; + private final AirParifIconProvider iconProvider; @Activate public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory, - final @Reference AirParifDeserializer deserializer) { + final @Reference AirParifDeserializer deserializer, final @Reference AirParifIconProvider iconProvider) { this.httpClient = httpClientFactory.getCommonHttpClient(); this.deserializer = deserializer; + this.iconProvider = iconProvider; } @Override @@ -59,7 +61,7 @@ public class AirParifHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); return APIBRIDGE_THING_TYPE.equals(thingTypeUID) - ? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer) + ? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer, iconProvider) : LOCATION_THING_TYPE.equals(thingTypeUID) ? new LocationHandler(thing) : null; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index 7a9007f2625..d464e0eb9f4 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -25,6 +25,9 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; +import org.openhab.binding.airparif.internal.api.ColorMap; +import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.ui.icon.IconProvider; import org.openhab.core.ui.icon.IconSet; @@ -45,13 +48,17 @@ import org.slf4j.LoggerFactory; @Component(service = { IconProvider.class, AirParifIconProvider.class }) @NonNullByDefault public class AirParifIconProvider implements IconProvider { + private static final String NEUTRAL_COLOR = "#3d3c3c"; private static final String DEFAULT_LABEL = "Air Parif Icons"; private static final String DEFAULT_DESCRIPTION = "Icons illustrating air quality levels provided by AirParif"; private static final List ICONS = List.of("average", "bad", "degrated", "extremely-bad", "good", "pollen"); + private static final List POLLEN_ICONS = Pollen.AS_SET.stream().map(Pollen::name).map(String::toLowerCase) + .toList(); private final Logger logger = LoggerFactory.getLogger(AirParifIconProvider.class); private final TranslationProvider i18nProvider; private final Bundle bundle; + private @Nullable ColorMap colorMap; @Activate public AirParifIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) { @@ -79,7 +86,8 @@ public class AirParifIconProvider implements IconProvider { @Override public @Nullable Integer hasIcon(String category, String iconSetId, Format format) { - return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) && ICONS.contains(category) ? 0 : null; + return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) + && (ICONS.contains(category) || POLLEN_ICONS.contains(category)) ? 0 : null; } @Override @@ -89,6 +97,16 @@ public class AirParifIconProvider implements IconProvider { String result; try (InputStream stream = iconResource.openStream()) { result = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + + if (POLLEN_ICONS.contains(category) && state != null) { + try { + int ordinal = Integer.valueOf(state); + PollenAlertLevel alertLevel = PollenAlertLevel.AS_SET.stream() + .filter(pal -> pal.riskLevel == ordinal).findFirst().orElse(PollenAlertLevel.UNKNOWN); + result = result.replaceAll(NEUTRAL_COLOR, alertLevel.color); + } catch (NumberFormatException ignore) { + } + } } catch (IOException e) { logger.warn("Unable to load ressource '{}': {}", iconResource.getPath(), e.getMessage()); result = ""; @@ -96,4 +114,8 @@ public class AirParifIconProvider implements IconProvider { return result.isEmpty() ? null : new ByteArrayInputStream(result.getBytes()); } + + public void setColorMap(ColorMap map) { + this.colorMap = map; + } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index bb2238f8692..ef9ad505fb4 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -13,11 +13,15 @@ package org.openhab.binding.airparif.internal.api; import java.time.LocalDate; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,7 +49,7 @@ public class AirParifDto { @SerializedName("droits") Set scopes) { } - private record Message(// + public record Message(// String fr, // @Nullable String en) { } @@ -92,36 +96,73 @@ public class AirParifDto { Map valeurs, // String commentaire, // String periode) { + } + public class PollensResponse { private static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yy"); private static Pattern PATTERN = Pattern.compile("\\d{2}.\\d{2}.\\d{2}"); + private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris"); - private static @Nullable LocalDate getValidity(String periode, boolean begin) { - Matcher matcher = PATTERN.matcher(periode); - if (matcher.find()) { - String extractedDate = matcher.group(); - if (begin) { - return LocalDate.parse(extractedDate, FORMATTER); + public List data = List.of(); + + public Optional getData() { + return Optional.ofNullable(data.isEmpty() ? null : data.get(0)); + } + + private Set getValidities() { + Set result = new TreeSet<>(); + getData().ifPresent(pollens -> { + Matcher matcher = PATTERN.matcher(pollens.periode); + while (matcher.find()) { + result.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); } - if (matcher.find()) { - extractedDate = matcher.group(); - return LocalDate.parse(extractedDate, FORMATTER); + }); + return result; + } + + public Optional getBeginValidity() { + return Optional.ofNullable(getValidities().iterator().next()); + } + + public Optional getEndValidity() { + return Optional.ofNullable(getValidities().stream().reduce((prev, next) -> next).orElse(null)); + } + + public Optional getComment() { + return getData().map(pollens -> pollens.commentaire); + } + + public Map getDepartment(String id) { + Map result = new HashMap<>(); + Optional donnees = getData(); + if (donnees.isPresent()) { + Pollens depts = donnees.get(); + PollenAlertLevel[] valeurs = depts.valeurs.get(id); + if (valeurs != null) { + for (int i = 0; i < valeurs.length; i++) { + result.put(depts.taxons[i], valeurs[i]); + } } } - return null; + return result; } + } - public @Nullable LocalDate beginValidity() { - return getValidity(periode, true); - } + public record Result(// + @SerializedName("polluant") Pollutant pollutant, // + ZonedDateTime date, // + @SerializedName("valeurs") double[] values, // + Message message) { + } - public @Nullable LocalDate endValidity() { - return getValidity(periode, false); - } + public record Route(// + @SerializedName("dateRequise") ZonedDateTime requestedDate, // + double[][] longlats, // + @SerializedName("resultats") Result[] results, // + @Nullable Message[] messages) { } - public record PollensResponse(ArrayList data) { + public record ItineraireResponse(@SerializedName("itineraires") Route[] routes) { } - } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java index 59aa08af91d..0c9d95d2764 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java @@ -26,8 +26,8 @@ public class ColorMap extends HashMap { private static final long serialVersionUID = -605462873565278453L; private static Appreciation fromApiName(String searched) { - return Objects.requireNonNull( - Appreciation.AS_SET.stream().filter(mt -> searched.equals(mt.apiName)).findFirst().orElse(Appreciation.UNKNOWN)); + return Objects.requireNonNull(Appreciation.AS_SET.stream().filter(mt -> searched.equals(mt.apiName)).findFirst() + .orElse(Appreciation.UNKNOWN)); } public String put(String key, String value) { diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java index 74b2517cded..59b2d821ba3 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java @@ -24,20 +24,22 @@ import com.google.gson.annotations.SerializedName; @NonNullByDefault public enum PollenAlertLevel { @SerializedName("0") - NONE(0), + NONE(0, "#3a8b2f"), @SerializedName("1") - LOW(1), + LOW(1, "#f9a825"), @SerializedName("2") - AVERAGE(2), + AVERAGE(2, "#ef6c00"), @SerializedName("3") - HIGH(3), - UNKNOWN(-1); + HIGH(3, "#b71c1c"), + UNKNOWN(-1, "#b3b3b3"); public static final EnumSet AS_SET = EnumSet.allOf(PollenAlertLevel.class); public final int riskLevel; + public final String color; - PollenAlertLevel(int riskLevel) { + PollenAlertLevel(int riskLevel, String color) { this.riskLevel = riskLevel; + this.color = color; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java index 21a1163a90b..4298a621aad 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java @@ -14,7 +14,12 @@ package org.openhab.binding.airparif.internal.api; import java.util.EnumSet; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.unit.Units; + +import com.google.gson.annotations.SerializedName; /** * The {@link Pollutant} enum lists all pollutants tracked by AirParif @@ -23,14 +28,31 @@ import org.eclipse.jdt.annotation.NonNullByDefault; */ @NonNullByDefault public enum Pollutant { - PM25, - PM10, - NO2, - O3, - UNKNOWN; + @SerializedName("pm25") + PM25(Units.MICROGRAM_PER_CUBICMETRE), + + @SerializedName("pm10") + PM10(Units.MICROGRAM_PER_CUBICMETRE), + + @SerializedName("no2") + NO2(Units.PARTS_PER_BILLION), + + @SerializedName("o3") + O3(Units.PARTS_PER_BILLION), + + @SerializedName("indice") + INDICE(Units.PERCENT), + + UNKNOWN(Units.PERCENT); public static final EnumSet AS_SET = EnumSet.allOf(Pollutant.class); + public final Unit unit; + + Pollutant(Unit unit) { + this.unit = unit; + } + public static Pollutant safeValueOf(String searched) { try { return Pollutant.valueOf(searched); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java index 0053cf0f9f5..3c7f8242997 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java @@ -23,7 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; @NonNullByDefault public class LocationConfiguration { public static final String LOCATION = "location"; + public static final String DEPARTMENT = "department"; public int refresh = 10; public String location = ""; + public String department = ""; } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java index b9278becd4f..851ef94c040 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java @@ -24,8 +24,6 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.library.types.PointType; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,7 +39,6 @@ import com.google.gson.JsonSyntaxException; * @author Gaël L'hopital - Initial Contribution */ -@Component(service = DepartmentDbService.class) @NonNullByDefault public class DepartmentDbService { private final Logger logger = LoggerFactory.getLogger(DepartmentDbService.class); @@ -51,7 +48,6 @@ public class DepartmentDbService { double westestLon) { } - @Activate public DepartmentDbService() { try (InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream("/db/departments.json"); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java index a96b359ea00..a66b32a87fc 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -44,7 +44,7 @@ public class AirParifDeserializer { @Activate public AirParifDeserializer(final @Reference TimeZoneProvider timeZoneProvider) { - gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) .registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer()) .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) .registerTypeAdapter(ColorMap.class, new ColorMapDeserializer()) @@ -52,11 +52,11 @@ public class AirParifDeserializer { .registerTypeAdapter(LocalDate.class, (JsonDeserializer) (json, type, context) -> LocalDate .parse(json.getAsJsonPrimitive().getAsString())) - .registerTypeAdapter(ZonedDateTime.class, - (JsonDeserializer) (json, type, context) -> ZonedDateTime - .parse(json.getAsJsonPrimitive().getAsString() + "Z") - .withZoneSameInstant(timeZoneProvider.getTimeZone())) - .create(); + .registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (json, type, context) -> { + String string = json.getAsJsonPrimitive().getAsString(); + string += string.contains("+") ? "" : "Z"; + return ZonedDateTime.parse(string).withZoneSameInstant(timeZoneProvider.getTimeZone()); + }).create(); } public T deserialize(Class clazz, String json) throws AirParifException { @@ -68,8 +68,7 @@ public class AirParifDeserializer { } throw new AirParifException("Deserialization of '%s' resulted in null value", json); } catch (JsonSyntaxException e) { - throw new AirParifException(e, "Unexpected error deserializing '%s'", json); + throw new AirParifException(e, "Unexpected error deserializing '%s'", e.getMessage()); } } - } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java index 112796875e2..42bcfbb6bc5 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java @@ -39,9 +39,8 @@ class PollenAlertLevelDeserializer implements JsonDeserializer } catch (JsonSyntaxException ignore) { return PollenAlertLevel.UNKNOWN; } - + return PollenAlertLevel.AS_SET.stream().filter(s -> s.riskLevel == level).findFirst() .orElse(PollenAlertLevel.UNKNOWN); - } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java index 6df88a8a2d1..61741ff9f7b 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java @@ -13,15 +13,17 @@ package org.openhab.binding.airparif.internal.discovery; import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; -import static org.openhab.binding.airparif.internal.config.LocationConfiguration.LOCATION; + +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.airparif.internal.config.LocationConfiguration; +import org.openhab.binding.airparif.internal.db.DepartmentDbService; +import org.openhab.binding.airparif.internal.db.DepartmentDbService.Department; import org.openhab.binding.airparif.internal.handler.AirParifBridgeHandler; import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocationProvider; -import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.library.types.PointType; import org.openhab.core.thing.ThingUID; import org.osgi.service.component.annotations.Component; @@ -41,40 +43,47 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi private static final int DISCOVER_TIMEOUT_SECONDS = 2; private final Logger logger = LoggerFactory.getLogger(AirParifDiscoveryService.class); + private final DepartmentDbService dbService; private @NonNullByDefault({}) LocationProvider locationProvider; public AirParifDiscoveryService() { super(AirParifBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS); + dbService = new DepartmentDbService(); } @Reference(unbind = "-") - public void bindTranslationProvider(TranslationProvider translationProvider) { - this.i18nProvider = translationProvider; - } - - @Reference(unbind = "-") - public void bindLocaleProvider(LocaleProvider localeProvider) { - this.localeProvider = localeProvider; - } - - @Reference(unbind = "-") - public void bindLocationProvider(LocationProvider locationProvider) { + public void setLocationProvider(LocationProvider locationProvider) { this.locationProvider = locationProvider; } @Override - protected void startScan() { + public void startScan() { logger.debug("Starting AirParif discovery scan"); - if (locationProvider.getLocation() instanceof PointType location) { - ThingUID bridgeUID = thingHandler.getThing().getUID(); - thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(LOCATION_THING_TYPE, bridgeUID, LOCAL)) - .withLabel("@text/discovery.airparif.location.local.label") // - .withProperty(LOCATION, location.toString()) // - .withRepresentationProperty(LOCATION) // - .withBridge(bridgeUID).build()); + + LocationProvider localLocation = locationProvider; + PointType location = localLocation != null ? localLocation.getLocation() : null; + if (location == null) { + logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results"); + return; + } + + createDepartmentResults(location); + } + + private void createDepartmentResults(PointType serverLocation) { + List candidates = dbService.getBounding(serverLocation); + ThingUID bridgeUID = thingHandler.getThing().getUID(); + if (!candidates.isEmpty()) { + candidates.forEach(dep -> thingDiscovered( + DiscoveryResultBuilder.create(new ThingUID(LOCATION_THING_TYPE, bridgeUID, dep.id()))// + .withLabel("Location Report: %s".formatted(dep.name())) // + .withProperty(LocationConfiguration.DEPARTMENT, dep.id()) // + .withProperty(LocationConfiguration.LOCATION, serverLocation.toFullString())// + .withRepresentationProperty(LocationConfiguration.DEPARTMENT) // + .withBridge(bridgeUID).build())); } else { - logger.debug("LocationProvider.getLocation() is not set, no discovery results can be provided"); + logger.info("No department could be discovered matching server location"); } } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index bb2883ee288..c3db0bb8c95 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -12,13 +12,22 @@ */ package org.openhab.binding.airparif.internal.handler; +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; import static org.openhab.binding.airparif.internal.api.AirParifApi.*; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; @@ -26,29 +35,38 @@ import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus.Code; import org.openhab.binding.airparif.internal.AirParifException; +import org.openhab.binding.airparif.internal.AirParifIconProvider; import org.openhab.binding.airparif.internal.api.AirParifDto.Bulletin; import org.openhab.binding.airparif.internal.api.AirParifDto.Episode; +import org.openhab.binding.airparif.internal.api.AirParifDto.ItineraireResponse; import org.openhab.binding.airparif.internal.api.AirParifDto.KeyInfo; -import org.openhab.binding.airparif.internal.api.AirParifDto.Pollens; import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; +import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.AirParifDto.Version; import org.openhab.binding.airparif.internal.api.ColorMap; import org.openhab.binding.airparif.internal.config.BridgeConfiguration; import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,13 +84,19 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(AirParifBridgeHandler.class); private final AirParifDeserializer deserializer; + private final AirParifIconProvider iconProvider; private final HttpClient httpClient; private BridgeConfiguration config = new BridgeConfiguration(); - public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer) { + private @Nullable ScheduledFuture pollensJob; + private @Nullable ScheduledFuture dailyJob; + + public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer, + AirParifIconProvider iconProvider) { super(bridge); this.deserializer = deserializer; + this.iconProvider = iconProvider; this.httpClient = httpClient; } @@ -85,16 +109,39 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { "@text/offline.config-error-unknown-apikey"); return; } - initiateConnexion(); + scheduler.execute(this::initiateConnexion); } - public synchronized String executeUri(URI uri) throws AirParifException { + private @Nullable ScheduledFuture cancelFuture(@Nullable ScheduledFuture job) { + if (job != null && !job.isCancelled()) { + job.cancel(true); + } + return null; + } + + @Override + public void dispose() { + logger.debug("Disposing the AirParif bridge handler."); + + pollensJob = cancelFuture(pollensJob); + dailyJob = cancelFuture(dailyJob); + } + + public synchronized String executeUri(URI uri, HttpMethod method, @Nullable String payload) + throws AirParifException { logger.debug("executeUrl: {} ", uri); - Request request = httpClient.newRequest(uri).method(HttpMethod.GET) - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) + Request request = httpClient.newRequest(uri).method(method).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) .header(HttpHeader.ACCEPT, MediaType.APPLICATION_JSON).header("X-Api-Key", config.apikey); + if (payload != null && HttpMethod.POST.equals(method)) { + InputStream stream = new ByteArrayInputStream(payload.getBytes(DEFAULT_CHARSET)); + try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) { + request.content(inputStreamContentProvider, MediaType.APPLICATION_JSON); + } + logger.trace(" -with payload : {} ", payload); + } + try { ContentResponse response = request.send(); @@ -107,7 +154,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { } else if (statusCode == Code.FORBIDDEN) { throw new AirParifException("@text/offline.config-error-invalid-apikey"); } - + String content = new String(response.getContent(), DEFAULT_CHARSET); throw new AirParifException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString()); } catch (TimeoutException | ExecutionException e) { throw new AirParifException(e, "Exception while calling %s", request.getURI()); @@ -117,7 +164,12 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { } public synchronized T executeUri(URI uri, Class clazz) throws AirParifException { - String content = executeUri(uri); + String content = executeUri(uri, HttpMethod.GET, null); + return deserializer.deserialize(clazz, content); + } + + public synchronized T executeUri(URI uri, Class clazz, String payload) throws AirParifException { + String content = executeUri(uri, HttpMethod.POST, payload); return deserializer.deserialize(clazz, content); } @@ -128,8 +180,6 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { private void initiateConnexion() { Version version; - KeyInfo keyInfo; - try { // This does validate communication with the server version = executeUri(VERSION_URI, Version.class); } catch (AirParifException e) { @@ -137,6 +187,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { return; } + KeyInfo keyInfo; try { // This validates the api key value keyInfo = executeUri(KEY_INFO_URI, KeyInfo.class); } catch (AirParifException e) { @@ -146,31 +197,101 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { getThing().setProperty("api-version", version.version()); getThing().setProperty("key-expiration", keyInfo.expiration().toString()); - logger.info("The api key is valid until {}", keyInfo.expiration().toString()); getThing().setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(","))); + logger.info("The api key is valid until {}", keyInfo.expiration().toString()); updateStatus(ThingStatus.ONLINE); try { ColorMap map = executeUri(PREV_COLORS_URI, ColorMap.class); - logger.info("The color map is {}", map.toString()); + logger.debug("The color map is {}", map.toString()); + iconProvider.setColorMap(map); + } catch (AirParifException e) { + logger.warn("Error reading ColorMap: {]", e.getMessage()); + } + pollensJob = scheduler.schedule(this::updatePollens, 1, TimeUnit.SECONDS); + dailyJob = scheduler.schedule(this::updateDaily, 2, TimeUnit.SECONDS); + } + + private void updateDaily() { + try { Bulletin bulletin = executeUri(PREV_BULLETIN_URI, Bulletin.class); - logger.info("The bulletin is {}", bulletin.today().dayDescription()); + logger.debug("The bulletin is {}", bulletin.today().dayDescription()); + + Set.of(bulletin.today(), bulletin.tomorrow()).stream().forEach(aq -> { + String groupName = aq.previsionDate().equals(LocalDate.now()) ? GROUP_AQ_BULLETIN + : GROUP_AQ_BULLETIN_TOMORROW + "#"; + updateState(groupName + CHANNEL_COMMENT, + !aq.available() ? UnDefType.UNDEF : new StringType(aq.bulletin().fr())); + aq.concentrations().forEach(measure -> { + String cName = groupName + measure.pollutant().name().toLowerCase(); + updateState(cName + "-min", !aq.available() ? UnDefType.UNDEF + : new QuantityType<>(measure.min(), measure.pollutant().unit)); + updateState(cName + "-max", !aq.available() ? UnDefType.UNDEF + : new QuantityType<>(measure.max(), measure.pollutant().unit)); + }); + }); Episode episode = executeUri(EPISODES_URI, Episode.class); - logger.info("The bulletin is {}", episode); + logger.debug("The episode is {}", episode); - Pollens pollens = executeUri(POLLENS_URI, PollensResponse.class).data().get(0); - logger.info("The pollens are {}", pollens); - LocalDate begin = pollens.beginValidity(); - LocalDate end = pollens.endValidity(); + // if (episode.active()) { + // updateState(GROUP_DAILY + "#" + CHANNEL_MESSAGE, new StringType(episode.message().fr())); + // updateState(GROUP_DAILY + "#" + CHANNEL_TOMORROW, new StringType(episode.message().fr())); + // } - String response = executeUri(POLLENS_DEPT_BUILDER.path("78").build()); - logger.info("The pollens 78 {}", response); + ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); + long delay = Duration.between(ZonedDateTime.now(), tomorrowMorning).getSeconds(); + logger.debug("Rescheduling daily job tomorrow morning"); + dailyJob = scheduler.schedule(this::updateDaily, delay, TimeUnit.SECONDS); } catch (AirParifException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - return; + logger.warn("Error update pollens data: {}", e.getMessage()); } } + private void updatePollens() { + try { + PollensResponse pollens = executeUri(POLLENS_URI, PollensResponse.class); + + pollens.getComment() + .ifPresent(comment -> updateState(GROUP_POLLENS + "#" + CHANNEL_COMMENT, new StringType(comment))); + pollens.getBeginValidity().ifPresent( + begin -> updateState(GROUP_POLLENS + "#" + CHANNEL_BEGIN_VALIDITY, new DateTimeType(begin))); + pollens.getEndValidity().ifPresent(end -> { + updateState(GROUP_POLLENS + "#" + CHANNEL_END_VALIDITY, new DateTimeType(end)); + logger.info("Pollens bulletin valid until {}", end); + long delay = Duration.between(ZonedDateTime.now(), end).getSeconds(); + if (delay < 0) { + // what if the bulletin was not updated and the delay is passed ? + delay = 3600; + logger.debug("Update time of the bulletin is in the past - will retry in one hour"); + } else { + delay += 60; + } + + pollensJob = scheduler.schedule(this::updatePollens, delay, TimeUnit.SECONDS); + }); + getThing().getThings().stream().map(Thing::getHandler).filter(LocationHandler.class::isInstance) + .map(LocationHandler.class::cast).forEach(locHand -> locHand.setPollens(pollens)); + } catch (AirParifException e) { + logger.warn("Error updating pollens data: {}", e.getMessage()); + } + } + + public @Nullable Route getConcentrations(String location) { + String[] elements = location.split(","); + if (elements.length >= 2) { + String req = "{\"itineraires\": [{\"date\": \"%s\",\"longlats\": [[%s,%s]]}],\"polluants\": [\"indice\",\"no2\",\"o3\",\"pm25\",\"pm10\"]}"; + req = req.formatted(LocalDateTime.now().truncatedTo(ChronoUnit.HOURS), elements[1], elements[0]); + try { + ItineraireResponse result = executeUri(HORAIR_URI, ItineraireResponse.class, req); + return result.routes()[0]; + } catch (AirParifException e) { + logger.warn("Error getting detailed concentrations: {}", e.getMessage()); + } + } else { + logger.warn("Wrong localisation as input : {}", location); + } + return null; + } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index 11450b1ecb2..55f8484dfd8 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -12,17 +12,25 @@ */ package org.openhab.binding.airparif.internal.handler; -import static org.openhab.binding.airparif.internal.AirParifBindingConstants.CHANNEL_1; +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.GROUP_POLLENS; + +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; +import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; +import org.openhab.binding.airparif.internal.api.AirParifDto.Route; +import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.config.LocationConfiguration; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,36 +51,50 @@ public class LocationHandler extends BaseThingHandler { super(thing); } - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (CHANNEL_1.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - // TODO: handle data refresh - } - - // TODO: handle command - - // Note: if communication with thing fails for some reason, - // indicate that by setting the status with detail information: - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - // "Could not control device at IP address x.x.x.x"); - } - } - @Override public void initialize() { config = getConfigAs(LocationConfiguration.class); updateStatus(ThingStatus.UNKNOWN); - // Example for background initialization: - scheduler.execute(() -> { - boolean thingReachable = true; // - // when done do: - if (thingReachable) { - updateStatus(ThingStatus.ONLINE); + scheduler.execute(this::getConcentrations); + } + + public void setPollens(PollensResponse pollens) { + LocationConfiguration local = config; + if (local != null) { + Map alerts = pollens.getDepartment(local.department); + alerts.forEach((pollen, level) -> { + updateState(GROUP_POLLENS + "#" + pollen.name().toLowerCase(), new DecimalType(level.ordinal())); + }); + updateStatus(ThingStatus.ONLINE); + } + } + + private void getConcentrations() { + AirParifBridgeHandler apiHandler = getApiBridgeHandler(); + LocationConfiguration local = config; + if (apiHandler != null && local != null) { + Route route = apiHandler.getConcentrations(local.location); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // TODO Auto-generated method stub + + } + + private @Nullable AirParifBridgeHandler getApiBridgeHandler() { + Bridge bridge = this.getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + if (bridge.getHandler() instanceof AirParifBridgeHandler airParifBridgeHandler) { + return airParifBridgeHandler; } else { - updateStatus(ThingStatus.OFFLINE); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge"); } - }); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + return null; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml index 74286c6bb4c..c6016d94731 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/addon/addon.xml @@ -6,7 +6,7 @@ binding AirParif Binding Air Quality data and forecasts provided by AirParif. - cloud - fr - + cloud + fr + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index 98191f8a6c2..448b12d4d94 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -1,3 +1,225 @@ +# add-on + +addon.airparif.name = AirParif Binding +addon.airparif.description = Air Quality data and forecasts provided by AirParif. + +# thing types + +thing-type.airparif.api.label = AirParif API Portal +thing-type.airparif.api.description = Bridge to the AirParif API Portal. In order to receive the data, you must register an account on https://www.airparif.fr/contact and receive your API token. +thing-type.airparif.location.label = Department Report +thing-type.airparif.location.description = AirParif air quality report for the given location + +# thing types config + +thing-type.config.airparif.api.apikey.label = API Key +thing-type.config.airparif.api.apikey.description = Token used to access the service +thing-type.config.airparif.location.department.label = Department +thing-type.config.airparif.location.department.description = Name of the department +thing-type.config.airparif.location.department.option.75 = Paris +thing-type.config.airparif.location.department.option.77 = Seine et Marne +thing-type.config.airparif.location.department.option.78 = Yvelines +thing-type.config.airparif.location.department.option.91 = Essonne +thing-type.config.airparif.location.department.option.92 = Hauts de Seine +thing-type.config.airparif.location.department.option.93 = Seine Saint Denis +thing-type.config.airparif.location.department.option.94 = Val de Marne +thing-type.config.airparif.location.department.option.95 = Val D'Oise +thing-type.config.airparif.location.location.label = Location + +# channel group types + +channel-group-type.airparif.bridge-pollens.label = Pollen information for the region +channel-group-type.airparif.bridge-pollens.channel.begin-validity.label = Begin Validity +channel-group-type.airparif.bridge-pollens.channel.begin-validity.description = Current bulletin validity start +channel-group-type.airparif.bridge-pollens.channel.comment.label = Begin Validity +channel-group-type.airparif.bridge-pollens.channel.comment.description = Current bulletin validity start +channel-group-type.airparif.bridge-pollens.channel.end-validity.label = End Validity +channel-group-type.airparif.bridge-pollens.channel.end-validity.description = Current bulletin validity ending +channel-group-type.airparif.dept-pollens.label = Pollen information for the department + +# channel types + +channel-type.airparif.alder-level.label = Alder +channel-type.airparif.alder-level.state.option.0 = None +channel-type.airparif.alder-level.state.option.1 = Low +channel-type.airparif.alder-level.state.option.2 = Average +channel-type.airparif.alder-level.state.option.3 = High +channel-type.airparif.ash-level.label = Ash +channel-type.airparif.ash-level.state.option.0 = None +channel-type.airparif.ash-level.state.option.1 = Low +channel-type.airparif.ash-level.state.option.2 = Average +channel-type.airparif.ash-level.state.option.3 = High +channel-type.airparif.birch-level.label = Birch Level +channel-type.airparif.birch-level.state.option.0 = None +channel-type.airparif.birch-level.state.option.1 = Low +channel-type.airparif.birch-level.state.option.2 = Average +channel-type.airparif.birch-level.state.option.3 = High +channel-type.airparif.chestnut-level.label = Chestnut +channel-type.airparif.chestnut-level.state.option.0 = None +channel-type.airparif.chestnut-level.state.option.1 = Low +channel-type.airparif.chestnut-level.state.option.2 = Average +channel-type.airparif.chestnut-level.state.option.3 = High +channel-type.airparif.comment.label = Comment +channel-type.airparif.cypress-level.label = Cypress +channel-type.airparif.cypress-level.state.option.0 = None +channel-type.airparif.cypress-level.state.option.1 = Low +channel-type.airparif.cypress-level.state.option.2 = Average +channel-type.airparif.cypress-level.state.option.3 = High +channel-type.airparif.grasses-level.label = Grasses +channel-type.airparif.grasses-level.state.option.0 = None +channel-type.airparif.grasses-level.state.option.1 = Low +channel-type.airparif.grasses-level.state.option.2 = Average +channel-type.airparif.grasses-level.state.option.3 = High +channel-type.airparif.hazel-level.label = Hazel Level +channel-type.airparif.hazel-level.state.option.0 = None +channel-type.airparif.hazel-level.state.option.1 = Low +channel-type.airparif.hazel-level.state.option.2 = Average +channel-type.airparif.hazel-level.state.option.3 = High +channel-type.airparif.hornbeam-level.label = Hornbeam +channel-type.airparif.hornbeam-level.state.option.0 = None +channel-type.airparif.hornbeam-level.state.option.1 = Low +channel-type.airparif.hornbeam-level.state.option.2 = Average +channel-type.airparif.hornbeam-level.state.option.3 = High +channel-type.airparif.linden-level.label = Linden +channel-type.airparif.linden-level.state.option.0 = None +channel-type.airparif.linden-level.state.option.1 = Low +channel-type.airparif.linden-level.state.option.2 = Average +channel-type.airparif.linden-level.state.option.3 = High +channel-type.airparif.oak-level.label = Oak +channel-type.airparif.oak-level.state.option.0 = None +channel-type.airparif.oak-level.state.option.1 = Low +channel-type.airparif.oak-level.state.option.2 = Average +channel-type.airparif.oak-level.state.option.3 = High +channel-type.airparif.olive-level.label = Olive +channel-type.airparif.olive-level.state.option.0 = None +channel-type.airparif.olive-level.state.option.1 = Low +channel-type.airparif.olive-level.state.option.2 = Average +channel-type.airparif.olive-level.state.option.3 = High +channel-type.airparif.plane-level.label = Plane +channel-type.airparif.plane-level.state.option.0 = None +channel-type.airparif.plane-level.state.option.1 = Low +channel-type.airparif.plane-level.state.option.2 = Average +channel-type.airparif.plane-level.state.option.3 = High +channel-type.airparif.plantain-level.label = Plantain +channel-type.airparif.plantain-level.state.option.0 = None +channel-type.airparif.plantain-level.state.option.1 = Low +channel-type.airparif.plantain-level.state.option.2 = Average +channel-type.airparif.plantain-level.state.option.3 = High +channel-type.airparif.poplar-level.label = Poplar +channel-type.airparif.poplar-level.state.option.0 = None +channel-type.airparif.poplar-level.state.option.1 = Low +channel-type.airparif.poplar-level.state.option.2 = Average +channel-type.airparif.poplar-level.state.option.3 = High +channel-type.airparif.ragweed-level.label = Ragweed +channel-type.airparif.ragweed-level.state.option.0 = None +channel-type.airparif.ragweed-level.state.option.1 = Low +channel-type.airparif.ragweed-level.state.option.2 = Average +channel-type.airparif.ragweed-level.state.option.3 = High +channel-type.airparif.rumex-level.label = Rumex +channel-type.airparif.rumex-level.state.option.0 = None +channel-type.airparif.rumex-level.state.option.1 = Low +channel-type.airparif.rumex-level.state.option.2 = Average +channel-type.airparif.rumex-level.state.option.3 = High +channel-type.airparif.timestamp.label = Timestamp +channel-type.airparif.urticaceae-level.label = Urticacea +channel-type.airparif.urticaceae-level.state.option.0 = None +channel-type.airparif.urticaceae-level.state.option.1 = Low +channel-type.airparif.urticaceae-level.state.option.2 = Average +channel-type.airparif.urticaceae-level.state.option.3 = High +channel-type.airparif.willow-level.label = Willow +channel-type.airparif.willow-level.state.option.0 = None +channel-type.airparif.willow-level.state.option.1 = Low +channel-type.airparif.willow-level.state.option.2 = Average +channel-type.airparif.willow-level.state.option.3 = High +channel-type.airparif.wormwood-level.label = Wormwood +channel-type.airparif.wormwood-level.state.option.0 = None +channel-type.airparif.wormwood-level.state.option.1 = Low +channel-type.airparif.wormwood-level.state.option.2 = Average +channel-type.airparif.wormwood-level.state.option.3 = High + +# thing types + +thing-type.airparif.location.channel.end-validity.label = End Of Validity +thing-type.airparif.location.channel.end-validity.description = Current bulletin validity ending + +# channel group types + +channel-group-type.airparif.pollens-group.label = Pollen information for the region +channel-group-type.airparif.pollens-group.channel.begin-validity.label = Begin Validity +channel-group-type.airparif.pollens-group.channel.begin-validity.description = Current bulletin validity start +channel-group-type.airparif.pollens-group.channel.comment.label = Begin Validity +channel-group-type.airparif.pollens-group.channel.comment.description = Current bulletin validity start +channel-group-type.airparif.pollens-group.channel.end-validity.label = End Validity +channel-group-type.airparif.pollens-group.channel.end-validity.description = Current bulletin validity ending + +# channel types + +channel-type.airparif.alert-level.state.option.0 = Good +channel-type.airparif.alert-level.state.option.1 = Average +channel-type.airparif.alert-level.state.option.2 = Degrated +channel-type.airparif.alert-level.state.option.3 = Bad +channel-type.airparif.alert-level.state.option.4 = Extremely Bad +channel-type.airparif.avalanches.label = Avalanches +channel-type.airparif.avalanches.description = Avalanche alert level +channel-type.airparif.avalanches.state.option.0 = No special vigilance +channel-type.airparif.avalanches.state.option.1 = Be attentive +channel-type.airparif.avalanches.state.option.2 = Be very vigilant +channel-type.airparif.avalanches.state.option.3 = Absolute vigilance +channel-type.airparif.canicule.label = Heat Wave +channel-type.airparif.canicule.description = High temperature alert level +channel-type.airparif.canicule.state.option.0 = No special vigilance +channel-type.airparif.canicule.state.option.1 = Be attentive +channel-type.airparif.canicule.state.option.2 = Be very vigilant +channel-type.airparif.canicule.state.option.3 = Absolute vigilance +channel-type.airparif.condition-icon.label = Icon +channel-type.airparif.condition-icon.description = Pictogram associated with the alert level. +channel-type.airparif.grand-froid.label = Extreme Cold +channel-type.airparif.grand-froid.description = Negative temperature alert level +channel-type.airparif.grand-froid.state.option.0 = No special vigilance +channel-type.airparif.grand-froid.state.option.1 = Be attentive +channel-type.airparif.grand-froid.state.option.2 = Be very vigilant +channel-type.airparif.grand-froid.state.option.3 = Absolute vigilance +channel-type.airparif.inondation.label = Flood +channel-type.airparif.inondation.description = Flood alert level +channel-type.airparif.inondation.state.option.0 = No special vigilance +channel-type.airparif.inondation.state.option.1 = Be attentive +channel-type.airparif.inondation.state.option.2 = Be very vigilant +channel-type.airparif.inondation.state.option.3 = Absolute vigilance +channel-type.airparif.neige.label = Snow +channel-type.airparif.neige.description = Snow event alert level +channel-type.airparif.neige.state.option.0 = No special vigilance +channel-type.airparif.neige.state.option.1 = Be attentive +channel-type.airparif.neige.state.option.2 = Be very vigilant +channel-type.airparif.neige.state.option.3 = Absolute vigilance +channel-type.airparif.orage.label = Storm +channel-type.airparif.orage.description = Storm alert level +channel-type.airparif.orage.state.option.0 = No special vigilance +channel-type.airparif.orage.state.option.1 = Be attentive +channel-type.airparif.orage.state.option.2 = Be very vigilant +channel-type.airparif.orage.state.option.3 = Absolute vigilance +channel-type.airparif.pluie-inondation.label = Rain Flood +channel-type.airparif.pluie-inondation.description = Flood caused by rainfall alert level +channel-type.airparif.pluie-inondation.state.option.0 = No special vigilance +channel-type.airparif.pluie-inondation.state.option.1 = Be attentive +channel-type.airparif.pluie-inondation.state.option.2 = Be very vigilant +channel-type.airparif.pluie-inondation.state.option.3 = Absolute vigilance +channel-type.airparif.pollen-level.label = Pollen Level +channel-type.airparif.pollen-level.state.option.0 = None +channel-type.airparif.pollen-level.state.option.1 = Low +channel-type.airparif.pollen-level.state.option.2 = Average +channel-type.airparif.pollen-level.state.option.3 = High +channel-type.airparif.vague-submersion.label = Wave Submersion +channel-type.airparif.vague-submersion.description = Submersion wave alert level +channel-type.airparif.vague-submersion.state.option.0 = No special vigilance +channel-type.airparif.vague-submersion.state.option.1 = Be attentive +channel-type.airparif.vague-submersion.state.option.2 = Be very vigilant +channel-type.airparif.vague-submersion.state.option.3 = Absolute vigilance +channel-type.airparif.vent.label = Wind +channel-type.airparif.vent.description = Wind event alert level +channel-type.airparif.vent.state.option.0 = No special vigilance +channel-type.airparif.vent.state.option.1 = Be attentive +channel-type.airparif.vent.state.option.2 = Be very vigilant +channel-type.airparif.vent.state.option.3 = Absolute vigilance # discovery result @@ -11,4 +233,5 @@ iconset.description = Icons illustrating air quality measures provided by AirPar # thing status descriptions offline.config-error-unknown-apikey = Parameter 'apikey' must be configured -offline.config-error-invalid-apikey = Parameter 'apikey' is invalid \ No newline at end of file +offline.config-error-invalid-apikey = Parameter 'apikey' is invalid +incorrect-bridge = Wrong bridge type diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml index 86e17c866e2..b1abb517927 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml @@ -10,6 +10,16 @@ Bridge to the AirParif API Portal. In order to receive the data, you must register an account on https://www.airparif.fr/contact and receive your API token. + + + + + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml new file mode 100644 index 00000000000..89e8600d7e7 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -0,0 +1,106 @@ + + + + + + + + + Current bulletin validity start + + + + Current bulletin validity start + + + + Current bulletin validity ending + + + + + + + + + + General message for the air quality bulletin + + + + Minimum level of NO2 concentation + + + + Maximum level of NO2 concentation + + + + Minimum level of O3 concentation + + + + Maximum level of O3 concentation + + + + Minimum level of PM 10 concentation + + + + Maximum level of PM 10 concentation + + + + Minimum level of PM 2.5 concentation + + + + Maximum level of PM 2.5 concentation + + + + + + + + + + Current bulletin validity start + + + + Current bulletin validity start + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml index a7876a8ccec..10cba528047 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml @@ -4,196 +4,11 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - Number - - @text/alertLevelChannelDescription - error - - Alarm - - - - - - - - - - - - - - DateTime - - @text/timestampChannelDescription - time - - - - - String - - - - - - - - - - - - - - - Number - - Rain intensity level - oh:meteofrance:intensity - - - - - - - - - - - - Number - - Wind event alert level - oh:meteofrance:vent - - - - - - - - - - - - Number - - Storm alert level - oh:meteofrance:orage - - - - - - - - - - - - Number - - Flood alert level - oh:meteofrance:inondation - - - - - - - - - - - - Number - - Snow event alert level - oh:meteofrance:neige - - - - - - - - - - - - Number - - High temperature alert level - oh:meteofrance:canicule - - - - - - - - - - - - Number - - Negative temperature alert level - oh:meteofrance:grand-froid - - - - - - - - - - - - Number - - Avalanche alert level - oh:meteofrance:avalanches - - - - - - - - - - - - Number - - Submersion wave alert level - oh:meteofrance:vague-submersion - - - - - - - - - - - - Number - - Flood caused by rainfall alert level - oh:meteofrance:pluie-inondation - - - - - - - - + + DateTime + + time + @@ -203,18 +18,290 @@ + DateTime - + + @text/timestampChannelDescription time - - Image - - Pictogram associated with the alert level. - + + Number + + oh:airparif:hazel + + + + + + + + + + Number + + oh:airparif:birch + + + + + + + + + + + + Number + + oh:airparif:cypress + + + + + + + + + + + + Number + + oh:airparif:alder + + + + + + + + + + + + Number + + oh:airparif:poplar + + + + + + + + + + + Number + + oh:airparif:ash + + + + + + + + + + + Number + + oh:airparif:olive + + + + + + + + + + + + Number + + oh:airparif:urticacea + + + + + + + + + + + + Number + + oh:airparif:wormwood + + + + + + + + + + + + Number + + oh:airparif:rumex + + + + + + + + + + + + Number + + oh:airparif:ragweed + + + + + + + + + + + + Number + + oh:airparif:grasses + + + + + + + + + + + + Number + + oh:airparif:plantain + + + + + + + + + + + + Number + + oh:airparif:chestnut + + + + + + + + + + + + Number + + oh:airparif:oak + + + + + + + + + + + + Number + + oh:airparif:linden + + + + + + + + + + + + Number + + oh:airparif:plane + + + + + + + + + + + + Number + + oh:airparif:hornbeam + + + + + + + + + + + + Number + + oh:airparif:willow + + + + + + + + + + + + Number:Dimensionless + + + + + + Number:Density + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml index 90fdbc3463f..49a01933a7e 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml @@ -5,23 +5,41 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - - - - + + + + + AirParif air quality report for the given location - - - + + + - - - location - - - + department + + + + location + + + + + Name of the department + + + + + + + + + + + true + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg new file mode 100644 index 00000000000..ea5fc88483e --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg new file mode 100644 index 00000000000..9c10179746d --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg new file mode 100644 index 00000000000..f8ff505f3b6 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg new file mode 100644 index 00000000000..8cb9360bf60 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg new file mode 100644 index 00000000000..0cc76819acd --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg new file mode 100644 index 00000000000..ae6279fd100 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg new file mode 100644 index 00000000000..e2dd22c17f3 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg new file mode 100644 index 00000000000..3a7d995bfaf --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg new file mode 100644 index 00000000000..c10749ecd98 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg new file mode 100644 index 00000000000..343163e1a04 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg new file mode 100644 index 00000000000..719798a0fbf --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg new file mode 100644 index 00000000000..ab64767b0f7 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg new file mode 100644 index 00000000000..740cf781f00 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg new file mode 100644 index 00000000000..5cd534d8b33 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg new file mode 100644 index 00000000000..0beffb9f6f5 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg new file mode 100644 index 00000000000..d3607e52a37 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg new file mode 100644 index 00000000000..07c76f90127 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg new file mode 100644 index 00000000000..bc52029973b --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg new file mode 100644 index 00000000000..9c02ae1e2e8 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file From 272fb89d3311c73a84aa9de5d69e3cf95587a0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Thu, 24 Oct 2024 18:03:04 +0200 Subject: [PATCH 03/17] Remove doc file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../org.openhab.binding.airparif/doc/info.txt | 29 ------------------- .../airparif/internal/api/AirParifDto.java | 4 ++- 2 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 bundles/org.openhab.binding.airparif/doc/info.txt diff --git a/bundles/org.openhab.binding.airparif/doc/info.txt b/bundles/org.openhab.binding.airparif/doc/info.txt deleted file mode 100644 index 53c913750cc..00000000000 --- a/bundles/org.openhab.binding.airparif/doc/info.txt +++ /dev/null @@ -1,29 +0,0 @@ - -Miguel Narvaez Miguel.Narvaez@airparif.fr via improvmx-mails.com -30 sept. 2024 10:06 (il y a 11 jours) -À gael@lhopital.org, api - -Bonjour, - -Veuillez trouver ci-dessous la clé API vous permettant d’accéder à nos API Indices de la qualité de l'air, Épisodes, Cartographie, Pollens : - -5a923300-e93d-2a0f-4321-96f115f19100 - -Cette clé sera active à partir de demain. - -Nous ouvrons ces API dans une démarche d’amélioration continue, n’hésitez pas à revenir vers nous pour des retours d’expérience et des propositions d’améliorations. - -La documentation des APIs se trouve à l’adresse suivante : -https://api.airparif.fr/docs - -Celle concernant les services Web de cartographie se trouve ici : -https://www.airparif.fr/doc_api_carto/ - -Les informations qui vous concernent sont destinées uniquement à Airparif. Votre adresse e-mail sera utilisée uniquement pour vous informer à propos des API. Vous disposez d'un droit d'accès, de modification, de rectification et de suppression de ces données (art. 34 de la loi "Informatique et Libertés"). - -Pour toute question concernant vos données personnelles ou nos API, nous vous invitons à nous contacter via notre formulaire en ligne : -https://www.airparif.asso.fr/contact - -Bonne journée ! - -Airparif \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index ef9ad505fb4..701ac785661 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -87,7 +87,9 @@ public class AirParifDto { } public record Episode( // - @SerializedName("actif") boolean active, Message message, @SerializedName("jour") DailyEpisode today, // + @SerializedName("actif") boolean active, // + Message message, // + @SerializedName("jour") DailyEpisode today, // @SerializedName("demain") DailyEpisode tomorrow) { } From dc1ac4d86e0c0811145d08345d22046d8f7a957e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Fri, 15 Nov 2024 14:42:08 +0100 Subject: [PATCH 04/17] Finalizing binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../internal/AirParifBindingConstants.java | 2 + .../airparif/internal/api/AirParifDto.java | 66 ++++-- .../handler/AirParifBridgeHandler.java | 211 +++++++++++------- .../internal/handler/HandlerUtils.java | 89 ++++++++ .../internal/handler/LocationHandler.java | 90 ++++++-- .../resources/OH-INF/i18n/airparif.properties | 54 +++++ .../src/main/resources/OH-INF/thing/api.xml | 10 +- .../resources/OH-INF/thing/channel-groups.xml | 164 +++++++++----- .../main/resources/OH-INF/thing/channels.xml | 30 ++- .../resources/OH-INF/thing/thing-types.xml | 13 ++ 10 files changed, 542 insertions(+), 187 deletions(-) create mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java index b56b567303a..8a9d22fbc2e 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -45,6 +45,8 @@ public class AirParifBindingConstants { public static final String CHANNEL_COMMENT = "comment"; public static final String CHANNEL_MESSAGE = "message"; public static final String CHANNEL_TOMORROW = "tomorrow"; + public static final String CHANNEL_TIMESTAMP = "timestamp"; + public static final String CHANNEL_VALUE = "value"; public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(APIBRIDGE_THING_TYPE, LOCATION_THING_TYPE); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 701ac785661..24c6e9a728a 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.airparif.internal.api; +import java.time.Duration; import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -19,6 +20,7 @@ import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -29,6 +31,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.AirParifApi.Scope; +import org.openhab.core.library.types.StringType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; import com.google.gson.annotations.SerializedName; @@ -74,6 +79,10 @@ public class AirParifDto { public String dayDescription() { return bulletin.fr; } + + public boolean isToday() { + return previsionDate.equals(LocalDate.now()); + } } public record DailyEpisode(// @@ -106,28 +115,51 @@ public class AirParifDto { private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris"); public List data = List.of(); + private @Nullable Set validities; + private @Nullable ZonedDateTime beginValidity; + private @Nullable ZonedDateTime endValidity; public Optional getData() { return Optional.ofNullable(data.isEmpty() ? null : data.get(0)); } private Set getValidities() { - Set result = new TreeSet<>(); - getData().ifPresent(pollens -> { - Matcher matcher = PATTERN.matcher(pollens.periode); - while (matcher.find()) { - result.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); - } - }); - return result; + Set local; + if (validities != null) { + local = validities; + } else { + local = new TreeSet<>(); + getData().ifPresent(pollens -> { + Matcher matcher = PATTERN.matcher(pollens.periode); + while (matcher.find()) { + local.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); + } + }); + validities = local; + } + + return local; } public Optional getBeginValidity() { - return Optional.ofNullable(getValidities().iterator().next()); + if (beginValidity == null) { + beginValidity = getValidities().iterator().next(); + } + return Optional.ofNullable(beginValidity); } public Optional getEndValidity() { - return Optional.ofNullable(getValidities().stream().reduce((prev, next) -> next).orElse(null)); + if (endValidity == null) { + endValidity = getValidities().stream().reduce((prev, next) -> next).orElse(null); + } + return Optional.ofNullable(endValidity); + } + + public Duration getValidityDuration() { + return Objects.requireNonNull(getEndValidity().map(end -> { + Duration duration = Duration.between(ZonedDateTime.now().withZoneSameInstant(end.getZone()), end); + return duration.isNegative() ? Duration.ZERO : duration; + }).orElse(Duration.ZERO)); } public Optional getComment() { @@ -150,17 +182,25 @@ public class AirParifDto { } } - public record Result(// + public record Concentration(// @SerializedName("polluant") Pollutant pollutant, // ZonedDateTime date, // @SerializedName("valeurs") double[] values, // - Message message) { + @Nullable Message message) { + + public State getMessage() { + return message != null ? new StringType(message.fr()) : UnDefType.NULL; + } + + public double getValue() { + return values[0]; + } } public record Route(// @SerializedName("dateRequise") ZonedDateTime requestedDate, // double[][] longlats, // - @SerializedName("resultats") Result[] results, // + @SerializedName("resultats") List concentrations, // @Nullable Message[] messages) { } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index c3db0bb8c95..353c22dfcde 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -21,12 +21,15 @@ import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -46,6 +49,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus.Code; import org.openhab.binding.airparif.internal.AirParifException; import org.openhab.binding.airparif.internal.AirParifIconProvider; +import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.AirParifDto.Bulletin; import org.openhab.binding.airparif.internal.api.AirParifDto.Episode; import org.openhab.binding.airparif.internal.api.AirParifDto.ItineraireResponse; @@ -54,18 +58,23 @@ import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.AirParifDto.Version; import org.openhab.binding.airparif.internal.api.ColorMap; +import org.openhab.binding.airparif.internal.api.PollenAlertLevel; +import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.config.BridgeConfiguration; import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,7 +87,7 @@ import org.slf4j.LoggerFactory; * */ @NonNullByDefault -public class AirParifBridgeHandler extends BaseBridgeHandler { +public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerUtils { private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30); private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; @@ -86,11 +95,10 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { private final AirParifDeserializer deserializer; private final AirParifIconProvider iconProvider; private final HttpClient httpClient; + private final Map> jobs = new HashMap<>(); private BridgeConfiguration config = new BridgeConfiguration(); - - private @Nullable ScheduledFuture pollensJob; - private @Nullable ScheduledFuture dailyJob; + private @Nullable PollensResponse pollens; public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer, AirParifIconProvider iconProvider) { @@ -112,19 +120,10 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { scheduler.execute(this::initiateConnexion); } - private @Nullable ScheduledFuture cancelFuture(@Nullable ScheduledFuture job) { - if (job != null && !job.isCancelled()) { - job.cancel(true); - } - return null; - } - @Override public void dispose() { logger.debug("Disposing the AirParif bridge handler."); - - pollensJob = cancelFuture(pollensJob); - dailyJob = cancelFuture(dailyJob); + cleanJobs(); } public synchronized String executeUri(URI uri, HttpMethod method, @Nullable String payload) @@ -154,10 +153,9 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { } else if (statusCode == Code.FORBIDDEN) { throw new AirParifException("@text/offline.config-error-invalid-apikey"); } - String content = new String(response.getContent(), DEFAULT_CHARSET); throw new AirParifException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString()); } catch (TimeoutException | ExecutionException e) { - throw new AirParifException(e, "Exception while calling %s", request.getURI()); + throw new AirParifException(e, "Exception while calling %s: %s", request.getURI(), e.getMessage()); } catch (InterruptedException e) { throw new AirParifException(e, "Execution interrupted: %s", e.getMessage()); } @@ -180,7 +178,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { private void initiateConnexion() { Version version; - try { // This does validate communication with the server + try { // This is only intended to validate communication with the server version = executeUri(VERSION_URI, Version.class); } catch (AirParifException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -195,9 +193,9 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { return; } - getThing().setProperty("api-version", version.version()); - getThing().setProperty("key-expiration", keyInfo.expiration().toString()); - getThing().setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(","))); + thing.setProperty("api-version", version.version()); + thing.setProperty("key-expiration", keyInfo.expiration().toString()); + thing.setProperty("scopes", keyInfo.scopes().stream().map(e -> e.name()).collect(Collectors.joining(","))); logger.info("The api key is valid until {}", keyInfo.expiration().toString()); updateStatus(ThingStatus.ONLINE); @@ -209,73 +207,92 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { logger.warn("Error reading ColorMap: {]", e.getMessage()); } - pollensJob = scheduler.schedule(this::updatePollens, 1, TimeUnit.SECONDS); - dailyJob = scheduler.schedule(this::updateDaily, 2, TimeUnit.SECONDS); + ThingUID thingUID = thing.getUID(); + + schedule("Pollens Update", () -> updatePollens(new ChannelGroupUID(thingUID, GROUP_POLLENS)), + Duration.ofSeconds(1)); + schedule("Air Quality Bulletin", () -> updateDailyAQBulletin(new ChannelGroupUID(thingUID, GROUP_AQ_BULLETIN), + new ChannelGroupUID(thingUID, GROUP_AQ_BULLETIN_TOMORROW)), Duration.ofSeconds(2)); + schedule("Episode", () -> updateEpisode(new ChannelGroupUID(thingUID, GROUP_DAILY)), Duration.ofSeconds(3)); } - private void updateDaily() { + private void updatePollens(ChannelGroupUID pollensGroupUID) { + PollensResponse localPollens; try { - Bulletin bulletin = executeUri(PREV_BULLETIN_URI, Bulletin.class); - logger.debug("The bulletin is {}", bulletin.today().dayDescription()); - - Set.of(bulletin.today(), bulletin.tomorrow()).stream().forEach(aq -> { - String groupName = aq.previsionDate().equals(LocalDate.now()) ? GROUP_AQ_BULLETIN - : GROUP_AQ_BULLETIN_TOMORROW + "#"; - updateState(groupName + CHANNEL_COMMENT, - !aq.available() ? UnDefType.UNDEF : new StringType(aq.bulletin().fr())); - aq.concentrations().forEach(measure -> { - String cName = groupName + measure.pollutant().name().toLowerCase(); - updateState(cName + "-min", !aq.available() ? UnDefType.UNDEF - : new QuantityType<>(measure.min(), measure.pollutant().unit)); - updateState(cName + "-max", !aq.available() ? UnDefType.UNDEF - : new QuantityType<>(measure.max(), measure.pollutant().unit)); - }); - }); - - Episode episode = executeUri(EPISODES_URI, Episode.class); - logger.debug("The episode is {}", episode); - - // if (episode.active()) { - // updateState(GROUP_DAILY + "#" + CHANNEL_MESSAGE, new StringType(episode.message().fr())); - // updateState(GROUP_DAILY + "#" + CHANNEL_TOMORROW, new StringType(episode.message().fr())); - // } - - ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); - long delay = Duration.between(ZonedDateTime.now(), tomorrowMorning).getSeconds(); - logger.debug("Rescheduling daily job tomorrow morning"); - dailyJob = scheduler.schedule(this::updateDaily, delay, TimeUnit.SECONDS); - } catch (AirParifException e) { - logger.warn("Error update pollens data: {}", e.getMessage()); - } - } - - private void updatePollens() { - try { - PollensResponse pollens = executeUri(POLLENS_URI, PollensResponse.class); - - pollens.getComment() - .ifPresent(comment -> updateState(GROUP_POLLENS + "#" + CHANNEL_COMMENT, new StringType(comment))); - pollens.getBeginValidity().ifPresent( - begin -> updateState(GROUP_POLLENS + "#" + CHANNEL_BEGIN_VALIDITY, new DateTimeType(begin))); - pollens.getEndValidity().ifPresent(end -> { - updateState(GROUP_POLLENS + "#" + CHANNEL_END_VALIDITY, new DateTimeType(end)); - logger.info("Pollens bulletin valid until {}", end); - long delay = Duration.between(ZonedDateTime.now(), end).getSeconds(); - if (delay < 0) { - // what if the bulletin was not updated and the delay is passed ? - delay = 3600; - logger.debug("Update time of the bulletin is in the past - will retry in one hour"); - } else { - delay += 60; - } - - pollensJob = scheduler.schedule(this::updatePollens, delay, TimeUnit.SECONDS); - }); - getThing().getThings().stream().map(Thing::getHandler).filter(LocationHandler.class::isInstance) - .map(LocationHandler.class::cast).forEach(locHand -> locHand.setPollens(pollens)); + localPollens = executeUri(POLLENS_URI, PollensResponse.class); } catch (AirParifException e) { logger.warn("Error updating pollens data: {}", e.getMessage()); + return; } + + updateState(new ChannelUID(pollensGroupUID, CHANNEL_COMMENT), Objects.requireNonNull( + localPollens.getComment().map(comment -> (State) new StringType(comment)).orElse(UnDefType.NULL))); + updateState(new ChannelUID(pollensGroupUID, CHANNEL_BEGIN_VALIDITY), Objects.requireNonNull( + localPollens.getBeginValidity().map(begin -> (State) new DateTimeType(begin)).orElse(UnDefType.NULL))); + updateState(new ChannelUID(pollensGroupUID, CHANNEL_END_VALIDITY), Objects.requireNonNull( + localPollens.getEndValidity().map(end -> (State) new DateTimeType(end)).orElse(UnDefType.NULL))); + + long delay = localPollens.getValidityDuration().getSeconds(); + // if delay is null, update in 3600 seconds + delay += delay == 0 ? 3600 : 60; + schedule("Pollens Update", () -> updatePollens(pollensGroupUID), Duration.ofSeconds(delay)); + + // Send pollens information to childs + getThing().getThings().stream().map(Thing::getHandler).filter(LocationHandler.class::isInstance) + .map(LocationHandler.class::cast).forEach(locHand -> locHand.setPollens(localPollens)); + pollens = localPollens; + } + + private void updateDailyAQBulletin(ChannelGroupUID todayGroupUID, ChannelGroupUID tomorrowGroupUID) { + Bulletin bulletin; + try { + bulletin = executeUri(PREV_BULLETIN_URI, Bulletin.class); + } catch (AirParifException e) { + logger.warn("Error updating Air Quality Bulletin: {}", e.getMessage()); + return; + } + + Set.of(bulletin.today(), bulletin.tomorrow()).stream().forEach(aq -> { + ChannelGroupUID groupUID = aq.isToday() ? todayGroupUID : tomorrowGroupUID; + updateState(new ChannelUID(groupUID, CHANNEL_COMMENT), + !aq.available() ? UnDefType.UNDEF : new StringType(aq.bulletin().fr())); + + aq.concentrations().forEach(measure -> { + Pollutant pollutant = measure.pollutant(); + String cName = pollutant.name().toLowerCase() + "-"; + updateState(new ChannelUID(groupUID, cName + "min"), + aq.available() ? new QuantityType<>(measure.min(), pollutant.unit) : UnDefType.UNDEF); + updateState(new ChannelUID(groupUID, cName + "max"), + aq.available() ? new QuantityType<>(measure.max(), pollutant.unit) : UnDefType.UNDEF); + }); + }); + + ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); + logger.debug("Rescheduling daily air quality bulletin job tomorrow morning"); + schedule("Air Quality Bulletin", () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), + Duration.between(ZonedDateTime.now(), tomorrowMorning)); + } + + private void updateEpisode(ChannelGroupUID dailyGroupUID) { + Episode episode; + try { + episode = executeUri(EPISODES_URI, Episode.class); + } catch (AirParifException e) { + logger.warn("Error updating Episode: {}", e.getMessage()); + return; + } + + logger.debug("The episode is {}", episode); + + updateState(new ChannelUID(dailyGroupUID, CHANNEL_MESSAGE), new StringType(episode.message().fr())); + updateState(new ChannelUID(dailyGroupUID, CHANNEL_TOMORROW), new StringType(episode.message().fr())); + + // Set.of(episode.today(), episode.tomorrow()).stream().forEach(aq -> { + + // }); + + ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); + schedule("Episode", () -> updateEpisode(dailyGroupUID), Duration.between(ZonedDateTime.now(), tomorrowMorning)); } public @Nullable Route getConcentrations(String location) { @@ -294,4 +311,34 @@ public class AirParifBridgeHandler extends BaseBridgeHandler { } return null; } + + public Map requestPollens(String department) { + PollensResponse localPollens = pollens; + return localPollens != null ? localPollens.getDepartment(department) : Map.of(); + } + + @Override + public @Nullable Bridge getBridge() { + return super.getBridge(); + } + + @Override + public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) { + super.updateStatus(status, statusDetail, description); + } + + @Override + public ScheduledExecutorService getScheduler() { + return scheduler; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public Map> getJobs() { + return jobs; + } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java new file mode 100644 index 00000000000..df2aec547da --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2024 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.airparif.internal.handler; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BridgeHandler; +import org.slf4j.Logger; + +/** + * The {@link HandlerUtils} defines and implements some common methods for Thing Handlers + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public interface HandlerUtils { + default @Nullable ScheduledFuture cancelFuture(@Nullable ScheduledFuture job) { + if (job != null && !job.isCancelled()) { + job.cancel(true); + } + return null; + } + + @SuppressWarnings("unchecked") + default @Nullable T getBridgeHandler(Class clazz) { + Bridge bridge = getBridge(); + if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler != null) { + if (bridgeHandler.getClass() == clazz) { + return (T) bridgeHandler; + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge"); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge"); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""); + } + return null; + } + + default void schedule(String jobName, Runnable job, Duration duration) { + ScheduledFuture result = getJobs().remove(jobName); + String operation = "Scheduling"; + if (result != null) { + operation = "Rescheduled"; + cancelFuture(result); + } + getLogger().info("{} {} in {}", operation, jobName, duration); + getJobs().put(jobName, getScheduler().schedule(job, duration.getSeconds(), TimeUnit.SECONDS)); + } + + default void cleanJobs() { + getJobs().values().forEach(job -> cancelFuture(job)); + getJobs().clear(); + } + + void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description); + + @Nullable + Bridge getBridge(); + + ScheduledExecutorService getScheduler(); + + Logger getLogger(); + + Map> getJobs(); +} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index 55f8484dfd8..3407afe2a09 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -12,9 +12,13 @@ */ package org.openhab.binding.airparif.internal.handler; -import static org.openhab.binding.airparif.internal.AirParifBindingConstants.GROUP_POLLENS; +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; +import java.time.Duration; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -23,8 +27,11 @@ import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.config.LocationConfiguration; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -41,11 +48,13 @@ import org.slf4j.LoggerFactory; * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault -public class LocationHandler extends BaseThingHandler { +public class LocationHandler extends BaseThingHandler implements HandlerUtils { private final Logger logger = LoggerFactory.getLogger(LocationHandler.class); + private final Map> jobs = new HashMap<>(); private @Nullable LocationConfiguration config; + private Map myPollens = Map.of(); public LocationHandler(Thing thing) { super(thing); @@ -55,46 +64,85 @@ public class LocationHandler extends BaseThingHandler { public void initialize() { config = getConfigAs(LocationConfiguration.class); updateStatus(ThingStatus.UNKNOWN); + schedule("Local Air Quality", this::getConcentrations, Duration.ofSeconds(2)); + } - scheduler.execute(this::getConcentrations); + @Override + public void dispose() { + logger.debug("Disposing the AirParif bridge handler."); + cleanJobs(); } public void setPollens(PollensResponse pollens) { LocationConfiguration local = config; if (local != null) { - Map alerts = pollens.getDepartment(local.department); - alerts.forEach((pollen, level) -> { - updateState(GROUP_POLLENS + "#" + pollen.name().toLowerCase(), new DecimalType(level.ordinal())); - }); + updatePollenChannels(pollens.getDepartment(local.department)); updateStatus(ThingStatus.ONLINE); } } + private void updatePollenChannels(Map pollens) { + ChannelGroupUID pollensUID = new ChannelGroupUID(thing.getUID(), GROUP_POLLENS); + myPollens = pollens; + pollens.forEach((pollen, level) -> updateState(new ChannelUID(pollensUID, pollen.name().toLowerCase()), + new DecimalType(level.ordinal()))); + } + private void getConcentrations() { - AirParifBridgeHandler apiHandler = getApiBridgeHandler(); + AirParifBridgeHandler apiHandler = getBridgeHandler(AirParifBridgeHandler.class); LocationConfiguration local = config; + long delay = 3600; if (apiHandler != null && local != null) { + if (myPollens.isEmpty()) { + updatePollenChannels(apiHandler.requestPollens(local.department)); + } + Route route = apiHandler.getConcentrations(local.location); + if (route != null) { + route.concentrations().forEach(concentration -> { + ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), + concentration.pollutant().name().toLowerCase()); + updateState(new ChannelUID(groupUID, CHANNEL_TIMESTAMP), new DateTimeType(concentration.date())); + updateState(new ChannelUID(groupUID, CHANNEL_MESSAGE), concentration.getMessage()); + updateState(new ChannelUID(groupUID, CHANNEL_VALUE), + new QuantityType<>(concentration.getValue(), concentration.pollutant().unit)); + + }); + updateStatus(ThingStatus.ONLINE); + } + } else { + delay = 10; } + schedule("Local Air Quality", this::getConcentrations, Duration.ofSeconds(delay)); } @Override public void handleCommand(ChannelUID channelUID, Command command) { // TODO Auto-generated method stub - } - private @Nullable AirParifBridgeHandler getApiBridgeHandler() { - Bridge bridge = this.getBridge(); - if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { - if (bridge.getHandler() instanceof AirParifBridgeHandler airParifBridgeHandler) { - return airParifBridgeHandler; - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge"); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - return null; + @Override + public @Nullable Bridge getBridge() { + return super.getBridge(); + } + + @Override + public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) { + super.updateStatus(status, statusDetail, description); + } + + @Override + public ScheduledExecutorService getScheduler() { + return scheduler; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public Map> getJobs() { + return jobs; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index 448b12d4d94..850db294e30 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -7,8 +7,14 @@ addon.airparif.description = Air Quality data and forecasts provided by AirParif thing-type.airparif.api.label = AirParif API Portal thing-type.airparif.api.description = Bridge to the AirParif API Portal. In order to receive the data, you must register an account on https://www.airparif.fr/contact and receive your API token. +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.location.label = Department Report 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.o3.label = Ozone Concentration Information +thing-type.airparif.location.group.pm10.label = PM10 Concentration Information +thing-type.airparif.location.group.pm25.label = PM2.5 Concentration Information # thing types config @@ -28,6 +34,25 @@ thing-type.config.airparif.location.location.label = Location # channel group types +channel-group-type.airparif.air-quality-bulletin.label = Air Quality Bulletin +channel-group-type.airparif.air-quality-bulletin.channel.comment.label = Message +channel-group-type.airparif.air-quality-bulletin.channel.comment.description = General message for the air quality bulletin +channel-group-type.airparif.air-quality-bulletin.channel.no2-max.label = NO2 Max +channel-group-type.airparif.air-quality-bulletin.channel.no2-max.description = Maximum level of NO2 concentation +channel-group-type.airparif.air-quality-bulletin.channel.no2-min.label = NO2 Min +channel-group-type.airparif.air-quality-bulletin.channel.no2-min.description = Minimum level of NO2 concentation +channel-group-type.airparif.air-quality-bulletin.channel.o3-max.label = O3 Max +channel-group-type.airparif.air-quality-bulletin.channel.o3-max.description = Maximum level of O3 concentation +channel-group-type.airparif.air-quality-bulletin.channel.o3-min.label = O3 Min +channel-group-type.airparif.air-quality-bulletin.channel.o3-min.description = Minimum level of O3 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm10-max.label = PM 10 Max +channel-group-type.airparif.air-quality-bulletin.channel.pm10-max.description = Maximum level of PM 10 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm10-min.label = PM 10 Min +channel-group-type.airparif.air-quality-bulletin.channel.pm10-min.description = Minimum level of PM 10 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm25-max.label = PM 2.5 Max +channel-group-type.airparif.air-quality-bulletin.channel.pm25-max.description = Maximum level of PM 2.5 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.label = PM 2.5 Min +channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.description = Minimum level of PM 2.5 concentation channel-group-type.airparif.bridge-pollens.label = Pollen information for the region channel-group-type.airparif.bridge-pollens.channel.begin-validity.label = Begin Validity channel-group-type.airparif.bridge-pollens.channel.begin-validity.description = Current bulletin validity start @@ -35,7 +60,33 @@ channel-group-type.airparif.bridge-pollens.channel.comment.label = Begin Validit channel-group-type.airparif.bridge-pollens.channel.comment.description = Current bulletin validity start channel-group-type.airparif.bridge-pollens.channel.end-validity.label = End Validity channel-group-type.airparif.bridge-pollens.channel.end-validity.description = Current bulletin validity ending +channel-group-type.airparif.daily.label = Daily information for the region +channel-group-type.airparif.daily.channel.message.label = Message +channel-group-type.airparif.daily.channel.message.description = Current bulletin validity start +channel-group-type.airparif.daily.channel.tomorrow.label = Tomorrow +channel-group-type.airparif.daily.channel.tomorrow.description = Current bulletin validity start channel-group-type.airparif.dept-pollens.label = Pollen information for the department +channel-group-type.airparif.pollutant-mpc.label = Pollutant Concentration Information +channel-group-type.airparif.pollutant-mpc.channel.message.label = Message +channel-group-type.airparif.pollutant-mpc.channel.message.description = Polllutant concentration alert message +channel-group-type.airparif.pollutant-mpc.channel.timestamp.label = Timestamp +channel-group-type.airparif.pollutant-mpc.channel.timestamp.description = Timestamp of the measure +channel-group-type.airparif.pollutant-mpc.channel.value.label = Concentration +channel-group-type.airparif.pollutant-mpc.channel.value.description = Concentration of the given pollutant +channel-group-type.airparif.pollutant-ndx.label = Global Pollutant Index +channel-group-type.airparif.pollutant-ndx.channel.message.label = Message +channel-group-type.airparif.pollutant-ndx.channel.message.description = Alert message associated to the value of the index +channel-group-type.airparif.pollutant-ndx.channel.timestamp.label = Timestamp +channel-group-type.airparif.pollutant-ndx.channel.timestamp.description = Timestamp of the evaluation +channel-group-type.airparif.pollutant-ndx.channel.value.label = Value +channel-group-type.airparif.pollutant-ndx.channel.value.description = Value of the global Index +channel-group-type.airparif.pollutant-ppb.label = Pollutant Concentration Information +channel-group-type.airparif.pollutant-ppb.channel.message.label = Message +channel-group-type.airparif.pollutant-ppb.channel.message.description = Polllutant concentration alert message +channel-group-type.airparif.pollutant-ppb.channel.timestamp.label = Timestamp +channel-group-type.airparif.pollutant-ppb.channel.timestamp.description = Timestamp of the measure +channel-group-type.airparif.pollutant-ppb.channel.value.label = Concentration +channel-group-type.airparif.pollutant-ppb.channel.value.description = Concentration of the given pollutant # channel types @@ -85,6 +136,8 @@ channel-type.airparif.linden-level.state.option.0 = None channel-type.airparif.linden-level.state.option.1 = Low channel-type.airparif.linden-level.state.option.2 = Average channel-type.airparif.linden-level.state.option.3 = High +channel-type.airparif.mpc-value.label = Measure +channel-type.airparif.ndx-value.label = Measure channel-type.airparif.oak-level.label = Oak channel-type.airparif.oak-level.state.option.0 = None channel-type.airparif.oak-level.state.option.1 = Low @@ -110,6 +163,7 @@ channel-type.airparif.poplar-level.state.option.0 = None channel-type.airparif.poplar-level.state.option.1 = Low channel-type.airparif.poplar-level.state.option.2 = Average channel-type.airparif.poplar-level.state.option.3 = High +channel-type.airparif.ppb-value.label = Measure channel-type.airparif.ragweed-level.label = Ragweed channel-type.airparif.ragweed-level.state.option.0 = None channel-type.airparif.ragweed-level.state.option.1 = Low diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml index b1abb517927..416faef5ed3 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/api.xml @@ -13,10 +13,12 @@ - - - - + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml index 89e8600d7e7..5494219bbcb 100644 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -22,61 +22,115 @@ - - - - - - General message for the air quality bulletin - - - - Minimum level of NO2 concentation - - - - Maximum level of NO2 concentation - - - - Minimum level of O3 concentation - - - - Maximum level of O3 concentation - - - - Minimum level of PM 10 concentation - - - - Maximum level of PM 10 concentation - - - - Minimum level of PM 2.5 concentation - - - - Maximum level of PM 2.5 concentation - - - - - - - - - - Current bulletin validity start - - - - Current bulletin validity start - - - + + + + + + General message for the air quality bulletin + + + + Minimum level of NO2 concentation + + + + Maximum level of NO2 concentation + + + + Minimum level of O3 concentation + + + + Maximum level of O3 concentation + + + + Minimum level of PM 10 concentation + + + + Maximum level of PM 10 concentation + + + + Minimum level of PM 2.5 concentation + + + + Maximum level of PM 2.5 concentation + + + + + + + + + + Polllutant concentration alert message + + + + Timestamp of the measure + + + + Concentration of the given pollutant + + + + + + + + + + Polllutant concentration alert message + + + + Timestamp of the measure + + + + Concentration of the given pollutant + + + + + + + + + + Alert message associated to the value of the index + + + + Timestamp of the evaluation + + + + Value of the global Index + + + + + + + + + + Current bulletin validity start + + + + Current bulletin validity start + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml index 10cba528047..e47a4ec856c 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml @@ -126,7 +126,7 @@ Number - oh:airparif:urticacea + oh:airparif:urticaceae @@ -291,17 +291,23 @@ - - Number:Dimensionless - - - - - - Number:Density - - - + + Number:Dimensionless + + + + + + Number + + + + + + Number:Density + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml index 49a01933a7e..80003f5c0e9 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml @@ -14,6 +14,19 @@ + + + + + + + + + + + + + department From 3bd53b764a6db0f003be3fb79f5d097b0a0864b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 18 Nov 2024 13:39:12 +0100 Subject: [PATCH 05/17] Added binding to pom.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../handler/AirParifBridgeHandler.java | 21 +++++++++++-------- .../internal/handler/LocationHandler.java | 9 ++++---- bundles/pom.xml | 1 + 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index 353c22dfcde..b22c3a6d459 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -90,12 +90,15 @@ import org.slf4j.LoggerFactory; public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerUtils { private static final int REQUEST_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(30); private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final String AQ_JOB = "Air Quality Bulletin"; + private static final String POLLENS_JOB = "Pollens Update"; + private static final String EPISODE_JOB = "Episode"; private final Logger logger = LoggerFactory.getLogger(AirParifBridgeHandler.class); + private final Map> jobs = new HashMap<>(); private final AirParifDeserializer deserializer; private final AirParifIconProvider iconProvider; private final HttpClient httpClient; - private final Map> jobs = new HashMap<>(); private BridgeConfiguration config = new BridgeConfiguration(); private @Nullable PollensResponse pollens; @@ -173,7 +176,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU @Override public void handleCommand(ChannelUID channelUID, Command command) { - logger.debug("The AirParif bridge does not handles commands"); + logger.debug("The AirParif bridge does not handle commands"); } private void initiateConnexion() { @@ -209,11 +212,10 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU ThingUID thingUID = thing.getUID(); - schedule("Pollens Update", () -> updatePollens(new ChannelGroupUID(thingUID, GROUP_POLLENS)), - Duration.ofSeconds(1)); - schedule("Air Quality Bulletin", () -> updateDailyAQBulletin(new ChannelGroupUID(thingUID, GROUP_AQ_BULLETIN), + schedule(POLLENS_JOB, () -> updatePollens(new ChannelGroupUID(thingUID, GROUP_POLLENS)), Duration.ofSeconds(1)); + schedule(AQ_JOB, () -> updateDailyAQBulletin(new ChannelGroupUID(thingUID, GROUP_AQ_BULLETIN), new ChannelGroupUID(thingUID, GROUP_AQ_BULLETIN_TOMORROW)), Duration.ofSeconds(2)); - schedule("Episode", () -> updateEpisode(new ChannelGroupUID(thingUID, GROUP_DAILY)), Duration.ofSeconds(3)); + schedule(EPISODE_JOB, () -> updateEpisode(new ChannelGroupUID(thingUID, GROUP_DAILY)), Duration.ofSeconds(3)); } private void updatePollens(ChannelGroupUID pollensGroupUID) { @@ -235,7 +237,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU long delay = localPollens.getValidityDuration().getSeconds(); // if delay is null, update in 3600 seconds delay += delay == 0 ? 3600 : 60; - schedule("Pollens Update", () -> updatePollens(pollensGroupUID), Duration.ofSeconds(delay)); + schedule(POLLENS_JOB, () -> updatePollens(pollensGroupUID), Duration.ofSeconds(delay)); // Send pollens information to childs getThing().getThings().stream().map(Thing::getHandler).filter(LocationHandler.class::isInstance) @@ -269,7 +271,7 @@ 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"); - schedule("Air Quality Bulletin", () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), + schedule(AQ_JOB, () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), Duration.between(ZonedDateTime.now(), tomorrowMorning)); } @@ -292,7 +294,8 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU // }); ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); - schedule("Episode", () -> updateEpisode(dailyGroupUID), Duration.between(ZonedDateTime.now(), tomorrowMorning)); + schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), + Duration.between(ZonedDateTime.now(), tomorrowMorning)); } public @Nullable Route getConcentrations(String location) { diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index 3407afe2a09..f84175eff5b 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -49,12 +49,13 @@ import org.slf4j.LoggerFactory; */ @NonNullByDefault public class LocationHandler extends BaseThingHandler implements HandlerUtils { + private static final String AQ_JOB = "Local Air Quality"; private final Logger logger = LoggerFactory.getLogger(LocationHandler.class); private final Map> jobs = new HashMap<>(); - private @Nullable LocationConfiguration config; private Map myPollens = Map.of(); + private @Nullable LocationConfiguration config; public LocationHandler(Thing thing) { super(thing); @@ -64,7 +65,7 @@ public class LocationHandler extends BaseThingHandler implements HandlerUtils { public void initialize() { config = getConfigAs(LocationConfiguration.class); updateStatus(ThingStatus.UNKNOWN); - schedule("Local Air Quality", this::getConcentrations, Duration.ofSeconds(2)); + schedule(AQ_JOB, this::getConcentrations, Duration.ofSeconds(2)); } @Override @@ -113,12 +114,12 @@ public class LocationHandler extends BaseThingHandler implements HandlerUtils { } else { delay = 10; } - schedule("Local Air Quality", this::getConcentrations, Duration.ofSeconds(delay)); + schedule(AQ_JOB, this::getConcentrations, Duration.ofSeconds(delay)); } @Override public void handleCommand(ChannelUID channelUID, Command command) { - // TODO Auto-generated method stub + logger.debug("This thing does not handle commands"); } @Override diff --git a/bundles/pom.xml b/bundles/pom.xml index 68102f6c939..8f1a5024627 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -48,6 +48,7 @@ org.openhab.binding.adorne org.openhab.binding.ahawastecollection org.openhab.binding.airgradient + org.openhab.binding.airparif org.openhab.binding.airq org.openhab.binding.airquality org.openhab.binding.airvisualnode From 838816c0333bd2f5ae3932c9f28a9796bb00cb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 18 Nov 2024 13:55:12 +0100 Subject: [PATCH 06/17] SAT solving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../binding/airparif/internal/AirParifIconProvider.java | 3 +-- .../openhab/binding/airparif/internal/api/AirParifDto.java | 4 ++-- .../binding/airparif/internal/api/PollenAlertLevel.java | 6 ++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index d464e0eb9f4..d893d010df8 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -101,8 +101,7 @@ public class AirParifIconProvider implements IconProvider { if (POLLEN_ICONS.contains(category) && state != null) { try { int ordinal = Integer.valueOf(state); - PollenAlertLevel alertLevel = PollenAlertLevel.AS_SET.stream() - .filter(pal -> pal.riskLevel == ordinal).findFirst().orElse(PollenAlertLevel.UNKNOWN); + PollenAlertLevel alertLevel = PollenAlertLevel.valueOf(ordinal); result = result.replaceAll(NEUTRAL_COLOR, alertLevel.color); } catch (NumberFormatException ignore) { } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 24c6e9a728a..a68ff1a1604 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -124,11 +124,11 @@ public class AirParifDto { } private Set getValidities() { - Set local; + final Set local; if (validities != null) { local = validities; } else { - local = new TreeSet<>(); + local = new TreeSet(); getData().ifPresent(pollens -> { Matcher matcher = PATTERN.matcher(pollens.periode); while (matcher.find()) { diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java index 59b2d821ba3..82fe12ae565 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java @@ -13,6 +13,7 @@ package org.openhab.binding.airparif.internal.api; import java.util.EnumSet; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -42,4 +43,9 @@ public enum PollenAlertLevel { this.riskLevel = riskLevel; this.color = color; } + + public static PollenAlertLevel valueOf(int ordinal) { + return Objects + .requireNonNull(AS_SET.stream().filter(pal -> pal.riskLevel == ordinal).findFirst().orElse(UNKNOWN)); + } } From ec0bb2f4634f42f35b4febf77989a21a8e24dc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 18 Nov 2024 14:01:36 +0100 Subject: [PATCH 07/17] Solving nullable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../airparif/internal/api/AirParifDto.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index a68ff1a1604..0dd508a2988 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -115,7 +115,6 @@ public class AirParifDto { private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris"); public List data = List.of(); - private @Nullable Set validities; private @Nullable ZonedDateTime beginValidity; private @Nullable ZonedDateTime endValidity; @@ -124,21 +123,15 @@ public class AirParifDto { } private Set getValidities() { - final Set local; - if (validities != null) { - local = validities; - } else { - local = new TreeSet(); - getData().ifPresent(pollens -> { - Matcher matcher = PATTERN.matcher(pollens.periode); - while (matcher.find()) { - local.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); - } - }); - validities = local; - } + Set validities = new TreeSet<>(); + getData().ifPresent(pollens -> { + Matcher matcher = PATTERN.matcher(pollens.periode); + while (matcher.find()) { + validities.add(LocalDate.parse(matcher.group(), FORMATTER).atStartOfDay(DEFAULT_ZONE)); + } + }); - return local; + return validities; } public Optional getBeginValidity() { From 8fcac57da6fcbea592de26c054f664b7548ac9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 18 Nov 2024 14:11:14 +0100 Subject: [PATCH 08/17] Placeholder corrected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../airparif/internal/handler/AirParifBridgeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index b22c3a6d459..c106fdcef1c 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -207,7 +207,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU logger.debug("The color map is {}", map.toString()); iconProvider.setColorMap(map); } catch (AirParifException e) { - logger.warn("Error reading ColorMap: {]", e.getMessage()); + logger.warn("Error reading ColorMap: {}", e.getMessage()); } ThingUID thingUID = thing.getUID(); From 9aa3255adab59f6a769e4fd8666c852203901539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Tue, 19 Nov 2024 12:22:41 +0100 Subject: [PATCH 09/17] Adding Air Quality index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../internal/AirParifBindingConstants.java | 1 + .../internal/AirParifHandlerFactory.java | 6 +-- .../internal/AirParifIconProvider.java | 33 +++++++------- .../airparif/internal/api/AirParifApi.java | 2 +- .../airparif/internal/api/AirParifDto.java | 28 ++++++++++++ .../airparif/internal/api/Pollutant.java | 43 +++++++++++++++---- .../handler/AirParifBridgeHandler.java | 22 ++-------- .../internal/handler/LocationHandler.java | 26 +++++++---- .../resources/OH-INF/i18n/airparif.properties | 41 ++++++++++++------ .../resources/OH-INF/thing/channel-groups.xml | 38 +++++----------- .../main/resources/OH-INF/thing/channels.xml | 22 +++++++--- .../resources/OH-INF/thing/thing-types.xml | 4 +- .../src/main/resources/icon/alder.svg | 0 .../src/main/resources/icon/aq-0.svg | 8 ++++ .../src/main/resources/icon/aq-1.svg | 7 +++ .../src/main/resources/icon/aq-2.svg | 7 +++ .../src/main/resources/icon/aq-3.svg | 9 ++++ .../src/main/resources/icon/aq-4.svg | 9 ++++ .../src/main/resources/icon/aq-5.svg | 6 +++ .../src/main/resources/icon/aq.svg | 7 +++ .../src/main/resources/icon/ash.svg | 0 .../src/main/resources/icon/average.svg | 7 --- .../src/main/resources/icon/bad.svg | 9 ---- .../src/main/resources/icon/birch.svg | 0 .../src/main/resources/icon/chestnut.svg | 0 .../src/main/resources/icon/cypress.svg | 0 .../src/main/resources/icon/degrated.svg | 7 --- .../src/main/resources/icon/extremely-bad.svg | 6 --- .../src/main/resources/icon/good.svg | 8 ---- .../src/main/resources/icon/grasses.svg | 0 .../src/main/resources/icon/hazel.svg | 0 .../src/main/resources/icon/hornbeam.svg | 0 .../src/main/resources/icon/linden.svg | 0 .../src/main/resources/icon/oak.svg | 0 .../src/main/resources/icon/olive.svg | 0 .../src/main/resources/icon/plane.svg | 0 .../src/main/resources/icon/plantain.svg | 0 .../src/main/resources/icon/pollen.svg | 0 .../src/main/resources/icon/poplar.svg | 0 .../src/main/resources/icon/ragweed.svg | 0 .../src/main/resources/icon/rumex.svg | 0 .../src/main/resources/icon/urticaceae.svg | 0 .../src/main/resources/icon/willow.svg | 0 .../src/main/resources/icon/wormwood.svg | 0 44 files changed, 216 insertions(+), 140 deletions(-) mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-0.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-1.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-2.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-3.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-4.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-5.svg create mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/aq.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg delete mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg delete mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg delete mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg delete mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg delete mode 100644 bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg mode change 100644 => 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java index 8a9d22fbc2e..8b6e19c9fd1 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -47,6 +47,7 @@ public class AirParifBindingConstants { public static final String CHANNEL_TOMORROW = "tomorrow"; public static final String CHANNEL_TIMESTAMP = "timestamp"; public static final String CHANNEL_VALUE = "value"; + public static final String CHANNEL_ALERT = "alert"; public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(APIBRIDGE_THING_TYPE, LOCATION_THING_TYPE); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java index ac814ef3a5c..ea9f49da607 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -41,14 +41,12 @@ import org.osgi.service.component.annotations.Reference; public class AirParifHandlerFactory extends BaseThingHandlerFactory { private final AirParifDeserializer deserializer; private final HttpClient httpClient; - private final AirParifIconProvider iconProvider; @Activate public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory, - final @Reference AirParifDeserializer deserializer, final @Reference AirParifIconProvider iconProvider) { + final @Reference AirParifDeserializer deserializer) { this.httpClient = httpClientFactory.getCommonHttpClient(); this.deserializer = deserializer; - this.iconProvider = iconProvider; } @Override @@ -61,7 +59,7 @@ public class AirParifHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); return APIBRIDGE_THING_TYPE.equals(thingTypeUID) - ? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer, iconProvider) + ? new AirParifBridgeHandler((Bridge) thing, httpClient, deserializer) : LOCATION_THING_TYPE.equals(thingTypeUID) ? new LocationHandler(thing) : null; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index d893d010df8..f95f4cdcf02 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -26,7 +26,6 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; -import org.openhab.binding.airparif.internal.api.ColorMap; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.ui.icon.IconProvider; @@ -50,15 +49,14 @@ import org.slf4j.LoggerFactory; public class AirParifIconProvider implements IconProvider { private static final String NEUTRAL_COLOR = "#3d3c3c"; private static final String DEFAULT_LABEL = "Air Parif Icons"; + private static final String AQ_ICON = "aq"; private static final String DEFAULT_DESCRIPTION = "Icons illustrating air quality levels provided by AirParif"; - private static final List ICONS = List.of("average", "bad", "degrated", "extremely-bad", "good", "pollen"); private static final List POLLEN_ICONS = Pollen.AS_SET.stream().map(Pollen::name).map(String::toLowerCase) .toList(); private final Logger logger = LoggerFactory.getLogger(AirParifIconProvider.class); private final TranslationProvider i18nProvider; private final Bundle bundle; - private @Nullable ColorMap colorMap; @Activate public AirParifIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) { @@ -87,24 +85,31 @@ public class AirParifIconProvider implements IconProvider { @Override public @Nullable Integer hasIcon(String category, String iconSetId, Format format) { return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) - && (ICONS.contains(category) || POLLEN_ICONS.contains(category)) ? 0 : null; + && (category.equals(AQ_ICON) || POLLEN_ICONS.contains(category)) ? 0 : null; } @Override public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) { - URL iconResource = bundle.getEntry("icon/%s.svg".formatted(category)); + int ordinal = -1; + try { + ordinal = state != null ? Integer.valueOf(state) : -1; + } catch (NumberFormatException ignore) { + } + + String iconName = "icon/%s.svg".formatted(category); + if (category.equals(AQ_ICON) && ordinal != -1) { + iconName = iconName.replace(".", "-%d.".formatted(ordinal)); + } + + URL iconResource = bundle.getEntry(iconName); String result; try (InputStream stream = iconResource.openStream()) { result = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - if (POLLEN_ICONS.contains(category) && state != null) { - try { - int ordinal = Integer.valueOf(state); - PollenAlertLevel alertLevel = PollenAlertLevel.valueOf(ordinal); - result = result.replaceAll(NEUTRAL_COLOR, alertLevel.color); - } catch (NumberFormatException ignore) { - } + if (POLLEN_ICONS.contains(category)) { + PollenAlertLevel alertLevel = PollenAlertLevel.valueOf(ordinal); + result = result.replaceAll(NEUTRAL_COLOR, alertLevel.color); } } catch (IOException e) { logger.warn("Unable to load ressource '{}': {}", iconResource.getPath(), e.getMessage()); @@ -113,8 +118,4 @@ public class AirParifIconProvider implements IconProvider { return result.isEmpty() ? null : new ByteArrayInputStream(result.getBytes()); } - - public void setColorMap(ColorMap map) { - this.colorMap = map; - } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java index 95f387064b3..5349d18caef 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -62,7 +62,7 @@ public class AirParifApi { AVERAGE("Moyen"), DEGRATED("Dégradé"), BAD("Mauvais"), - REALLY_BAD("Très Mauvais"), + VERY_BAD("Très Mauvais"), EXTREMELY_BAD("Extrêmement Mauvais"), UNKNOWN(""); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 0dd508a2988..c13da7d22b6 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -27,10 +27,13 @@ import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.AirParifApi.Scope; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -63,6 +66,22 @@ public class AirParifDto { Pollutant pollutant, // int min, // int max) { + + private State getQuantity(int value) { + Unit unit = pollutant.unit; + if (unit != null) { + return new QuantityType<>(value, unit); + } + return UnDefType.NULL; + } + + public State getMin() { + return getQuantity(min); + } + + public State getMax() { + return getQuantity(max); + } } public record PollutantEpisode(// @@ -185,9 +204,18 @@ public class AirParifDto { return message != null ? new StringType(message.fr()) : UnDefType.NULL; } + public State getQuantity() { + Unit unit = pollutant.unit; + return unit != null ? new QuantityType<>(getValue(), unit) : UnDefType.NULL; + } + public double getValue() { return values[0]; } + + public int getAlertLevel() { + return pollutant.getAppreciation(getValue()).ordinal(); + } } public record Route(// diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java index 4298a621aad..bf21998970b 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java @@ -17,6 +17,8 @@ import java.util.EnumSet; import javax.measure.Unit; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifApi.Appreciation; import org.openhab.core.library.unit.Units; import com.google.gson.annotations.SerializedName; @@ -28,29 +30,37 @@ import com.google.gson.annotations.SerializedName; */ @NonNullByDefault public enum Pollutant { + // Concentration thresholds per pollutant are available here: + // https://www.airparif.fr/sites/default/files/pdf/guide_calcul_nouvel_indice_fedeAtmo_14122020.pdf + @SerializedName("pm25") - PM25(Units.MICROGRAM_PER_CUBICMETRE), + PM25(Units.MICROGRAM_PER_CUBICMETRE, new int[] { 10, 20, 25, 50, 75 }), @SerializedName("pm10") - PM10(Units.MICROGRAM_PER_CUBICMETRE), + PM10(Units.MICROGRAM_PER_CUBICMETRE, new int[] { 20, 40, 50, 100, 150 }), @SerializedName("no2") - NO2(Units.PARTS_PER_BILLION), + NO2(Units.MICROGRAM_PER_CUBICMETRE, new int[] { 40, 90, 120, 230, 340 }), @SerializedName("o3") - O3(Units.PARTS_PER_BILLION), + O3(Units.MICROGRAM_PER_CUBICMETRE, new int[] { 50, 100, 130, 240, 380 }), + + @SerializedName("so2") + SO2(Units.MICROGRAM_PER_CUBICMETRE, new int[] { 100, 200, 350, 500, 750 }), @SerializedName("indice") - INDICE(Units.PERCENT), + INDICE(null, new int[] {}), - UNKNOWN(Units.PERCENT); + UNKNOWN(null, new int[] {}); public static final EnumSet AS_SET = EnumSet.allOf(Pollutant.class); - public final Unit unit; + public final @Nullable Unit unit; + private final int[] thresholds; - Pollutant(Unit unit) { + Pollutant(@Nullable Unit unit, int[] thresholds) { this.unit = unit; + this.thresholds = thresholds; } public static Pollutant safeValueOf(String searched) { @@ -60,4 +70,21 @@ public enum Pollutant { return Pollutant.UNKNOWN; } } + + public boolean hasUnit() { + return unit != null; + } + + public Appreciation getAppreciation(double concentration) { + if (thresholds.length == 0) { + return Appreciation.UNKNOWN; + } + + for (int i = 0; i < thresholds.length; i++) { + if (concentration <= thresholds[i]) { + return Appreciation.values()[i]; + } + } + return Appreciation.EXTREMELY_BAD; + } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index c106fdcef1c..efdc27e31be 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -48,7 +48,6 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus.Code; import org.openhab.binding.airparif.internal.AirParifException; -import org.openhab.binding.airparif.internal.AirParifIconProvider; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.AirParifDto.Bulletin; import org.openhab.binding.airparif.internal.api.AirParifDto.Episode; @@ -57,13 +56,11 @@ import org.openhab.binding.airparif.internal.api.AirParifDto.KeyInfo; import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.AirParifDto.Version; -import org.openhab.binding.airparif.internal.api.ColorMap; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.config.BridgeConfiguration; import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelGroupUID; @@ -97,17 +94,14 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU private final Logger logger = LoggerFactory.getLogger(AirParifBridgeHandler.class); private final Map> jobs = new HashMap<>(); private final AirParifDeserializer deserializer; - private final AirParifIconProvider iconProvider; private final HttpClient httpClient; private BridgeConfiguration config = new BridgeConfiguration(); private @Nullable PollensResponse pollens; - public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer, - AirParifIconProvider iconProvider) { + public AirParifBridgeHandler(Bridge bridge, HttpClient httpClient, AirParifDeserializer deserializer) { super(bridge); this.deserializer = deserializer; - this.iconProvider = iconProvider; this.httpClient = httpClient; } @@ -202,14 +196,6 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU logger.info("The api key is valid until {}", keyInfo.expiration().toString()); updateStatus(ThingStatus.ONLINE); - try { - ColorMap map = executeUri(PREV_COLORS_URI, ColorMap.class); - logger.debug("The color map is {}", map.toString()); - iconProvider.setColorMap(map); - } catch (AirParifException e) { - logger.warn("Error reading ColorMap: {}", e.getMessage()); - } - ThingUID thingUID = thing.getUID(); schedule(POLLENS_JOB, () -> updatePollens(new ChannelGroupUID(thingUID, GROUP_POLLENS)), Duration.ofSeconds(1)); @@ -257,15 +243,15 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU Set.of(bulletin.today(), bulletin.tomorrow()).stream().forEach(aq -> { ChannelGroupUID groupUID = aq.isToday() ? todayGroupUID : tomorrowGroupUID; updateState(new ChannelUID(groupUID, CHANNEL_COMMENT), - !aq.available() ? UnDefType.UNDEF : new StringType(aq.bulletin().fr())); + !aq.available() ? UnDefType.NULL : new StringType(aq.bulletin().fr())); aq.concentrations().forEach(measure -> { Pollutant pollutant = measure.pollutant(); String cName = pollutant.name().toLowerCase() + "-"; updateState(new ChannelUID(groupUID, cName + "min"), - aq.available() ? new QuantityType<>(measure.min(), pollutant.unit) : UnDefType.UNDEF); + aq.available() ? measure.getMin() : UnDefType.NULL); updateState(new ChannelUID(groupUID, cName + "max"), - aq.available() ? new QuantityType<>(measure.max(), pollutant.unit) : UnDefType.UNDEF); + aq.available() ? measure.getMax() : UnDefType.NULL); }); }); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index f84175eff5b..ed58336d5fa 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -15,6 +15,7 @@ package org.openhab.binding.airparif.internal.handler; import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; import java.time.Duration; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; @@ -23,13 +24,14 @@ import java.util.concurrent.ScheduledFuture; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; +import org.openhab.binding.airparif.internal.api.AirParifDto.Concentration; import org.openhab.binding.airparif.internal.api.AirParifDto.PollensResponse; import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; +import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.config.LocationConfiguration; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; @@ -100,14 +102,22 @@ public class LocationHandler extends BaseThingHandler implements HandlerUtils { Route route = apiHandler.getConcentrations(local.location); if (route != null) { - route.concentrations().forEach(concentration -> { - ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), - concentration.pollutant().name().toLowerCase()); - updateState(new ChannelUID(groupUID, CHANNEL_TIMESTAMP), new DateTimeType(concentration.date())); - updateState(new ChannelUID(groupUID, CHANNEL_MESSAGE), concentration.getMessage()); - updateState(new ChannelUID(groupUID, CHANNEL_VALUE), - new QuantityType<>(concentration.getValue(), concentration.pollutant().unit)); + int maxAlert = route.concentrations().stream().filter(conc -> conc.pollutant().hasUnit()) + .map(Concentration::getAlertLevel).max(Comparator.comparing(Integer::valueOf)).get(); + route.concentrations().stream().forEach(concentration -> { + Pollutant pollutant = concentration.pollutant(); + ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), pollutant.name().toLowerCase()); + updateState(new ChannelUID(groupUID, CHANNEL_MESSAGE), concentration.getMessage()); + if (!pollutant.hasUnit()) { + updateState(new ChannelUID(groupUID, CHANNEL_TIMESTAMP), + new DateTimeType(concentration.date())); + updateState(new ChannelUID(groupUID, CHANNEL_ALERT), new DecimalType(maxAlert)); + } else { + updateState(new ChannelUID(groupUID, CHANNEL_VALUE), concentration.getQuantity()); + updateState(new ChannelUID(groupUID, CHANNEL_ALERT), + new DecimalType(concentration.getAlertLevel())); + } }); updateStatus(ThingStatus.ONLINE); } diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index 850db294e30..23e096b9881 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -67,26 +67,19 @@ channel-group-type.airparif.daily.channel.tomorrow.label = Tomorrow channel-group-type.airparif.daily.channel.tomorrow.description = Current bulletin validity start channel-group-type.airparif.dept-pollens.label = Pollen information for the department 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.description = Alert Level associated to pollutant concentration channel-group-type.airparif.pollutant-mpc.channel.message.label = Message channel-group-type.airparif.pollutant-mpc.channel.message.description = Polllutant concentration alert message -channel-group-type.airparif.pollutant-mpc.channel.timestamp.label = Timestamp -channel-group-type.airparif.pollutant-mpc.channel.timestamp.description = Timestamp of the measure channel-group-type.airparif.pollutant-mpc.channel.value.label = Concentration channel-group-type.airparif.pollutant-mpc.channel.value.description = Concentration of the given pollutant channel-group-type.airparif.pollutant-ndx.label = Global Pollutant Index +channel-group-type.airparif.pollutant-ndx.channel.alert.label = Alert Level +channel-group-type.airparif.pollutant-ndx.channel.alert.description = Alert Level associated to highest pollutant concentration channel-group-type.airparif.pollutant-ndx.channel.message.label = Message channel-group-type.airparif.pollutant-ndx.channel.message.description = Alert message associated to the value of the index channel-group-type.airparif.pollutant-ndx.channel.timestamp.label = Timestamp channel-group-type.airparif.pollutant-ndx.channel.timestamp.description = Timestamp of the evaluation -channel-group-type.airparif.pollutant-ndx.channel.value.label = Value -channel-group-type.airparif.pollutant-ndx.channel.value.description = Value of the global Index -channel-group-type.airparif.pollutant-ppb.label = Pollutant Concentration Information -channel-group-type.airparif.pollutant-ppb.channel.message.label = Message -channel-group-type.airparif.pollutant-ppb.channel.message.description = Polllutant concentration alert message -channel-group-type.airparif.pollutant-ppb.channel.timestamp.label = Timestamp -channel-group-type.airparif.pollutant-ppb.channel.timestamp.description = Timestamp of the measure -channel-group-type.airparif.pollutant-ppb.channel.value.label = Concentration -channel-group-type.airparif.pollutant-ppb.channel.value.description = Concentration of the given pollutant # channel types @@ -95,6 +88,13 @@ channel-type.airparif.alder-level.state.option.0 = None channel-type.airparif.alder-level.state.option.1 = Low channel-type.airparif.alder-level.state.option.2 = Average channel-type.airparif.alder-level.state.option.3 = High +channel-type.airparif.appreciation.label = Air Quality +channel-type.airparif.appreciation.state.option.0 = Good +channel-type.airparif.appreciation.state.option.1 = Average +channel-type.airparif.appreciation.state.option.2 = Degrated +channel-type.airparif.appreciation.state.option.3 = Bad +channel-type.airparif.appreciation.state.option.4 = Very Bad +channel-type.airparif.appreciation.state.option.5 = Extremely Bad channel-type.airparif.ash-level.label = Ash channel-type.airparif.ash-level.state.option.0 = None channel-type.airparif.ash-level.state.option.1 = Low @@ -163,7 +163,6 @@ channel-type.airparif.poplar-level.state.option.0 = None channel-type.airparif.poplar-level.state.option.1 = Low channel-type.airparif.poplar-level.state.option.2 = Average channel-type.airparif.poplar-level.state.option.3 = High -channel-type.airparif.ppb-value.label = Measure channel-type.airparif.ragweed-level.label = Ragweed channel-type.airparif.ragweed-level.state.option.0 = None channel-type.airparif.ragweed-level.state.option.1 = Low @@ -191,6 +190,24 @@ channel-type.airparif.wormwood-level.state.option.1 = Low channel-type.airparif.wormwood-level.state.option.2 = Average channel-type.airparif.wormwood-level.state.option.3 = High +# channel group types + +channel-group-type.airparif.pollutant-mpc.channel.timestamp.label = Timestamp +channel-group-type.airparif.pollutant-mpc.channel.timestamp.description = Timestamp of the measure +channel-group-type.airparif.pollutant-ndx.channel.value.label = Value +channel-group-type.airparif.pollutant-ndx.channel.value.description = Value of the global Index +channel-group-type.airparif.pollutant-ppb.label = Pollutant Concentration Information +channel-group-type.airparif.pollutant-ppb.channel.message.label = Message +channel-group-type.airparif.pollutant-ppb.channel.message.description = Polllutant concentration alert message +channel-group-type.airparif.pollutant-ppb.channel.timestamp.label = Timestamp +channel-group-type.airparif.pollutant-ppb.channel.timestamp.description = Timestamp of the measure +channel-group-type.airparif.pollutant-ppb.channel.value.label = Concentration +channel-group-type.airparif.pollutant-ppb.channel.value.description = Concentration of the given pollutant + +# channel types + +channel-type.airparif.ppb-value.label = Measure + # thing types thing-type.airparif.location.channel.end-validity.label = End Of Validity diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml index 5494219bbcb..d84547ff8ed 100644 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -29,19 +29,19 @@ General message for the air quality bulletin - + Minimum level of NO2 concentation - + Maximum level of NO2 concentation - + Minimum level of O3 concentation - + Maximum level of O3 concentation @@ -71,31 +71,13 @@ Polllutant concentration alert message - - - Timestamp of the measure - Concentration of the given pollutant - - - - - - - - - Polllutant concentration alert message - - - - Timestamp of the measure - - - - Concentration of the given pollutant + + + Alert Level associated to pollutant concentration @@ -111,9 +93,9 @@ Timestamp of the evaluation - - - Value of the global Index + + + Alert Level associated to highest pollutant concentration diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml index e47a4ec856c..18810b36d3f 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channels.xml @@ -291,12 +291,6 @@ - - Number:Dimensionless - - - - Number @@ -309,5 +303,21 @@ + + Number + + oh:airparif:aq + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml index 80003f5c0e9..bf92bdc8ed2 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml @@ -15,10 +15,10 @@ - + - + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/alder.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-0.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-0.svg new file mode 100755 index 00000000000..80e94fe7fd2 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-0.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-1.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-1.svg new file mode 100755 index 00000000000..2e78da14543 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-1.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-2.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-2.svg new file mode 100755 index 00000000000..a7b3aa5ac8f --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-2.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-3.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-3.svg new file mode 100755 index 00000000000..547740458c9 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-3.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-4.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-4.svg new file mode 100755 index 00000000000..8db5142887d --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-4.svg @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-5.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-5.svg new file mode 100755 index 00000000000..80b94df18ba --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq-5.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq.svg new file mode 100755 index 00000000000..056c569a4a6 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/src/main/resources/icon/aq.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ash.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg deleted file mode 100644 index f690930b938..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/average.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg deleted file mode 100644 index aff876d3508..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/bad.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/birch.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/chestnut.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/cypress.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg deleted file mode 100644 index df239efc873..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/degrated.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg deleted file mode 100644 index bce1d6ef083..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/extremely-bad.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg deleted file mode 100644 index 22a5d524ec6..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/good.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/grasses.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hazel.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/hornbeam.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/linden.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/oak.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/olive.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plane.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/plantain.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/poplar.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/ragweed.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/rumex.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/urticaceae.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/willow.svg old mode 100644 new mode 100755 diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/wormwood.svg old mode 100644 new mode 100755 From 58ef294232f9cc93bd0170436df2dee8ee13667f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Tue, 19 Nov 2024 14:13:59 +0100 Subject: [PATCH 10/17] Cleaning resource files Bulletproofed icon servlet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../internal/AirParifIconProvider.java | 6 +- .../airparif/internal/api/AirParifDto.java | 5 + .../airparif/internal/api/ColorMap.java | 36 -------- .../deserialization/AirParifDeserializer.java | 2 - .../deserialization/ColorMapDeserializer.java | 42 --------- .../internal/handler/LocationHandler.java | 4 +- .../resources/OH-INF/i18n/airparif.properties | 91 +++---------------- .../resources/OH-INF/thing/channel-groups.xml | 22 ++--- .../thing/{thing-types.xml => location.xml} | 0 9 files changed, 31 insertions(+), 177 deletions(-) delete mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java delete mode 100644 bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java rename bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/{thing-types.xml => location.xml} (100%) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index f95f4cdcf02..f5f4ec0df1f 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -25,6 +25,7 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.airparif.internal.api.AirParifApi.Appreciation; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.core.i18n.TranslationProvider; @@ -97,13 +98,13 @@ public class AirParifIconProvider implements IconProvider { } String iconName = "icon/%s.svg".formatted(category); - if (category.equals(AQ_ICON) && ordinal != -1) { + if (category.equals(AQ_ICON) && ordinal != -1 && ordinal < Appreciation.values().length - 2) { iconName = iconName.replace(".", "-%d.".formatted(ordinal)); } URL iconResource = bundle.getEntry(iconName); - String result; + String result = ""; try (InputStream stream = iconResource.openStream()) { result = new String(stream.readAllBytes(), StandardCharsets.UTF_8); @@ -113,7 +114,6 @@ public class AirParifIconProvider implements IconProvider { } } catch (IOException e) { logger.warn("Unable to load ressource '{}': {}", iconResource.getPath(), e.getMessage()); - result = ""; } return result.isEmpty() ? null : new ByteArrayInputStream(result.getBytes()); diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index c13da7d22b6..73374d491c4 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -33,6 +33,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifApi.Pollen; import org.openhab.binding.airparif.internal.api.AirParifApi.Scope; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.State; @@ -209,6 +210,10 @@ public class AirParifDto { return unit != null ? new QuantityType<>(getValue(), unit) : UnDefType.NULL; } + public State getDate() { + return new DateTimeType(date); + } + public double getValue() { return values[0]; } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java deleted file mode 100644 index 0c9d95d2764..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/ColorMap.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.airparif.internal.api; - -import java.util.HashMap; -import java.util.Objects; - -import org.openhab.binding.airparif.internal.api.AirParifApi.Appreciation; - -/** - * Class association between air quality appreciation and its color - * - * @author Gaël L'hopital - Initial contribution - */ -public class ColorMap extends HashMap { - private static final long serialVersionUID = -605462873565278453L; - - private static Appreciation fromApiName(String searched) { - return Objects.requireNonNull(Appreciation.AS_SET.stream().filter(mt -> searched.equals(mt.apiName)).findFirst() - .orElse(Appreciation.UNKNOWN)); - } - - public String put(String key, String value) { - return super.put(fromApiName(key), value); - } -} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java index a66b32a87fc..edafa433730 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.AirParifException; import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; -import org.openhab.binding.airparif.internal.api.ColorMap; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.core.i18n.TimeZoneProvider; import org.osgi.service.component.annotations.Activate; @@ -47,7 +46,6 @@ public class AirParifDeserializer { gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) .registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer()) .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) - .registerTypeAdapter(ColorMap.class, new ColorMapDeserializer()) .registerTypeAdapter(PollutantConcentration.class, new PollutantConcentrationDeserializer()) .registerTypeAdapter(LocalDate.class, (JsonDeserializer) (json, type, context) -> LocalDate diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java deleted file mode 100644 index c0804753b37..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/ColorMapDeserializer.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.airparif.internal.deserialization; - -import java.lang.reflect.Type; -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.airparif.internal.api.ColorMap; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; - -/** - * Specialized deserializer for ColorMap class - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -class ColorMapDeserializer implements JsonDeserializer { - - @Override - public @Nullable ColorMap deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) { - ColorMap result = new ColorMap(); - Set> entrySet = json.getAsJsonObject().entrySet(); - entrySet.forEach(entry -> result.put(entry.getKey(), entry.getValue().getAsString())); - return result; - } -} diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index ed58336d5fa..3fc7af83db7 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -30,7 +30,6 @@ import org.openhab.binding.airparif.internal.api.AirParifDto.Route; import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.config.LocationConfiguration; -import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelGroupUID; @@ -110,8 +109,7 @@ public class LocationHandler extends BaseThingHandler implements HandlerUtils { ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), pollutant.name().toLowerCase()); updateState(new ChannelUID(groupUID, CHANNEL_MESSAGE), concentration.getMessage()); if (!pollutant.hasUnit()) { - updateState(new ChannelUID(groupUID, CHANNEL_TIMESTAMP), - new DateTimeType(concentration.date())); + updateState(new ChannelUID(groupUID, CHANNEL_TIMESTAMP), concentration.getDate()); updateState(new ChannelUID(groupUID, CHANNEL_ALERT), new DecimalType(maxAlert)); } else { updateState(new ChannelUID(groupUID, CHANNEL_VALUE), concentration.getQuantity()); diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index 23e096b9881..d24b264b692 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -38,21 +38,21 @@ channel-group-type.airparif.air-quality-bulletin.label = Air Quality Bulletin channel-group-type.airparif.air-quality-bulletin.channel.comment.label = Message channel-group-type.airparif.air-quality-bulletin.channel.comment.description = General message for the air quality bulletin channel-group-type.airparif.air-quality-bulletin.channel.no2-max.label = NO2 Max -channel-group-type.airparif.air-quality-bulletin.channel.no2-max.description = Maximum level of NO2 concentation +channel-group-type.airparif.air-quality-bulletin.channel.no2-max.description = Maximum level of NO2 concentration channel-group-type.airparif.air-quality-bulletin.channel.no2-min.label = NO2 Min -channel-group-type.airparif.air-quality-bulletin.channel.no2-min.description = Minimum level of NO2 concentation +channel-group-type.airparif.air-quality-bulletin.channel.no2-min.description = Minimum level of NO2 concentration channel-group-type.airparif.air-quality-bulletin.channel.o3-max.label = O3 Max -channel-group-type.airparif.air-quality-bulletin.channel.o3-max.description = Maximum level of O3 concentation +channel-group-type.airparif.air-quality-bulletin.channel.o3-max.description = Maximum level of O3 concentration channel-group-type.airparif.air-quality-bulletin.channel.o3-min.label = O3 Min -channel-group-type.airparif.air-quality-bulletin.channel.o3-min.description = Minimum level of O3 concentation +channel-group-type.airparif.air-quality-bulletin.channel.o3-min.description = Minimum level of O3 concentration channel-group-type.airparif.air-quality-bulletin.channel.pm10-max.label = PM 10 Max -channel-group-type.airparif.air-quality-bulletin.channel.pm10-max.description = Maximum level of PM 10 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm10-max.description = Maximum level of PM 10 concentration channel-group-type.airparif.air-quality-bulletin.channel.pm10-min.label = PM 10 Min -channel-group-type.airparif.air-quality-bulletin.channel.pm10-min.description = Minimum level of PM 10 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm10-min.description = Minimum level of PM 10 concentration channel-group-type.airparif.air-quality-bulletin.channel.pm25-max.label = PM 2.5 Max -channel-group-type.airparif.air-quality-bulletin.channel.pm25-max.description = Maximum level of PM 2.5 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm25-max.description = Maximum level of PM 2.5 concentration channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.label = PM 2.5 Min -channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.description = Minimum level of PM 2.5 concentation +channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.description = Minimum level of PM 2.5 concentration channel-group-type.airparif.bridge-pollens.label = Pollen information for the region channel-group-type.airparif.bridge-pollens.channel.begin-validity.label = Begin Validity channel-group-type.airparif.bridge-pollens.channel.begin-validity.description = Current bulletin validity start @@ -73,9 +73,9 @@ channel-group-type.airparif.pollutant-mpc.channel.message.label = Message channel-group-type.airparif.pollutant-mpc.channel.message.description = Polllutant concentration alert message channel-group-type.airparif.pollutant-mpc.channel.value.label = Concentration channel-group-type.airparif.pollutant-mpc.channel.value.description = Concentration of the given pollutant -channel-group-type.airparif.pollutant-ndx.label = Global Pollutant Index -channel-group-type.airparif.pollutant-ndx.channel.alert.label = Alert Level -channel-group-type.airparif.pollutant-ndx.channel.alert.description = Alert Level associated to highest pollutant concentration +channel-group-type.airparif.pollutant-ndx.label = ATMO Index +channel-group-type.airparif.pollutant-ndx.channel.alert.label = Index +channel-group-type.airparif.pollutant-ndx.channel.alert.description = ATMO Index associated to highest pollutant concentration channel-group-type.airparif.pollutant-ndx.channel.message.label = Message channel-group-type.airparif.pollutant-ndx.channel.message.description = Alert message associated to the value of the index channel-group-type.airparif.pollutant-ndx.channel.timestamp.label = Timestamp @@ -223,75 +223,6 @@ channel-group-type.airparif.pollens-group.channel.comment.description = Current channel-group-type.airparif.pollens-group.channel.end-validity.label = End Validity channel-group-type.airparif.pollens-group.channel.end-validity.description = Current bulletin validity ending -# channel types - -channel-type.airparif.alert-level.state.option.0 = Good -channel-type.airparif.alert-level.state.option.1 = Average -channel-type.airparif.alert-level.state.option.2 = Degrated -channel-type.airparif.alert-level.state.option.3 = Bad -channel-type.airparif.alert-level.state.option.4 = Extremely Bad -channel-type.airparif.avalanches.label = Avalanches -channel-type.airparif.avalanches.description = Avalanche alert level -channel-type.airparif.avalanches.state.option.0 = No special vigilance -channel-type.airparif.avalanches.state.option.1 = Be attentive -channel-type.airparif.avalanches.state.option.2 = Be very vigilant -channel-type.airparif.avalanches.state.option.3 = Absolute vigilance -channel-type.airparif.canicule.label = Heat Wave -channel-type.airparif.canicule.description = High temperature alert level -channel-type.airparif.canicule.state.option.0 = No special vigilance -channel-type.airparif.canicule.state.option.1 = Be attentive -channel-type.airparif.canicule.state.option.2 = Be very vigilant -channel-type.airparif.canicule.state.option.3 = Absolute vigilance -channel-type.airparif.condition-icon.label = Icon -channel-type.airparif.condition-icon.description = Pictogram associated with the alert level. -channel-type.airparif.grand-froid.label = Extreme Cold -channel-type.airparif.grand-froid.description = Negative temperature alert level -channel-type.airparif.grand-froid.state.option.0 = No special vigilance -channel-type.airparif.grand-froid.state.option.1 = Be attentive -channel-type.airparif.grand-froid.state.option.2 = Be very vigilant -channel-type.airparif.grand-froid.state.option.3 = Absolute vigilance -channel-type.airparif.inondation.label = Flood -channel-type.airparif.inondation.description = Flood alert level -channel-type.airparif.inondation.state.option.0 = No special vigilance -channel-type.airparif.inondation.state.option.1 = Be attentive -channel-type.airparif.inondation.state.option.2 = Be very vigilant -channel-type.airparif.inondation.state.option.3 = Absolute vigilance -channel-type.airparif.neige.label = Snow -channel-type.airparif.neige.description = Snow event alert level -channel-type.airparif.neige.state.option.0 = No special vigilance -channel-type.airparif.neige.state.option.1 = Be attentive -channel-type.airparif.neige.state.option.2 = Be very vigilant -channel-type.airparif.neige.state.option.3 = Absolute vigilance -channel-type.airparif.orage.label = Storm -channel-type.airparif.orage.description = Storm alert level -channel-type.airparif.orage.state.option.0 = No special vigilance -channel-type.airparif.orage.state.option.1 = Be attentive -channel-type.airparif.orage.state.option.2 = Be very vigilant -channel-type.airparif.orage.state.option.3 = Absolute vigilance -channel-type.airparif.pluie-inondation.label = Rain Flood -channel-type.airparif.pluie-inondation.description = Flood caused by rainfall alert level -channel-type.airparif.pluie-inondation.state.option.0 = No special vigilance -channel-type.airparif.pluie-inondation.state.option.1 = Be attentive -channel-type.airparif.pluie-inondation.state.option.2 = Be very vigilant -channel-type.airparif.pluie-inondation.state.option.3 = Absolute vigilance -channel-type.airparif.pollen-level.label = Pollen Level -channel-type.airparif.pollen-level.state.option.0 = None -channel-type.airparif.pollen-level.state.option.1 = Low -channel-type.airparif.pollen-level.state.option.2 = Average -channel-type.airparif.pollen-level.state.option.3 = High -channel-type.airparif.vague-submersion.label = Wave Submersion -channel-type.airparif.vague-submersion.description = Submersion wave alert level -channel-type.airparif.vague-submersion.state.option.0 = No special vigilance -channel-type.airparif.vague-submersion.state.option.1 = Be attentive -channel-type.airparif.vague-submersion.state.option.2 = Be very vigilant -channel-type.airparif.vague-submersion.state.option.3 = Absolute vigilance -channel-type.airparif.vent.label = Wind -channel-type.airparif.vent.description = Wind event alert level -channel-type.airparif.vent.state.option.0 = No special vigilance -channel-type.airparif.vent.state.option.1 = Be attentive -channel-type.airparif.vent.state.option.2 = Be very vigilant -channel-type.airparif.vent.state.option.3 = Absolute vigilance - # discovery result discovery.airparif.location.local.label = Air Quality Report diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml index d84547ff8ed..4a40daa0ae1 100644 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -31,35 +31,35 @@ - Minimum level of NO2 concentation + Minimum level of NO2 concentration - Maximum level of NO2 concentation + Maximum level of NO2 concentration - Minimum level of O3 concentation + Minimum level of O3 concentration - Maximum level of O3 concentation + Maximum level of O3 concentration - Minimum level of PM 10 concentation + Minimum level of PM 10 concentration - Maximum level of PM 10 concentation + Maximum level of PM 10 concentration - Minimum level of PM 2.5 concentation + Minimum level of PM 2.5 concentration - Maximum level of PM 2.5 concentation + Maximum level of PM 2.5 concentration @@ -83,7 +83,7 @@ - + @@ -94,8 +94,8 @@ Timestamp of the evaluation - - Alert Level associated to highest pollutant concentration + + ATMO Index associated to highest pollutant concentration diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml similarity index 100% rename from bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/thing-types.xml rename to bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml From 815de298a2387c078d299f5d1944d83c93091e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Tue, 19 Nov 2024 14:25:38 +0100 Subject: [PATCH 11/17] Activating discovery service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../internal/discovery/AirParifDiscoveryService.java | 10 +++++----- .../internal/handler/AirParifBridgeHandler.java | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java index 61741ff9f7b..c479d6abe47 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java @@ -12,9 +12,10 @@ */ package org.openhab.binding.airparif.internal.discovery; -import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; +import static org.openhab.binding.airparif.internal.AirParifBindingConstants.LOCATION_THING_TYPE; import java.util.List; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.airparif.internal.config.LocationConfiguration; @@ -43,13 +44,12 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi private static final int DISCOVER_TIMEOUT_SECONDS = 2; private final Logger logger = LoggerFactory.getLogger(AirParifDiscoveryService.class); - private final DepartmentDbService dbService; + private final DepartmentDbService dbService = new DepartmentDbService(); private @NonNullByDefault({}) LocationProvider locationProvider; public AirParifDiscoveryService() { - super(AirParifBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS); - dbService = new DepartmentDbService(); + super(AirParifBridgeHandler.class, Set.of(LOCATION_THING_TYPE), DISCOVER_TIMEOUT_SECONDS); } @Reference(unbind = "-") @@ -77,7 +77,7 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi if (!candidates.isEmpty()) { candidates.forEach(dep -> thingDiscovered( DiscoveryResultBuilder.create(new ThingUID(LOCATION_THING_TYPE, bridgeUID, dep.id()))// - .withLabel("Location Report: %s".formatted(dep.name())) // + .withLabel("Air Quality Report: %s".formatted(dep.name())) // .withProperty(LocationConfiguration.DEPARTMENT, dep.id()) // .withProperty(LocationConfiguration.LOCATION, serverLocation.toFullString())// .withRepresentationProperty(LocationConfiguration.DEPARTMENT) // diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index efdc27e31be..8f92c27cc61 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -24,6 +24,7 @@ import java.time.Duration; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -60,6 +61,7 @@ import org.openhab.binding.airparif.internal.api.PollenAlertLevel; import org.openhab.binding.airparif.internal.api.Pollutant; import org.openhab.binding.airparif.internal.config.BridgeConfiguration; import org.openhab.binding.airparif.internal.deserialization.AirParifDeserializer; +import org.openhab.binding.airparif.internal.discovery.AirParifDiscoveryService; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; @@ -70,6 +72,7 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -330,4 +333,9 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU public Map> getJobs() { return jobs; } + + @Override + public Collection> getServices() { + return Set.of(AirParifDiscoveryService.class); + } } From 86458b1cc79e008cb3aed28e186b2a2ad3886076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Tue, 19 Nov 2024 14:35:48 +0100 Subject: [PATCH 12/17] Code cleansing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../openhab/binding/airparif/internal/AirParifException.java | 2 +- .../binding/airparif/internal/AirParifIconProvider.java | 2 +- .../openhab/binding/airparif/internal/api/AirParifApi.java | 2 +- .../airparif/internal/handler/AirParifBridgeHandler.java | 4 ---- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java index 3d7f00492de..109a3555059 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java @@ -15,7 +15,7 @@ package org.openhab.binding.airparif.internal; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * An exception that occurred while communicating with Air Parif API server or related processes. + * An exception that occurred while communicating with AirParif API server or related processes. * * @author Gaël L'hopital - Initial contribution */ diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index f5f4ec0df1f..fd6bd5337b1 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class AirParifIconProvider implements IconProvider { private static final String NEUTRAL_COLOR = "#3d3c3c"; - private static final String DEFAULT_LABEL = "Air Parif Icons"; + private static final String DEFAULT_LABEL = "AirParif Icons"; private static final String AQ_ICON = "aq"; private static final String DEFAULT_DESCRIPTION = "Icons illustrating air quality levels provided by AirParif"; private static final List POLLEN_ICONS = Pollen.AS_SET.stream().map(Pollen::name).map(String::toLowerCase) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java index 5349d18caef..bd270779d50 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -36,7 +36,6 @@ public class AirParifApi { public static final URI EPISODES_URI = AIRPARIF_BUILDER.clone().path("episodes").path("en-cours-et-prevus").build(); private static final UriBuilder INDICES_BUILDER = AIRPARIF_BUILDER.clone().path("indices").path("prevision"); - public static final URI PREV_COLORS_URI = INDICES_BUILDER.clone().path("couleurs").build(); public static final URI PREV_BULLETIN_URI = INDICES_BUILDER.clone().path("bulletin").build(); private static final UriBuilder POLLENS_BUILDER = AIRPARIF_BUILDER.clone().path("pollens"); @@ -44,6 +43,7 @@ public class AirParifApi { // 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 { @SerializedName("Cartes et résultats Hor'Air") diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index 8f92c27cc61..8784ce3147c 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -278,10 +278,6 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU updateState(new ChannelUID(dailyGroupUID, CHANNEL_MESSAGE), new StringType(episode.message().fr())); updateState(new ChannelUID(dailyGroupUID, CHANNEL_TOMORROW), new StringType(episode.message().fr())); - // Set.of(episode.today(), episode.tomorrow()).stream().forEach(aq -> { - - // }); - ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), Duration.between(ZonedDateTime.now(), tomorrowMorning)); From 02bf3c13340d0ba9d864fe7a3a255425dec497a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Wed, 20 Nov 2024 17:33:20 +0100 Subject: [PATCH 13/17] Written documentation and reviewed channels descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../org.openhab.binding.airparif/README.md | 204 ++++++++++++------ .../doc/images/alder.svg | 7 + .../doc/images/aq.svg | 7 + .../doc/images/ash.svg | 19 ++ .../doc/images/birch.svg | 5 + .../doc/images/chestnut.svg | 5 + .../doc/images/cypress.svg | 14 ++ .../doc/images/grasses.svg | 5 + .../doc/images/hazel.svg | 17 ++ .../doc/images/hornbeam.svg | 11 + .../doc/images/linden.svg | 8 + .../doc/images/oak.svg | 5 + .../doc/images/olive.svg | 5 + .../doc/images/plane.svg | 5 + .../doc/images/plantain.svg | 11 + .../doc/images/poplar.svg | 5 + .../doc/images/ragweed.svg | 14 ++ .../doc/images/rumex.svg | 5 + .../doc/images/urticaceae.svg | 14 ++ .../doc/images/willow.svg | 5 + .../doc/images/wormwood.svg | 13 ++ .../resources/OH-INF/i18n/airparif.properties | 14 +- .../resources/OH-INF/thing/channel-groups.xml | 12 +- .../main/resources/OH-INF/thing/location.xml | 2 +- .../src/main/resources/icon/pollen.svg | 4 - 25 files changed, 333 insertions(+), 83 deletions(-) create mode 100755 bundles/org.openhab.binding.airparif/doc/images/alder.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/aq.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/ash.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/birch.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/chestnut.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/cypress.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/grasses.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/hazel.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/hornbeam.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/linden.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/oak.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/olive.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/plane.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/plantain.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/poplar.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/ragweed.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/rumex.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/urticaceae.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/willow.svg create mode 100755 bundles/org.openhab.binding.airparif/doc/images/wormwood.svg delete mode 100755 bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg diff --git a/bundles/org.openhab.binding.airparif/README.md b/bundles/org.openhab.binding.airparif/README.md index d7a6687bb29..3e515749d42 100755 --- a/bundles/org.openhab.binding.airparif/README.md +++ b/bundles/org.openhab.binding.airparif/README.md @@ -1,94 +1,168 @@ # AirParif Binding -_Give some details about what this binding is meant for - a protocol, system, specific device._ - -_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ -_You can place such resources into a `doc` folder next to this README.md._ - -_Put each sentence in a separate line to improve readability of diffs._ +This binding uses the [AirParif service](https://www.airparif.fr/) for providing air quality information for Paris and departments of the Ile-de-France. +To use it, you first need to [register and get your API key](https://www.airparif.fr/interface-de-programmation-applicative). +You'll receive your API Key by mail. ## Supported Things -_Please describe the different supported things / devices including their ThingTypeUID within this section._ -_Which different types are supported, which models were tested etc.?_ -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +- `api`: bridge used to connect to the AirParif service. Provides some general informations for the whole area. +- `location`: Presents the pollen and air quality information for a given location. -- `bridge`: Short description of the Bridge, if any -- `sample`: Short description of the Thing with the ThingTypeUID `sample` +Of course, you can add multiple `location`s, e.g. for gathering pollen or air quality data for different locations. ## Discovery -_Describe the available auto-discovery features here._ -_Mention for what it works and what needs to be kept in mind when using it._ - -## Binding Configuration - -_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ -_In this section, you should link to this file and provide some information about the options._ -_The file could e.g. look like:_ - -``` -# Configuration for the AirParif Binding -# -# Default secret key for the pairing of the AirParif Thing. -# It has to be between 10-40 (alphanumeric) characters. -# This may be changed by the user for security reasons. -secret=openHABSecret -``` - -_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ - -_If your binding does not offer any generic configurations, you can remove this section completely._ +Once your `api` bridge is created and configured with the API Key, a default `location` can be auto-discovered based on system location. +It will be configured with the system location and detected department. ## Thing Configuration -_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ -_This should be mainly about its mandatory and optional configuration parameters._ +### `api` Thing Configuration -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|-----------------------------------|---------|----------|----------| +| apikey | text | Token used to access the service | N/A | yes | no | -### `sample` Thing Configuration +### `location` Thing Configuration -| Name | Type | Description | Default | Required | Advanced | -|-----------------|---------|---------------------------------------|---------|----------|----------| -| hostname | text | Hostname or IP address of the device | N/A | yes | no | -| password | text | Password to access the device | N/A | yes | no | -| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|--------------------------------------------------------------------------------|---------|----------|----------| +| location | text | Geo coordinates to be considered (as ,[,]) | N/A | yes | no | +| department | text | Code of the department (two digits) (*) | N/A | yes | no | + +(*) When auto-discovered, the department will be pre-filled based on the location and bounding limits defined in the internal department database. +Please check that proposed value is correct according to the place. ## Channels -_Here you should provide information about available channel types, what their meaning is and how they can be used._ +### `api` Thing Channels -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ +| Group | Channel | Type | Read/Write | Description | +|----------------------|----------------|----------------|------------|----------------------------------------------| +| pollens | comment | String | R | Current pollens situation | +| pollens | begin-validity | DateTime | R | Bulletin validity start | +| pollens | end-validity | DateTime | R | Bulletin validity end | +| aq-bulletin | comment | String | R | General message for the air quality bulletin | +| aq-bulletin | no2-min | Number:Density | R | Minimum level of NO2 concentration | +| aq-bulletin | no2-max | Number:Density | R | Maximum level of NO2 concentration | +| aq-bulletin | o3-min | Number:Density | R | Minimum level of O3 concentration | +| aq-bulletin | o3-max | Number:Density | R | Maximum level of O3 concentration | +| aq-bulletin | pm10-min | Number:Density | R | Minimum level of PM 10 concentration | +| aq-bulletin | pm10-max | Number:Density | R | Maximum level of PM 10 concentration | +| aq-bulletin | pm25-min | Number:Density | R | Minimum level of PM 2.5 concentration | +| aq-bulletin | pm25-max | Number:Density | R | Maximum level of PM 2.5 concentration | +| aq-bulletin-tomorrow | comment | String | R | General message for the air quality bulletin | +| aq-bulletin-tomorrow | no2-min | Number:Density | R | Minimum level of NO2 concentration | +| aq-bulletin-tomorrow | no2-max | Number:Density | R | Maximum level of NO2 concentration | +| aq-bulletin-tomorrow | o3-min | Number:Density | R | Minimum level of O3 concentration | +| aq-bulletin-tomorrow | o3-max | Number:Density | R | Maximum level of O3 concentration | +| aq-bulletin-tomorrow | pm10-min | Number:Density | R | Minimum 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-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 | -| Channel | Type | Read/Write | Description | -|---------|--------|------------|-----------------------------| -| control | Switch | RW | This is the control channel | +### `location` Thing Channels -## Full Example +| Group | Channel | Type | Read/Write | Description | +|---------|------------|----------------|------------|----------------------------------------------------------| +| pollens | cypress | Number | R | Alert level associated to this taxon (*) | +| pollens | hazel | Number | R | Alert level associated to this taxon (*) | +| pollens | alder | Number | R | Alert level associated to this taxon (*) | +| pollens | poplar | Number | R | Alert level associated to this taxon (*) | +| pollens | willow | Number | R | Alert level associated to this taxon (*) | +| pollens | ash | Number | R | Alert level associated to this taxon (*) | +| pollens | hornbeam | Number | R | Alert level associated to this taxon (*) | +| pollens | birch | Number | R | Alert level associated to this taxon (*) | +| pollens | plane | Number | R | Alert level associated to this taxon (*) | +| pollens | oak | Number | R | Alert level associated to this taxon (*) | +| pollens | olive | Number | R | Alert level associated to this taxon (*) | +| pollens | linden | Number | R | Alert level associated to this taxon (*) | +| pollens | chestnut | Number | R | Alert level associated to this taxon (*) | +| pollens | rumex | Number | R | Alert level associated to this taxon (*) | +| pollens | grasses | Number | R | Alert level associated to this taxon (*) | +| pollens | plantain | Number | R | Alert level associated to this taxon (*) | +| pollens | urticaceae | Number | R | Alert level associated to this taxon (*) | +| pollens | wormwood | Number | R | Alert level associated to this taxon (*) | +| pollens | ragweed | Number | R | Alert level associated to this taxon (*) | +| indice | message | String | R | Alert message associated to the value of the index | +| indice | timestamp | DateTime | R | Timestamp of the evaluation | +| indice | alert | Number | R | ATMO Index associated to highest pollutant concentration | +| o3 | message | String | R | Polllutant concentration alert message | +| o3 | value | Number:Density | R | Concentration of the given pollutant | +| o3 | alert | Number | R | Alert Level associated to pollutant concentration (**) | +| no2 | message | String | R | Polllutant concentration alert message | +| no2 | value | Number:Density | R | Concentration of the given pollutant | +| no2 | alert | Number | R | Alert Level associated to pollutant concentration (**) | +| pm25 | message | String | R | Polllutant concentration alert message | +| pm25 | value | Number:Density | R | Concentration of the given pollutant | +| pm25 | alert | Number | R | Alert Level associated to pollutant concentration (**) | +| pm10 | message | String | R | Polllutant concentration alert message | +| pm10 | value | Number:Density | R | Concentration of the given pollutant | +| pm10 | alert | Number | R | Alert Level associated to pollutant concentration (**) | -_Provide a full usage example based on textual configuration files._ -_*.things, *.items examples are mandatory as textual configuration is well used by many users._ -_*.sitemap examples are optional._ +(*) Each pollen alert level has an associated color and description: -### Thing Configuration +| Code | Color | Description | +|------|--------|-----------------------| +| 0 | Green | No allergic risk | +| 1 | Yellow | Low allergic risk | +| 2 | Orange | Average allergic risk | +| 3 | Red | High allergic risk | -```java -Example thing configuration goes here. +(*) Each pollutant concentration is associated to an alert level (and an icon) : + +| Code | Description | +|------|---------------| +| 0 | Good | +| 1 | Average | +| 2 | Degrated | +| 3 | Bad | +| 4 | Very Bad | +| 5 | Extremely Bad | + +## Provided icon set + +This binding has its own IconProvider and makes available the following list of icons + +| Icon Name | Dynamic | Illustration | +|------------------------|---------|--------------| +| oh:airparif:aq | Yes | ![](doc/images/aq.svg) | +| oh:airparif:alder | Yes | ![](doc/images/alder.svg) | +| oh:airparif:ash | Yes | ![](doc/images/ash.svg) | +| oh:airparif:birch | Yes | ![](doc/images/birch.svg) | +| oh:airparif:chestnut | Yes | ![](doc/images/chestnut.svg) | +| oh:airparif:cypress | Yes | ![](doc/images/cypress.svg) | +| oh:airparif:grasses | Yes | ![](doc/images/grasses.svg) | +| oh:airparif:hazel | Yes | ![](doc/images/hazel.svg) | +| oh:airparif:hornbeam | Yes | ![](doc/images/hornbeam.svg) | +| oh:airparif:linden | Yes | ![](doc/images/linden.svg) | +| oh:airparif:oak | Yes | ![](doc/images/oak.svg) | +| oh:airparif:olive | Yes | ![](doc/images/olive.svg) | +| oh:airparif:plane | Yes | ![](doc/images/plane.svg) | +| oh:airparif:plantain | Yes | ![](doc/images/plantain.svg) | +| oh:airparif:pollen | Yes | ![](doc/images/pollen.svg) | +| oh:airparif:poplar | Yes | ![](doc/images/poplar.svg) | +| oh:airparif:ragweed | Yes | ![](doc/images/ragweed.svg) | +| oh:airparif:rumex | Yes | ![](doc/images/rumex.svg) | +| oh:airparif:urticaceae | Yes | ![](doc/images/urticaceae.svg) | +| oh:airparif:willow | Yes | ![](doc/images/willow.svg) | +| oh:airparif:wormwood | Yes | ![](doc/images/wormwood.svg) | + + +## Full Examplee + +### Thing Configurationn + +```jav +Bridge airparif:api:local "AirParif" [ apikey="xxxxx-dddd-cccc-4321-zzzzzzzzzzzzz" ] { + location yvelines "Yvelines" [ department="78", location="52.639,1.8284" ] +}a ``` -### Item Configuration +### Item Configurationn ```java Example item configuration goes here. -``` +`` -### Sitemap Configuration - -```perl -Optional Sitemap configuration goes here. -Remove this section, if not needed. -``` - -## Any custom content here! - -_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.airparif/doc/images/alder.svg b/bundles/org.openhab.binding.airparif/doc/images/alder.svg new file mode 100755 index 00000000000..ea5fc88483e --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/alder.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/aq.svg b/bundles/org.openhab.binding.airparif/doc/images/aq.svg new file mode 100755 index 00000000000..056c569a4a6 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/aq.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/ash.svg b/bundles/org.openhab.binding.airparif/doc/images/ash.svg new file mode 100755 index 00000000000..9c10179746d --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/ash.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/birch.svg b/bundles/org.openhab.binding.airparif/doc/images/birch.svg new file mode 100755 index 00000000000..f8ff505f3b6 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/birch.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/chestnut.svg b/bundles/org.openhab.binding.airparif/doc/images/chestnut.svg new file mode 100755 index 00000000000..8cb9360bf60 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/chestnut.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/cypress.svg b/bundles/org.openhab.binding.airparif/doc/images/cypress.svg new file mode 100755 index 00000000000..0cc76819acd --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/cypress.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/grasses.svg b/bundles/org.openhab.binding.airparif/doc/images/grasses.svg new file mode 100755 index 00000000000..ae6279fd100 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/grasses.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/hazel.svg b/bundles/org.openhab.binding.airparif/doc/images/hazel.svg new file mode 100755 index 00000000000..e2dd22c17f3 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/hazel.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/hornbeam.svg b/bundles/org.openhab.binding.airparif/doc/images/hornbeam.svg new file mode 100755 index 00000000000..3a7d995bfaf --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/hornbeam.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/linden.svg b/bundles/org.openhab.binding.airparif/doc/images/linden.svg new file mode 100755 index 00000000000..c10749ecd98 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/linden.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/oak.svg b/bundles/org.openhab.binding.airparif/doc/images/oak.svg new file mode 100755 index 00000000000..343163e1a04 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/oak.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/olive.svg b/bundles/org.openhab.binding.airparif/doc/images/olive.svg new file mode 100755 index 00000000000..719798a0fbf --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/olive.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/plane.svg b/bundles/org.openhab.binding.airparif/doc/images/plane.svg new file mode 100755 index 00000000000..ab64767b0f7 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/plane.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/plantain.svg b/bundles/org.openhab.binding.airparif/doc/images/plantain.svg new file mode 100755 index 00000000000..740cf781f00 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/plantain.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/poplar.svg b/bundles/org.openhab.binding.airparif/doc/images/poplar.svg new file mode 100755 index 00000000000..5cd534d8b33 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/poplar.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/ragweed.svg b/bundles/org.openhab.binding.airparif/doc/images/ragweed.svg new file mode 100755 index 00000000000..0beffb9f6f5 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/ragweed.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/rumex.svg b/bundles/org.openhab.binding.airparif/doc/images/rumex.svg new file mode 100755 index 00000000000..d3607e52a37 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/rumex.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/urticaceae.svg b/bundles/org.openhab.binding.airparif/doc/images/urticaceae.svg new file mode 100755 index 00000000000..07c76f90127 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/urticaceae.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/willow.svg b/bundles/org.openhab.binding.airparif/doc/images/willow.svg new file mode 100755 index 00000000000..bc52029973b --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/willow.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/doc/images/wormwood.svg b/bundles/org.openhab.binding.airparif/doc/images/wormwood.svg new file mode 100755 index 00000000000..9c02ae1e2e8 --- /dev/null +++ b/bundles/org.openhab.binding.airparif/doc/images/wormwood.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index d24b264b692..c6250fc92f7 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -21,7 +21,7 @@ thing-type.airparif.location.group.pm25.label = PM2.5 Concentration Information thing-type.config.airparif.api.apikey.label = API Key thing-type.config.airparif.api.apikey.description = Token used to access the service thing-type.config.airparif.location.department.label = Department -thing-type.config.airparif.location.department.description = Name of the department +thing-type.config.airparif.location.department.description = Code of the department thing-type.config.airparif.location.department.option.75 = Paris thing-type.config.airparif.location.department.option.77 = Seine et Marne thing-type.config.airparif.location.department.option.78 = Yvelines @@ -55,16 +55,16 @@ channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.label = PM 2.5 channel-group-type.airparif.air-quality-bulletin.channel.pm25-min.description = Minimum level of PM 2.5 concentration channel-group-type.airparif.bridge-pollens.label = Pollen information for the region channel-group-type.airparif.bridge-pollens.channel.begin-validity.label = Begin Validity -channel-group-type.airparif.bridge-pollens.channel.begin-validity.description = Current bulletin validity start -channel-group-type.airparif.bridge-pollens.channel.comment.label = Begin Validity -channel-group-type.airparif.bridge-pollens.channel.comment.description = Current bulletin validity start +channel-group-type.airparif.bridge-pollens.channel.begin-validity.description = Bulletin validity start +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.end-validity.label = End Validity -channel-group-type.airparif.bridge-pollens.channel.end-validity.description = Current bulletin validity ending +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.channel.message.label = Message -channel-group-type.airparif.daily.channel.message.description = Current bulletin validity start +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.description = Current bulletin validity start +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.pollutant-mpc.label = Pollutant Concentration Information channel-group-type.airparif.pollutant-mpc.channel.alert.label = Alert Level diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml index 4a40daa0ae1..aff93e79e02 100644 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -8,16 +8,16 @@ - - Current bulletin validity start + + Current pollens situation - Current bulletin validity start + Bulletin validity start - Current bulletin validity ending + Bulletin validity end @@ -105,11 +105,11 @@ - Current bulletin validity start + Today's daily general information - Current bulletin validity start + Tomorrow's daily general information diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml index bf92bdc8ed2..c9a814c19d7 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml @@ -39,7 +39,7 @@ - Name of the department + Code of the department diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg b/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg deleted file mode 100755 index 1d75a230199..00000000000 --- a/bundles/org.openhab.binding.airparif/src/main/resources/icon/pollen.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - From 080f7a5172aa7f75602a9e29306a4172824e6764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Thu, 12 Dec 2024 12:46:58 +0100 Subject: [PATCH 14/17] Code review corrections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- .../org.openhab.binding.airparif/README.md | 69 +++++++++++++++++-- .../internal/AirParifHandlerFactory.java | 7 +- .../airparif/internal/api/AirParifApi.java | 4 -- .../airparif/internal/api/AirParifDto.java | 24 +++---- .../deserialization/AirParifDeserializer.java | 10 ++- .../PollutantConcentrationDeserializer.java | 5 +- .../discovery/AirParifDiscoveryService.java | 4 +- .../handler/AirParifBridgeHandler.java | 16 +++-- .../internal/handler/HandlerUtils.java | 6 +- .../resources/OH-INF/i18n/airparif.properties | 12 ++-- .../resources/OH-INF/thing/channel-groups.xml | 4 +- .../main/resources/OH-INF/thing/location.xml | 8 +-- 12 files changed, 111 insertions(+), 58 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/README.md b/bundles/org.openhab.binding.airparif/README.md index 3e515749d42..0f0cd51e323 100755 --- a/bundles/org.openhab.binding.airparif/README.md +++ b/bundles/org.openhab.binding.airparif/README.md @@ -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 | 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 | -| 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 @@ -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:wormwood | Yes | ![](doc/images/wormwood.svg) | - ## Full Examplee ### Thing Configurationn -```jav +```java Bridge airparif:api:local "AirParif" [ apikey="xxxxx-dddd-cccc-4321-zzzzzzzzzzzzz" ] { - location yvelines "Yvelines" [ department="78", location="52.639,1.8284" ] -}a + location 78 "Yvelines" [ department="78", location="52.639,1.8284" ] +} ``` ### Item Configurationn ```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"} `` diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java index ea9f49da607..76805b24c17 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -16,7 +16,6 @@ import static org.openhab.binding.airparif.internal.AirParifBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; 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.handler.AirParifBridgeHandler; 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) public class AirParifHandlerFactory extends BaseThingHandlerFactory { private final AirParifDeserializer deserializer; - private final HttpClient httpClient; + private final HttpClientFactory httpClientFactory; @Activate public AirParifHandlerFactory(final @Reference HttpClientFactory httpClientFactory, final @Reference AirParifDeserializer deserializer) { - this.httpClient = httpClientFactory.getCommonHttpClient(); + this.httpClientFactory = httpClientFactory; this.deserializer = deserializer; } @@ -59,7 +58,7 @@ public class AirParifHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); 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; } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java index bd270779d50..94ed8c3ed6f 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -41,10 +41,6 @@ public class AirParifApi { private static final UriBuilder POLLENS_BUILDER = AIRPARIF_BUILDER.clone().path("pollens"); 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 { @SerializedName("Cartes et résultats Hor'Air") MAPS, diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 73374d491c4..0edb8a6315e 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -13,9 +13,9 @@ package org.openhab.binding.airparif.internal.api; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; @@ -54,7 +54,7 @@ public class AirParifDto { } public record KeyInfo(// - ZonedDateTime expiration, // + Instant expiration, // @SerializedName("droits") Set scopes) { } @@ -135,33 +135,33 @@ public class AirParifDto { private static ZoneId DEFAULT_ZONE = ZoneId.of("Europe/Paris"); public List data = List.of(); - private @Nullable ZonedDateTime beginValidity; - private @Nullable ZonedDateTime endValidity; + private @Nullable Instant beginValidity; + private @Nullable Instant endValidity; public Optional getData() { return Optional.ofNullable(data.isEmpty() ? null : data.get(0)); } - private Set getValidities() { - Set validities = new TreeSet<>(); + private Set getValidities() { + Set validities = new TreeSet<>(); getData().ifPresent(pollens -> { Matcher matcher = PATTERN.matcher(pollens.periode); 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; } - public Optional getBeginValidity() { + public Optional getBeginValidity() { if (beginValidity == null) { beginValidity = getValidities().iterator().next(); } return Optional.ofNullable(beginValidity); } - public Optional getEndValidity() { + public Optional getEndValidity() { if (endValidity == null) { endValidity = getValidities().stream().reduce((prev, next) -> next).orElse(null); } @@ -170,7 +170,7 @@ public class AirParifDto { public Duration getValidityDuration() { 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; }).orElse(Duration.ZERO)); } @@ -197,7 +197,7 @@ public class AirParifDto { public record Concentration(// @SerializedName("polluant") Pollutant pollutant, // - ZonedDateTime date, // + Instant date, // @SerializedName("valeurs") double[] values, // @Nullable Message message) { @@ -224,7 +224,7 @@ public class AirParifDto { } public record Route(// - @SerializedName("dateRequise") ZonedDateTime requestedDate, // + @SerializedName("dateRequise") Instant requestedDate, // double[][] longlats, // @SerializedName("resultats") List concentrations, // @Nullable Message[] messages) { diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java index edafa433730..90d0199655a 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -12,18 +12,16 @@ */ package org.openhab.binding.airparif.internal.deserialization; +import java.time.Instant; import java.time.LocalDate; -import java.time.ZonedDateTime; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.AirParifException; import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; 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.Component; -import org.osgi.service.component.annotations.Reference; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; @@ -42,7 +40,7 @@ public class AirParifDeserializer { private final Gson gson; @Activate - public AirParifDeserializer(final @Reference TimeZoneProvider timeZoneProvider) { + public AirParifDeserializer() { gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) .registerTypeAdapter(PollenAlertLevel.class, new PollenAlertLevelDeserializer()) .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()) @@ -50,10 +48,10 @@ public class AirParifDeserializer { .registerTypeAdapter(LocalDate.class, (JsonDeserializer) (json, type, context) -> LocalDate .parse(json.getAsJsonPrimitive().getAsString())) - .registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (json, type, context) -> { + .registerTypeAdapter(Instant.class, (JsonDeserializer) (json, type, context) -> { String string = json.getAsJsonPrimitive().getAsString(); string += string.contains("+") ? "" : "Z"; - return ZonedDateTime.parse(string).withZoneSameInstant(timeZoneProvider.getTimeZone()); + return Instant.parse(string); }).create(); } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java index d66045b6d77..656417fea64 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java @@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.airparif.internal.api.AirParifDto.PollutantConcentration; 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.JsonDeserializationContext; @@ -32,6 +34,7 @@ import com.google.gson.JsonSyntaxException; */ @NonNullByDefault class PollutantConcentrationDeserializer implements JsonDeserializer { + private final Logger logger = LoggerFactory.getLogger(PollutantConcentrationDeserializer.class); @Override public @Nullable PollutantConcentration deserialize(JsonElement json, Type clazz, @@ -44,7 +47,7 @@ class PollutantConcentrationDeserializer implements JsonDeserializer Will not provide any discovery results"); + logger.warn("LocationProvider.getLocation() is not set -> Will not provide any discovery results"); return; } @@ -82,8 +82,6 @@ public class AirParifDiscoveryService extends AbstractThingHandlerDiscoveryServi .withProperty(LocationConfiguration.LOCATION, serverLocation.toFullString())// .withRepresentationProperty(LocationConfiguration.DEPARTMENT) // .withBridge(bridgeUID).build())); - } else { - logger.info("No department could be discovered matching server location"); } } } diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index 8784ce3147c..64683f85798 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -117,6 +117,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU "@text/offline.config-error-unknown-apikey"); return; } + updateStatus(ThingStatus.UNKNOWN); scheduler.execute(this::initiateConnexion); } @@ -196,7 +197,7 @@ public class AirParifBridgeHandler extends BaseBridgeHandler implements HandlerU thing.setProperty("api-version", version.version()); thing.setProperty("key-expiration", keyInfo.expiration().toString()); 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); 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"); - schedule(AQ_JOB, () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), - Duration.between(ZonedDateTime.now(), tomorrowMorning)); + schedule(AQ_JOB, () -> updateDailyAQBulletin(todayGroupUID, tomorrowGroupUID), untilTomorrowMorning()); } 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_TOMORROW), new StringType(episode.message().fr())); - ZonedDateTime tomorrowMorning = ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1); - schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), - Duration.between(ZonedDateTime.now(), tomorrowMorning)); + schedule(EPISODE_JOB, () -> updateEpisode(dailyGroupUID), untilTomorrowMorning()); + } + + private Duration untilTomorrowMorning() { + return Duration.between(ZonedDateTime.now(), + ZonedDateTime.now().plusDays(1).truncatedTo(ChronoUnit.DAYS).plusMinutes(1)); } public @Nullable Route getConcentrations(String location) { diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java index df2aec547da..4b3528f8102 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java @@ -62,12 +62,12 @@ public interface HandlerUtils { default void schedule(String jobName, Runnable job, Duration duration) { ScheduledFuture result = getJobs().remove(jobName); - String operation = "Scheduling"; + + getLogger().debug("{} {} in {}", result != null ? "Rescheduled" : "Scheduling", jobName, duration); if (result != null) { - operation = "Rescheduled"; cancelFuture(result); } - getLogger().info("{} {} in {}", operation, jobName, duration); + getJobs().put(jobName, getScheduler().schedule(job, duration.getSeconds(), TimeUnit.SECONDS)); } diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties index c6250fc92f7..ef0d892ad5f 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/i18n/airparif.properties @@ -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.location.label = Department Report 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.o3.label = Ozone Concentration Information -thing-type.airparif.location.group.pm10.label = PM10 Concentration Information -thing-type.airparif.location.group.pm25.label = PM2.5 Concentration Information +thing-type.airparif.location.group.no2.label = NO2 Concentration +thing-type.airparif.location.group.o3.label = Ozone Concentration +thing-type.airparif.location.group.pm10.label = PM10 Concentration +thing-type.airparif.location.group.pm25.label = PM2.5 Concentration # 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.end-validity.label = End Validity 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.description = Today's daily general information 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.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.channel.alert.label = Alert Level channel-group-type.airparif.pollutant-mpc.channel.alert.description = Alert Level associated to pollutant concentration diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml index aff93e79e02..fb2bf70230c 100644 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/channel-groups.xml @@ -101,7 +101,7 @@ - + @@ -115,7 +115,7 @@ - + diff --git a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml index c9a814c19d7..aab4dd83b7f 100755 --- a/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml +++ b/bundles/org.openhab.binding.airparif/src/main/resources/OH-INF/thing/location.xml @@ -16,16 +16,16 @@ - + - + - + - + From 3dc97e9685237d79e6438089fef3a2e9b30d401c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 16 Dec 2024 10:56:34 +0100 Subject: [PATCH 15/17] POM update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël L'hopital --- bundles/org.openhab.binding.airparif/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.airparif/pom.xml b/bundles/org.openhab.binding.airparif/pom.xml index c38d9fba93e..355042dd5f6 100755 --- a/bundles/org.openhab.binding.airparif/pom.xml +++ b/bundles/org.openhab.binding.airparif/pom.xml @@ -7,7 +7,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT org.openhab.binding.airparif From da4c636841f672358328adc42554b6cf2f0b788c Mon Sep 17 00:00:00 2001 From: clinique Date: Thu, 9 Jan 2025 15:52:23 +0100 Subject: [PATCH 16/17] Fix headers Signed-off-by: clinique --- .../binding/airparif/internal/AirParifBindingConstants.java | 2 +- .../openhab/binding/airparif/internal/AirParifException.java | 2 +- .../binding/airparif/internal/AirParifHandlerFactory.java | 2 +- .../openhab/binding/airparif/internal/AirParifIconProvider.java | 2 +- .../org/openhab/binding/airparif/internal/api/AirParifApi.java | 2 +- .../org/openhab/binding/airparif/internal/api/AirParifDto.java | 2 +- .../openhab/binding/airparif/internal/api/PollenAlertLevel.java | 2 +- .../org/openhab/binding/airparif/internal/api/Pollutant.java | 2 +- .../binding/airparif/internal/config/BridgeConfiguration.java | 2 +- .../binding/airparif/internal/config/LocationConfiguration.java | 2 +- .../binding/airparif/internal/db/DepartmentDbService.java | 2 +- .../airparif/internal/deserialization/AirParifDeserializer.java | 2 +- .../internal/deserialization/PollenAlertLevelDeserializer.java | 2 +- .../deserialization/PollutantConcentrationDeserializer.java | 2 +- .../internal/deserialization/StrictEnumTypeAdapterFactory.java | 2 +- .../airparif/internal/discovery/AirParifDiscoveryService.java | 2 +- .../airparif/internal/handler/AirParifBridgeHandler.java | 2 +- .../openhab/binding/airparif/internal/handler/HandlerUtils.java | 2 +- .../binding/airparif/internal/handler/LocationHandler.java | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java index 8b6e19c9fd1..b8dcec288f9 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java index 109a3555059..c934809f3cc 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java index 76805b24c17..736dbdd52d6 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index fd6bd5337b1..d7b626c95ee 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java index 94ed8c3ed6f..0e84d33697a 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 0edb8a6315e..3ec6c238b5e 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java index 82fe12ae565..6b81e2619f3 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java index bf21998970b..00776c0418b 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java index 710581478df..c7b7ca7bf0b 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java index 3c7f8242997..5eecff41b77 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java index 851ef94c040..67a3875abbf 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java index 90d0199655a..ffb3a9e0678 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java index 42bcfbb6bc5..8906a6393e5 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java index 656417fea64..6274228f297 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java index a68f243895e..fb4cff9785f 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java index 16d9b53cfb0..61ff52c0a27 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index 64683f85798..8b6661c8cf6 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java index 4b3528f8102..fcb307df098 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index 3fc7af83db7..81c4c2fc834 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional From 8e279a001f8af9878d69f22dbb2d5ca5289d379c Mon Sep 17 00:00:00 2001 From: clinique Date: Thu, 9 Jan 2025 16:23:19 +0100 Subject: [PATCH 17/17] Forgot to change the year Signed-off-by: clinique --- .../binding/airparif/internal/AirParifBindingConstants.java | 2 +- .../openhab/binding/airparif/internal/AirParifException.java | 2 +- .../binding/airparif/internal/AirParifHandlerFactory.java | 2 +- .../openhab/binding/airparif/internal/AirParifIconProvider.java | 2 +- .../org/openhab/binding/airparif/internal/api/AirParifApi.java | 2 +- .../org/openhab/binding/airparif/internal/api/AirParifDto.java | 2 +- .../openhab/binding/airparif/internal/api/PollenAlertLevel.java | 2 +- .../org/openhab/binding/airparif/internal/api/Pollutant.java | 2 +- .../binding/airparif/internal/config/BridgeConfiguration.java | 2 +- .../binding/airparif/internal/config/LocationConfiguration.java | 2 +- .../binding/airparif/internal/db/DepartmentDbService.java | 2 +- .../airparif/internal/deserialization/AirParifDeserializer.java | 2 +- .../internal/deserialization/PollenAlertLevelDeserializer.java | 2 +- .../deserialization/PollutantConcentrationDeserializer.java | 2 +- .../internal/deserialization/StrictEnumTypeAdapterFactory.java | 2 +- .../airparif/internal/discovery/AirParifDiscoveryService.java | 2 +- .../airparif/internal/handler/AirParifBridgeHandler.java | 2 +- .../openhab/binding/airparif/internal/handler/HandlerUtils.java | 2 +- .../binding/airparif/internal/handler/LocationHandler.java | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java index b8dcec288f9..decda6e9c28 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifBindingConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java index c934809f3cc..68f4ea25595 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java index 736dbdd52d6..3403b9fe6ce 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifHandlerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java index d7b626c95ee..38180df2d0d 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/AirParifIconProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java index 0e84d33697a..1349f604af7 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java index 3ec6c238b5e..141325de531 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/AirParifDto.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java index 6b81e2619f3..2ca46e4ac2c 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/PollenAlertLevel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java index 00776c0418b..ab9f0cac84e 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/api/Pollutant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java index c7b7ca7bf0b..2e5d04a1928 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/BridgeConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java index 5eecff41b77..9fae95ff7a1 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/config/LocationConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java index 67a3875abbf..11cec499091 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/db/DepartmentDbService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java index ffb3a9e0678..1623de3f3b3 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/AirParifDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java index 8906a6393e5..5ba327b548e 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollenAlertLevelDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java index 6274228f297..ced52560e79 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/PollutantConcentrationDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java index fb4cff9785f..7dbf563f147 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/deserialization/StrictEnumTypeAdapterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java index 61ff52c0a27..f33170003fd 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/discovery/AirParifDiscoveryService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java index 8b6661c8cf6..cc7ae374e9e 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/AirParifBridgeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java index fcb307df098..f018bf019fc 100644 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/HandlerUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java index 81c4c2fc834..7e6fcc1ae05 100755 --- a/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java +++ b/bundles/org.openhab.binding.airparif/src/main/java/org/openhab/binding/airparif/internal/handler/LocationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2024 Contributors to the openHAB project + * Copyright (c) 2010-2025 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information.