]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jsscripting] Upgrade openhab-js & Remove SharedCache (#13908)
authorFlorian Hotze <florianh_dev@icloud.com>
Sun, 11 Dec 2022 14:25:39 +0000 (15:25 +0100)
committerGitHub <noreply@github.com>
Sun, 11 Dec 2022 14:25:39 +0000 (15:25 +0100)
Upgrades the included openhab-js version to 3.1.0, which uses the new
caches from core (introduced in
https://github.com/openhab/openhab-core/pull/2887) and provides many
doc improvements.

Removes the SharedCache from the addon because this functionality is
now provided by core (see
https://github.com/openhab/openhab-core/pull/2887).

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/scope/SharedCache.java [deleted file]

index 12c46e083bf5cb2434af3a3ff8aca6bd6c547a1c..0d84bd154103e2f93dacd6a8b0d8719bfe97a11e 100644 (file)
@@ -82,7 +82,7 @@ actions.NotificationAction.sendNotification("romeo@montague.org", "Balcony door
 Querying the status of a thing
 
 ```javascript
-const thingStatusInfo = actions.Things.getThingStatusInfo("zwave:serial_zstick:512");
+var thingStatusInfo = actions.Things.getThingStatusInfo("zwave:serial_zstick:512");
 console.log("Thing status",thingStatusInfo.getStatus());
 ```
 
@@ -124,32 +124,34 @@ console.log(event.itemState.toString() == "test") // OK
 
 ## Scripting Basics
 
-The openHAB JSScripting runtime attempts to provide a familiar environment to Javascript developers.
+The openHAB JavaScript Scripting runtime attempts to provide a familiar environment to JavaScript developers.
 
 ### Require
 
 Scripts may include standard NPM based libraries by using CommonJS `require`.
 The library search will look in the path `automation/js/node_modules` in the user configuration directory.
+See [libraries](#libraries) for more information.
 
 ### Console
 
 The JS Scripting binding supports the standard `console` object for logging.
-Script debug logging is enabled by default at the `INFO` level, but can be configured using the console logging commands.
+Script logging is enabled by default at the `INFO` level (messages from `console.debug` and `console.trace` won't be displayed), but can be configured using the [openHAB console](https://www.openhab.org/docs/administration/console.html):
 
 ```text
 log:set DEBUG org.openhab.automation.script
+log:set TRACE org.openhab.automation.script
+log:set DEFAULT org.openhab.automation.script
 ```
 
-The default logger name prefix is `org.openhab.automation.script`, this can be changed by assigning a new string to the `loggerName` property of the console.
-
-Please be aware that messages might not appear in the logs if the logger name does not start with `org.openhab`.
-This behaviour is due to [log4j2](https://logging.apache.org/log4j/2.x/) requiring definition for each logger prefix.
-
+The default logger name prefix is `org.openhab.automation.script`, this can be changed by assigning a new string to the `loggerName` property of the console:
 
 ```javascript
-console.loggerName = "org.openhab.custom"
+console.loggerName = 'org.openhab.custom';
 ```
 
+Please be aware that messages do not appear in the logs if the logger name does not start with `org.openhab`.
+This behaviour is due to [log4j2](https://logging.apache.org/log4j/2.x/) requiring a setting for each logger prefix in `$OPENHAB_USERDATA/etc/log4j2.xml` (on openHABian: `/srv/openhab-userdata/etc/log4j2.xml`).
+
 Supported logging functions include:
 
 - `console.log(obj1 [, obj2, ..., objN])`
@@ -164,11 +166,13 @@ The string representations of each of these objects are appended together in the
 
 See <https://developer.mozilla.org/en-US/docs/Web/API/console> for more information about console logging.
 
+Note: [openhab-js](https://github.com/openhab/openhab-js/) is logging to `org.openhab.automation.openhab-js`.
+
 ### Timers
 
 JS Scripting provides access to the global `setTimeout`, `setInterval`, `clearTimeout` and `clearInterval` methods specified in the [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API).
 
-When a script is unloaded, all created timers and intervals are automatically cancelled.
+When a script is unloaded, all created timeouts and intervals are automatically cancelled.
 
 #### SetTimeout
 
@@ -243,10 +247,49 @@ For [file based rules](#file-based-rules), scripts will be loaded from `automati
 
 NPM libraries will be loaded from `automation/js/node_modules` in the user configuration directory.
 
+### Deinitialization Hook
+
+It is possible to hook into unloading of a script and register a function that is called when the script is unloaded.
+
+```javascript
+require('@runtime').lifecycleTracker.addDisposeHook(() => functionToCall());
+
+// Example
+require('@runtime').lifecycleTracker.addDisposeHook(() => {
+  console.log("Deinitialization hook runs...")
+});
+```
+
+## `SCRIPT` 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.
+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.
+
+Use the `SCRIPT` transformation with JavaScript Scripting by:
+
+1. Creating a script in the `$OPENHAB_CONF/transform` folder with the `.script` 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;
+   })(input);
+   ```
+
+2. Using `SCRIPT(graaljs:<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(graaljs:<scriptname>.script?arg=value):%s`.
+   Parameters are injected into the script and can be referenced like variables.
+
 ## Standard Library
 
 Full documentation for the openHAB JavaScript library can be found at [openhab-js](https://openhab.github.io/openhab-js).
 
+The openHAB JavaScript library provides type definitions for most of its APIs to enable code completion is IDEs like [VS Code](https://code.visualstudio.com).
+To use the type definitions, install the [`openhab` npm package](https://npmjs.com/openhab) (read the [installation guide](https://github.com/openhab/openhab-js#custom-installation) for more information).
+If an API does not provide type definitions and therefore autocompletion won‘t work, the documentation will include a note.
+
 ### Items
 
 The items namespace allows interactions with openHAB items.
@@ -263,7 +306,7 @@ See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for fu
   - .safeItemName(s) ⇒ `string`
 
 ```javascript
-const item = items.getItem("KitchenLight");
+var item = items.getItem("KitchenLight");
 console.log("Kitchen Light State", item.state);
 ```
 
@@ -298,12 +341,12 @@ Calling `getItem(...)` returns an `Item` object with the following properties:
   - .removeTags(...tagNames)
 
 ```javascript
-const item = items.getItem("KitchenLight");
-//send an ON command
+var item = items.getItem("KitchenLight");
+// Send an ON command
 item.sendCommand("ON");
-//Post an update
+// Post an update
 item.postUpdate("OFF");
-//Get state
+// Get state
 console.log("KitchenLight state", item.state)
 ```
 
@@ -439,7 +482,7 @@ Calling `getThing(...)` returns a `Thing` object with the following properties:
   - .setEnabled(enabled)
 
 ```javascript
-const thing = things.getThing('astro:moon:home');
+var thing = things.getThing('astro:moon:home');
 console.log('Thing label: ' + thing.label);
 // Set Thing location
 thing.setLocation('living room');
@@ -452,6 +495,8 @@ thing.setEnabled(false);
 The actions namespace allows interactions with openHAB actions.
 The following are a list of standard actions.
 
+Note that most of the actions currently do **not** provide type definitions and therefore auto-completion does not work.
+
 See [openhab-js : actions](https://openhab.github.io/openhab-js/actions.html) for full API documentation and additional actions.
 
 #### Audio Actions
@@ -472,7 +517,7 @@ Additional information can be found on the  [Ephemeris Actions Docs](https://www
 
 ```javascript
 // Example
-let weekend = actions.Ephemeris.isWeekend();
+var weekend = actions.Ephemeris.isWeekend();
 ```
 
 #### Exec Actions
@@ -487,11 +532,11 @@ Execute a command line.
 actions.Exec.executeCommandLine('echo', 'Hello World!');
 
 // Execute command line with timeout.
-let Duration = Java.type('java.time.Duration');
+var Duration = Java.type('java.time.Duration');
 actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hello World!');
 
 // Get response from command line.
-let response = actions.Exec.executeCommandLine('echo', 'Hello World!');
+var response = actions.Exec.executeCommandLine('echo', 'Hello World!');
 
 // Get response from command line with timeout.
 response = actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hello World!');
@@ -515,6 +560,9 @@ The `ScriptExecution` actions provide the `callScript(string scriptName)` method
 You can also create timers using the [native JS methods for timer creation](#timers), your choice depends on the versatility you need.
 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 unmanagable 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).
+
 ##### `createTimer`
 
 ```javascript
@@ -566,12 +614,24 @@ See [openhab-js : actions.ScriptExecution](https://openhab.github.io/openhab-js/
 
 See [openhab-js : actions.Semantics](https://openhab.github.io/openhab-js/actions.html#.Semantics) for complete documentation.
 
-#### Things Actions
+#### Thing Actions
 
 It is possible to get the actions for a Thing using `actions.Things.getActions(bindingId, thingUid)`, e.g. `actions.Things.getActions('network', 'network:pingdevice:pc')`.
 
 See [openhab-js : actions.Things](https://openhab.github.io/openhab-js/actions.html#.Things) for complete documentation.
 
+#### Transformation Actions
+
+openHAB provides various [data transformation services](https://www.openhab.org/addons/#transform) which can translate between technical and human-readable values.
+Usually, they are used directly on Items, but it is also possible to access them from scripts.
+
+```javascript
+console.log(actions.Transformation.transform('MAP', 'en.map', 'OPEN')); // open
+console.log(actions.Transformation.transform('MAP', 'de.map', 'OPEN')); // offen
+```
+
+See [openhab-js : actions.Transformation](https://openhab.github.io/openhab-js/actions.Transformation.html) for complete documentation.
+
 #### Voice Actions
 
 See [openhab-js : actions.Voice](https://openhab.github.io/openhab-js/actions.html#.Voice) for complete documentation.
@@ -595,34 +655,48 @@ Replace `<message>` with the notification text.
 
 ### Cache
 
-The cache namespace provides a default cache that can be used to set and retrieve objects that will be persisted between reloads of scripts.
+The cache namespace provides both a private and a shared cache that can be used to set and retrieve objects that will be persisted between subsequent runs of the same or between scripts.
+
+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.
+
+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.
+If that key stored a timer, the timer is cancelled.
 
 See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation.
 
 - cache : <code>object</code>
-  - .get(key, defaultSupplier) ⇒ <code>Object | null</code>
-  - .put(key, value) ⇒ <code>Previous Object | null</code>
-  - .remove(key) ⇒ <code>Previous Object | null</code>
-  - .exists(key) ⇒ <code>boolean</code>
+  - .private
+    - .get(key, defaultSupplier) ⇒ <code>Object | null</code>
+    - .put(key, value) ⇒ <code>Previous Object | null</code>
+    - .remove(key) ⇒ <code>Previous Object | null</code>
+    - .exists(key) ⇒ <code>boolean</code>
+  - .shared
+    - .get(key, defaultSupplier) ⇒ <code>Object | null</code>
+    - .put(key, value) ⇒ <code>Previous Object | null</code>
+    - .remove(key) ⇒ <code>Previous Object | null</code>
+    - .exists(key) ⇒ <code>boolean</code>
 
 The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value.
 
-**Example** *(Get a previously set value with a default value (times &#x3D; 0))*
+**Example** *(Get a previously set value with a default value (times = 0))*
 
 ```js
-let counter = cache.get("counter", () => ({ "times": 0 }));
-console.log("Count",counter.times++);
+var counter = cache.private.get('counter', () => ({ 'times': 0 }));
+console.log('Count', counter.times++);
 ```
 
 **Example** *(Get a previously set object)*
 
 ```js
-let counter = cache.get("counter");
-if(counter == null){
-  counter = {times: 0};
-  cache.put("counter", counter);
+var counter = cache.private.get('counter');
+if (counter === null) {
+  counter = { times: 0 };
+  cache.private.put('counter', counter);
 }
-console.log("Count",counter.times++);
+console.log('Count', counter.times++);
 ```
 
 ### Log
@@ -631,7 +705,7 @@ By default, the JS Scripting binding supports console logging like `console.log(
 Additionally, scripts may create their own native openHAB logger using the log namespace.
 
 ```javascript
-let logger = log('my_logger');
+var logger = log('my_logger');
 
 //prints "Hello World!"
 logger.debug("Hello {}!", "world");
@@ -690,7 +764,7 @@ When you have a `time.ZonedDateTime`, a new `toToday()` method was added which w
 For example, if the time was 13:45 and today was a DST changeover, the time will still be 13:45 instead of one hour off.
 
 ```javascript
-const alarm = items.getItem('Alarm');
+var alarm = items.getItem('Alarm');
 alarm.postUpdate(time.toZDT(alarm).toToday());
 ```
 
@@ -714,7 +788,7 @@ time.toZDT(items.getItem('StartTime')).isBetweenTimes(time.toZDT(), 'PT1H'); //
 Tests to see if the delta between the `time.ZonedDateTime` and the passed in `time.ZonedDateTime` is within the passed in `time.Duration`.
 
 ```javascript
-const timestamp = time.toZDT();
+var timestamp = time.toZDT();
 // do some stuff
 if(timestamp.isClose(time.toZDT(), time.Duration.ofMillis(100))) {
   // did "do some stuff" take longer than 100 msecs to run?
@@ -726,7 +800,7 @@ if(timestamp.isClose(time.toZDT(), time.Duration.ofMillis(100))) {
 This method on `time.ZonedDateTime` returns the milliseconds from now to the passed in `time.ZonedDateTime`.
 
 ```javascript
-const timestamp = time.ZonedDateTime.now().plusMinutes(5);
+var timestamp = time.ZonedDateTime.now().plusMinutes(5);
 console.log(timestamp.getMillisFromNow());
 ```
 
@@ -752,7 +826,7 @@ See [openhab-js : rules](https://openhab.github.io/openhab-js/rules.html) for fu
 JSRules provides a simple, declarative syntax for defining rules that will be executed based on a trigger condition
 
 ```javascript
-const email = "juliet@capulet.org"
+var email = "juliet@capulet.org"
 
 rules.JSRule({
   name: "Balcony Lights ON at 5pm",
@@ -842,10 +916,12 @@ 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");
 ```
 
+Note that the Rule Builder currently does **not** provide type definitions and therefore auto-completion does not work.
+
 See [Examples](#rule-builder-examples) for further patterns.
 
 #### Rule Builder Triggers
@@ -884,7 +960,7 @@ See [Examples](#rule-builder-examples) for further patterns.
     - `from(state)`
     - `to(state)`
 
-Additionally all the above triggers have the following functions:
+Additionally, all the above triggers have the following functions:
 
 - `.if()` or `.if(fn)` -> a [rule condition](#rule-builder-conditions)
 - `.then()` or `.then(fn)` -> a [rule operation](#rule-builder-operations)
@@ -917,7 +993,7 @@ Additionally all the above triggers have the following functions:
 ```javascript
 // Basic rule, when the BedroomLight1 is changed, run a custom function
 rules.when().item('BedroomLight1').changed().then(e => {
-  console.log("BedroomLight1 state", e.newState)
+    console.log("BedroomLight1 state", e.newState)
 }).build();
 
 // Turn on the kitchen light at SUNSET
@@ -979,30 +1055,55 @@ Time triggers do not provide any event instance, therefore no property is popula
 
 See [openhab-js : EventObject](https://openhab.github.io/openhab-js/rules.html#.EventObject) for full API documentation.
 
-### Initialization hook: scriptLoaded
+## Advanced Scripting
+
+### Libraries
+
+#### Third Party Libraries
 
-For file based scripts, this function will be called if found when the script is loaded.
+Loading of third party libraries is supported the same way as loading the openHAB JavaScript library:
 
 ```javascript
-scriptLoaded = function () {
-  console.log("script loaded");
-  loadedDate = Date.now();
-};
+var myLibrary = require('my-library');
 ```
 
-### Deinitialization hook: scriptUnloaded
+Note: Only CommonJS `require` is supported, ES module loading using `import` is not supported.
 
-For file based scripts, this function will be called if found when the script is unloaded.
+Run the `npm` command from the `automation/js` folder to install third party libraries, e.g. from [npm](https://www.npmjs.com/search?q=openhab).
+This will create a `node_modules` folder (if it doesn't already exist) and install the library and it's dependencies there.
 
-```javascript
-scriptUnloaded = function () {
-  console.log("script unloaded");
-  // clean up rouge timers
-  clearInterval(timer);
-};
-```
+There are already some openHAB specific libraries available on [npm](https://www.npmjs.com/search?q=openhab), you may also search the forum for details.
 
-## Advanced Scripting
+#### Creating Your Own Library
+
+You can also create your own personal JavaScript library for openHAB, but you can not just create a folder in `node_modules` and put your library code in it!
+When it is run, `npm` will remove everything from `node_modules` that has not been properly installed.
+
+Follow these steps to create your own library (it's called a CommonJS module):
+
+1. Create a separate folder for your library outside of `automation/js`, you may also initialize a Git repository.
+2. Run `npm init` from your newly created folder; at least provide responses for the `name`, `version` and `main` (e.g. `index.js`) fields.
+3. Create the main file of your library (`index.js`) and add some exports:
+
+   ```javascript
+   var someProperty = 'Hello world!';
+   function someFunction () {
+     console.log('Hello from your personal library!');
+   }
+   
+   module.exports = {
+     someProperty,
+     someFunction
+   };
+   ```
+
+4. Tar it up by running `npm pack` from your library's folder.
+5. Install it by running `npm install <name>-<version>.tgz` from the `automation/js` folder.
+6. After you've installed it with `npm`, you can continue development of the library inside `node_modules`.
+
+It is also possible to upload your library to [npm](https://npmjs.com) to share it with other users.
+
+If you want to get some advanced information, you can read [this blog post](https://bugfender.com/blog/how-to-create-an-npm-package/) or just google it.
 
 ### @runtime
 
index ded636d15b6a419180638b77a4ac8823b07915d6..6247b4058df9df7059617f70894a7a35659d68f4 100644 (file)
@@ -25,7 +25,7 @@
     <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 -->
     <asm.version>6.2.1</asm.version>
     <oh.version>${project.version}</oh.version>
-    <ohjs.version>openhab@2.1.1</ohjs.version>
+    <ohjs.version>openhab@3.1.2</ohjs.version>
   </properties>
 
   <build>
diff --git a/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/scope/SharedCache.java b/bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/scope/SharedCache.java
deleted file mode 100644 (file)
index 6b9329f..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Copyright (c) 2010-2022 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.jsscripting.internal.scope;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.automation.module.script.ScriptExtensionProvider;
-import org.osgi.service.component.annotations.Component;
-
-/**
- * Shared Cache implementation for JS scripting.
- *
- * @author Jonathan Gilbert - Initial contribution
- */
-@Component(immediate = true)
-@NonNullByDefault
-public class SharedCache implements ScriptExtensionProvider {
-
-    private static final String PRESET_NAME = "cache";
-    private static final String OBJECT_NAME = "sharedcache";
-
-    private JSCache cache = new JSCache();
-
-    @Override
-    public Collection<String> getDefaultPresets() {
-        return Set.of(PRESET_NAME);
-    }
-
-    @Override
-    public Collection<String> getPresets() {
-        return Set.of(PRESET_NAME);
-    }
-
-    @Override
-    public Collection<String> getTypes() {
-        return Set.of(OBJECT_NAME);
-    }
-
-    @Override
-    public @Nullable Object get(String scriptIdentifier, String type) throws IllegalArgumentException {
-        if (OBJECT_NAME.equals(type)) {
-            return cache;
-        }
-
-        return null;
-    }
-
-    @Override
-    public Map<String, Object> importPreset(String scriptIdentifier, String preset) {
-        if (PRESET_NAME.equals(preset)) {
-            final Object requestedType = get(scriptIdentifier, OBJECT_NAME);
-            if (requestedType != null) {
-                return Map.of(OBJECT_NAME, requestedType);
-            }
-        }
-
-        return Collections.emptyMap();
-    }
-
-    @Override
-    public void unload(String scriptIdentifier) {
-        // ignore for now
-    }
-
-    public static class JSCache {
-        private Map<String, Object> backingMap = new HashMap<>();
-
-        public void put(String k, Object v) {
-            backingMap.put(k, v);
-        }
-
-        public @Nullable Object remove(String k) {
-            return backingMap.remove(k);
-        }
-
-        public @Nullable Object get(String k) {
-            return backingMap.get(k);
-        }
-
-        public @Nullable Object get(String k, Supplier<Object> supplier) {
-            return backingMap.computeIfAbsent(k, (unused_key) -> supplier.get());
-        }
-    }
-}