diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java index 8c9a14c2f97..f5fcfc8f086 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandler.java @@ -24,6 +24,7 @@ import org.openhab.core.events.Event; import org.openhab.core.events.EventFilter; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.events.TopicEventFilter; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.events.ChannelTriggeredEvent; import org.osgi.framework.BundleContext; @@ -35,6 +36,7 @@ * This is a ModuleHandler implementation for trigger channels with specific events * * @author Stefan Triller - Initial contribution + * @author Jimmy Tanagra - Add support for wildcard channel matching */ @NonNullByDefault public class ChannelEventTriggerHandler extends BaseTriggerModuleHandler implements EventSubscriber, EventFilter { @@ -48,7 +50,8 @@ public class ChannelEventTriggerHandler extends BaseTriggerModuleHandler impleme private final Logger logger = LoggerFactory.getLogger(ChannelEventTriggerHandler.class); private @Nullable final String eventOnChannel; - private final ChannelUID channelUID; + private final @Nullable ChannelUID channelUID; + private final @Nullable TopicEventFilter eventTopicFilter; private final Set types; private final BundleContext bundleContext; private final ServiceRegistration eventSubscriberRegistration; @@ -56,8 +59,24 @@ public class ChannelEventTriggerHandler extends BaseTriggerModuleHandler impleme public ChannelEventTriggerHandler(Trigger module, BundleContext bundleContext) { super(module); + String cfgChannel = (String) module.getConfiguration().get(CFG_CHANNEL); + + if (cfgChannel == null || cfgChannel.isBlank()) { + throw new IllegalArgumentException("Configuration must contain a non-empty channelUID"); + } + this.eventOnChannel = (String) module.getConfiguration().get(CFG_CHANNEL_EVENT); - this.channelUID = new ChannelUID((String) module.getConfiguration().get(CFG_CHANNEL)); + TopicEventFilter topicFilter = null; + ChannelUID parsedChannel = null; + if (cfgChannel.contains("?") || cfgChannel.contains("*")) { + String topicRegex = "^openhab/channels/" + cfgChannel.replace("?", ".?").replace("*", ".*?") + + "/triggered$"; + topicFilter = new TopicEventFilter(topicRegex); + } else { + parsedChannel = new ChannelUID(cfgChannel); + } + this.channelUID = parsedChannel; + this.eventTopicFilter = topicFilter; this.types = Set.of("ChannelTriggeredEvent"); this.bundleContext = bundleContext; @@ -78,12 +97,15 @@ public void receive(Event event) { public boolean apply(Event event) { boolean eventMatches = false; if (event instanceof ChannelTriggeredEvent cte) { - if (channelUID.equals(cte.getChannel())) { - String eventOnChannel = this.eventOnChannel; - logger.trace("->FILTER: {}:{}", cte.getEvent(), eventOnChannel); - eventMatches = eventOnChannel == null || eventOnChannel.isBlank() - || eventOnChannel.equals(cte.getEvent()); + if (channelUID != null && !channelUID.equals(cte.getChannel())) { + return false; + } + if (eventTopicFilter != null && !eventTopicFilter.apply(event)) { + return false; } + String eventOnChannel = this.eventOnChannel; + logger.trace("->FILTER: {}:{}", cte.getEvent(), eventOnChannel); + eventMatches = eventOnChannel == null || eventOnChannel.isBlank() || eventOnChannel.equals(cte.getEvent()); } return eventMatches; } diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ChannelTrigger.json b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ChannelTrigger.json index b58f30b57b5..430c19fae5c 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ChannelTrigger.json +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ChannelTrigger.json @@ -10,7 +10,7 @@ "type": "TEXT", "context": "channel", "label": "Channel", - "description": "the id of the channel which should be observed for triggers", + "description": "the id of the channel which should be observed for triggers. '*' and '?' can be used as wildcards to match multiple channels.", "required": true, "filterCriteria": [ { diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties index a2f8b4eb4e2..a9b3bcecd30 100644 --- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties +++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation.properties @@ -3,7 +3,7 @@ module-type.core.ChannelEventTrigger.label = a trigger channel fires module-type.core.ChannelEventTrigger.description = React on events from a trigger channel of a thing. module-type.core.ChannelEventTrigger.config.channelUID.label = Channel -module-type.core.ChannelEventTrigger.config.channelUID.description = the id of the channel which should be observed for triggers +module-type.core.ChannelEventTrigger.config.channelUID.description = the id of the channel which should be observed for triggers. '*' and '?' can be used as wildcards to match multiple channels. module-type.core.ChannelEventTrigger.config.event.label = Event module-type.core.ChannelEventTrigger.config.event.description = the event on the channel to react on module-type.core.ChannelEventTrigger.output.event.label = Event diff --git a/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandlerTest.java b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandlerTest.java index be2b06594da..60e35fde345 100644 --- a/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandlerTest.java +++ b/bundles/org.openhab.core.automation/src/test/java/org/openhab/core/automation/internal/module/handler/ChannelEventTriggerHandlerTest.java @@ -31,6 +31,7 @@ * Basic test cases for {@link ChannelEventTriggerHandler} * * @author Thomas Weißschuh - Initial contribution + * @author Jimmy Tanagra - Add test cases for wildcard channel matching and channel event matching */ @NonNullByDefault class ChannelEventTriggerHandlerTest { @@ -61,4 +62,97 @@ public void testSubstringMatchingChannelIsNotApplied() { assertFalse(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); } + + @Test + public void testWildcardAsteriskMatchingChannelIsApplied() { + when(moduleMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, "foo:bar:baz:*"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testWildcardAsteriskNonMatchingChannelIsNotApplied() { + when(moduleMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, "foo:bar:baz:*"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertFalse(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baa:quux")))); + } + + @Test + public void testWildcardQuestionMarkMatchingChannelIsApplied() { + when(moduleMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, "foo:bar:baz:quu?"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testWildcardQuestionMarkNonMatchingChannelIsNotApplied() { + when(moduleMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, "foo:bar:baz:quu?"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertFalse( + handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quuxx")))); + } + + @Test + public void testMatchingChannelEventIsApplied() { + when(moduleMock.getConfiguration()).thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, + "foo:bar:baz:quux", ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, "PRESSED"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testNonMatchingChannelEventIsNotApplied() { + when(moduleMock.getConfiguration()).thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, + "foo:bar:baz:quux", ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, "RELEASED"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertFalse(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testBlankChannelEventMatchesAllEvents() { + when(moduleMock.getConfiguration()).thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, + "foo:bar:baz:quux", ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, ""))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("RELEASED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testOmittedChannelEventMatchesAllEvents() { + when(moduleMock.getConfiguration()) + .thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, "foo:bar:baz:quux"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("RELEASED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testWildcardChannelWithMatchingChannelEventIsApplied() { + when(moduleMock.getConfiguration()).thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, + "foo:bar:baz:*", ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, "PRESSED"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertTrue(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } + + @Test + public void testWildcardChannelWithNonMatchingChannelEventIsNotApplied() { + when(moduleMock.getConfiguration()).thenReturn(new Configuration(Map.of(ChannelEventTriggerHandler.CFG_CHANNEL, + "foo:bar:baz:*", ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, "RELEASED"))); + handler = new ChannelEventTriggerHandler(moduleMock, contextMock); + + assertFalse(handler.apply(ThingEventFactory.createTriggerEvent("PRESSED", new ChannelUID("foo:bar:baz:quux")))); + } }