From c4b7e62e3aebbe7ae9f32c24c317b6c270783858 Mon Sep 17 00:00:00 2001 From: Arne Seime Date: Wed, 25 Dec 2024 17:54:56 +0100 Subject: [PATCH 1/4] Thing channels access --- pom.xml | 7 ++-- src/main/history/dependencies.xml | 4 +- .../internal/handler/JRuleEventHandler.java | 8 ++++ .../internal/handler/JRuleThingHandler.java | 38 +++++++++++++++++-- .../automation/jrule/items/JRuleItem.java | 6 ++- .../jrule/things/JRuleAbstractThing.java | 12 ++++++ .../automation/jrule/things/JRuleChannel.java | 13 +++++++ .../jrule/rules/integration_test/ITJRule.java | 21 ++++++++++ .../rules/integration_test/JRuleITBase.java | 2 +- .../jrule/rules/user/TestRules.java | 28 ++++++++++++-- .../resources/docker/conf/items/default.items | 2 + .../resources/docker/conf/things/mqtt.things | 4 ++ 12 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java diff --git a/pom.xml b/pom.xml index ce272243..ab97fe60 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ 4.2.1-SNAPSHOT + 1.20.4 com.sun.org.apache.xml.internal.utils.*;resolution:=optional,\ com.sun.org.apache.xpath.internal.*;resolution:=optional,\ org.apache.log.*;resolution:=optional,\ @@ -77,19 +78,19 @@ org.testcontainers junit-jupiter - 1.17.6 + ${testcontainers.version} test org.testcontainers toxiproxy - 1.17.6 + ${testcontainers.version} test org.testcontainers mockserver - 1.17.6 + ${testcontainers.version} test diff --git a/src/main/history/dependencies.xml b/src/main/history/dependencies.xml index b169ca58..a2083ad4 100644 --- a/src/main/history/dependencies.xml +++ b/src/main/history/dependencies.xml @@ -1,11 +1,11 @@ - + openhab-runtime-base wrap mvn:javax.el/javax.el-api/2.2.4 mvn:org.freemarker/freemarker/2.3.32 - mvn:org.openhab.addons.bundles/org.openhab.automation.jrule/4.2.0 + mvn:org.openhab.addons.bundles/org.openhab.automation.jrule/4.2.1-SNAPSHOT wrap:mvn:javax.servlet/jsp-api/2.0 wrap:mvn:javax.servlet/servlet-api/2.4 wrap:mvn:org.lastnpe.eea/eea-all/2.2.1 diff --git a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleEventHandler.java b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleEventHandler.java index e7a51f3d..e04e36f1 100644 --- a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleEventHandler.java +++ b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleEventHandler.java @@ -139,6 +139,14 @@ public void postUpdate(String itemName, JRuleValue value) { } } + public void postUndef(String itemName) { + postUpdate(itemName, UnDefType.UNDEF); + } + + public void postNull(String itemName) { + postUpdate(itemName, UnDefType.NULL); + } + public void postUpdate(String itemName, double value, String unit) { QuantityType type = new QuantityType<>(value + " " + unit); postUpdate(itemName, type); diff --git a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleThingHandler.java b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleThingHandler.java index 8096544f..84f8d904 100644 --- a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleThingHandler.java +++ b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleThingHandler.java @@ -13,12 +13,16 @@ package org.openhab.automation.jrule.internal.handler; import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.openhab.automation.jrule.items.JRuleItem; +import org.openhab.automation.jrule.items.JRuleItemRegistry; +import org.openhab.automation.jrule.things.JRuleChannel; import org.openhab.automation.jrule.things.JRuleThingStatus; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingManager; -import org.openhab.core.thing.ThingRegistry; -import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.*; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; /** * The {@link JRuleThingHandler} provides access to thing actions @@ -36,6 +40,8 @@ private JRuleThingHandler() { private ThingManager thingManager; + private ItemChannelLinkRegistry itemChannelLinkRegistry; + public void setThingManager(ThingManager thingManager) { this.thingManager = thingManager; } @@ -44,6 +50,10 @@ public void setThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = thingRegistry; } + public void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + } + public static JRuleThingHandler get() { if (instance == null) { synchronized (JRuleThingHandler.class) { @@ -83,4 +93,24 @@ public JRuleThingStatus getStatus(String thingUID) { return JRuleThingStatus.THING_UNKNOWN; } } + + /** + * Get all channels of a thing + * + * @param thingUID the thing UID + * @return list of all channels + */ + public List getChannels(String thingUID) { + Thing thing = thingRegistry.get(new ThingUID(thingUID)); + if (thing != null) { + return thing.getChannels().stream().map(channel -> new JRuleChannel(channel.getUID().toString())).toList(); + } else { + return List.of(); + } + } + + public Set getLinkedItems(JRuleChannel channel) { + return itemChannelLinkRegistry.getLinkedItemNames(new ChannelUID(channel.getChannelUID())).stream() + .map(itemName -> JRuleItemRegistry.get(itemName)).collect(Collectors.toSet()); + } } diff --git a/src/main/java/org/openhab/automation/jrule/items/JRuleItem.java b/src/main/java/org/openhab/automation/jrule/items/JRuleItem.java index f5519bf6..81fa8afd 100644 --- a/src/main/java/org/openhab/automation/jrule/items/JRuleItem.java +++ b/src/main/java/org/openhab/automation/jrule/items/JRuleItem.java @@ -98,7 +98,11 @@ default void postUpdate(JRuleRefreshValue state) { } default void postNullUpdate() { - JRuleEventHandler.get().postUpdate(getName(), null); + JRuleEventHandler.get().postNull(getName()); + } + + default void postUndefUpdate() { + JRuleEventHandler.get().postUndef(getName()); } default Optional lastUpdated() { diff --git a/src/main/java/org/openhab/automation/jrule/things/JRuleAbstractThing.java b/src/main/java/org/openhab/automation/jrule/things/JRuleAbstractThing.java index 385080a8..dc645c14 100644 --- a/src/main/java/org/openhab/automation/jrule/things/JRuleAbstractThing.java +++ b/src/main/java/org/openhab/automation/jrule/things/JRuleAbstractThing.java @@ -12,7 +12,11 @@ */ package org.openhab.automation.jrule.things; +import java.util.List; +import java.util.Set; + import org.openhab.automation.jrule.internal.handler.JRuleThingHandler; +import org.openhab.automation.jrule.items.JRuleItem; /** * The {@link JRuleAbstractThing} represents a thing that is either a bridge, a bridged (sub thing of a bridge) or a @@ -49,4 +53,12 @@ public void restart() { disable(); enable(); } + + public List getChannels() { + return JRuleThingHandler.get().getChannels(thingUID); + } + + public Set getLinkedItems(JRuleChannel channel) { + return JRuleThingHandler.get().getLinkedItems(channel); + } } diff --git a/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java b/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java new file mode 100644 index 00000000..b871bd09 --- /dev/null +++ b/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java @@ -0,0 +1,13 @@ +package org.openhab.automation.jrule.things; + +public class JRuleChannel { + private String channelUID; + + public JRuleChannel(String channelUID) { + this.channelUID = channelUID; + } + + public String getChannelUID() { + return channelUID; + } +} diff --git a/src/test/java/org/openhab/automation/jrule/rules/integration_test/ITJRule.java b/src/test/java/org/openhab/automation/jrule/rules/integration_test/ITJRule.java index 5ea66a3e..1ac0cc73 100644 --- a/src/test/java/org/openhab/automation/jrule/rules/integration_test/ITJRule.java +++ b/src/test/java/org/openhab/automation/jrule/rules/integration_test/ITJRule.java @@ -89,6 +89,27 @@ public void mqttThingChangedToOffline() { verifyRuleWasExecuted(TestRules.NAME_MQTT_THING_CHANGED_TO_OFFLINE); } + @Test + public void mqttThingChangedFromOnline() throws MqttException { + + Awaitility.await().with().pollDelay(1, TimeUnit.SECONDS).timeout(20, TimeUnit.SECONDS) + .pollInterval(200, TimeUnit.MILLISECONDS).await("thing online") + .until(() -> getThingState("mqtt:topic:mqtt:fromonlinetest"), s -> s.equals("ONLINE")); + publishMqttMessage("fromonlinetest/state", "1"); + Awaitility.await().with().pollDelay(100, TimeUnit.MILLISECONDS).timeout(5, TimeUnit.SECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS).await("item updated") + .until(() -> getState(TestRules.ITEM_MQTT_TOPIC_FROM_ONLINE_TEST), s -> "1".equals(s)); + mqttProxy.setConnectionCut(true); + Awaitility.await().with().pollDelay(1, TimeUnit.SECONDS).timeout(20, TimeUnit.SECONDS) + .pollInterval(200, TimeUnit.MILLISECONDS).await("thing online") + .until(() -> getThingState("mqtt:topic:mqtt:fromonlinetest"), s -> s.equals("OFFLINE")); + Awaitility.await().with().pollDelay(1, TimeUnit.SECONDS).timeout(30, TimeUnit.SECONDS) + .pollInterval(200, TimeUnit.MILLISECONDS).await("item undef") + .until(() -> getState(TestRules.ITEM_MQTT_TOPIC_FROM_ONLINE_TEST), s -> "UNDEF".equals(s)); + + verifyRuleWasExecuted(TestRules.NAME_MQTT_THING_CHANGED_FROM_ONLINE); + } + @Test public void memberOfGroupReceivedCommand() throws IOException { sendCommand(TestRules.ITEM_SWITCH_GROUP_MEMBER1, JRuleSwitchItem.ON); diff --git a/src/test/java/org/openhab/automation/jrule/rules/integration_test/JRuleITBase.java b/src/test/java/org/openhab/automation/jrule/rules/integration_test/JRuleITBase.java index 5d083903..a93deacf 100644 --- a/src/test/java/org/openhab/automation/jrule/rules/integration_test/JRuleITBase.java +++ b/src/test/java/org/openhab/automation/jrule/rules/integration_test/JRuleITBase.java @@ -312,7 +312,7 @@ private Optional getDoubleState(String itemName) throws IOException, Par return Optional.ofNullable(getState(itemName)).map(Double::parseDouble); } - private String getState(String itemName) throws IOException, ParseException { + protected static String getState(String itemName) throws IOException, ParseException { try (CloseableHttpClient client = HttpClientBuilder.create().build()) { HttpGet request = new HttpGet(String.format("http://%s:%s/rest/items/" + itemName + "/state", getOpenhabHost(), getOpenhabPort())); diff --git a/src/test/java/org/openhab/automation/jrule/rules/user/TestRules.java b/src/test/java/org/openhab/automation/jrule/rules/user/TestRules.java index 0ead8a28..7b07a616 100755 --- a/src/test/java/org/openhab/automation/jrule/rules/user/TestRules.java +++ b/src/test/java/org/openhab/automation/jrule/rules/user/TestRules.java @@ -19,10 +19,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntFunction; import java.util.stream.Collectors; @@ -36,6 +33,9 @@ import org.openhab.automation.jrule.rules.event.JRuleThingEvent; import org.openhab.automation.jrule.rules.event.JRuleTimerEvent; import org.openhab.automation.jrule.rules.value.*; +import org.openhab.automation.jrule.things.JRuleChannel; +import org.openhab.automation.jrule.things.JRuleSubThing; +import org.openhab.automation.jrule.things.JRuleThingRegistry; import org.openhab.automation.jrule.things.JRuleThingStatus; /** @@ -57,6 +57,8 @@ public class TestRules extends JRule { public static final String NAME_EXEC_COMMAND_LINE = "Exec Command Line"; public static final String NAME_MQTT_CHANNEL_TRIGGERED = "Mqtt Channel Triggered"; public static final String NAME_MQTT_THING_CHANGED_TO_OFFLINE = "Mqtt Thing Changed To Offline"; + public static final String NAME_MQTT_THING_CHANGED_FROM_ONLINE = "Mqtt Thing Changed From Online"; + public static final String ITEM_MQTT_TOPIC_FROM_ONLINE_TEST = "ItemToUndef"; public static final String NAME_MEMBER_OF_GROUP_RECEIVED_COMMAND = "Member Of Group Received Command"; public static final String NAME_MEMBER_OF_GROUP_RECEIVED_UPDATE = "Member Of Group Received Update"; public static final String NAME_MEMBER_OF_GROUP_CHANGED = "Member Of Group Changed"; @@ -184,6 +186,24 @@ public void mqttThingChangedToOffline(JRuleThingEvent event) { logInfo("thing '{}' goes '{}'", event.getThing(), event.getStatus()); } + @JRuleName(NAME_MQTT_THING_CHANGED_FROM_ONLINE) + @JRuleWhenThingTrigger(thing = "mqtt:topic:mqtt:fromonlinetest", from = JRuleThingStatus.ONLINE) + public void mqttThingChangedFromOnline(JRuleThingEvent event) { + logInfo("Thing '{}' goes '{}'", event.getThing(), event.getStatus()); + String thing = event.getThing(); + JRuleSubThing mqttTopicThing = JRuleThingRegistry.get(thing, JRuleSubThing.class); + List channels = mqttTopicThing.getChannels(); + logInfo("Channels size: {}", channels.size()); + channels.stream().forEach(channel -> { + Set linkedItems = mqttTopicThing.getLinkedItems(channel); + logInfo("Linked items size: {}", linkedItems.size()); + linkedItems.stream().forEach(item -> { + logInfo("Linked item: {}, setting to UNDEF", item.getName()); + item.postUndefUpdate(); + }); + }); + } + @JRuleName(NAME_MEMBER_OF_GROUP_RECEIVED_COMMAND) @JRuleWhenItemReceivedCommand(item = ITEM_SWITCH_GROUP, memberOf = JRuleMemberOf.All) public synchronized void memberOfGroupReceivedCommand(JRuleItemEvent event) { diff --git a/src/test/resources/docker/conf/items/default.items b/src/test/resources/docker/conf/items/default.items index 3be1dc89..bbc1c897 100644 --- a/src/test/resources/docker/conf/items/default.items +++ b/src/test/resources/docker/conf/items/default.items @@ -87,3 +87,5 @@ Number Number_To_Persist_Future (InfluxDbPersist) Dimmer Dimmer_With_Tags_And_Metadata ["Control", "Light"] { Speech="SetLightState" [ location="Livingroom" ] } + +Number ItemToUndef {channel="mqtt:topic:mqtt:fromonlinetest:number"} diff --git a/src/test/resources/docker/conf/things/mqtt.things b/src/test/resources/docker/conf/things/mqtt.things index ff28a5bb..f6138324 100644 --- a/src/test/resources/docker/conf/things/mqtt.things +++ b/src/test/resources/docker/conf/things/mqtt.things @@ -5,4 +5,8 @@ Bridge mqtt:broker:mqtt [ host="mqtt", port=8666, username="admin", password="ad Type number : number [ stateTopic="number/state" ] Type number : numberTrigger [ stateTopic="number/state", trigger="true" ] } + Thing topic fromonlinetest { + Channels: + Type number : number [ stateTopic="fromonlinetest/state" ] + } } \ No newline at end of file From 6479598e9cf0b0c9afab06fe69410f0a6a793b3e Mon Sep 17 00:00:00 2001 From: Arne Seime Date: Wed, 25 Dec 2024 18:11:51 +0100 Subject: [PATCH 2/4] Fix --- .../openhab/automation/jrule/internal/handler/JRuleHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleHandler.java b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleHandler.java index e09ff78b..b7ec77e3 100644 --- a/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleHandler.java +++ b/src/main/java/org/openhab/automation/jrule/internal/handler/JRuleHandler.java @@ -158,6 +158,7 @@ public JRuleHandler(JRuleConfig config, ItemRegistry itemRegistry, ItemChannelLi final JRuleThingHandler thingHandler = JRuleThingHandler.get(); thingHandler.setThingManager(thingManager); thingHandler.setThingRegistry(thingRegistry); + thingHandler.setItemChannelLinkRegistry(itemChannelLinkRegistry); final JRuleItemHandler itemHandler = JRuleItemHandler.get(); itemHandler.setItemRegistry(itemRegistry); From 3faaddd014ed4d3bd21aa86cc606b4c509d60ff9 Mon Sep 17 00:00:00 2001 From: Arne Seime Date: Thu, 26 Dec 2024 10:33:48 +0100 Subject: [PATCH 3/4] Add mandatory headers --- .../automation/jrule/things/JRuleChannel.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java b/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java index b871bd09..e03f2f8e 100644 --- a/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java +++ b/src/main/java/org/openhab/automation/jrule/things/JRuleChannel.java @@ -1,5 +1,22 @@ +/** + * Copyright (c) 2010-2023 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.automation.jrule.things; +/** + * The {@link JRuleChannel} represents a Thing channel, possibly linked to an Item. + * + * @author Arne Seime - Initial contribution + */ public class JRuleChannel { private String channelUID; From 526a06e4761a222d384d6e1f562e88fcf17c48b1 Mon Sep 17 00:00:00 2001 From: Arne Seime Date: Thu, 26 Dec 2024 10:39:06 +0100 Subject: [PATCH 4/4] Add example using thing channels and undef update --- doc/EXAMPLES.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/EXAMPLES.md b/doc/EXAMPLES.md index 1844b818..60f65cbc 100644 --- a/doc/EXAMPLES.md +++ b/doc/EXAMPLES.md @@ -45,6 +45,7 @@ + [Example 41 - Get Metadata and Tags](#example-41---get-metadata-and-tags) + [Example 42 - Persist future data](#example-42---persist-future-data) + [Example 43 - Creating a rule dynamically using JRuleBuilder](#example-43---creating-a-rule-dynamically-using-jrulebuilder) + + [Example 44 - Setting items linked to a thing to UNDEF when thing goes offline](#example-44---setting-items-linked-to-a-thing-to-undef-when-thing-goes-offline) ### Example 1 - Invoke another item Switch from rule @@ -1125,3 +1126,34 @@ public class DynamicRuleModule extends JRule { } } ``` + +## Example 44 - Setting items linked to a thing to UNDEF when thing goes offline + +Use case: No longer be fooled by outdated item states when a thing goes offline + +```java +package org.openhab.automation.jrule.rules.user; + +import java.util.List; + +import org.openhab.automation.jrule.items.JRuleItem; +import org.openhab.automation.jrule.rules.JRule; +import org.openhab.automation.jrule.rules.JRuleName; +import org.openhab.automation.jrule.rules.JRuleWhenThingTrigger; +import org.openhab.automation.jrule.rules.event.JRuleThingEvent; +import org.openhab.automation.jrule.things.JRuleAbstractThing; +import org.openhab.automation.jrule.things.JRuleChannel; +import org.openhab.automation.jrule.things.JRuleThingRegistry; +import org.openhab.automation.jrule.things.JRuleThingStatus; + +public class ChannelsToUndefWhenTingOffline extends JRule { + @JRuleName("Device monitoring - Set linked items to UNDEF if thing goes offline") + @JRuleWhenThingTrigger(thing = "*", from = JRuleThingStatus.ONLINE) + public void setChannelsToUndefWhenThingOffline(JRuleThingEvent thingEvent) { + String thingUID = thingEvent.getThing(); + JRuleAbstractThing thing = JRuleThingRegistry.get(thingUID, JRuleAbstractThing.class); + List channels = thing.getChannels(); + channels.forEach(channel -> thing.getLinkedItems(channel).forEach(JRuleItem::postUndefUpdate)); + } +} +```