]> git.basschouten.com Git - openhab-addons.git/blob
bdbc675e97f5b485dfb3f596ee14bf121a4f5470
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dwdunwetter.internal.handler;
14
15 import static org.openhab.binding.dwdunwetter.internal.DwdUnwetterBindingConstants.*;
16
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;
22
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.dto.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;
43
44 /**
45  * The {@link DwdUnwetterHandler} is responsible for handling commands, which are sent to one of the channels.
46  *
47  * @author Martin Koehler - Initial contribution
48  */
49 @NonNullByDefault
50 public class DwdUnwetterHandler extends BaseThingHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(DwdUnwetterHandler.class);
53
54     private @Nullable ScheduledFuture<?> refreshJob;
55     private int warningCount;
56     private @Nullable DwdWarningsData data;
57
58     private boolean inRefresh;
59
60     public DwdUnwetterHandler(Thing thing) {
61         super(thing);
62     }
63
64     @Override
65     public void handleCommand(ChannelUID channelUID, Command command) {
66         if (command instanceof RefreshType) {
67             scheduler.submit(this::refresh);
68         }
69     }
70
71     /**
72      * Refreshes the Warning Data.
73      *
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.
76      */
77     private void refresh() {
78         if (inRefresh) {
79             logger.trace("Already refreshing. Ignoring refresh request.");
80             return;
81         }
82
83         if (!ThingHandlerHelper.isHandlerInitialized(getThing())) {
84             logger.debug("Unable to refresh. Thing status is '{}'", getThing().getStatus());
85             return;
86         }
87
88         final DwdWarningsData warningsData = data;
89         if (warningsData == null) {
90             logger.debug("Unable to refresh. No data to use.");
91             return;
92         }
93
94         inRefresh = true;
95
96         boolean refreshSucceeded = warningsData.refresh();
97         if (!refreshSucceeded) {
98             logger.debug("Failed to retrieve new data from the server.");
99             inRefresh = false;
100             return;
101         }
102
103         updateStatus(ThingStatus.ONLINE);
104
105         updateState(getChannelUuid(CHANNEL_LAST_UPDATED), new DateTimeType());
106
107         for (int i = 0; i < warningCount; i++) {
108             State warning = warningsData.getWarning(i);
109             int warningNumber = i + 1;
110             if (warning == OnOffType.OFF) {
111                 updateState(getChannelUuid(CHANNEL_WARNING, warningNumber), warning);
112             }
113             updateState(getChannelUuid(CHANNEL_SEVERITY, warningNumber), warningsData.getSeverity(i));
114             updateState(getChannelUuid(CHANNEL_DESCRIPTION, warningNumber), warningsData.getDescription(i));
115             updateState(getChannelUuid(CHANNEL_EFFECTIVE, warningNumber), warningsData.getEffective(i));
116             updateState(getChannelUuid(CHANNEL_EXPIRES, warningNumber), warningsData.getExpires(i));
117             updateState(getChannelUuid(CHANNEL_ONSET, warningNumber), warningsData.getOnset(i));
118             updateState(getChannelUuid(CHANNEL_EVENT, warningNumber), warningsData.getEvent(i));
119             updateState(getChannelUuid(CHANNEL_HEADLINE, warningNumber), warningsData.getHeadline(i));
120             updateState(getChannelUuid(CHANNEL_ALTITUDE, warningNumber), warningsData.getAltitude(i));
121             updateState(getChannelUuid(CHANNEL_CEILING, warningNumber), warningsData.getCeiling(i));
122             updateState(getChannelUuid(CHANNEL_INSTRUCTION, warningNumber), warningsData.getInstruction(i));
123             updateState(getChannelUuid(CHANNEL_URGENCY, warningNumber), warningsData.getUrgency(i));
124             if (warning == OnOffType.ON) {
125                 updateState(getChannelUuid(CHANNEL_WARNING, warningNumber), warning);
126             }
127             if (warningsData.isNew(i)) {
128                 triggerChannel(getChannelUuid(CHANNEL_UPDATED, warningNumber), "NEW");
129             }
130         }
131
132         warningsData.updateCache();
133         inRefresh = false;
134     }
135
136     @Override
137     public void initialize() {
138         logger.debug("Start initializing!");
139         updateStatus(ThingStatus.UNKNOWN);
140
141         DwdUnwetterConfiguration config = getConfigAs(DwdUnwetterConfiguration.class);
142         int newWarningCount = config.warningCount;
143
144         if (warningCount != newWarningCount) {
145             List<Channel> toBeAddedChannels = new ArrayList<>();
146             List<Channel> toBeRemovedChannels = new ArrayList<>();
147             if (warningCount > newWarningCount) {
148                 for (int i = newWarningCount + 1; i <= warningCount; ++i) {
149                     toBeRemovedChannels.addAll(removeChannels(i));
150                 }
151             } else {
152                 for (int i = warningCount + 1; i <= newWarningCount; ++i) {
153                     toBeAddedChannels.addAll(createChannels(i));
154                 }
155             }
156             warningCount = newWarningCount;
157
158             ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
159             for (Channel channel : toBeAddedChannels) {
160                 builder.withChannel(channel);
161             }
162             updateThing(builder.build());
163         }
164
165         data = new DwdWarningsData(config.cellId);
166
167         refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.MINUTES);
168
169         logger.debug("Finished initializing!");
170     }
171
172     private ChannelUID getChannelUuid(String typeId, int warningNumber) {
173         return new ChannelUID(getThing().getUID(), typeId + warningNumber);
174     }
175
176     private ChannelUID getChannelUuid(String typeId) {
177         return new ChannelUID(getThing().getUID(), typeId);
178     }
179
180     /**
181      * Creates a normal, state based, channel associated with a warning.
182      */
183     private void createChannelIfNotExist(ThingHandlerCallback cb, List<Channel> channels, String typeId, String label,
184             int warningNumber) {
185         ChannelUID channelUID = getChannelUuid(typeId, warningNumber);
186         Channel existingChannel = getThing().getChannel(channelUID);
187         if (existingChannel != null) {
188             logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
189                     getThing().getUID(), existingChannel.getUID(), channelUID);
190         } else {
191             channels.add(cb.createChannelBuilder(channelUID, new ChannelTypeUID(BINDING_ID, typeId))
192                     .withLabel(label + " " + getChannelLabelSuffix(warningNumber)).build());
193         }
194     }
195
196     private String getChannelLabelSuffix(int warningNumber) {
197         return "(" + warningNumber + ")";
198     }
199
200     /**
201      * Creates the Channels for each warning.
202      *
203      * @return The List of Channels to be added
204      */
205     private List<Channel> createChannels(int warningNumber) {
206         logger.debug("Building channels for thing '{}'.", getThing().getUID());
207         List<Channel> channels = new ArrayList<>();
208         ThingHandlerCallback callback = getCallback();
209         if (callback != null) {
210             createChannelIfNotExist(callback, channels, CHANNEL_UPDATED, "Updated", warningNumber);
211             createChannelIfNotExist(callback, channels, CHANNEL_WARNING, "Warning", warningNumber);
212             createChannelIfNotExist(callback, channels, CHANNEL_SEVERITY, "Severity", warningNumber);
213             createChannelIfNotExist(callback, channels, CHANNEL_DESCRIPTION, "Description", warningNumber);
214             createChannelIfNotExist(callback, channels, CHANNEL_EFFECTIVE, "Issued", warningNumber);
215             createChannelIfNotExist(callback, channels, CHANNEL_ONSET, "Valid From", warningNumber);
216             createChannelIfNotExist(callback, channels, CHANNEL_EXPIRES, "Valid To", warningNumber);
217             createChannelIfNotExist(callback, channels, CHANNEL_EVENT, "Type", warningNumber);
218             createChannelIfNotExist(callback, channels, CHANNEL_HEADLINE, "Headline", warningNumber);
219             createChannelIfNotExist(callback, channels, CHANNEL_ALTITUDE, "Height (from)", warningNumber);
220             createChannelIfNotExist(callback, channels, CHANNEL_CEILING, "Height (to)", warningNumber);
221             createChannelIfNotExist(callback, channels, CHANNEL_INSTRUCTION, "Instruction", warningNumber);
222             createChannelIfNotExist(callback, channels, CHANNEL_URGENCY, "Urgency", warningNumber);
223         }
224         return channels;
225     }
226
227     /**
228      * Filters the Channels for each warning
229      *
230      * @return The List of Channels to be removed
231      */
232     @SuppressWarnings("null")
233     private List<Channel> removeChannels(int warningNumber) {
234         return getThing().getChannels().stream()
235                 .filter(channel -> channel.getLabel() != null
236                         && channel.getLabel().endsWith(getChannelLabelSuffix(warningNumber)))
237                 .collect(Collectors.toList());
238     }
239
240     @Override
241     public void dispose() {
242         final ScheduledFuture<?> job = refreshJob;
243         if (job != null) {
244             job.cancel(true);
245         }
246         super.dispose();
247     }
248 }