> getJobs() {
+ return jobs;
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("This thing does not handle commands");
+ }
+
+ private > @Nullable P getFeatureProperties(URI uri,
+ Class clazz) {
+ try {
+ T response = executeUri(uri, clazz);
+ return response.getProperties();
+ } catch (AtmoFranceException e) {
+ ThingStatusDetail detail = e.getStatusDetail();
+ if (detail == null) {
+ detail = ThingStatusDetail.COMMUNICATION_ERROR;
+ }
+ updateStatus(ThingStatus.OFFLINE, detail, e.getMessage());
+ }
+ return null;
+ }
+
+ public @Nullable IndexProperties getAtmoIndex(String codeInsee) {
+ URI atmoUri = AtmoFranceApi.getAtmoUri(LocalDate.now(AtmoFranceDto.DEFAULT_TZ), codeInsee);
+ return getFeatureProperties(atmoUri, AtmoResponse.class);
+ }
+
+ public @Nullable PollensProperties getPollens(String codeInsee) {
+ URI pollensUri = AtmoFranceApi.getPollensUri(LocalDate.now(AtmoFranceDto.DEFAULT_TZ), codeInsee);
+ return getFeatureProperties(pollensUri, PollensResponse.class);
+ }
+}
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/CityHandler.java b/bundles/org.openhab.binding.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/CityHandler.java
new file mode 100755
index 0000000000..b699d2cd14
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/CityHandler.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2010-2026 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.atmofrance.internal.handler;
+
+import static org.openhab.binding.atmofrance.internal.AtmoFranceBindingConstants.*;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.atmofrance.internal.api.dto.AtmoFranceDto.BaseProperties;
+import org.openhab.binding.atmofrance.internal.api.dto.AtmoFranceDto.IndexProperties;
+import org.openhab.binding.atmofrance.internal.api.dto.AtmoFranceDto.PollensProperties;
+import org.openhab.binding.atmofrance.internal.api.dto.Pollutant;
+import org.openhab.binding.atmofrance.internal.api.dto.Taxon;
+import org.openhab.binding.atmofrance.internal.configuration.CityConfiguration;
+import org.openhab.binding.atmofrance.internal.configuration.ConfigurationLevel;
+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.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.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link CityHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class CityHandler extends BaseThingHandler implements HandlerUtils {
+ private static final String AQ_JOB = "%s Air Quality";
+ private static final String POLLENS_JOB = "%s Pollens";
+
+ private final Logger logger = LoggerFactory.getLogger(CityHandler.class);
+ private final Map> jobs = new ConcurrentHashMap<>();
+
+ private @Nullable CityConfiguration config;
+
+ public CityHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ var localConfig = getConfigAs(CityConfiguration.class);
+ ConfigurationLevel configLevel = localConfig.check();
+
+ if (configLevel != ConfigurationLevel.COMPLETED) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configLevel.message);
+ return;
+ }
+
+ config = localConfig;
+
+ updateStatus(ThingStatus.UNKNOWN);
+ schedule(AQ_JOB.formatted(thing.getUID()), this::getAtmoIndex, Duration.ofSeconds(2));
+ schedule(POLLENS_JOB.formatted(thing.getUID()), this::getPollens, Duration.ofSeconds(3));
+ }
+
+ @Override
+ public void dispose() {
+ logger.debug("Disposing the Atmo France city handler");
+ cleanJobs();
+ }
+
+ private void getAtmoIndex() {
+ AtmoFranceApiHandler apiHandler = getBridgeHandler(AtmoFranceApiHandler.class);
+ CityConfiguration local = config;
+ long delay = 3600;
+ if (apiHandler != null && local != null) {
+ IndexProperties properties = apiHandler.getAtmoIndex(local.codeInsee);
+ if (properties != null) {
+ updateState(GROUP_AQ, CHANNEL_INDEX, properties.codeQual.value);
+ Pollutant.AS_SET.forEach(pollutant -> {
+ String channelName = pollutant.name().toLowerCase(Locale.ROOT) + "-index";
+ updateState(GROUP_AQ, channelName, properties.getPollutantIndex(pollutant));
+ });
+ updateCommon(properties, GROUP_AQ);
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ if (getBridgeHandler(AtmoFranceApiHandler.class) != null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-data");
+ } else {
+ // the call to getBridgeHandler will already have put me on OFFLINE/BRIDGE_OFFLINE
+ return;
+ }
+ }
+ } else {
+ delay = 10;
+ }
+ schedule(AQ_JOB.formatted(thing.getUID()), this::getAtmoIndex, Duration.ofSeconds(delay));
+ }
+
+ private void updateCommon(BaseProperties properties, String group) {
+ updateState(group, CHANNEL_EFFECTIVE_DATE, properties.getEffectiveDate());
+ updateState(group, CHANNEL_RELEASE_DATE, properties.getDiffusionDate());
+
+ updateProperty("%s-source".formatted(group), properties.source);
+ }
+
+ private void getPollens() {
+ @Nullable
+ AtmoFranceApiHandler apiHandler = getBridgeHandler(AtmoFranceApiHandler.class);
+ @Nullable
+ CityConfiguration local = config;
+ long delay = 3600;
+ if (apiHandler != null && local != null) {
+ PollensProperties properties = apiHandler.getPollens(local.codeInsee);
+ if (properties != null) {
+ updateState(GROUP_POLLENS, CHANNEL_INDEX, properties.getGlobal());
+ Taxon.AS_SET.forEach(taxon -> {
+ String channelName = taxon.name().toLowerCase(Locale.ROOT) + "-";
+ updateState(GROUP_POLLENS, channelName + "level", properties.getTaxonIndex(taxon));
+ updateState(GROUP_POLLENS, channelName + "conc", properties.getTaxonConc(taxon));
+ });
+ updateCommon(properties, GROUP_POLLENS);
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ if (getBridgeHandler(AtmoFranceApiHandler.class) != null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-pollens");
+ } else {
+ // the call to getBridgeHandler will already have put me on OFFLINE/BRIDGE_OFFLINE
+ return;
+ }
+ }
+ } else {
+ delay = 10;
+ }
+ schedule(POLLENS_JOB.formatted(thing.getUID()), this::getPollens, Duration.ofSeconds(delay));
+ }
+
+ private void updateState(String group, String channel, int value) {
+ updateState(group, channel, new DecimalType(value));
+ }
+
+ private void updateState(String group, String channel, Instant date) {
+ updateState(group, channel, new DateTimeType(date));
+ }
+
+ private void updateState(String group, String channel, State state) {
+ ChannelUID channelUID = new ChannelUID(getThing().getUID(), group, channel);
+ if (isLinked(channelUID)) {
+ updateState(channelUID, state);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("This thing does not handle commands");
+ }
+
+ @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.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/HandlerUtils.java b/bundles/org.openhab.binding.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/HandlerUtils.java
new file mode 100644
index 0000000000..3d659360a5
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/java/org/openhab/binding/atmofrance/internal/handler/HandlerUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2010-2026 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.atmofrance.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 {
+ /**
+ * Cancels the given scheduled future if it exists and is not already cancelled.
+ *
+ * @param job the scheduled future to cancel
+ */
+ default void cancelFuture(ScheduledFuture> job) {
+ if (!job.isCancelled()) {
+ job.cancel(true);
+ }
+ }
+
+ /**
+ * Retrieves the bridge handler of the specified class type if the bridge is online and correctly configured.
+ *
+ * @param the type of the bridge handler
+ * @param clazz the class of the bridge handler to retrieve
+ * @return the bridge handler instance, or null if not available or incorrect type
+ */
+ default @Nullable T getBridgeHandler(Class clazz) {
+ Bridge bridge = getBridge();
+ if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
+ if (bridge.getHandler() instanceof BridgeHandler bridgeHandler) {
+ if (bridgeHandler.getClass() == clazz) {
+ return clazz.cast(bridgeHandler);
+ }
+ }
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge");
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "");
+ }
+ return null;
+ }
+
+ /**
+ * Schedules a job to run after the specified duration, cancelling any existing job with the same name.
+ *
+ * @param jobName the name of the job for tracking
+ * @param job the runnable job to execute
+ * @param duration the duration to wait before executing the job
+ */
+ default void schedule(String jobName, Runnable job, Duration duration) {
+ ScheduledFuture> result = getJobs().remove(jobName);
+
+ getLogger().debug("{} {} in {}", result != null ? "Rescheduled" : "Scheduling", jobName, duration);
+ if (result != null) {
+ cancelFuture(result);
+ }
+
+ getJobs().put(jobName, getScheduler().schedule(job, duration.getSeconds(), TimeUnit.SECONDS));
+ }
+
+ /**
+ * Cancels all scheduled jobs and clears the jobs map.
+ */
+ default void cleanJobs() {
+ getJobs().values().forEach(this::cancelFuture);
+ 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.atmofrance/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 0000000000..897eceab40
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,12 @@
+
+
+
+ binding
+ AtmoFrance Binding
+ Air Quality Monitoring data provided by AtmoFrance.
+ cloud
+ fr
+
+
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/i18n/atmofrance.properties b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/i18n/atmofrance.properties
new file mode 100644
index 0000000000..44f123a3b0
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/i18n/atmofrance.properties
@@ -0,0 +1,160 @@
+# add-on
+
+addon.atmofrance.name = AtmoFrance Binding
+addon.atmofrance.description = Air Quality Monitoring data provided by AtmoFrance.
+
+# thing types
+
+thing-type.atmofrance.api.label = Atmo Data API Portal
+thing-type.atmofrance.api.description = Bridge to the Atmo Data API Portal. In order to receive the data, you must register an account at https://admindata.atmo-france.org/inscription-api and receive your account credentials.
+thing-type.atmofrance.city.label = City Report
+thing-type.atmofrance.city.description = Atmo France air quality report for the given city
+
+# thing types config
+
+thing-type.config.atmofrance.api.password.label = Password
+thing-type.config.atmofrance.api.password.description = Password from Atmo Data API Account
+thing-type.config.atmofrance.api.username.label = Username
+thing-type.config.atmofrance.api.username.description = Username from Atmo Data API Account
+thing-type.config.atmofrance.city.codeInsee.label = Insee Code
+
+# channel group types
+
+channel-group-type.atmofrance.aq.label = Air Quality Information
+channel-group-type.atmofrance.aq.channel.release-date.label = Release date
+channel-group-type.atmofrance.aq.channel.effective-date.label = Effective date
+channel-group-type.atmofrance.aq.channel.no2-index.label = NO₂
+channel-group-type.atmofrance.aq.channel.no2-index.description = Nitrogen dioxide
+channel-group-type.atmofrance.aq.channel.o3-index.label = O₃
+channel-group-type.atmofrance.aq.channel.o3-index.description = Ozone
+channel-group-type.atmofrance.aq.channel.pm10-index.label = PM 10
+channel-group-type.atmofrance.aq.channel.pm10-index.description = Particulate matter < 10 µm
+channel-group-type.atmofrance.aq.channel.pm25-index.label = PM 2.5
+channel-group-type.atmofrance.aq.channel.pm25-index.description = Particulate matter < 2,5 µm
+channel-group-type.atmofrance.aq.channel.so2-index.label = SO₂
+channel-group-type.atmofrance.aq.channel.so2-index.description = Sulfur dioxide
+channel-group-type.atmofrance.pollens.label = Pollen Information
+channel-group-type.atmofrance.pollens.channel.alder-conc.label = Alder concentration
+channel-group-type.atmofrance.pollens.channel.alder-level.label = Alder index
+channel-group-type.atmofrance.pollens.channel.birch-conc.label = Birch concentration
+channel-group-type.atmofrance.pollens.channel.birch-level.label = Birch index
+channel-group-type.atmofrance.pollens.channel.release-date.label = Publication date
+channel-group-type.atmofrance.pollens.channel.effective-date.label = Effective date
+channel-group-type.atmofrance.pollens.channel.grasses-conc.label = Grasses concentration
+channel-group-type.atmofrance.pollens.channel.grasses-level.label = Grasses index
+channel-group-type.atmofrance.pollens.channel.olive-conc.label = Olive concentration
+channel-group-type.atmofrance.pollens.channel.olive-level.label = Olive index
+channel-group-type.atmofrance.pollens.channel.ragweed-conc.label = Ragweed concentration
+channel-group-type.atmofrance.pollens.channel.ragweed-level.label = Ragweed index
+channel-group-type.atmofrance.pollens.channel.wormwood-conc.label = Wormwood concentration
+channel-group-type.atmofrance.pollens.channel.wormwood-level.label = Wormwood index
+
+# channel types
+
+channel-type.atmofrance.alder-level.label = Alder
+channel-type.atmofrance.alder-level.state.option.0 = Very Low
+channel-type.atmofrance.alder-level.state.option.1 = Low
+channel-type.atmofrance.alder-level.state.option.2 = Moderate
+channel-type.atmofrance.alder-level.state.option.3 = High
+channel-type.atmofrance.alder-level.state.option.4 = Very High
+channel-type.atmofrance.alder-level.state.option.5 = Extremely High
+channel-type.atmofrance.alder-level.state.option.9 = Absent
+channel-type.atmofrance.birch-level.label = Birch Level
+channel-type.atmofrance.birch-level.state.option.0 = Very Low
+channel-type.atmofrance.birch-level.state.option.1 = Low
+channel-type.atmofrance.birch-level.state.option.2 = Moderate
+channel-type.atmofrance.birch-level.state.option.3 = High
+channel-type.atmofrance.birch-level.state.option.4 = Very High
+channel-type.atmofrance.birch-level.state.option.5 = Extremely High
+channel-type.atmofrance.birch-level.state.option.9 = Absent
+channel-type.atmofrance.concentration.label = Measure
+channel-type.atmofrance.grasses-level.label = Grasses
+channel-type.atmofrance.grasses-level.state.option.0 = Very Low
+channel-type.atmofrance.grasses-level.state.option.1 = Low
+channel-type.atmofrance.grasses-level.state.option.2 = Moderate
+channel-type.atmofrance.grasses-level.state.option.3 = High
+channel-type.atmofrance.grasses-level.state.option.4 = Very High
+channel-type.atmofrance.grasses-level.state.option.5 = Extremely High
+channel-type.atmofrance.grasses-level.state.option.9 = Absent
+channel-type.atmofrance.index-adv.label = Air Quality
+channel-type.atmofrance.index-adv.state.option.0 = Good
+channel-type.atmofrance.index-adv.state.option.1 = Average
+channel-type.atmofrance.index-adv.state.option.2 = Degraded
+channel-type.atmofrance.index-adv.state.option.3 = Bad
+channel-type.atmofrance.index-adv.state.option.4 = Very Bad
+channel-type.atmofrance.index-adv.state.option.5 = Extremely Bad
+channel-type.atmofrance.index-adv.state.option.7 = Event
+channel-type.atmofrance.index-adv.state.option.9 = Absent
+channel-type.atmofrance.index.label = Air Quality
+channel-type.atmofrance.index.state.option.0 = Good
+channel-type.atmofrance.index.state.option.1 = Average
+channel-type.atmofrance.index.state.option.2 = Degraded
+channel-type.atmofrance.index.state.option.3 = Bad
+channel-type.atmofrance.index.state.option.4 = Very Bad
+channel-type.atmofrance.index.state.option.5 = Extremely Bad
+channel-type.atmofrance.index.state.option.7 = Event
+channel-type.atmofrance.index.state.option.9 = Absent
+channel-type.atmofrance.localdate.label = Timestamp
+channel-type.atmofrance.localdate.state.pattern = %1$td/%1$tm
+channel-type.atmofrance.olive-level.label = Olive
+channel-type.atmofrance.olive-level.state.option.0 = Very Low
+channel-type.atmofrance.olive-level.state.option.1 = Low
+channel-type.atmofrance.olive-level.state.option.2 = Moderate
+channel-type.atmofrance.olive-level.state.option.3 = High
+channel-type.atmofrance.olive-level.state.option.4 = Very High
+channel-type.atmofrance.olive-level.state.option.5 = Extremely High
+channel-type.atmofrance.olive-level.state.option.9 = Absent
+channel-type.atmofrance.pollen-index.label = Pollen Index
+channel-type.atmofrance.pollen-index.state.option.0 = Very Low
+channel-type.atmofrance.pollen-index.state.option.1 = Low
+channel-type.atmofrance.pollen-index.state.option.2 = Moderate
+channel-type.atmofrance.pollen-index.state.option.3 = High
+channel-type.atmofrance.pollen-index.state.option.4 = Very High
+channel-type.atmofrance.pollen-index.state.option.5 = Extremely High
+channel-type.atmofrance.pollen-index.state.option.9 = Absent
+channel-type.atmofrance.ragweed-level.label = Ragweed
+channel-type.atmofrance.ragweed-level.state.option.0 = Very Low
+channel-type.atmofrance.ragweed-level.state.option.1 = Low
+channel-type.atmofrance.ragweed-level.state.option.2 = Moderate
+channel-type.atmofrance.ragweed-level.state.option.3 = High
+channel-type.atmofrance.ragweed-level.state.option.4 = Very High
+channel-type.atmofrance.ragweed-level.state.option.5 = Extremely High
+channel-type.atmofrance.ragweed-level.state.option.9 = Absent
+channel-type.atmofrance.wormwood-level.label = Wormwood
+channel-type.atmofrance.wormwood-level.state.option.0 = Very Low
+channel-type.atmofrance.wormwood-level.state.option.1 = Low
+channel-type.atmofrance.wormwood-level.state.option.2 = Moderate
+channel-type.atmofrance.wormwood-level.state.option.3 = High
+channel-type.atmofrance.wormwood-level.state.option.4 = Very High
+channel-type.atmofrance.wormwood-level.state.option.5 = Extremely High
+channel-type.atmofrance.wormwood-level.state.option.9 = Absent
+
+# channel group types
+
+channel-group-type.atmofrance.pollens.channel.alder-conc.description = Alder concentration
+channel-group-type.atmofrance.pollens.channel.alder-level.description = Alder index
+channel-group-type.atmofrance.pollens.channel.birch-conc.description = Birch concentration
+channel-group-type.atmofrance.pollens.channel.birch-level.description = Birch index
+channel-group-type.atmofrance.pollens.channel.grasses-conc.description = Grasses concentration
+channel-group-type.atmofrance.pollens.channel.grasses-level.description = Grasses index
+channel-group-type.atmofrance.pollens.channel.olive-conc.description = Olive concentration
+channel-group-type.atmofrance.pollens.channel.olive-level.description = Olive index
+channel-group-type.atmofrance.pollens.channel.ragweed-conc.description = Ragweed concentration
+channel-group-type.atmofrance.pollens.channel.ragweed-level.description = Ragweed index
+channel-group-type.atmofrance.pollens.channel.wormwood-conc.description = Wormwood concentration
+channel-group-type.atmofrance.pollens.channel.wormwood-level.description = Wormwood index
+
+# iconprovider
+
+iconset.label = AtmoFrance Icons
+iconset.description = Icons illustrating air quality measures provided by Atmo France
+
+# error messages
+
+conf-error-no-username = A username must be provided
+conf-error-no-password = A password must be provided
+conf-error-no-insee = The INSEE code of the city must be provided
+com-error-unauthorized = Communication error: {0}
+incorrect-bridge = Wrong bridge type
+no-data = No Atmo data provided
+no-pollens = No Pollens data provided
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/api.xml b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/api.xml
new file mode 100755
index 0000000000..55dd1381c0
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/api.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Bridge to the Atmo Data API Portal. In order to receive the data, you must register an account at
+ https://admindata.atmo-france.org/inscription-api and receive your account credentials.
+
+ WebService
+
+
+
+
+ Username from Atmo Data API Account
+
+
+
+ Password from Atmo Data API Account
+ password
+
+
+
+
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channel-groups.xml b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channel-groups.xml
new file mode 100644
index 0000000000..4e0d535d6d
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channel-groups.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+ Nitrogen dioxide
+
+
+
+ Sulfur dioxide
+
+
+
+ Ozone
+
+
+
+ Particulate matter < 10 µm
+
+
+
+ Particulate matter < 2,5 µm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channels.xml
new file mode 100755
index 0000000000..da5d0bf011
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/channels.xml
@@ -0,0 +1,220 @@
+
+
+
+
+ DateTime
+
+ time
+
+ Status
+ Timestamp
+
+
+
+
+
+ Number:Density
+
+
+ Measurement
+ AirQuality
+
+
+
+
+
+ Number
+
+ oh:atmofrance:aq
+
+ Measurement
+ AirQuality
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:pollen
+
+ Measurement
+ AirQuality
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:aq
+
+ Measurement
+ AirQuality
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:alder
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:birch
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:olive
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:grasses
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:wormwood
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ oh:atmofrance:ragweed
+
+ Measurement
+ Level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/city.xml b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/city.xml
new file mode 100755
index 0000000000..3752ce68c5
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/OH-INF/thing/city.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ Atmo France air quality report for the given city
+ WebService
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/alder.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/alder.svg
new file mode 100755
index 0000000000..30cc5e33a0
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/alder.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-0.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-0.svg
new file mode 100755
index 0000000000..f64c4e1486
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-0.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-1.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-1.svg
new file mode 100755
index 0000000000..2ec54bd4a7
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-1.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-2.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-2.svg
new file mode 100755
index 0000000000..9c275cd0e3
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-2.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-3.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-3.svg
new file mode 100755
index 0000000000..cea0c40423
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-3.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-4.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-4.svg
new file mode 100755
index 0000000000..33541b393d
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-4.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-5.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-5.svg
new file mode 100755
index 0000000000..653f93afe5
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-5.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-7.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-7.svg
new file mode 100644
index 0000000000..08588dda33
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-7.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-9.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-9.svg
new file mode 100644
index 0000000000..df6cc178c8
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq-9.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq.svg
new file mode 100755
index 0000000000..8fc376eb4c
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/aq.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ash.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ash.svg
new file mode 100755
index 0000000000..618f3c0b11
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ash.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/birch.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/birch.svg
new file mode 100755
index 0000000000..54604c9793
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/birch.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/chestnut.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/chestnut.svg
new file mode 100755
index 0000000000..57f5188ffc
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/chestnut.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/cypress.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/cypress.svg
new file mode 100755
index 0000000000..5311089b19
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/cypress.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/grasses.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/grasses.svg
new file mode 100755
index 0000000000..74ddca07b0
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/grasses.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hazel.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hazel.svg
new file mode 100755
index 0000000000..acfba4a13e
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hazel.svg
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hornbeam.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hornbeam.svg
new file mode 100755
index 0000000000..501877ee77
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/hornbeam.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/linden.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/linden.svg
new file mode 100755
index 0000000000..7dc5db9eab
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/linden.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/oak.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/oak.svg
new file mode 100755
index 0000000000..a7ee6cfd42
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/oak.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/olive.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/olive.svg
new file mode 100755
index 0000000000..820ef168ce
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/olive.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plane.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plane.svg
new file mode 100755
index 0000000000..ef1bddb7e9
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plane.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plantain.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plantain.svg
new file mode 100755
index 0000000000..8907a5f429
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/plantain.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-0.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-0.svg
new file mode 100644
index 0000000000..2ccad4ff20
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-0.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-1.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-1.svg
new file mode 100644
index 0000000000..17c4bd8248
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-1.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-2.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-2.svg
new file mode 100644
index 0000000000..ecde6941f8
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-2.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-3.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-3.svg
new file mode 100644
index 0000000000..38ac475918
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-3.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-4.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-4.svg
new file mode 100644
index 0000000000..693058a527
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-4.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-5.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-5.svg
new file mode 100644
index 0000000000..7168a49cfc
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen-5.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen.svg
new file mode 100644
index 0000000000..f03f3420d2
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/pollen.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/poplar.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/poplar.svg
new file mode 100755
index 0000000000..d582efa92d
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/poplar.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ragweed.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ragweed.svg
new file mode 100755
index 0000000000..9da22d6825
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/ragweed.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/rumex.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/rumex.svg
new file mode 100755
index 0000000000..73194f0e80
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/rumex.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/urticaceae.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/urticaceae.svg
new file mode 100755
index 0000000000..c1b8b69052
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/urticaceae.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/willow.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/willow.svg
new file mode 100755
index 0000000000..52e6631136
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/willow.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/wormwood.svg b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/wormwood.svg
new file mode 100755
index 0000000000..c8c8c90f6d
--- /dev/null
+++ b/bundles/org.openhab.binding.atmofrance/src/main/resources/icon/wormwood.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 0aa434dafc..d6a4208208 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -69,6 +69,7 @@
org.openhab.binding.astro
org.openhab.binding.asuswrt
org.openhab.binding.atlona
+ org.openhab.binding.atmofrance
org.openhab.binding.autelis
org.openhab.binding.automower
org.openhab.binding.avmfritz