[jsscripting] Upgrade openhab-js & Remove SharedCache (#13908)

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>
This commit is contained in:
Florian Hotze 2022-12-11 15:25:39 +01:00 committed by GitHub
parent 506c7387c5
commit 438552d485
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 166 deletions

View 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 wont 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,31 +1055,56 @@ 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
For file based scripts, this function will be called if found when the script is loaded.
```javascript
scriptLoaded = function () {
console.log("script loaded");
loadedDate = Date.now();
};
```
### Deinitialization hook: scriptUnloaded
For file based scripts, this function will be called if found when the script is unloaded.
```javascript
scriptUnloaded = function () {
console.log("script unloaded");
// clean up rouge timers
clearInterval(timer);
};
```
## Advanced Scripting
### Libraries
#### Third Party Libraries
Loading of third party libraries is supported the same way as loading the openHAB JavaScript library:
```javascript
var myLibrary = require('my-library');
```
Note: Only CommonJS `require` is supported, ES module loading using `import` is not supported.
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.
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.
#### 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
One can access many useful utilities and types using `require("@runtime")`, e.g.

View 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>

View File

@ -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());
}
}
}