]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jsscripting] Add type translation for `Instant` & Minor improvements (#14984)
authorFlorian Hotze <florianh_dev@icloud.com>
Sat, 13 May 2023 10:25:06 +0000 (12:25 +0200)
committerGitHub <noreply@github.com>
Sat, 13 May 2023 10:25:06 +0000 (12:25 +0200)
* [jsscripting] Minor code improvements
* [jsscripting] Add type mapping for `Instant`
* [jsscripting] Upgrade openhab-js to 4.3.0

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
bundles/org.openhab.automation.jsscripting/README.md
bundles/org.openhab.automation.jsscripting/pom.xml
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/GraalJSScriptEngineFactory.java
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSDependencyTracker.java
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/fs/watch/JSScriptFileWatcher.java

index 1ba278bc248317ffd6c324d973aae455b8abf630..e3b8e2e04b69fce94c032a689d34b22fbe5bfdef 100644 (file)
@@ -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:<scriptname>.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:<scriptname>.script?arg=value):%s`.
+2. Using `JS(<scriptname>.js):%s` as Item state transformation.
+3. Passing parameters is also possible by using a URL like syntax: `JS(<scriptname>.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");
 ```
 
index 76f835fb9855428e6c9e9a635dd0856f07aa6896..3cb3aaabc052b653cd0e40673a77fa2d3e9a94b5 100644 (file)
@@ -24,7 +24,7 @@
     </bnd.importpackage>
     <graal.version>22.0.0.2</graal.version> <!-- DO NOT UPGRADE: 22.0.0.2 is the latest version working on armv7l / OpenJDK 11.0.16 & armv7l / Zulu 17.0.5+8 -->
     <oh.version>${project.version}</oh.version>
-    <ohjs.version>openhab@4.2.1</ohjs.version>
+    <ohjs.version>openhab@4.3.0</ohjs.version>
   </properties>
 
   <build>
index 7302b517ed741eeef63c9083d573bbadc5c65c1a..ac41b22bddb600f61f2ec4c15bc0c4f421baff2b 100644 (file)
@@ -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<String> SCRIPT_TYPES = createScriptTypes();
+    private static final List<String> scriptTypes = createScriptTypes();
 
     private static List<String> createScriptTypes() {
         // Add those for backward compatibility (existing scripts may rely on those MIME types)
-        List<String> backwardCompat = List.of("application/javascript;version=ECMAScript-2021", ALIAS);
+        List<String> 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<String> 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,
index 91132e4f401b7fa32a26efe404d27bd7a979261d..5efdc81992d26f526233ac2c24cfaf37f6ef3b38 100644 (file)
@@ -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)
index f744537fd93fc12853a7fedb4d350ce63147f2ea..08a43b7d769c5785b1ad5fbb13d75f46491c0ed1 100644 (file)
@@ -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
index 8e573090971fadeeaf6234f0c3c938873edf71c9..2390182658a9dd5f4a039f9b07f7944b34692120 100644 (file)
@@ -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<String> 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();
     }
 }