@NonNullByDefault
public class DwdUnwetterConfiguration {
public int refresh;
- public int warningCount;
+ public int warningCount = 1;
public String cellId = "";
}
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.thing.binding.builder.ChannelBuilder;
-import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.util.ThingHandlerHelper;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
private @Nullable DwdWarningsData data;
private boolean inRefresh;
- private boolean initializing;
public DwdUnwetterHandler(Thing thing) {
super(thing);
return;
}
- if (initializing) {
- logger.trace("Still initializing. Ignoring refresh request.");
- return;
- }
-
- ThingStatus status = getThing().getStatus();
- if (status != ThingStatus.ONLINE && status != ThingStatus.UNKNOWN) {
- logger.debug("Unable to refresh. Thing status is {}", status);
+ if (!ThingHandlerHelper.isHandlerInitialized(getThing())) {
+ logger.debug("Unable to refresh. Thing status is '{}'", getThing().getStatus());
return;
}
return;
}
- if (status == ThingStatus.UNKNOWN) {
- updateStatus(ThingStatus.ONLINE);
- }
+ updateStatus(ThingStatus.ONLINE);
updateState(getChannelUuid(CHANNEL_LAST_UPDATED), new DateTimeType());
@Override
public void initialize() {
logger.debug("Start initializing!");
- initializing = true;
updateStatus(ThingStatus.UNKNOWN);
DwdUnwetterConfiguration config = getConfigAs(DwdUnwetterConfiguration.class);
- warningCount = config.warningCount;
+ int newWarningCount = config.warningCount;
+
+ if (warningCount != newWarningCount) {
+ List<Channel> toBeAddedChannels = new ArrayList<>();
+ List<Channel> toBeRemovedChannels = new ArrayList<>();
+ if (warningCount > newWarningCount) {
+ for (int i = newWarningCount + 1; i <= warningCount; ++i) {
+ toBeRemovedChannels.addAll(removeChannels(i));
+ }
+ } else {
+ for (int i = warningCount + 1; i <= newWarningCount; ++i) {
+ toBeAddedChannels.addAll(createChannels(i));
+ }
+ }
+ warningCount = newWarningCount;
- data = new DwdWarningsData(config.cellId);
+ ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
+ for (Channel channel : toBeAddedChannels) {
+ builder.withChannel(channel);
+ }
+ updateThing(builder.build());
+ }
- updateThing(editThing().withChannels(createChannels()).build());
+ data = new DwdWarningsData(config.cellId);
refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.MINUTES);
- initializing = false;
+
logger.debug("Finished initializing!");
}
}
/**
- * Creates a trigger Channel.
+ * Creates a normal, state based, channel associated with a warning.
*/
- private Channel createTriggerChannel(String typeId, String label, int warningNumber) {
+ private void createChannelIfNotExist(ThingHandlerCallback cb, List<Channel> channels, String typeId, String label,
+ int warningNumber) {
ChannelUID channelUID = getChannelUuid(typeId, warningNumber);
- return ChannelBuilder.create(channelUID, "String") //
- .withType(new ChannelTypeUID(BINDING_ID, typeId)) //
- .withLabel(label + " (" + (warningNumber + 1) + ")")//
- .withKind(ChannelKind.TRIGGER) //
- .build();
+ Channel existingChannel = getThing().getChannel(channelUID);
+ if (existingChannel != null) {
+ logger.trace("Thing '{}' already has an existing channel '{}'. Omit adding new channel '{}'.",
+ getThing().getUID(), existingChannel.getUID(), channelUID);
+ } else {
+ channels.add(cb.createChannelBuilder(channelUID, new ChannelTypeUID(BINDING_ID, typeId))
+ .withLabel(label + " " + getChannelLabelSuffix(warningNumber)).build());
+ }
}
- /**
- * Creates a normal, state based, channel associated with a warning.
- */
- private Channel createChannel(String typeId, String itemType, String label, int warningNumber) {
- ChannelUID channelUID = getChannelUuid(typeId, warningNumber);
- return ChannelBuilder.create(channelUID, itemType) //
- .withType(new ChannelTypeUID(BINDING_ID, typeId)) //
- .withLabel(label + " (" + (warningNumber + 1) + ")")//
- .build();
+ private String getChannelLabelSuffix(int warningNumber) {
+ return "(" + (warningNumber + 1) + ")";
}
/**
- * Creates a normal, state based, channel not associated with a warning.
+ * Creates the Channels for each warning.
+ *
+ * @return The List of Channels to be added
*/
- private Channel createChannel(String typeId, String itemType, String label) {
- ChannelUID channelUID = getChannelUuid(typeId);
- return ChannelBuilder.create(channelUID, itemType) //
- .withType(new ChannelTypeUID(BINDING_ID, typeId)) //
- .withLabel(label)//
- .build();
+ private List<Channel> createChannels(int warningNumber) {
+ logger.debug("Building channels for thing '{}'.", getThing().getUID());
+ List<Channel> channels = new ArrayList<>();
+ ThingHandlerCallback callback = getCallback();
+ if (callback != null) {
+ createChannelIfNotExist(callback, channels, CHANNEL_UPDATED, "Updated", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_WARNING, "Warning", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_SEVERITY, "Severity", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_DESCRIPTION, "Description", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_EFFECTIVE, "Issued", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_ONSET, "Valid From", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_EXPIRES, "Valid To", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_EVENT, "Type", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_HEADLINE, "Headline", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_ALTITUDE, "Height (from)", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_CEILING, "Height (to)", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_INSTRUCTION, "Instruction", warningNumber);
+ createChannelIfNotExist(callback, channels, CHANNEL_URGENCY, "Urgency", warningNumber);
+ }
+ return channels;
}
/**
- * Creates the ChannelsT for each warning.
+ * Filters the Channels for each warning
*
- * @return The List of Channels
+ * @return The List of Channels to be removed
*/
- private List<Channel> createChannels() {
- List<Channel> channels = new ArrayList<>(warningCount * 11 + 1);
- channels.add(createChannel(CHANNEL_LAST_UPDATED, "DateTime", "Last Updated"));
- for (int i = 0; i < warningCount; i++) {
- channels.add(createChannel(CHANNEL_WARNING, "Switch", "Warning", i));
- channels.add(createTriggerChannel(CHANNEL_UPDATED, "Updated", i));
- channels.add(createChannel(CHANNEL_SEVERITY, "String", "Severity", i));
- channels.add(createChannel(CHANNEL_DESCRIPTION, "String", "Description", i));
- channels.add(createChannel(CHANNEL_EFFECTIVE, "DateTime", "Issued", i));
- channels.add(createChannel(CHANNEL_ONSET, "DateTime", "Valid From", i));
- channels.add(createChannel(CHANNEL_EXPIRES, "DateTime", "Valid To", i));
- channels.add(createChannel(CHANNEL_EVENT, "String", "Type", i));
- channels.add(createChannel(CHANNEL_HEADLINE, "String", "Headline", i));
- channels.add(createChannel(CHANNEL_ALTITUDE, "Number:Length", "Height (from)", i));
- channels.add(createChannel(CHANNEL_CEILING, "Number:Length", "Height (to)", i));
- channels.add(createChannel(CHANNEL_INSTRUCTION, "String", "Instruction", i));
- channels.add(createChannel(CHANNEL_URGENCY, "String", "Urgency", i));
- }
- return channels;
+ @SuppressWarnings("null")
+ private List<Channel> removeChannels(int warningNumber) {
+ return getThing().getChannels().stream()
+ .filter(channel -> channel.getLabel() != null
+ && channel.getLabel().endsWith(getChannelLabelSuffix(warningNumber)))
+ .collect(Collectors.toList());
}
@Override
public void dispose() {
final ScheduledFuture<?> job = refreshJob;
-
if (job != null) {
job.cancel(true);
}
<thing-type id="dwdwarnings">
<label>Weather Warnings</label>
<description>Weather Warnings for an area</description>
+ <channels>
+ <channel typeId="lastUpdated" id="lastUpdated"></channel>
+ </channels>
<config-description>
<parameter name="cellId" type="text" required="true">
<label>Cell-ID</label>
+++ /dev/null
-/**
- * Copyright (c) 2010-2020 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.dwdunwetter;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
-
-import java.io.InputStream;
-import java.util.List;
-import java.util.Objects;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.dwdunwetter.internal.DwdUnwetterBindingConstants;
-import org.openhab.binding.dwdunwetter.internal.handler.DwdUnwetterHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.test.java.JavaTest;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerCallback;
-import org.openhab.core.thing.type.ChannelTypeUID;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Test cases for {@link DwdUnwetterHandler}. The tests provide mocks for supporting entities using Mockito.
- *
- * @author Martin Koehler - Initial contribution
- */
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.WARN)
-public class DwdUnwetterHandlerTest extends JavaTest {
-
- private ThingHandler handler;
-
- private @Mock ThingHandlerCallback callback;
- private @Mock Thing thing;
-
- @BeforeEach
- public void setUp() {
- handler = new DwdUnwetterHandler(thing);
- handler.setCallback(callback);
- // mock getConfiguration to prevent NPEs
- when(thing.getUID()).thenReturn(new ThingUID(DwdUnwetterBindingConstants.BINDING_ID, "test"));
- Configuration configuration = new Configuration();
- configuration.put("refresh", Integer.valueOf("1"));
- configuration.put("warningCount", Integer.valueOf("1"));
- when(thing.getConfiguration()).thenReturn(configuration);
- }
-
- @Test
- public void testInitializeShouldCallTheCallback() {
- // we expect the handler#initialize method to call the callback during execution and
- // pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
- handler.initialize();
-
- // the argument captor will capture the argument of type ThingStatusInfo given to the
- // callback#statusUpdated method.
- ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
-
- // verify the interaction with the callback and capture the ThingStatusInfo argument:
- waitForAssert(() -> {
- verify(callback, times(1)).statusUpdated(eq(thing), statusInfoCaptor.capture());
- });
-
- // assert that the (temporary) UNKNOWN status was to the mocked thing first:
- assertThat(statusInfoCaptor.getAllValues().get(0).getStatus(), is(ThingStatus.UNKNOWN));
- }
-
- /**
- * Tests that the labels of the channels are equal to the ChannelType Definition
- */
- @Test
- public void testLabels() throws Exception {
- handler.initialize();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- InputStream stream = getClass().getResourceAsStream("/OH-INF/thing/thing-types.xml");
- Document document = builder.parse(stream);
- NodeList nodeList = document.getElementsByTagName("channel-type");
-
- thing = handler.getThing();
- List<Channel> channels = thing.getChannels();
- for (Channel channel : channels) {
- String label = getLabel(nodeList, channel.getChannelTypeUID());
- assertThat(channel.getLabel(), CoreMatchers.startsWith(label));
- }
- }
-
- private String getLabel(NodeList nodeList, ChannelTypeUID uuid) {
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- Node nodeId = node.getAttributes().getNamedItem("id");
- if (nodeId == null) {
- continue;
- }
- if (Objects.equals(nodeId.getTextContent(), uuid.getId())) {
- return getLabel(node.getChildNodes());
- }
- }
- return null;
- }
-
- private String getLabel(NodeList nodeList) {
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- if (node.getNodeName().equals("label")) {
- return node.getTextContent();
- }
- }
- return null;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 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.dwdunwetter.internal.handler;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.openhab.binding.dwdunwetter.internal.DwdUnwetterBindingConstants;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.test.java.JavaTest;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Test cases for {@link DwdUnwetterHandler}. The tests provide mocks for supporting entities using Mockito.
+ *
+ * @author Martin Koehler - Initial contribution
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.WARN)
+public class DwdUnwetterHandlerTest extends JavaTest {
+
+ private ThingHandler handler;
+
+ private @Mock ThingHandlerCallback callback;
+ private @Mock Thing thing;
+
+ @BeforeEach
+ public void setUp() {
+ when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
+ .thenAnswer(invocation -> ChannelBuilder.create(invocation.getArgument(0, ChannelUID.class))
+ .withType(invocation.getArgument(1, ChannelTypeUID.class)));
+
+ handler = new DwdUnwetterHandler(thing);
+ handler.setCallback(callback);
+ // mock getConfiguration to prevent NPEs
+ when(thing.getUID()).thenReturn(new ThingUID(DwdUnwetterBindingConstants.BINDING_ID, "test"));
+ Configuration configuration = new Configuration();
+ configuration.put("refresh", Integer.valueOf("1"));
+ configuration.put("warningCount", Integer.valueOf("1"));
+ when(thing.getConfiguration()).thenReturn(configuration);
+ }
+
+ @Test
+ public void testInitializeShouldCallTheCallback() {
+ // we expect the handler#initialize method to call the callback during execution and
+ // pass it the thing and a ThingStatusInfo object containing the ThingStatus of the thing.
+ handler.initialize();
+
+ // the argument captor will capture the argument of type ThingStatusInfo given to the
+ // callback#statusUpdated method.
+ ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
+
+ // verify the interaction with the callback and capture the ThingStatusInfo argument:
+ waitForAssert(() -> {
+ verify(callback, times(1)).statusUpdated(eq(thing), statusInfoCaptor.capture());
+ });
+
+ // assert that the (temporary) UNKNOWN status was to the mocked thing first:
+ assertThat(statusInfoCaptor.getAllValues().get(0).getStatus(), is(ThingStatus.UNKNOWN));
+ }
+
+ /**
+ * Tests that the labels of the channels are equal to the ChannelType Definition
+ */
+ @Test
+ public void testLabels() throws Exception {
+ handler.initialize();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ InputStream stream = getClass().getResourceAsStream("/OH-INF/thing/thing-types.xml");
+ Document document = builder.parse(stream);
+ NodeList nodeList = document.getElementsByTagName("channel-type");
+
+ thing = handler.getThing();
+ List<Channel> channels = thing.getChannels();
+ for (Channel channel : channels) {
+ String label = getLabel(nodeList, channel.getChannelTypeUID());
+ assertThat(channel.getLabel(), CoreMatchers.startsWith(label));
+ }
+ }
+
+ private String getLabel(NodeList nodeList, ChannelTypeUID uuid) {
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ Node nodeId = node.getAttributes().getNamedItem("id");
+ if (nodeId == null) {
+ continue;
+ }
+ if (Objects.equals(nodeId.getTextContent(), uuid.getId())) {
+ return getLabel(node.getChildNodes());
+ }
+ }
+ return null;
+ }
+
+ private String getLabel(NodeList nodeList) {
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node.getNodeName().equals("label")) {
+ return node.getTextContent();
+ }
+ }
+ return null;
+ }
+}