2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dwdpollenflug.internal.handler;
15 import java.net.SocketTimeoutException;
18 import java.util.concurrent.CompletableFuture;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.HttpResponse;
28 import org.eclipse.jetty.client.api.Request;
29 import org.eclipse.jetty.client.api.Result;
30 import org.eclipse.jetty.client.util.BufferingResponseListener;
31 import org.eclipse.jetty.http.HttpMethod;
32 import org.openhab.binding.dwdpollenflug.internal.DWDPollingException;
33 import org.openhab.binding.dwdpollenflug.internal.config.DWDPollenflugBridgeConfiguration;
34 import org.openhab.binding.dwdpollenflug.internal.dto.DWDPollenflug;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseBridgeHandler;
41 import org.openhab.core.thing.binding.ThingHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 import com.google.gson.Gson;
48 import com.google.gson.JsonSyntaxException;
51 * The {@link DWDPollenflugBridgeHandler} is the handler for bridge thing
53 * @author Johannes Ott - Initial contribution
56 public class DWDPollenflugBridgeHandler extends BaseBridgeHandler {
57 private static final String DWD_URL = "https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json";
59 private final Logger logger = LoggerFactory.getLogger(DWDPollenflugBridgeHandler.class);
61 private DWDPollenflugBridgeConfiguration bridgeConfig = new DWDPollenflugBridgeConfiguration();
62 private @Nullable ScheduledFuture<?> pollingJob;
63 private @Nullable DWDPollenflug pollenflug;
64 private final Set<DWDPollenflugRegionHandler> regionListeners = ConcurrentHashMap.newKeySet();
65 private final HttpClient client;
66 private final Gson gson = new Gson();
68 public DWDPollenflugBridgeHandler(Bridge bridge, HttpClient client) {
74 public void handleCommand(ChannelUID channelUID, Command command) {
75 if (command instanceof RefreshType) {
76 final DWDPollenflug localPollenflug = pollenflug;
77 if (localPollenflug != null) {
78 notifyOnUpdate(localPollenflug);
84 public void initialize() {
85 logger.debug("Initializing DWD Pollenflug bridge handler");
86 bridgeConfig = getConfigAs(DWDPollenflugBridgeConfiguration.class);
88 if (bridgeConfig.isValid()) {
89 updateStatus(ThingStatus.UNKNOWN);
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
93 "Refresh interval has to be at least 15 minutes.");
98 public void dispose() {
99 logger.debug("Handler disposed.");
103 private void startPolling() {
104 final ScheduledFuture<?> localPollingJob = this.pollingJob;
105 if (localPollingJob == null || localPollingJob.isCancelled()) {
106 logger.debug("Start polling.");
107 pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, bridgeConfig.refresh, TimeUnit.MINUTES);
111 private void stopPolling() {
112 final ScheduledFuture<?> localPollingJob = this.pollingJob;
113 if (localPollingJob != null && !localPollingJob.isCancelled()) {
114 logger.debug("Stop polling.");
115 localPollingJob.cancel(true);
120 private void poll() {
121 logger.debug("Polling");
122 requestRefresh().handle((resultPollenflug, pollException) -> {
123 if (resultPollenflug == null) {
124 if (pollException == null) {
125 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
128 pollException.getMessage());
131 updateStatus(ThingStatus.ONLINE);
132 notifyOnUpdate(resultPollenflug);
139 private CompletableFuture<@Nullable DWDPollenflug> requestRefresh() {
140 CompletableFuture<@Nullable DWDPollenflug> f = new CompletableFuture<>();
141 Request request = client.newRequest(URI.create(DWD_URL));
143 request.method(HttpMethod.GET).timeout(2000, TimeUnit.SECONDS).send(new BufferingResponseListener() {
144 @NonNullByDefault({})
146 public void onComplete(Result result) {
147 final HttpResponse response = (HttpResponse) result.getResponse();
148 if (result.getFailure() != null) {
149 Throwable e = result.getFailure();
150 if (e instanceof SocketTimeoutException || e instanceof TimeoutException) {
151 f.completeExceptionally(new DWDPollingException("Request timeout", e));
153 f.completeExceptionally(new DWDPollingException("Request failed", e));
155 } else if (response.getStatus() != 200) {
156 f.completeExceptionally(new DWDPollingException(getContentAsString()));
159 DWDPollenflug pollenflugJSON = gson.fromJson(getContentAsString(), DWDPollenflug.class);
160 f.complete(pollenflugJSON);
161 } catch (JsonSyntaxException ex2) {
162 f.completeExceptionally(new DWDPollingException("Parsing of response failed"));
172 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
173 if (childHandler instanceof DWDPollenflugRegionHandler regionListener) {
174 logger.debug("Register region listener.");
175 if (regionListeners.add(regionListener)) {
176 final DWDPollenflug localPollenflug = pollenflug;
177 if (localPollenflug != null) {
178 regionListener.notifyOnUpdate(localPollenflug);
181 logger.warn("Tried to add listener {} but it was already present. This is probably an error.",
188 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
189 if (childHandler instanceof DWDPollenflugRegionHandler handler) {
190 logger.debug("Unregister region listener.");
191 if (!regionListeners.remove(handler)) {
192 logger.warn("Tried to remove listener {} but it was not registered. This is probably an error.",
198 public void notifyOnUpdate(@Nullable DWDPollenflug newPollenflug) {
199 if (newPollenflug != null) {
200 pollenflug = newPollenflug;
201 updateProperties(newPollenflug.getProperties());
202 regionListeners.forEach(listener -> listener.notifyOnUpdate(newPollenflug));
203 newPollenflug.getChannelsStateMap().forEach(this::updateState);
207 public @Nullable DWDPollenflug getPollenflug() {