2 * Copyright (c) 2010-2020 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.dwdunwetter.internal.handler;
15 import static org.openhab.binding.dwdunwetter.internal.DwdUnwetterBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.dwdunwetter.internal.config.DwdUnwetterConfiguration;
26 import org.openhab.binding.dwdunwetter.internal.data.DwdWarningsData;
27 import org.openhab.core.library.types.DateTimeType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.thing.binding.ThingHandlerCallback;
35 import org.openhab.core.thing.binding.builder.ThingBuilder;
36 import org.openhab.core.thing.type.ChannelTypeUID;
37 import org.openhab.core.thing.util.ThingHandlerHelper;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link DwdUnwetterHandler} is responsible for handling commands, which are sent to one of the channels.
47 * @author Martin Koehler - Initial contribution
50 public class DwdUnwetterHandler extends BaseThingHandler {
52 private final Logger logger = LoggerFactory.getLogger(DwdUnwetterHandler.class);
54 private @Nullable ScheduledFuture<?> refreshJob;
55 private int warningCount;
56 private @Nullable DwdWarningsData data;
58 private boolean inRefresh;
60 public DwdUnwetterHandler(Thing thing) {
65 public void handleCommand(ChannelUID channelUID, Command command) {
66 if (command instanceof RefreshType) {
67 scheduler.submit(this::refresh);
72 * Refreshes the Warning Data.
74 * The Switch Channel is switched to ON only after all other Channels are updated.
75 * The Switch Channel is switched to OFF before all other Channels are updated.
77 private void refresh() {
79 logger.trace("Already refreshing. Ignoring refresh request.");
83 if (!ThingHandlerHelper.isHandlerInitialized(getThing())) {
84 logger.debug("Unable to refresh. Thing status is '{}'", getThing().getStatus());
88 final DwdWarningsData warningsData = data;
89 if (warningsData == null) {
90 logger.debug("Unable to refresh. No data to use.");
96 boolean refreshSucceeded = warningsData.refresh();
97 if (!refreshSucceeded) {
98 logger.debug("Failed to retrieve new data from the server.");
103 updateStatus(ThingStatus.ONLINE);
105 updateState(getChannelUuid(CHANNEL_LAST_UPDATED), new DateTimeType());
107 for (int i = 0; i < warningCount; i++) {
108 State warning = warningsData.getWarning(i);
109 if (warning == OnOffType.OFF) {
110 updateState(getChannelUuid(CHANNEL_WARNING, i), warning);
112 updateState(getChannelUuid(CHANNEL_SEVERITY, i), warningsData.getSeverity(i));
113 updateState(getChannelUuid(CHANNEL_DESCRIPTION, i), warningsData.getDescription(i));
114 updateState(getChannelUuid(CHANNEL_EFFECTIVE, i), warningsData.getEffective(i));
115 updateState(getChannelUuid(CHANNEL_EXPIRES, i), warningsData.getExpires(i));
116 updateState(getChannelUuid(CHANNEL_ONSET, i), warningsData.getOnset(i));
117 updateState(getChannelUuid(CHANNEL_EVENT, i), warningsData.getEvent(i));
118 updateState(getChannelUuid(CHANNEL_HEADLINE, i), warningsData.getHeadline(i));
119 updateState(getChannelUuid(CHANNEL_ALTITUDE, i), warningsData.getAltitude(i));
120 updateState(getChannelUuid(CHANNEL_CEILING, i), warningsData.getCeiling(i));
121 updateState(getChannelUuid(CHANNEL_INSTRUCTION, i), warningsData.getInstruction(i));
122 updateState(getChannelUuid(CHANNEL_URGENCY, i), warningsData.getUrgency(i));
123 if (warning == OnOffType.ON) {
124 updateState(getChannelUuid(CHANNEL_WARNING, i), warning);
126 if (warningsData.isNew(i)) {
127 triggerChannel(getChannelUuid(CHANNEL_UPDATED, i), "NEW");
131 warningsData.updateCache();
136 public void initialize() {
137 logger.debug("Start initializing!");
138 updateStatus(ThingStatus.UNKNOWN);
140 DwdUnwetterConfiguration config = getConfigAs(DwdUnwetterConfiguration.class);
141 int newWarningCount = config.warningCount;
143 if (warningCount != newWarningCount) {
144 List<Channel> toBeAddedChannels = new ArrayList<>();
145 List<Channel> toBeRemovedChannels = new ArrayList<>();
146 if (warningCount > newWarningCount) {
147 for (int i = newWarningCount + 1; i <= warningCount; ++i) {
148 toBeRemovedChannels.addAll(removeChannels(i));
151 for (int i = warningCount + 1; i <= newWarningCount; ++i) {
152 toBeAddedChannels.addAll(createChannels(i));
155 warningCount = newWarningCount;
157 ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
158 for (Channel channel : toBeAddedChannels) {
159 builder.withChannel(channel);
161 updateThing(builder.build());
164 data = new DwdWarningsData(config.cellId);
166 refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.MINUTES);
168 logger.debug("Finished initializing!");
171 private ChannelUID getChannelUuid(String typeId, int warningNumber) {
172 return new ChannelUID(getThing().getUID(), typeId + warningNumber);
175 private ChannelUID getChannelUuid(String typeId) {
176 return new ChannelUID(getThing().getUID(), typeId);
180 * Creates a normal, state based, channel associated with a warning.
182 private void createChannelIfNotExist(ThingHandlerCallback cb, List<Channel> channels, String typeId, String label,
184 ChannelUID channelUID = getChannelUuid(typeId, warningNumber);
185 Channel existingChannel = getThing().getChannel(channelUID);
186 if (existingChannel != null) {
187 logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
188 getThing().getUID(), existingChannel.getUID(), channelUID);
190 channels.add(cb.createChannelBuilder(channelUID, new ChannelTypeUID(BINDING_ID, typeId))
191 .withLabel(label + " " + getChannelLabelSuffix(warningNumber)).build());
195 private String getChannelLabelSuffix(int warningNumber) {
196 return "(" + warningNumber + ")";
200 * Creates the Channels for each warning.
202 * @return The List of Channels to be added
204 private List<Channel> createChannels(int warningNumber) {
205 logger.debug("Building channels for thing '{}'.", getThing().getUID());
206 List<Channel> channels = new ArrayList<>();
207 ThingHandlerCallback callback = getCallback();
208 if (callback != null) {
209 createChannelIfNotExist(callback, channels, CHANNEL_UPDATED, "Updated", warningNumber);
210 createChannelIfNotExist(callback, channels, CHANNEL_WARNING, "Warning", warningNumber);
211 createChannelIfNotExist(callback, channels, CHANNEL_SEVERITY, "Severity", warningNumber);
212 createChannelIfNotExist(callback, channels, CHANNEL_DESCRIPTION, "Description", warningNumber);
213 createChannelIfNotExist(callback, channels, CHANNEL_EFFECTIVE, "Issued", warningNumber);
214 createChannelIfNotExist(callback, channels, CHANNEL_ONSET, "Valid From", warningNumber);
215 createChannelIfNotExist(callback, channels, CHANNEL_EXPIRES, "Valid To", warningNumber);
216 createChannelIfNotExist(callback, channels, CHANNEL_EVENT, "Type", warningNumber);
217 createChannelIfNotExist(callback, channels, CHANNEL_HEADLINE, "Headline", warningNumber);
218 createChannelIfNotExist(callback, channels, CHANNEL_ALTITUDE, "Height (from)", warningNumber);
219 createChannelIfNotExist(callback, channels, CHANNEL_CEILING, "Height (to)", warningNumber);
220 createChannelIfNotExist(callback, channels, CHANNEL_INSTRUCTION, "Instruction", warningNumber);
221 createChannelIfNotExist(callback, channels, CHANNEL_URGENCY, "Urgency", warningNumber);
227 * Filters the Channels for each warning
229 * @return The List of Channels to be removed
231 @SuppressWarnings("null")
232 private List<Channel> removeChannels(int warningNumber) {
233 return getThing().getChannels().stream()
234 .filter(channel -> channel.getLabel() != null
235 && channel.getLabel().endsWith(getChannelLabelSuffix(warningNumber)))
236 .collect(Collectors.toList());
240 public void dispose() {
241 final ScheduledFuture<?> job = refreshJob;