diff --git a/bundles/org.openhab.automation.jsscripting/README.md b/bundles/org.openhab.automation.jsscripting/README.md index 1ba278bc2..e3b8e2e04 100644 --- a/bundles/org.openhab.automation.jsscripting/README.md +++ b/bundles/org.openhab.automation.jsscripting/README.md @@ -18,7 +18,7 @@ to common openHAB functionality within rules including items, things, actions, l - [Timers](#timers) - [Paths](#paths) - [Deinitialization Hook](#deinitialization-hook) -- [`SCRIPT` Transformation](#script-transformation) +- [`JS` Transformation](#js-transformation) - [Standard Library](#standard-library) - [Items](#items) - [Things](#things) @@ -204,7 +204,7 @@ JS Scripting provides access to the global `setTimeout`, `setInterval`, `clearTi When a script is unloaded, all created timeouts and intervals are automatically cancelled. -#### 'setTimeout' +#### `setTimeout` The global [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) method sets a timer which executes a function once the timer expires. `setTimeout()` returns a `timeoutId` (a positive integer value) which identifies the timer created. @@ -217,6 +217,8 @@ var timeoutId = setTimeout(callbackFunction, delay); The global [`clearTimeout(timeoutId)`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) method cancels a timeout previously established by calling `setTimeout()`. +If you need a more verbose way of creating timers, consider to use [`createTimer`](#createtimer) instead. + #### `setInterval` The global [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) method repeatedly calls a function, with a fixed time delay between each call. @@ -290,28 +292,31 @@ require('@runtime').lifecycleTracker.addDisposeHook(() => { }); ``` -## `SCRIPT` Transformation +## `JS` Transformation -openHAB provides several [data transformation services](https://www.openhab.org/addons/#transform) as well as the `SCRIPT` transformation, that is available from the framework and needs no additional installation. +openHAB provides several [data transformation services](https://www.openhab.org/addons/#transform) as well as the script transformations, that are available from the framework and need no additional installation. It allows transforming values using any of the available scripting languages, which means JavaScript Scripting is supported as well. -See the [transformation docs](https://openhab.org/docs/configuration/transformations.html#script-transformation) for more general information on the usage of `SCRIPT` transformation. +See the [transformation docs](https://openhab.org/docs/configuration/transformations.html#script-transformation) for more general information on the usage of script transformations. -Use the `SCRIPT` transformation with JavaScript Scripting by: +Use JavaScript Scripting as script transformation by: -1. Creating a script in the `$OPENHAB_CONF/transform` folder with the `.script` extension. +1. Creating a script in the `$OPENHAB_CONF/transform` folder with the `.js` extension. The script should take one argument `input` and return a value that supports `toString()` or `null`: ```javascript (function(data) { - // Do some data transformation here - return data; + // Do some data transformation here, e.g. + return "String has" + data.length + "characters"; })(input); ``` -2. Using `SCRIPT(js:.script):%s` as the transformation profile, e.g. on an Item. -3. Passing parameters is also possible by using a URL like syntax: `SCRIPT(js:.script?arg=value):%s`. +2. Using `JS(.js):%s` as Item state transformation. +3. Passing parameters is also possible by using a URL like syntax: `JS(.js?arg=value)`. Parameters are injected into the script and can be referenced like variables. +Simple transformations can aso be given as an inline script: `JS(|...)`, e.g. `JS(|"String has " + input.length + "characters")`. +It should start with the `|` character, quotes within the script may need to be escaped with a backslash `\` when used with another quoted string as in text configurations. + ## Standard Library Full documentation for the openHAB JavaScript library can be found at [openhab-js](https://openhab.github.io/openhab-js). @@ -355,7 +360,7 @@ Calling `getItem(...)` or `...` returns an `Item` object with the following prop - .label ⇒ `string` - .state ⇒ `string` - .numericState ⇒ `number|null`: State as number, if state can be represented as number, or `null` if that's not the case - - .quantityState ⇒ [`Quantity|null`](#quantity): Item state as Quantity or `null` if state is not Quantity-compatible + - .quantityState ⇒ [`Quantity|null`](#quantity): Item state as Quantity or `null` if state is not Quantity-compatible or without unit - .rawState ⇒ `HostState` - .members ⇒ `Array[Item]` - .descendents ⇒ `Array[Item]` @@ -606,7 +611,7 @@ You can also create timers using the [native JS methods for timer creation](#tim Sometimes, using `setTimer` is much faster and easier, but other times, you need the versatility that `createTimer` provides. Keep in mind that you should somehow manage the timers you create using `createTimer`, otherwise you could end up with unmanageable timers running until you restart openHAB. -A possible solution is to store all timers in an array and cancel all timers in the [Deinitialization Hook](#deinitialization-hook). +A possible solution is to store all timers in the [private cache](#cache) and let openHAB automatically cancel them when the script is unloaded and the cache is cleared. ##### `createTimer` @@ -704,7 +709,7 @@ The cache namespace provides both a private and a shared cache that can be used The private cache can only be accessed by the same script and is cleared when the script is unloaded. You can use it to e.g. store timers or counters between subsequent runs of that script. -When a script is unloaded and its cache is cleared, all timers (see [ScriptExecution Actions](#scriptexecution-actions)) stored in its private cache are cancelled. +When a script is unloaded and its cache is cleared, all timers (see [`createTimer`](#createtimer)) stored in its private cache are automatically cancelled. The shared cache is shared across all rules and scripts, it can therefore be accessed from any automation language. The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded. @@ -940,6 +945,8 @@ qty = Quantity('1 m^2 s^2'); // / is required qty = Quantity('1 m2/s2'); // ^ is required ``` +Note: It is possible to create a unit-less (without unit) Quantity, however there is no advantage over using a `number` instead. + #### Conversion It is possible to convert a `Quantity` to a new `Quantity` with a different unit or to get a `Quantity`'s amount as integer or float: @@ -1102,7 +1109,7 @@ Operations and conditions can also optionally take functions: ```javascript rules.when().item("F1_light").changed().then(event => { - console.log(event); + console.log(event); }).build("Test Rule", "My Test Rule"); ``` diff --git a/bundles/org.openhab.automation.jsscripting/pom.xml b/bundles/org.openhab.automation.jsscripting/pom.xml index 76f835fb9..3cb3aaabc 100644 --- a/bundles/org.openhab.automation.jsscripting/pom.xml +++ b/bundles/org.openhab.automation.jsscripting/pom.xml @@ -24,7 +24,7 @@ 22.0.0.2 ${project.version} - openhab@4.2.1 + openhab@4.3.0 diff --git a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/GraalJSScriptEngineFactory.java b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/GraalJSScriptEngineFactory.java index 7302b517e..ac41b22bd 100644 --- a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/GraalJSScriptEngineFactory.java +++ b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/GraalJSScriptEngineFactory.java @@ -14,7 +14,6 @@ package org.openhab.automation.jsscripting.internal; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.script.ScriptEngine; @@ -49,15 +48,13 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory { private static final GraalJSEngineFactory factory = new GraalJSEngineFactory(); - public static final String MIME_TYPE = "application/javascript"; - private static final String ALIAS = "graaljs"; - private static final List SCRIPT_TYPES = createScriptTypes(); + private static final List scriptTypes = createScriptTypes(); private static List createScriptTypes() { // Add those for backward compatibility (existing scripts may rely on those MIME types) - List backwardCompat = List.of("application/javascript;version=ECMAScript-2021", ALIAS); + List backwardCompat = List.of("application/javascript;version=ECMAScript-2021", "graaljs"); return Stream.of(factory.getMimeTypes(), factory.getExtensions(), backwardCompat).flatMap(List::stream) - .collect(Collectors.toUnmodifiableList()); + .toList(); } private boolean injectionEnabled = true; @@ -76,7 +73,7 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory { @Override public List getScriptTypes() { - return SCRIPT_TYPES; + return scriptTypes; } @Override @@ -86,7 +83,7 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory { @Override public @Nullable ScriptEngine createScriptEngine(String scriptType) { - if (!SCRIPT_TYPES.contains(scriptType)) { + if (!scriptTypes.contains(scriptType)) { return null; } return new DebuggingGraalScriptEngine<>(new OpenhabGraalJSScriptEngine(injectionEnabled, useIncludedLibrary, diff --git a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java index 91132e4f4..5efdc8199 100644 --- a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java +++ b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileAttribute; import java.time.Duration; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.Collections; import java.util.Map; @@ -113,6 +114,13 @@ public class OpenhabGraalJSScriptEngine v -> v.hasMember("minusDuration") && v.hasMember("toNanos"), v -> Duration.ofNanos(v.invokeMember("toNanos").asLong()), HostAccess.TargetMappingPrecedence.LOW) + // Translate JS-Joda Instant to java.time.Instant + .targetTypeMapping(Value.class, Instant.class, + // picking two members to check as Instant has many common function names + v -> v.hasMember("toEpochMilli") && v.hasMember("epochSecond"), + v -> Instant.ofEpochMilli(v.invokeMember("toEpochMilli").asLong()), + HostAccess.TargetMappingPrecedence.LOW) + // Translate openhab-js Item to org.openhab.core.items.Item .targetTypeMapping(Value.class, Item.class, v -> v.hasMember("rawItem"), v -> v.getMember("rawItem").as(Item.class), HostAccess.TargetMappingPrecedence.LOW) diff --git a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSDependencyTracker.java b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSDependencyTracker.java index f744537fd..08a43b7d7 100644 --- a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSDependencyTracker.java +++ b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSDependencyTracker.java @@ -24,8 +24,6 @@ import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Tracks JS module dependencies @@ -37,8 +35,6 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class JSDependencyTracker extends AbstractScriptDependencyTracker { - private final Logger logger = LoggerFactory.getLogger(JSDependencyTracker.class); - private static final String LIB_PATH = String.join(File.separator, "automation", "js", "node_modules"); @Activate diff --git a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSScriptFileWatcher.java b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSScriptFileWatcher.java index 8e5730909..239018265 100644 --- a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSScriptFileWatcher.java +++ b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSScriptFileWatcher.java @@ -17,7 +17,6 @@ import java.nio.file.Path; import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.automation.jsscripting.internal.GraalJSScriptEngineFactory; import org.openhab.core.automation.module.script.ScriptDependencyTracker; import org.openhab.core.automation.module.script.ScriptEngineManager; import org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher; @@ -49,11 +48,10 @@ public class JSScriptFileWatcher extends AbstractScriptFileWatcher { @Override protected Optional getScriptType(Path scriptFilePath) { - if (!scriptFilePath.startsWith(getWatchPath().resolve("node_modules")) - && "js".equals(super.getScriptType(scriptFilePath).orElse(null))) { - return Optional.of(GraalJSScriptEngineFactory.MIME_TYPE); - } else { - return Optional.empty(); + String scriptType = super.getScriptType(scriptFilePath).orElse(null); + if (!scriptFilePath.startsWith(getWatchPath().resolve("node_modules")) && ("js".equals(scriptType))) { + return Optional.of(scriptType); } + return Optional.empty(); } }