diff --git a/bundles/org.openhab.core.model.script/bnd.bnd b/bundles/org.openhab.core.model.script/bnd.bnd
index a03f2dda384..4d49f171a14 100644
--- a/bundles/org.openhab.core.model.script/bnd.bnd
+++ b/bundles/org.openhab.core.model.script/bnd.bnd
@@ -19,6 +19,7 @@ Export-Package: org.openhab.core.model.script,\
org.openhab.core.model.script.validation
Import-Package: \
org.openhab.core.audio,\
+ org.openhab.core.automation,\
org.openhab.core.automation.module.script.action,\
org.openhab.core.automation.module.script.rulesupport.shared,\
org.openhab.core.common.registry,\
diff --git a/bundles/org.openhab.core.model.script/pom.xml b/bundles/org.openhab.core.model.script/pom.xml
index b666248ae38..c63df47a5cc 100644
--- a/bundles/org.openhab.core.model.script/pom.xml
+++ b/bundles/org.openhab.core.model.script/pom.xml
@@ -50,6 +50,11 @@
org.openhab.core.io.net
${project.version}
+
+ org.openhab.core.bundles
+ org.openhab.core.automation
+ ${project.version}
+
org.openhab.core.bundles
org.openhab.core.automation.module.script
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java
index 0ba56d99ec0..fd0348bd8d8 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java
@@ -16,8 +16,12 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.automation.RuleManager;
+import org.openhab.core.automation.RuleRegistry;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.ItemRegistry;
+import org.openhab.core.items.MetadataRegistry;
import org.openhab.core.model.core.ModelRepository;
import org.openhab.core.model.script.engine.ScriptEngine;
import org.openhab.core.model.script.engine.action.ActionService;
@@ -50,6 +54,9 @@ public class ScriptServiceUtil {
private final ThingRegistry thingRegistry;
private final EventPublisher eventPublisher;
private final ModelRepository modelRepository;
+ private final MetadataRegistry metadataRegistry;
+ private final RuleRegistry ruleRegistry;
+ private volatile @Nullable RuleManager ruleManager;
private final Scheduler scheduler;
private final AtomicReference scriptEngine = new AtomicReference<>();
@@ -59,11 +66,14 @@ public class ScriptServiceUtil {
@Activate
public ScriptServiceUtil(final @Reference ItemRegistry itemRegistry, final @Reference ThingRegistry thingRegistry,
final @Reference EventPublisher eventPublisher, final @Reference ModelRepository modelRepository,
+ final @Reference MetadataRegistry metadataRegistry, final @Reference RuleRegistry ruleRegistry,
final @Reference Scheduler scheduler) {
this.itemRegistry = itemRegistry;
this.thingRegistry = thingRegistry;
this.eventPublisher = eventPublisher;
this.modelRepository = modelRepository;
+ this.metadataRegistry = metadataRegistry;
+ this.ruleRegistry = ruleRegistry;
this.scheduler = scheduler;
if (instance != null) {
@@ -73,6 +83,15 @@ public ScriptServiceUtil(final @Reference ItemRegistry itemRegistry, final @Refe
logger.debug("ScriptServiceUtil started");
}
+ @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
+ void setRuleManager(RuleManager ruleManager) {
+ this.ruleManager = ruleManager;
+ }
+
+ void unsetRuleManager(RuleManager ruleManager) {
+ this.ruleManager = null;
+ }
+
@Deactivate
public void deactivate() {
logger.debug("ScriptServiceUtil stopped");
@@ -91,6 +110,10 @@ public ItemRegistry getItemRegistryInstance() {
return itemRegistry;
}
+ public static ThingRegistry getThingRegistry() {
+ return getInstance().thingRegistry;
+ }
+
public ThingRegistry getThingRegistryInstance() {
return thingRegistry;
}
@@ -107,6 +130,30 @@ public ModelRepository getModelRepositoryInstance() {
return modelRepository;
}
+ public static MetadataRegistry getMetadataRegistry() {
+ return getInstance().metadataRegistry;
+ }
+
+ public MetadataRegistry getMetadataRegistryInstance() {
+ return metadataRegistry;
+ }
+
+ public static RuleRegistry getRuleRegistry() {
+ return getInstance().ruleRegistry;
+ }
+
+ public RuleRegistry getRuleRegistryInstance() {
+ return ruleRegistry;
+ }
+
+ public @Nullable static RuleManager getRuleManager() {
+ return getInstance().ruleManager;
+ }
+
+ public @Nullable RuleManager getRuleManagerInstance() {
+ return ruleManager;
+ }
+
public static Scheduler getScheduler() {
return getInstance().scheduler;
}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java
new file mode 100644
index 00000000000..390a26496b1
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010-2026 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.core.model.script.actions;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.items.Item;
+import org.openhab.core.items.Metadata;
+
+/**
+ * {@link ItemExtensions} provides DSL access to things like OSGi instances, system registries and the ability to run
+ * other
+ * rules.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class ItemExtensions {
+
+ @NonNullByDefault({})
+ public static void addMetadata(Item item, String namespace, String value) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ Items.addMetadata(item.getName(), namespace, value, (String) null);
+ }
+
+ @NonNullByDefault({})
+ public static void addMetadata(Item item, String namespace, String value, Object... configuration) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ Items.addMetadata(item.getName(), namespace, value, parseObjectArray(configuration));
+ }
+
+ @NonNullByDefault({})
+ public static void addMetadata(Item item, String namespace, String value,
+ @Nullable Map<@NonNull String, @NonNull Object> configuration) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ Items.addMetadata(item.getName(), namespace, value, configuration);
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata getMetadata(Item item, String namespace) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return Items.getMetadata(item.getName(), namespace);
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata removeMetadata(Item item, String namespace) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return Items.removeMetadata(item.getName(), namespace);
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return Items.updateMetadata(item.getName(), namespace, value);
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value,
+ Object... configuration) {
+ return Items.updateMetadata(item.getName(), namespace, value, parseObjectArray(configuration));
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value,
+ @Nullable Map<@NonNull String, @NonNull Object> configuration) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return Items.updateMetadata(item.getName(), namespace, value);
+ }
+
+ private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException {
+ if (objects == null || objects.length == 0) {
+ return Map.of();
+ }
+ if ((objects.length % 2) != 0) {
+ throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')');
+ }
+ Map result = new LinkedHashMap<>();
+ for (int i = 0; i < objects.length; i += 2) {
+ if (objects[i] instanceof String key) {
+ result.put(key, objects[i + 1]);
+ } else {
+ throw new IllegalArgumentException("Keys must be strings: " + objects[i]);
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java
new file mode 100644
index 00000000000..d54e2766e0d
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2010-2026 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.core.model.script.actions;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.common.registry.ManagedProvider;
+import org.openhab.core.items.Item;
+import org.openhab.core.items.Metadata;
+import org.openhab.core.items.MetadataKey;
+import org.openhab.core.items.MetadataProvider;
+import org.openhab.core.model.script.ScriptServiceUtil;
+
+/**
+ * {@link Items} provides DSL access to things like OSGi instances, system registries and the ability to run other
+ * rules.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Items {
+
+ public static @Nullable Item getItem(String itemName) {
+ return ScriptServiceUtil.getItemRegistry().get(itemName);
+ }
+
+ /**
+ *
+ * @param itemName
+ * @param namespace
+ * @param value
+ * @throws IllegalArgumentException If either value is {@code null} or {@code namespace} or
+ * {@code itemName} is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no ManagedProvider is available.
+ */
+ @NonNullByDefault({})
+ public static void addMetadata(String itemName, String namespace, String value) {
+ addMetadata(itemName, namespace, value, (String) null);
+ }
+
+ @NonNullByDefault({})
+ public static void addMetadata(String itemName, String namespace, String value, Object... configuration) {
+ addMetadata(itemName, namespace, value, parseObjectArray(configuration));
+ }
+
+ /**
+ *
+ * @param itemName
+ * @param namespace
+ * @param value
+ * @param configuration
+ * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null} or if
+ * {@code namespace} or {@code itemName} is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no ManagedProvider is available.
+ */
+ @NonNullByDefault({})
+ public static void addMetadata(String itemName, String namespace, String value,
+ @Nullable Map<@NonNull String, @NonNull Object> configuration) {
+ if (itemName == null) {
+ throw new IllegalArgumentException("itemName cannot be null");
+ }
+ if (namespace == null) {
+ throw new IllegalArgumentException("namespace cannot be null");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("value cannot be null");
+ }
+ ScriptServiceUtil.getMetadataRegistry()
+ .add(new Metadata(new MetadataKey(namespace, itemName), value, configuration));
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata getMetadata(String itemName, String namespace) {
+ if (itemName == null) {
+ throw new IllegalArgumentException("itemName cannot be null");
+ }
+ if (namespace == null) {
+ throw new IllegalArgumentException("namespace cannot be null");
+ }
+ return ScriptServiceUtil.getMetadataRegistry().get(new MetadataKey(namespace, itemName));
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata removeMetadata(String itemName, String namespace) {
+ if (itemName == null) {
+ throw new IllegalArgumentException("itemName cannot be null");
+ }
+ if (namespace == null) {
+ throw new IllegalArgumentException("namespace cannot be null");
+ }
+ return ScriptServiceUtil.getMetadataRegistry().remove(new MetadataKey(namespace, itemName));
+ }
+
+ /**
+ *
+ * @param namespace
+ * @param itemName
+ * @param value
+ * @throws IllegalArgumentException If either value is {@code null} or {@code namespace} or
+ * {@code itemName} is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no ManagedProvider is available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value) {
+ return updateMetadata(itemName, namespace, value, (Map) null);
+ }
+
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value,
+ Object... configuration) {
+ return updateMetadata(itemName, namespace, value, parseObjectArray(configuration));
+ }
+
+ /**
+ *
+ * @param itemName
+ * @param namespace
+ * @param value
+ * @param configuration
+ * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null} or if
+ * {@code namespace} or {@code itemName} is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no ManagedProvider is available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value,
+ @Nullable Map<@NonNull String, @NonNull Object> configuration) {
+ if (itemName == null) {
+ throw new IllegalArgumentException("itemName cannot be null");
+ }
+ if (namespace == null) {
+ throw new IllegalArgumentException("namespace cannot be null");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("value cannot be null");
+ }
+ return ScriptServiceUtil.getMetadataRegistry()
+ .update(new Metadata(new MetadataKey(namespace, itemName), value, configuration));
+ }
+
+ private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException {
+ if (objects == null || objects.length == 0) {
+ return Map.of();
+ }
+ if ((objects.length % 2) != 0) {
+ throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')');
+ }
+ Map result = new LinkedHashMap<>();
+ for (int i = 0; i < objects.length; i += 2) {
+ if (objects[i] instanceof String key) {
+ result.put(key, objects[i + 1]);
+ } else {
+ throw new IllegalArgumentException("Keys must be strings: " + objects[i]);
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java
index e252bf1c0fb..ed55132aa40 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java
@@ -32,15 +32,15 @@ public class Ping {
* is specified (which is the default when configuring just the host), a
* regular ping is issued. If other ports are specified we try open a new
* Socket with the given timeout.
- *
+ *
* @param host
* @param port
* @param timeout
- *
+ *
* @return true when host is reachable on port within the given
* timeout and false in all other
* cases.
- *
+ *
* @throws IOException
* @throws SocketTimeoutException
*/
@@ -53,10 +53,10 @@ public static boolean checkVitality(String host, int port, int timeout) throws I
} else {
SocketAddress socketAddress = new InetSocketAddress(host, port);
- Socket socket = new Socket();
- socket.connect(socketAddress, timeout);
- success = true;
- socket.close();
+ try (Socket socket = new Socket()) {
+ socket.connect(socketAddress, timeout);
+ success = true;
+ }
}
}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java
new file mode 100644
index 00000000000..7a7e53c1f2e
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010-2026 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.core.model.script.actions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.automation.RuleRegistry;
+import org.openhab.core.items.ItemRegistry;
+import org.openhab.core.items.MetadataRegistry;
+import org.openhab.core.model.script.ScriptServiceUtil;
+import org.openhab.core.model.script.engine.action.ActionDoc;
+import org.openhab.core.thing.ThingRegistry;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * {@link Registries} provides DSL access to things like OSGi instances, system registries and the ability to run other
+ * rules.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Registries {
+
+ /**
+ * Retrieve an OSGi instance of the specified {@link Class}, if it exists.
+ *
+ * @param the class type.
+ * @param clazz the class of the instance to get.
+ * @return The instance or {@code null} if the instance wasn't found.
+ */
+ @ActionDoc(text = "acquire an OSGi instance")
+ public static @Nullable T getInstance(Class clazz) {
+ Bundle bundle = FrameworkUtil.getBundle(clazz);
+ if (bundle != null) {
+ BundleContext bc = bundle.getBundleContext();
+ if (bc != null) {
+ ServiceReference ref = bc.getServiceReference(clazz);
+ if (ref != null) {
+ return bc.getService(ref);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return The {@link ThingRegistry}.
+ */
+ @ActionDoc(text = "get the thing registry")
+ public static ThingRegistry getThingRegistry() {
+ return ScriptServiceUtil.getThingRegistry();
+ }
+
+ /**
+ * @return The {@link MetadataRegistry}.
+ */
+ @ActionDoc(text = "get the metadata registry")
+ public static MetadataRegistry getMetadataRegistry() {
+ return ScriptServiceUtil.getMetadataRegistry();
+ }
+
+ /**
+ * @return The {@link ItemRegistry}.
+ */
+ @ActionDoc(text = "get the item registry")
+ public static ItemRegistry getItemRegistry() {
+ return ScriptServiceUtil.getItemRegistry();
+ }
+
+ /**
+ * @return The {@link RuleRegistry}.
+ */
+ @ActionDoc(text = "get the rule registry")
+ public static RuleRegistry getRuleRegistry() {
+ return ScriptServiceUtil.getRuleRegistry();
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java
new file mode 100644
index 00000000000..d0efe331b83
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2010-2026 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.core.model.script.actions;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.automation.Rule;
+import org.openhab.core.automation.RuleManager;
+import org.openhab.core.model.script.ScriptServiceUtil;
+
+/**
+ * {@link RuleExtensions} provides DSL access to things like OSGi instances, system registries and the ability to run
+ * other
+ * rules.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class RuleExtensions {
+
+ /**
+ * Run the rule with the specified UID.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID);
+ }
+
+ public static Map run(Rule rule, boolean considerConditions) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, null);
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, Map context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, false, context);
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context, while optionally taking conditions into
+ * account.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param considerConditions {@code true} to not run the rule if its conditions don't qualify.
+ * @param context the pairs of {@link String}s and {@link Object}s that constitutes the context. Must be in pairs,
+ * the first is the key, the second is the value.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, boolean considerConditions, Object... context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, parseObjectArray(context));
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context, while optionally taking conditions into
+ * account.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param considerConditions {@code true} to not run the rule if its conditions don't qualify.
+ * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, boolean considerConditions,
+ @Nullable Map context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, context);
+ }
+
+ /**
+ * Check whether the specified rule is enabled.
+ *
+ * @param ruleUID the UID of the rule to check.
+ * @return {@code true} if the rule is enabled, {@code false} otherwise.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static boolean isEnabled(Rule rule) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ String ruleUID = rule.getUID();
+ Boolean result = ruleManager.isEnabled(ruleUID);
+ if (result == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return result.booleanValue();
+ }
+
+ /**
+ * Set whether the specified rule is enabled.
+ *
+ * @param ruleUID the UID of the rule to enable or disable.
+ * @param enabled {@code true} to enable the rule, {@code false} to disable the rule.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static void setEnabled(Rule rule, boolean enabled) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ ruleManager.setEnabled(rule.getUID(), enabled);
+ }
+
+ private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException {
+ if (objects == null || objects.length == 0) {
+ return Map.of();
+ }
+ if ((objects.length % 2) != 0) {
+ throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')');
+ }
+ Map result = new LinkedHashMap<>();
+ for (int i = 0; i < objects.length; i += 2) {
+ if (objects[i] instanceof String key) {
+ result.put(key, objects[i + 1]);
+ } else {
+ throw new IllegalArgumentException("Keys must be strings: " + objects[i]);
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java
new file mode 100644
index 00000000000..4d98c70f2a0
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2010-2026 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.core.model.script.actions;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.automation.Rule;
+import org.openhab.core.automation.RuleManager;
+import org.openhab.core.model.script.ScriptServiceUtil;
+import org.openhab.core.model.script.engine.action.ActionDoc;
+
+/**
+ * {@link Rules} provides DSL access to things like OSGi instances, system registries and the ability to run other
+ * rules.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Rules {
+
+ public static @Nullable Rule getRule(String ruleUID) {
+ return ScriptServiceUtil.getRuleRegistry().get(ruleUID);
+ }
+
+ /**
+ * Run the rule with the specified UID.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "run the rule with the specified UID")
+ public static Map runRule(String ruleUID) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID);
+ }
+
+ @ActionDoc(text = "run the rule with the specified UID and condition evaluation setting")
+ public static Map runRule(String ruleUID, boolean considerConditions) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, null);
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "run the rule with the specified UID and context")
+ public static Map runRule(String ruleUID, Map context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, false, context);
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context, while optionally taking conditions into
+ * account.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param considerConditions {@code true} to not run the rule if its conditions don't qualify.
+ * @param context the pairs of {@link String}s and {@link Object}s that constitutes the context. Must be in pairs,
+ * the first is the key, the second is the value.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "run the rule with the specified UID, condition evaluation setting and context")
+ public static Map runRule(String ruleUID, boolean considerConditions, Object... context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, parseObjectArray(context));
+ }
+
+ /**
+ * Run the rule with the specified UID with the specified context, while optionally taking conditions into
+ * account.
+ *
+ * @param ruleUID the UID of the rule to run.
+ * @param considerConditions {@code true} to not run the rule if its conditions don't qualify.
+ * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "run the rule with the specified UID, condition evaluation setting and context")
+ public static Map runRule(String ruleUID, boolean considerConditions, Map context) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ if (ruleManager.getStatus(ruleUID) == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return ruleManager.runNow(ruleUID, considerConditions, context);
+ }
+
+ /**
+ * Check whether the specified rule is enabled.
+ *
+ * @param ruleUID the UID of the rule to check.
+ * @return {@code true} if the rule is enabled, {@code false} otherwise.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "check whether the specified rule rule is enabled")
+ public static boolean isRuleEnabled(String ruleUID) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ Boolean result = ruleManager.isEnabled(ruleUID);
+ if (result == null) {
+ throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist");
+ }
+ return result.booleanValue();
+ }
+
+ /**
+ * Set whether the specified rule is enabled.
+ *
+ * @param ruleUID the UID of the rule to enable or disable.
+ * @param enabled {@code true} to enable the rule, {@code false} to disable the rule.
+ * @throws IllegalArgumentException If a rule with the specified UID doesn't exist.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ @ActionDoc(text = "set whether the specified rule is enabled")
+ public static void setRuleEnabled(String ruleUID, boolean enabled) {
+ RuleManager ruleManager = ScriptServiceUtil.getRuleManager();
+ if (ruleManager == null) {
+ throw new IllegalStateException("RuleManager doesn't exist");
+ }
+ ruleManager.setEnabled(ruleUID, enabled);
+ }
+
+ /**
+ * @return The {@link RuleManager} or {@code null}.
+ */
+ @ActionDoc(text = "get the rule manager")
+ public static @Nullable RuleManager getRuleManager() {
+ return ScriptServiceUtil.getRuleManager();
+ }
+
+ private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException {
+ if (objects == null || objects.length == 0) {
+ return Map.of();
+ }
+ if ((objects.length % 2) != 0) {
+ throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')');
+ }
+ Map result = new LinkedHashMap<>();
+ for (int i = 0; i < objects.length; i += 2) {
+ if (objects[i] instanceof String key) {
+ result.put(key, objects[i + 1]);
+ } else {
+ throw new IllegalArgumentException("Keys must be strings: " + objects[i]);
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java
index d8c68693d23..93405f09f1b 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java
@@ -32,8 +32,13 @@
import org.openhab.core.model.script.actions.CoreUtil;
import org.openhab.core.model.script.actions.Exec;
import org.openhab.core.model.script.actions.HTTP;
+import org.openhab.core.model.script.actions.ItemExtensions;
+import org.openhab.core.model.script.actions.Items;
import org.openhab.core.model.script.actions.Log;
import org.openhab.core.model.script.actions.Ping;
+import org.openhab.core.model.script.actions.Registries;
+import org.openhab.core.model.script.actions.RuleExtensions;
+import org.openhab.core.model.script.actions.Rules;
import org.openhab.core.model.script.actions.ScriptExecution;
import org.openhab.core.model.script.actions.Transformation;
import org.openhab.core.model.script.engine.IActionServiceProvider;
@@ -76,6 +81,8 @@ protected List> getExtensionClasses() {
result.add(Ping.class);
result.add(Transformation.class);
result.add(ScriptExecution.class);
+ result.add(ItemExtensions.class);
+ result.add(RuleExtensions.class);
result.add(URLEncoder.class);
result.addAll(getActionClasses());
@@ -92,6 +99,9 @@ protected List> getStaticImportClasses() {
result.add(Ping.class);
result.add(Transformation.class);
result.add(ScriptExecution.class);
+ result.add(Items.class);
+ result.add(Registries.class);
+ result.add(Rules.class);
result.add(URLEncoder.class);
result.add(CoreUtil.class);