From b2087e3807dece9c347b8a9482aec44f41b3984a Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Sat, 18 Jun 2022 13:24:05 +0200 Subject: [PATCH] [jsscripting] Upgrade openhab-js to version 2.0.0 (#12945) * Bump openhab-js version to 2.0.0 * Update README for current openhab-js version * README: Fix paths to images Signed-off-by: Florian Hotze --- .../README.md | 562 ++++++++++++++---- .../pom.xml | 2 +- 2 files changed, 458 insertions(+), 106 deletions(-) diff --git a/bundles/org.openhab.automation.jsscripting/README.md b/bundles/org.openhab.automation.jsscripting/README.md index 003858ce0..9cc203172 100644 --- a/bundles/org.openhab.automation.jsscripting/README.md +++ b/bundles/org.openhab.automation.jsscripting/README.md @@ -9,16 +9,15 @@ to common openHAB functionality within rules including items, things, actions, l - [UI Based Rules](#ui-based-rules) - [Adding Triggers](#adding-triggers) - [Adding Actions](#adding-actions) + - [Event Object](#event-object) - [Scripting Basics](#scripting-basics) - [Require](#require) - [Console](#console) - - [SetTimeout](#settimeout) - - [SetInterval](#setinterval) - - [ScriptLoaded](#scriptloaded) - - [ScriptUnLoaded](#scriptunloaded) + - [Timers](#timers) - [Paths](#paths) - [Standard Library](#standard-library) - [Items](#items) + - [Things](#things) - [Actions](#actions) - [Cache](#cache) - [Log](#log) @@ -27,11 +26,18 @@ to common openHAB functionality within rules including items, things, actions, l - [File Based Rules](#file-based-rules) - [JSRule](#jsrule) - [Rule Builder](#rule-builder) + - [Event Object](#event-object-1) + - [Initialization hook: scriptLoaded](#initialization-hook-scriptloaded) + - [Deinitialization hook: scriptUnloaded](#deinitialization-hook-scriptunloaded) +- [Advanced Scripting](#advanced-scripting) + - [@runtime](#runtime) ## Configuration -This add-on includes by default the [openhab-js](https://github.com/openhab/openhab-js/) NPM library and exports it's namespaces onto the global namespace. This allows the use of `items`, `actions`, `cache` and other objects without the need to explicitly import using `require()`. This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options. +This add-on includes by default the [openhab-js](https://github.com/openhab/openhab-js/) NPM library and exports it's namespaces onto the global namespace. +This allows the use of `items`, `actions`, `cache` and other objects without the need to explicitly import using `require()`. +This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options. ![openHAB Rule Configuration](doc/settings.png) @@ -42,19 +48,19 @@ The quickest way to add rules is through the openHAB Web UI. Advanced users, or users migrating scripts from existing systems may want to use [File Based Rules](#file-based-rules) for managing rules using files in the user configuration directory. ### Adding Triggers -Using the openHAB UI, first create a new rule and set a trigger condition +Using the openHAB UI, first create a new rule and set a trigger condition. ![openHAB Rule Configuration](doc/rule-config.png) ### Adding Actions -Select "Add Action" and then select "ECMAScript 262 Edition 11". -Its important this is "Edition 11" or higher, earlier versions will not work. -This will bring up a empty script editor where you can enter your javascript. +Select "Add Action" and then select "Run Script" with "ECMAScript 262 Edition 11". +It’s important this is "Edition 11" or higher, earlier versions will not work. +This will bring up a empty script editor where you can enter your JavaScript. ![openHAB Rule Engines](doc/rule-engines.png) -You can now write rules using standard ES6 Javascript along with the included openHAB [standard library](#standard-library). +You can now write rules using standard ES6 JavaScript along with the included openHAB [standard library](#standard-library). ![openHAB Rule Script](doc/rule-script.png) @@ -75,7 +81,41 @@ const thingStatusInfo = actions.Things.getThingStatusInfo("zwave:serial_zstick:5 console.log("Thing status",thingStatusInfo.getStatus()); ``` -See [openhab-js](https://openhab.github.io/openhab-js) for a complete list of functionality +See [openhab-js](https://openhab.github.io/openhab-js) for a complete list of functionality. + +### Event Object + +**NOTE**: Note that `event` object is different in UI based rules and file based rules! This section is only valid for UI based rules. If you use file based rules, refer to [file based rules event object documentation](#event-object-1). + +When you use "Item event" as trigger (i.e. "[item] received a command", "[item] was updated", "[item] changed"), there is additional context available for the action in a variable called `event`. + +This tables gives an overview over the `event` object for most common trigger types: + +| Property Name | Type | Trigger Types | Description | Rules DSL Equivalent | +| -------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `itemState` | sub-class of [org.openhab.core.types.State](https://www.openhab.org/javadoc/latest/org/openhab/core/types/state) | `[item] changed`, `[item] was updated` | State that triggered event | `triggeringItem.state` | +| `oldItemState` | sub-class of [org.openhab.core.types.State](https://www.openhab.org/javadoc/latest/org/openhab/core/types/state) | `[item] changed` | Previous state of Item or Group that triggered event | `previousState` | +| `itemCommand` | sub-class of [org.openhab.core.types.Command](https://www.openhab.org/javadoc/latest/org/openhab/core/types/command) | `[item] received a command` | Command that triggered event | `receivedCommand` | +| `itemName` | string | all | Name of Item that triggered event | `triggeringItem.name` | +| `type` | string | all | Type of event that triggered event (`"ItemStateEvent"`, `"ItemStateChangedEvent"`, `"ItemCommandEvent"`, ...) | N/A | + +Note that in UI based rules `event.itemState`, `event.oldItemState`, and `event.itemCommand` are Java types (not JavaScript), and care must be taken when comparing these with JavaScript types: + +```javascript +var { ON } = require("@runtime") + +console.log(event.itemState == "ON") // WRONG. Java type does not equal with string, not even with "relaxed" equals (==) comparison +console.log(event.itemState.toString() == "ON") // OK. Comparing strings +console.log(event.itemState == ON) // OK. Comparing Java types +``` + +**NOTE**: Even with `String` items, simple comparison with `==` is not working as one would expect! See below example: + +```javascript +// Example assumes String item trigger +console.log(event.itemState == "test") // WRONG. Will always log "false" +console.log(event.itemState.toString() == "test") // OK +``` ## Scripting Basics @@ -83,18 +123,24 @@ The openHAB JSScripting runtime attempts to provide a familiar environment to Ja ### Require -Scripts may include standard NPM based libraries by using CommonJS require. -The library search path will look in `automation/js/node_modules` in the user configuration directory. +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. ### Console -The JSScripting 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]({{base}}/administration/logging.html) commands. +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]({{base}}/administration/logging.html) commands. ```text log:set DEBUG org.openhab.automation.script ``` +The default logger name prefix is `org.openhab.automation.script`, this can be changed by assigning a new prefix to the `loggerName` property of the console. + +```javascript +console.loggerName = "custom" +``` + Supported logging functions include: - `console.log(obj1 [, obj2, ..., objN])` - `console.info(obj1 [, obj2, ..., objN])` @@ -103,14 +149,16 @@ Supported logging functions include: - `console.debug(obj1 [, obj2, ..., objN])` - `console.trace(obj1 [, obj2, ..., objN])` -where `obj1 ... objN` is a list of JavaScript objects to output. +Where `obj1 ... objN` is a list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. -see https://developer.mozilla.org/en-US/docs/Web/API/console for more information about console logging. +See https://developer.mozilla.org/en-US/docs/Web/API/console for more information about console logging. -### SetTimeout +### Timers -The global setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires. +#### SetTimeout + +The global `setTimeout()` method sets a timer which executes a function or specified piece of code once the timer expires. ```javascript var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]); var timeoutID = setTimeout(function[, delay]); @@ -118,9 +166,11 @@ var timeoutID = setTimeout(function[, delay]); The global `clearTimeout()` method cancels a timeout previously established by calling `setTimeout()`. -see https://developer.mozilla.org/en-US/docs/Web/API/setTimeout for more information about setTimeout. +See https://developer.mozilla.org/en-US/docs/Web/API/setTimeout for more information about `setTimeout()`. -### SetInterval +openHAB does not return the integer timeoutID as standard JS does, instead it returns an instance of [openHAB Timer](#openhab-timer). + +#### SetInterval The setInterval() method repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. @@ -133,30 +183,31 @@ The global `clearInterval()` method cancels a timed, repeating action which was NOTE: Timers will not be canceled if a script is deleted or modified, it is up to the user to manage timers. See using the [cache](#cache) namespace as well as [ScriptLoaded](#scriptloaded) and [ScriptUnLoaded](#scriptunloaded) for a convenient way of managing persisted objects, such as timers between reloads or deletions of scripts. -see https://developer.mozilla.org/en-US/docs/Web/API/setInterval for more information about setInterval. +See https://developer.mozilla.org/en-US/docs/Web/API/setInterval for more information about `setInterval()`. -### ScriptLoaded +openHAB does not return the integer timeoutID as standard JS does, instead it returns an instance of [openHAB Timer](#openhab-timer). -For file based scripts, this function will be called if found when the script is loaded. +#### openHAB Timer +A native openHAB Timer instance has the following methods: +* `cancel()`: Cancels the timer. ⇒ `boolean`: true, if cancellation was successful +* `getExecutionTime()`: The scheduled execution time or null if timer was cancelled. ⇒ `time.ZonedDateTime` or `null` +* `isActive()`: Whether the scheduled execution is yet to happen. ⇒ `boolean` +* `isCancelled()`: Whether the timer has been cancelled. ⇒ `boolean` +* `isRunning()`: Whether the scheduled code is currently executed. ⇒ `boolean` +* `hasTerminated()`: Whether the scheduled execution has already terminated. ⇒ `boolean` +* `reschedule(time.ZonedDateTime)`: Reschedules a timer to a new starting time. This can also be called after a timer has terminated, which will result in another execution of the same code. ⇒ `boolean`: true, if rescheduling was successful + +Examples: ```javascript -scriptLoaded = function () { - console.log("script loaded"); - loadedDate = Date.now(); -} +var timer = setTimeout(() => { console.log('Timer expired.'); }, 10000); // Would log 'Timer expired.' in 10s. +if (timer.isActive()) console.log('Timer is waiting to execute.'); +timer.cancel(); +if (timer.isCancelled()) console.log('Timer has been cancelled.'); +timer.reschedule(time.ZonedDateTime.now().plusSeconds(2)); // Logs 'Timer expired.' in 2s. ``` -### 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); -} -``` +See [openHAB JavaDoc - Timer](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/timer) for full API documentation. ### Paths @@ -166,22 +217,21 @@ NPM libraries will be loaded from `automation/js/node_modules` in the user confi ## Standard Library -Full documentation for the openHAB JavaScript library can be found at [openhab-js](https://openhab.github.io/openhab-js) +Full documentation for the openHAB JavaScript library can be found at [openhab-js](https://openhab.github.io/openhab-js). ### Items The items namespace allows interactions with openHAB items. -See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for full API documentation +See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for full API documentation. * items : object * .getItem(name, nullIfMissing) ⇒ Item * .getItems() ⇒ Array.<Item> * .getItemsByTag(...tagNames) ⇒ Array.<Item> - * .createItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction], [itemMetadata]) - * .addItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction]) + * .addItem([itemConfig](#itemconfig)) * .removeItem(itemOrItemName) ⇒ Boolean - * .replaceItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction]) + * .replaceItem([itemConfig](#itemconfig)) * .safeItemName(s) ⇒ String ```javascript @@ -189,6 +239,8 @@ const item = items.getItem("KitchenLight"); console.log("Kitchen Light State", item.state); ``` +#### `getItem(name, nullIfMissing)` + Calling `getItem(...)` returns an `Item` object with the following properties: * Item : object @@ -225,9 +277,68 @@ item.postUpdate("OFF"); console.log("KitchenLight state", item.state) ``` -calling `item.history...` returns a ItemHistory object with the following functions: +#### `itemConfig` +Calling `addItem(itemConfig)` or `replaceItem(itemConfig)` requires the `itemConfig` object with the following properties: -Note `serviceId` is optional, if omitted, the default persistance service will be used. +* itemConfig : object + * .type ⇒ String + * .name ⇒ String + * .label ⇒ String + * .category (icon) ⇒ String + * .groups ⇒ Array.<String> + * .tags ⇒ Array.<String> + * .channels ⇒ String|Object { channeluid: { config } } + * .metadata ⇒ Object { namespace: value }|Object { namespace: { value: value , config: { config } } } + * .giBaseType ⇒ String + * .groupFunction ⇒ String + +Note: `.type` and `.name` are required. +Basic UI and the mobile apps need `metadata.stateDescription.config.pattern` to render the state of an Item. + +Example: +```javascript +// more advanced example +items.replaceItem({ + type: 'String', + name: 'Hallway_Light', + label: 'Hallway Light', + category: 'light', + groups: ['Hallway', 'Light'], + tags: ['Lightbulb'], + channels: { + 'binding:thing:device:hallway#light': {}, + 'binding:thing:device:livingroom#light': { + profile: 'system:follow' + } + }, + metadata: { + expire: '10m,command=1', + stateDescription: { + config: { + pattern: '%d%%', + options: '1=Red, 2=Green, 3=Blue' + } + } + } +}); +// minimal example +items.replaceItem({ + type: 'Switch', + name: 'MySwitch', + metadata: { + stateDescription: { + config: { + pattern: '%s' + } + } + } +}); +``` + +See [openhab-js : ItemConfig](https://openhab.github.io/openhab-js/items.html#.ItemConfig) for full API documentation. + +#### `item.history` +Calling `item.history` returns a ItemHistory object with the following functions: * ItemHistory : object * .averageSince(timestamp, serviceId) ⇒ Number @@ -246,12 +357,51 @@ Note `serviceId` is optional, if omitted, the default persistance service will b * .updatedSince(timestamp, serviceId) ⇒ Boolean * .varianceSince(timestamp,serviceId) ⇒ state +Note: `serviceId` is optional, if omitted, the default persistance service will be used. + ```javascript var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000)); var item = items.getItem("KitchenDimmer"); console.log("KitchenDimmer averageSince", item.history.averageSince(yesterday)); ``` +### Things + +The Things namespace allows to interact with openHAB Things. + +See [openhab-js : things](https://openhab.github.io/openhab-js/things.html) for full API documentation. + +* things : object + * .getThing(uid, nullIfMissing) ⇒ Thing + * .getThings() ⇒ Array.<Thing> + +#### `getThing(uid, nullIfMissing)` + +Calling `getThing(...)` returns a `Thing` object with the following properties: + +* Thing : object + * .bridgeUID ⇒ String + * .label ⇒ String + * .location ⇒ String + * .status ⇒ String + * .statusInfo ⇒ String + * .thingTypeUID ⇒ String + * .uid ⇒ String + * .isEnabled ⇒ Boolean + * .setLabel(label) + * .setLocation(location) + * .setProperty(name, value) + * .setEnabled(enabled) + +```javascript +const thing = things.getThing('astro:moon:home'); +console.log('Thing label: ' + thing.label); +// Set Thing location +thing.setLocation('living room'); +// Disable Thing +thing.setEnabled(false); +``` + ### Actions The actions namespace allows interactions with openHAB actions. @@ -264,15 +414,15 @@ See [openhab-js : actions](https://openhab.github.io/openhab-js/actions.html) fo #### Audio Actions -See [openhab-js : actions.Audio](https://openhab.github.io/openhab-js/actions.html#.Audio) for complete documentation +See [openhab-js : actions.Audio](https://openhab.github.io/openhab-js/actions.html#.Audio) for complete documentation. #### BusEvent -See [openhab-js : actions.BusEvent](https://openhab.github.io/openhab-js/actions.html#.BusEvent) for complete documentation +See [openhab-js : actions.BusEvent](https://openhab.github.io/openhab-js/actions.html#.BusEvent) for complete documentation. #### Ephemeris Actions -See [openhab-js : actions.Ephemeris](https://openhab.github.io/openhab-js/actions.html#.Ephemeris) for complete documentation +See [openhab-js : actions.Ephemeris](https://openhab.github.io/openhab-js/actions.html#.Ephemeris) for complete documentation. Ephemeris is a way to determine what type of day today or a number of days before or after today is. For example, a way to determine if today is a weekend, a bank holiday, someone’s birthday, trash day, etc. @@ -285,7 +435,7 @@ let weekend = actions.Ephemeris.isWeekend(); #### Exec Actions -See [openhab-js : actions.Exec](https://openhab.github.io/openhab-js/actions.html#.Exec) for complete documentation +See [openhab-js : actions.Exec](https://openhab.github.io/openhab-js/actions.html#.Exec) for complete documentation. Execute a command line. @@ -307,7 +457,7 @@ response = actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hell #### HTTP Actions -See [openhab-js : actions.HTTP](https://openhab.github.io/openhab-js/actions.html#.HTTP) for complete documentation +See [openhab-js : actions.HTTP](https://openhab.github.io/openhab-js/actions.html#.HTTP) for complete documentation. ```javascript // Example GET Request @@ -318,7 +468,7 @@ Replace `` with the request url. #### ScriptExecution Actions -See [openhab-js : actions.ScriptExecution](https://openhab.github.io/openhab-js/actions.html#.ScriptExecution) for complete documentation +See [openhab-js : actions.ScriptExecution](https://openhab.github.io/openhab-js/actions.html#.ScriptExecution) for complete documentation. ```javascript @@ -343,21 +493,21 @@ this.myTimer.reschedule(now.plusSeconds(5)); ``` #### Semantics Actions -See [openhab-js : actions.Semantics](https://openhab.github.io/openhab-js/actions.html#.Semantics) for complete documentation +See [openhab-js : actions.Semantics](https://openhab.github.io/openhab-js/actions.html#.Semantics) for complete documentation. #### Things Actions -See [openhab-js : actions.Things](https://openhab.github.io/openhab-js/actions.html#.Things) for complete documentation +See [openhab-js : actions.Things](https://openhab.github.io/openhab-js/actions.html#.Things) for complete documentation. #### Voice Actions -See [openhab-js : actions.Voice](https://openhab.github.io/openhab-js/actions.html#.Voice) for complete documentation +See [openhab-js : actions.Voice](https://openhab.github.io/openhab-js/actions.html#.Voice) for complete documentation. #### Cloud Notification Actions -(optional action if openhab-cloud is installed) +Note: Optional action if [openHAB Cloud Connector](https://www.openhab.org/addons/integrations/openhabcloud/) is installed. -Notification actions may be placed in Rules to send alerts to mobile devices registered with an [openHAB Cloud instance](https://github.com/openhab/openhab-cloud) such as [myopenHAB.org](https://myopenhab.org/). +Notification actions may be placed in rules to send alerts to mobile devices registered with an [openHAB Cloud instance](https://github.com/openhab/openhab-cloud) such as [myopenHAB.org](https://myopenhab.org/). For available actions have a look at the [Cloud Notification Actions Docs](https://www.openhab.org/docs/configuration/actions.html#cloud-notification-actions). @@ -372,16 +522,17 @@ Replace `` with the notification text. ### Cache -The cache namespace provides a default cache that can be use to set and retrieve objects that will be persisted between reloads of scripts. +The cache namespace provides a default cache that can be used to set and retrieve objects that will be persisted between reloads of scripts. -See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation +See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation. * cache : object * .get(key, defaultSupplier) ⇒ Object | null * .put(key, value) ⇒ Previous Object | null * .remove(key) ⇒ Previous Object | null + * .exists(key) ⇒ boolean -The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value +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 = 0))* ```js @@ -401,7 +552,7 @@ console.log("Count",counter.times++); ### Log By default the JS Scripting binding supports console logging like `console.log()` and `console.debug()` to the openHAB default log. -Additionally scripts may create their own native openHAB logs using the log namespace. +Additionally scripts may create their own native openHAB logger using the log namespace. ```javascript let logger = log('my_logger'); @@ -413,41 +564,109 @@ logger.debug("Hello {}!", "world"); ### Time openHAB internally makes extensive use of the `java.time` package. -openHAB-JS exports the excellent [JS-Joda](https://js-joda.github.io/js-joda/) library via the `time` namespace, which is a native Javascript port of the same API standard used in Java for `java.time`. +openHAB-JS exports the excellent [JS-Joda](#https://js-joda.github.io/js-joda/) library via the `time` namespace, which is a native JavaScript port of the same API standard used in Java for `java.time`. Anywhere that a native Java `ZonedDateTime` or `Duration` is required, the runtime will automatically convert a JS-Joda `ZonedDateTime` or `Duration` to its Java counterpart. +The exported JS-Joda library is also extended with convenient functions relevant to openHAB usage. + Examples: ```javascript var now = time.ZonedDateTime.now(); var yesterday = time.ZonedDateTime.now().minusHours(24); - var item = items.getItem("Kitchen"); console.log("averageSince", item.history.averageSince(yesterday)); ``` - ```javascript actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), 'echo', 'Hello World!'); ``` - See [JS-Joda](https://js-joda.github.io/js-joda/) for more examples and complete API usage. +#### `time.toZDT()` + +There will be times where this automatic conversion is not available (for example when working with date times within a rule). +To ease having to deal with these cases a `time.toZDT()` function will accept almost any type that can be converted to a `time.ZonedDateTime`. +The following rules are used during the conversion: + +| Argument Type | Rule | Examples | +|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| `null` or `undefined` | `time.ZonedDateTime.now()` | `time.toZDT();` | +| `time.ZonedDateTime` | passed through unmodified | | +| `java.time.ZonedDateTime` | converted to the `time.ZonedDateTime` equivalent | | +| JavaScript native `Date` | converted to the equivalent `time.ZonedDateTime` using `SYSTEM` as the timezone | | +| `number`, `bingint`, `java.lang.Number`, `DecimalType` | rounded to the nearest integer and added to `now` as milliseconds | `time.toZDT(1000);` | +| `QuantityType` | if the units are `Time`, that time is added to `now` | `time.toZDT(item.getItem('MyTimeItem').rawState);` | +| `items.Item` or `org.openhab.core.types.Item` | if the state is supported (see the `Type` rules in this table, e.g. `DecimalType`), the state is converted | `time.toZDT(items.getItem('MyItem'));` | +| `String`, `java.lang.String`, `StringType` | parsed based on the following rules | | +| RFC String (output from a Java `ZonedDateTime.toString()`) | parsed | `time.toZDT(new DateTimeType().getZonedDateTime().toString());` | +| `"HH:MM[:ss]"` (24 hour time) | today's date with the time indicated, seconds is optional | `time.toZDT('13:45:12');` | +| `"kk:mm[:ss][ ]a"` (12 hour time) | today's date with the time indicated, the space between the time and am/pm and seconds are optional | `time.toZDT('1:23:45 PM');` | +| Duration String | any duration string supported by `time.Duration` added to `now()`, see [the docs](https://js-joda.github.io/js-joda/class/packages/core/src/Duration.js~Duration.html#static-method-parse) for details | `time.toZDT('PT1H4M6.789S');` | + + +When a type or string that cannot be handled is encountered, an error is thrown. + +#### `toToday()` + +When you have a `time.ZonedDateTime`, a new `toToday()` method was added which will return a new `time.ZonedDateTime` with today's date but the original's time, accounting for DST changes. + +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'); +alarm.postUpdate(time.toZDT(alarm).toToday()); +``` + +#### `betweenTimes(start, end)` + +Tests whether this `time.ZonedDateTime` is between the passed in `start` and `end`. +However, the function only compares the time portion of the three, ignoring the date portion. +The function takes into account times that span midnight. +`start` and `end` can be anything supported by `time.toZDT()`. + +Examples: + +```javascript +time.toZDT().betweenTimes('22:00', '05:00') // currently between 11:00 pm and 5:00 am +time.toZDT().betweenTimes(items.getItem('Sunset'), '11:30 PM') // is now between sunset and 11:30 PM? +time.toZDT(items.getItem('StartTime')).betweenTimes(time.toZDT(), 'PT1H'); // is the state of StartTime between now and one hour from now +``` + +#### `isClose(zdt, maxDur)` + +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(); +// do some stuff +if(timestamp.isClose(time.toZDT(), time.Duration.ofMillis(100))) { // did "do some stuff" take longer than 100 msecs to run? +``` + +#### `getMillisFromNow` + +This method on `time.ZonedDateTime` returns the milliseconds from now to the passed in `time.ZonedDateTime`. + +```javascript +const timestamp = time.ZonedDateTime.now().plusMinutes(5); +console.log(timestamp.getMillisFromNow()); +``` + ### Utils openHAB internally is a Java program. openHAB-JS converts between Java and JavaScript data types and reverse. -See [openhab-js : utils](https://openhab.github.io/openhab-js/utils.html) for full API documentation +See [openhab-js : utils](https://openhab.github.io/openhab-js/utils.html) for full API documentation. ## File Based Rules -The JSScripting binding will load scripts from `automation/js` in the user configuration directory. +The JS Scripting binding will load scripts from `automation/js` in the user configuration directory. The system will automatically reload scripts when changes are detected to files. Local variable state is not persisted among reloads, see using the [cache](#cache) for a convenient way to persist objects. File based rules can be created in 2 different ways: using [JSRule](#jsrule) or the [Rule Builder](#rule-builder). -See [openhab-js : rules ](https://openhab.github.io/openhab-js/rules.html) for full API documentation +See [openhab-js : rules ](https://openhab.github.io/openhab-js/rules.html) for full API documentation. ### JSRule @@ -460,7 +679,7 @@ rules.JSRule({ name: "Balcony Lights ON at 5pm", description: "Light will turn on when it's 5:00pm", triggers: [triggers.GenericCronTrigger("0 0 17 * * ?")], - execute: data => { + execute: (event) => { items.getItem("BalconyLights").sendCommand("ON"); actions.NotificationAction.sendNotification(email, "Balcony lights are ON"); }, @@ -471,41 +690,43 @@ rules.JSRule({ Note: `description`, `tags` and `id` are optional. -Multiple triggers can be added, some example triggers include: +Note: You can use the passed `event` object to get information about the trigger that triggered the rule. +See [Event Object](#event-object) for documentation. + +Multiple triggers can be added, some example triggers include: ```javascript -triggers.ChannelEventTrigger('astro:sun:local:rise#event', 'START') +triggers.ChannelEventTrigger('astro:sun:local:rise#event', 'START'); -triggers.ItemStateChangeTrigger('my_item', 'OFF', 'ON') +triggers.ItemStateChangeTrigger('my_item', 'OFF', 'ON'); -triggers.ItemStateUpdateTrigger('my_item', 'OFF') +triggers.ItemStateUpdateTrigger('my_item', 'OFF'); -triggers.ItemCommandTrigger('my_item', 'OFF') +triggers.ItemCommandTrigger('my_item', 'OFF'); -triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON') +triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON'); -triggers.GroupStateUpdateTrigger('my_group', 'OFF') +triggers.GroupStateUpdateTrigger('my_group', 'OFF'); -triggers.GroupCommandTrigger('my_group', 'OFF') +triggers.GroupCommandTrigger('my_group', 'OFF'); -triggers.ThingStatusUpdateTrigger('some:thing:uuid','OFFLINE') +triggers.ThingStatusUpdateTrigger('some:thing:uuid','OFFLINE'); -triggers.ThingStatusChangeTrigger('some:thing:uuid','ONLINE','OFFLINE') +triggers.ThingStatusChangeTrigger('some:thing:uuid','ONLINE','OFFLINE'); -triggers.SystemStartlevelTrigger(40) //Rules loaded +triggers.SystemStartlevelTrigger(40) // Rules loaded -triggers.SystemStartlevelTrigger(50) //Rule engine started +triggers.SystemStartlevelTrigger(50) // Rule engine started -triggers.SystemStartlevelTrigger(70) //User interfaces started +triggers.SystemStartlevelTrigger(70) // User interfaces started -triggers.SystemStartlevelTrigger(80) //Things initialized +triggers.SystemStartlevelTrigger(80) // Things initialized -triggers.SystemStartlevelTrigger(100) //Startup Complete +triggers.SystemStartlevelTrigger(100) // Startup Complete -triggers.GenericCronTrigger('0 30 16 * * ? *') - -triggers.TimeOfDayTrigger('19:00') +triggers.GenericCronTrigger('0 30 16 * * ? *'); +triggers.TimeOfDayTrigger('19:00'); ``` See [openhab-js : triggers ](https://openhab.github.io/openhab-js/triggers.html) in the API documentation for a full list of all triggers. @@ -541,7 +762,7 @@ rules.when().item("F1_light").changed().then(event => { console.log(event); }).build("Test Rule", "My Test Rule"); ``` -see [Examples](#rule-builder-examples) for further patterns +See [Examples](#rule-builder-examples) for further patterns. #### Rule Builder Triggers @@ -608,35 +829,166 @@ Additionally all the above triggers have the following functions: #### Rule Builder Examples ```javascript -//Basic rule, when the BedroomLight1 is changed, run a custom function +// Basic rule, when the BedroomLight1 is changed, run a custom function rules.when().item('BedroomLight1').changed().then(e => { console.log("BedroomLight1 state", e.newState) -}).build(); +}.build(); -//turn on the kitchen light at SUNSET -rules.when().timeOfDay("SUNSET").then().sendOn().toItem("KitchenLight").build("Sunset Rule","turn on the kitchen light -at SUNSET"); +// Turn on the kitchen light at SUNSET +rules.when().timeOfDay("SUNSET").then().sendOn().toItem("KitchenLight").build("Sunset Rule","turn on the kitchen light at SUNSET"); -//turn off the kitchen light at 9PM and tag rule -rules.when().cron("0 0 21 * * ?").then().sendOff().toItem("KitchenLight").build("9PM Rule", "turn off the kitchen light -at 9PM", ["Tag1", "Tag2"]); +// Turn off the kitchen light at 9PM and tag rule +rules.when().cron("0 0 21 * * ?").then().sendOff().toItem("KitchenLight").build("9PM Rule", "turn off the kitchen light at 9PM", ["Tag1", "Tag2"]); -//set the colour of the hall light to pink at 9PM, tag rule and use a custom ID -rules.when().cron("0 0 21 * * ?").then().send("300,100,100").toItem("HallLight").build("Pink Rule", "set the colour of -the hall light to pink at 9PM", ["Tag1", "Tag2"], "MyCustomID"); +// Set the colour of the hall light to pink at 9PM, tag rule and use a custom ID +rules.when().cron("0 0 21 * * ?").then().send("300,100,100").toItem("HallLight").build("Pink Rule", "set the colour of the hall light to pink at 9PM", ["Tag1", "Tag2"], "MyCustomID"); -//when the switch S1 status changes to ON, then turn on the HallLight +// When the switch S1 status changes to ON, then turn on the HallLight rules.when().item('S1').changed().toOn().then(sendOn().toItem('HallLight')).build("S1 Rule"); -//when the HallLight colour changes pink, if the function fn returns true, then toggle the state of the OutsideLight +// When the HallLight colour changes pink, if the function fn returns true, then toggle the state of the OutsideLight rules.when().item('HallLight').changed().to("300,100,100").if(fn).then().sendToggle().toItem('OutsideLight').build(); -//and some rules which can be toggled by the items created in the 'gRules' Group: +// And some rules which can be toggled by the items created in the 'gRules' Group: -//when the HallLight receives a command, send the same command to the KitchenLight +// When the HallLight receives a command, send the same command to the KitchenLight rules.when().item('HallLight').receivedCommand().then().sendIt().toItem('KitchenLight').build("Hall Light", ""); -//when the HallLight is updated to ON, make sure that BedroomLight1 is set to the same state as the BedroomLight2 +// When the HallLight is updated to ON, make sure that BedroomLight1 is set to the same state as the BedroomLight2 rules.when().item('HallLight').receivedUpdate().then().copyState().fromItem('BedroomLight1').toItem('BedroomLight2').build(); - ``` + +### Event Object + +**NOTE**: The `event` object is different in UI Based Rules and File Based Rules! +This section is only valid for File Based Rules. +If you use UI Based Rules, refer to [UI based rules event object documentation](#event-object). + +When a rule is triggered, the script is provided the event instance that triggered it. +The specific data depends on the event type. +The `event` object provides several information about that trigger. + +This tables gives an overview over the `event` object: +| Property Name | Trigger Types | Description | Rules DSL Equivalent | +|-------------------|-----------------------------------------------------|-------------------------------------------------------------------------------------|------------------------| +| `oldState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | Previous state of Item or Group that triggered event | `previousState` | +| `newState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | New state of Item or Group that triggered event | N/A | +| `receivedState` | `ItemStateUpdateTrigger`, `GroupStateUpdateTrigger` | State of Item that triggered event | `triggeringItem.state` | +| `receivedCommand` | `ItemCommandTrigger`, `GroupCommandTrigger` | Command that triggered event | `receivedCommand` | +| `itemName` | `Item****Trigger` | Name of Item that triggered event | `triggeringItem.name` | +| `receivedEvent` | `ChannelEventTrigger` | Channel event that triggered event | N/A | +| `channelUID` | `ChannelEventTrigger` | UID of channel that triggered event | N/A | +| `oldStatus` | `ThingStatusChangeTrigger` | Previous state of Thing that triggered event | N/A | +| `newStatus` | `ThingStatusChangeTrigger` | New state of Thing that triggered event | N/A | +| `status` | `ThingStatusUpdateTrigger` | State of Thing that triggered event | N/A | +| `thingUID` | `Thing****Trigger` | UID of Thing that triggered event | N/A | +| `eventType` | all | Type of event that triggered event (change, command, time, triggered, update) | N/A | +| `triggerType` | all except `PWMTrigger`, `PIDTrigger` | Type of trigger that triggered event (for `TimeOfDayTrigger`: `GenericCronTrigger`) | N/A | + +All properties are typeof `string`. + +**NOTE:** +`Group****Trigger`s use the equivalent `Item****Trigger` as trigger for each member. + +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 + +### @runtime + +One can access many useful utilities and types using `require("@runtime")`, e.g. + +```javascript +var { ON, OFF, QuantityType } = require("@runtime"); +// Alternative, more verbose way to achieve the same: +// +// var runtime = require("@runtime"); +// +// var ON = runtime.ON; +// var OFF = runtime.OFF; +// var QuantityType = runtime.QuantityType; +``` + +| Variable | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `State` | [`org.openhab.core.types.State`](https://www.openhab.org/javadoc/latest/org/openhab/core/types/state) | +| `Command` | [`org.openhab.core.types.Command`](https://www.openhab.org/javadoc/latest/org/openhab/core/types/command) | +| `URLEncoder` | [`java.net.URLEncoder`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URLEncoder.html) | +| `File` | [`java.io.File`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html) | +| `Files` | [`java.nio.file.Files`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html) | +| `Path` | [`java.nio.file.Path`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html) | +| `Paths` | [`java.nio.file.Paths`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Paths.html) | +| `IncreaseDecreaseType` | [`org.openhab.core.library.types.IncreaseDecreaseType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/increasedecreasetype) | +| `DECREASE` | `IncreaseDecreaseType` enum item | +| `INCREASE` | `IncreaseDecreaseType` enum item | +| `OnOffType` | [`org.openhab.core.library.types.OnOffType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/onofftype) | +| `ON` | `OnOffType` enum item | +| `OFF` | `OnOffType` enum item | +| `OpenClosedType` | [`org.openhab.core.library.types.OpenClosedType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/openclosedtype) | +| `OPEN` | `OpenClosedType` enum item | +| `CLOSED` | `OpenClosedType` enum item | +| `StopMoveType` | [`org.openhab.core.library.types.StopMoveType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/stopmovetype) | +| `STOP` | `StopMoveType` enum item | +| `MOVE` | `StopMoveType` enum item | +| `UpDownType` | [`org.openhab.core.library.types.UpDownType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/updowntype) | +| `UP` | `UpDownType` enum item | +| `DOWN` | `UpDownType` enum item | +| `UnDefType` | [`org.openhab.core.library.types.UnDefType`](https://www.openhab.org/javadoc/latest/org/openhab/core/types/undeftype) | +| `NULL` | `UnDefType` enum item | +| `UNDEF` | `UnDefType` enum item | +| `RefreshType` | [`org.openhab.core.library.types.RefreshType`](https://www.openhab.org/javadoc/latest/org/openhab/core/types/refreshtype) | +| `REFRESH` | `RefreshType` enum item | +| `NextPreviousType` | [`org.openhab.core.library.types.NextPreviusType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/nextprevioustype) | +| `NEXT` | `NextPreviousType` enum item | +| `PREVIOUS` | `NextPreviousType` enum item | +| `PlayPauseType` | [`org.openhab.core.library.types.PlayPauseType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/playpausetype) | +| `PLAY` | `PlayPauseType` enum item | +| `PAUSE` | `PlayPauseType` enum item | +| `RewindFastforwardType` | [`org.openhab.core.library.types.RewindFastforwardType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/rewindfastforwardtype) | +| `REWIND` | `RewindFastforwardType` enum item | +| `FASTFORWARD` | `RewindFastforwardType` enum item | +| `QuantityType` | [`org.openhab.core.library.types.QuantityType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/quantitytype) | +| `StringListType` | [`org.openhab.core.library.types.StringListType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/stringlisttype) | +| `RawType` | [`org.openhab.core.library.types.RawType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/rawtype) | +| `DateTimeType` | [`org.openhab.core.library.types.DateTimeType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/datetimetype) | +| `DecimalType` | [`org.openhab.core.library.types.DecimalType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/decimaltype) | +| `HSBType` | [`org.openhab.core.library.types.HSBType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/hsbtype) | +| `PercentType` | [`org.openhab.core.library.types.PercentType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/percenttype) | +| `PointType` | [`org.openhab.core.library.types.PointType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/pointtype) | +| `StringType` | [`org.openhab.core.library.types.StringType`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/types/stringtype) | +| `SIUnits` | [`org.openhab.core.library.unit.SIUnits`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/unit/siunits) | +| `ImperialUnits` | [`org.openhab.core.library.unit.ImperialUnits`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/unit/imperialunits) | +| `MetricPrefix` | [`org.openhab.core.library.unit.MetricPrefix`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/unit/metricprefix) | +| `Units` | [`org.openhab.core.library.unit.Units`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/unit/units) | +| `BinaryPrefix` | [`org.openhab.core.library.unit.BinaryPrefix`](https://www.openhab.org/javadoc/latest/org/openhab/core/library/unit/binaryprefix) | +| `ChronoUnit` | [`java.time.temporal.ChronoUnit`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/temporal/ChronoUnit.html) | +| `Duration` | [`java.time.Duration`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html) | +| `ZoneId` | [`java.time.ZoneId`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html) | +| `ZonedDateTime` | [`java.time.ZonedDateTime`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZonedDateTime.html) | + +`require("@runtime")` also defines "services" such as `items`, `things`, `rules`, `events`, `actions`, `ir`, `itemRegistry`. +You can use these services for backwards compatibility purposes or ease migration from JSR223 scripts. +Generally speaking, you should prefer to use [Standard Library](#standard-library) provided by this library instead. diff --git a/bundles/org.openhab.automation.jsscripting/pom.xml b/bundles/org.openhab.automation.jsscripting/pom.xml index cfd994155..de0cc7495 100644 --- a/bundles/org.openhab.automation.jsscripting/pom.xml +++ b/bundles/org.openhab.automation.jsscripting/pom.xml @@ -25,7 +25,7 @@ 21.3.0 6.2.1 ${project.version} - openhab@1.2.2 + openhab@2.0.0