layout | title | source | description |
---|---|---|---|
documentation |
openHAB Javascript Library |
Fairly high-level ES6 library to support automation in openHAB. It provides convenient access to common openHAB functionality within rules including items, things, actions, logging and more. |
This library aims to be a fairly high-level ES6 library to support automation in openHAB. It provides convenient access to common openHAB functionality within rules including Items, Things, actions, logging and more.
This library is included by default in the openHAB JavaScript Scripting add-on.
- Installation
- Compatibility
- Configuration
- Scripting Basics
JS
Transformation- Standard Library
- File Based Rules
- Advanced Scripting
Install the openHAB JavaScript Scripting add-on, a version of this library will be automatically installed and available to ECMAScript 2022+ rules created using File Based Rules or UI Based Rules.
openHAB also provides the JavaScript Scripting (Nashorn) add-on, which is based on the older Nashorn JavaScript engine. This is referred to as ECMA - 262 Edition 5.1
or application/javascript;version=ECMAScript-5.1
in the Main UI.
This library is not compatible with this older runtime.
If you want to install the openHAB JavaScript library manually, you need to disable the caching of the internal library in the add-on's settings.
On openHABian:
- Open the openHABian config tool:
sudo openhabian-config
. - Select
40 | openHAB Related
. - Select
46 | Install openhab-js
.
Manually:
- Go to the JavaScript user scripts directory:
cd $OPENHAB_CONF/automation/js
. - You may need to install
npm
. - Install the latest release: Run
npm i openhab
. - Install the latest development version: Run
npm i git+https://github.com/openhab/openhab-js.git
.
NPM will create a node_modules
directory containing the latest version of this library.
This will be used instead of the binding provided version.
All openhab-js
versions (latest is 4.2.1
as of writing) are fully compatible with openHAB 3.1.0 or newer.
openHAB 3.4.0 or newer requires at least openhab-js
3.1.0!
openHAB 4.0.0(.M2) (or >= SNAPSHOT #3391
) or newer requires at least openhab-js
4.2.1!
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 for managing rules using files in the user configuration directory.
Using the openHAB UI, first create a new rule and set a trigger condition.
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 an empty script editor where you can enter your JavaScript.
You can now write rules using standard ES6 JavaScript along with the included openHAB standard library.
For example, turning a light on:
items.KitchenLight.sendCommand("ON");
console.log("Kitchen Light State", items.KitchenLight.state);
Sending a notification
actions.NotificationAction.sendNotification("[email protected]", "Balcony door is open");
Querying the status of a thing
var thingStatusInfo = actions.Things.getThingStatusInfo("zwave:serial_zstick:512");
console.log("Thing status",thingStatusInfo.getStatus());
See openhab-js for a complete list of functionality.
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.
Note that event
object is only available when the UI based rule was triggered by an event and is not manually run!
Trying to access event
on manual run does not work (and will lead to an error), use this.event
instead (will be undefined
in case of manual run).
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 table 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 | [item] changed , [item] was updated |
State that triggered event | triggeringItem.state |
oldItemState |
sub-class of 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 | [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:
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:
// Example assumes String item trigger
console.log(event.itemState == "test") // WRONG. Will always log "false"
console.log(event.itemState.toString() == "test") // OK
The openHAB JavaScript Scripting runtime attempts to provide a familiar environment to JavaScript developers.
Due to the way how openHAB runs UI based scripts, let
, const
and class
are not supported at top-level.
Use var
instead or wrap your script inside a self-invoking function:
// Wrap script inside a self-invoking function:
(function (data) {
const C = 'Hello world';
console.log(C);
})(this.event);
// Defining a class using var:
var Tree = class {
constructor (height) {
this.height = height;
}
}
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 for more information.
The JS Scripting binding supports the standard console
object for logging.
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:
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 consists of the prefix org.openhab.automation.script
and the script’s individual part .file.filename
or .ui.ruleUID
.
This logger name can be changed by assigning a new string to the loggerName
property of the console:
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 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])
console.info(obj1 [, obj2, ..., objN])
console.warn(obj1 [, obj2, ..., objN])
console.error(obj1 [, obj2, ..., objN])
console.debug(obj1 [, obj2, ..., objN])
console.trace(obj1 [, obj2, ..., objN])
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.
Note: openhab-js is logging to org.openhab.automation.openhab-js
.
JS Scripting provides access to the global setTimeout
, setInterval
, clearTimeout
and clearInterval
methods specified in the Web APIs.
When a script is unloaded, all created timeouts and intervals are automatically cancelled.
The global setTimeout()
method sets a timer which executes a function once the timer expires.
setTimeout()
returns a timeoutId
(a positive integer value) which identifies the timer created.
var timeoutId = setTimeout(callbackFunction, delay);
delay
is an integer value that represents the amount of milliseconds to wait before the timer expires.
The global clearTimeout(timeoutId)
method cancels a timeout previously established by calling setTimeout()
.
If you need a more verbose way of creating timers, consider to use createTimer
instead.
The global setInterval()
method repeatedly calls a function, with a fixed time delay between each call.
setInterval()
returns an intervalId
(a positive integer value) which identifies the interval created.
var intervalId = setInterval(callbackFunction, delay);
delay
is an integer value that represents the amount of milliseconds to wait before the timer expires.
The global clearInterval(intervalId)
method cancels a timed, repeating action which was previously established by a call to setInterval()
.
You can access all variables of the current context in the created timers.
Note: Variables can be mutated (changed) after the timer has been created. Be aware that this can lead to unattended side effects, e.g. when you change the variable after timer creation, which can make debugging quite difficult!
var myVar = 'Hello world!';
// Schedule a timer that expires in ten seconds
setTimeout(() => {
console.info(`Timer expired with myVar = "${myVar}"`);
}, 10000);
myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello mutation!" instead of "Hello world!"
If you need to pass some variables to the timer but avoid that they can get mutated, use a function generator.
Pass variables using a function generator
This allows you to pass variables to the timer's callback function during timer creation. The variables can NOT be mutated after the timer function generator was used to create the callback function.
// Function generator for the timer's callback function
function cbFuncGen (myVariable) {
return function () {
console.info(`Timer expired with myVar = "${myVariable}"`);
};
}
var myVar = 'Hello world!';
// Schedule a timer that expires in ten seconds
setTimeout(cbFuncGen(myVar), 10000);
myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello world!"
For file based rules, scripts will be loaded from automation/js
in the user configuration directory.
NPM libraries will be loaded from automation/js/node_modules
in the user configuration directory.
It is possible to hook into unloading of a script and register a function that is called when the script is unloaded.
require('@runtime').lifecycleTracker.addDisposeHook(() => functionToCall());
// Example
require('@runtime').lifecycleTracker.addDisposeHook(() => {
console.log("Deinitialization hook runs...")
});
openHAB provides several data transformation services as well as the script transformations, that are available from the framework and need no additional installation. It allows transforming values using any of the available scripting languages, which means JavaScript Scripting is supported as well. See the transformation docs for more general information on the usage of script transformations.
Use JavaScript Scripting as script transformation by:
-
Creating a script in the
$OPENHAB_CONF/transform
folder with the.js
extension. The script should take one argumentinput
and return a value that supportstoString()
ornull
:(function(data) { // Do some data transformation here, e.g. return "String has" + data.length + "characters"; })(input);
-
Using
JS(<scriptname>.js):%s
as Item state transformation. -
Passing parameters is also possible by using a URL like syntax:
JS(<scriptname>.js?arg=value)
. Parameters are injected into the script and can be referenced like variables.
Simple transformations can aso be given as an inline script: JS(|...)
, e.g. JS(|"String has " + input.length + "characters")
.
It should start with the |
character, quotes within the script may need to be escaped with a backslash \
when used with another quoted string as in text configurations.
Full documentation for the openHAB JavaScript library can be found at openhab-js.
The openHAB JavaScript library provides type definitions for most of its APIs to enable code completion is IDEs like VS Code.
To use the type definitions, install the openhab
npm package (read the installation guide for more information), and import the used namespaces with const { rules, triggers, items } = require('openhab');
(adjust this to your needs).
If an API does not provide type definitions and therefore autocompletion won't work, the documentation will include a note.
The items
namespace allows interactions with openHAB Items.
Anywhere a native openHAB Item
is required, the runtime will automatically convert the JS-Item
to its Java counterpart.
See openhab-js : items for full API documentation.
- items :
object
- .NAME ⇒
Item
- .getItem(name, nullIfMissing) ⇒
Item
- .getItems() ⇒
Array[Item]
- .getItemsByTag(...tagNames) ⇒
Array[Item]
- .addItem(itemConfig)
- .removeItem(itemOrItemName) ⇒
boolean
- .replaceItem(itemConfig)
- .safeItemName(s) ⇒
string
- .NAME ⇒
var item = items.KitchenLight;
console.log("Kitchen Light State", item.state);
Calling getItem(...)
or ...
returns an Item
object with the following properties:
- Item :
object
- .rawItem ⇒
HostItem
- .history ⇒
ItemHistory
- .semantics ⇒
ItemSemantics
- .type ⇒
string
- .name ⇒
string
- .label ⇒
string
- .state ⇒
string
- .numericState ⇒
number|null
: State as number, if state can be represented as number, ornull
if that's not the case - .quantityState ⇒
Quantity|null
: Item state as Quantity ornull
if state is not Quantity-compatible or without unit - .rawState ⇒
HostState
- .members ⇒
Array[Item]
- .descendents ⇒
Array[Item]
- .isUninitialized ⇒
boolean
- .groupNames ⇒
Array[string]
- .tags ⇒
Array[string]
- .getMetadata(namespace) ⇒
object|null
- .replaceMetadata(namespace, value, configuration) ⇒
object
- .removeMetadata(namespace) ⇒
object|null
- .sendCommand(value):
value
can be a string, atime.ZonedDateTime
or aQuantity
- .sendCommandIfDifferent(value) ⇒
boolean
:value
can be a string, atime.ZonedDateTime
or aQuantity
- .postUpdate(value):
value
can be a string, atime.ZonedDateTime
or aQuantity
- .addGroups(...groupNamesOrItems)
- .removeGroups(...groupNamesOrItems)
- .addTags(...tagNames)
- .removeTags(...tagNames)
- .rawItem ⇒
// Equivalent to items.KitchenLight
var item = items.getItem("KitchenLight");
// Send an ON command
item.sendCommand("ON");
// Post an update
item.postUpdate("OFF");
// Get state
console.log("KitchenLight state", item.state);
See openhab-js : Item for full API documentation.
Calling addItem(itemConfig)
or replaceItem(itemConfig)
requires the itemConfig
object with the following properties:
- 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
- .type ⇒
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:
// 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 for full API documentation.
Calling Item.history
returns an ItemHistory
object with the following functions:
- ItemHistory :
object
- .averageBetween(begin, end, serviceId) ⇒
number | null
- .averageSince(timestamp, serviceId) ⇒
number | null
- .changedBetween(begin, end, serviceId) ⇒
boolean
- .changedSince(timestamp, serviceId) ⇒
boolean
- .deltaBetween(begin, end, serviceId) ⇒
number | null
- .deltaSince(timestamp, serviceId) ⇒
number | null
- .deviationBetween(begin, end, serviceId) ⇒
number | null
- .deviationSince(timestamp, serviceId) ⇒
number | null
- .evolutionRateBetween(begin, end, serviceId) ⇒
number | null
- .evolutionRateSince(timestamp, serviceId) ⇒
number | null
- .historicState(timestamp, serviceId) ⇒
HistoricItem | null
- .lastUpdate(serviceId) ⇒
ZonedDateTime | null
- .latestState(serviceId) ⇒
string | null
- .maximumBetween(begin, end, serviceId) ⇒
HistoricItem | null
- .maximumSince(timestamp,serviceId) ⇒
HistoricItem | null
- .minimumSince(begin, end, serviceId) ⇒
HistoricItem | null
- .minimumSince(timestamp, serviceId) ⇒
HistoricItem | null
- .persist(serviceId)
- .previousState(skipEqual, serviceId) ⇒
HistoricItem | null
- .sumBetween(begin, end, serviceId) ⇒
number | null
- .sumSince(timestamp, serviceId) ⇒
number | null
- .updatedBetween(begin, end, serviceId) ⇒
boolean
- .updatedSince(timestamp, serviceId) ⇒
boolean
- .varianceBetween(begin, end, serviceId) ⇒
number | null
- .varianceSince(timestamp, serviceId) ⇒
number | null
- .averageBetween(begin, end, serviceId) ⇒
Note: serviceId
is optional, if omitted, the default persistence service will be used.
var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
var item = items.KitchenDimmer;
console.log('KitchenDimmer averageSince', item.history.averageSince(yesterday));
The HistoricItem
object contains the following properties, representing Item state and the respective timestamp:
state
: State as stringnumericState
: State as number, if state can be represented as number, ornull
if that's not the casequantityState
: Item state asQuantity
ornull
if state is not Quantity-compatiblerawState
: State as JavaState
objecttimestamp
: Timestamp astime.ZonedDateTime
var midnight = time.toZDT('00:00');
var historic = items.KitchenDimmer.history.maximumSince(midnight);
console.log('KitchenDimmer maximum was ', historic.state, ' at ', historic.timestamp);
See openhab-js : ItemHistory for full API documentation.
The Things namespace allows to interact with openHAB Things.
See openhab-js : things 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)
- .bridgeUID ⇒
var thing = things.getThing('astro:moon:home');
console.log('Thing label: ' + thing.label);
// Set Thing location
thing.setLocation('living room');
// Disable Thing
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 for full API documentation and additional actions.
See openhab-js : actions.Audio for complete documentation.
See openhab-js : actions.BusEvent for complete documentation.
See openhab-js : actions.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.
Additional information can be found on the Ephemeris Actions Docs as well as the Ephemeris JavaDoc.
// Example
var weekend = actions.Ephemeris.isWeekend();
See openhab-js : actions.Exec for complete documentation.
Execute a command line.
// Execute command line.
actions.Exec.executeCommandLine('echo', 'Hello World!');
// Execute command line with timeout.
actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), 'echo', 'Hello World!');
// Get response from command line with timeout.
var response = actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), 'echo', 'Hello World!');
See openhab-js : actions.HTTP for complete documentation.
// Example GET Request
var response = actions.HTTP.sendHttpGetRequest('<url>');
Replace <url>
with the request url.
The ScriptExecution
actions provide the callScript(string scriptName)
method, which calls a script located at the $OH_CONF/scripts
folder, as well as the createTimer
method.
You can also create timers using the native JS methods for timer creation, your choice depends on the versatility you need.
Sometimes, using setTimeout
is much faster and easier, but other times, you need the versatility that createTimer
provides.
Keep in mind that you should somehow manage the timers you create using createTimer
, otherwise you could end up with unmanageable timers running until you restart openHAB.
A possible solution is to store all timers in the private cache and let openHAB automatically cancel them when the script is unloaded and the cache is cleared.
actions.ScriptExecution.createTimer(time.ZonedDateTime instant, function callback);
actions.ScriptExecution.createTimer(string identifier, time.ZonedDateTime instant, function callback);
createTimer
accepts the following arguments:
string
identifier (optional): Identifies the timer by a string, used e.g. for logging errors that occur during the callback execution.time.ZonedDateTime
instant: Point in time when the callback should be executed.function
callback: Callback function to execute when the timer expires.
createTimer
returns an openHAB Timer, that provides the following methods:
cancel()
: Cancels the timer. ⇒boolean
: true, if cancellation was successfulgetExecutionTime()
: The scheduled execution time or null if timer was cancelled. ⇒time.ZonedDateTime
ornull
isActive()
: Whether the scheduled execution is yet to happen. ⇒boolean
isCancelled()
: Whether the timer has been cancelled. ⇒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
var now = time.ZonedDateTime.now();
// Function to run when the timer goes off.
function timerOver () {
console.info('The timer expired.');
}
// Create the Timer.
var myTimer = actions.ScriptExecution.createTimer('My Timer', now.plusSeconds(10), timerOver);
// Cancel the timer.
myTimer.cancel();
// Check whether the timer is active. Returns true if the timer is active and will be executed as scheduled.
var active = myTimer.isActive();
// Reschedule the timer.
myTimer.reschedule(now.plusSeconds(5));
See openhab-js : actions.ScriptExecution for complete documentation.
See openhab-js : actions.Semantics for complete documentation.
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 for complete documentation.
openHAB provides various data transformation services 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.
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 for complete documentation.
See openhab-js : actions.Voice for complete documentation.
Note: Optional action if openHAB Cloud Connector is installed.
Notification actions may be placed in rules to send alerts to mobile devices registered with an openHAB Cloud instance such as myopenHAB.org.
For available actions have a look at the Cloud Notification Actions Docs.
// Example
actions.NotificationAction.sendNotification('<email>', '<message>'); // to a single myopenHAB user identified by e-mail
actions.NotificationAction.sendBroadcastNotification('<message>'); // to all myopenHAB users
Replace <email>
with the e-mail address of the user.
Replace <message>
with the notification text.
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 createTimer
) stored in its private cache are automatically cancelled.
The shared cache is shared across all rules and scripts, it can therefore be accessed from any automation language. The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded. If that key stored a timer, the timer is cancelled.
See openhab-js : cache for full API documentation.
- cache :
object
- .private
- .get(key, defaultSupplier) ⇒
Object | null
- .put(key, value) ⇒
Previous Object | null
- .remove(key) ⇒
Previous Object | null
- .exists(key) ⇒
boolean
- .get(key, defaultSupplier) ⇒
- .shared
- .get(key, defaultSupplier) ⇒
Object | null
- .put(key, value) ⇒
Previous Object | null
- .remove(key) ⇒
Previous Object | null
- .exists(key) ⇒
boolean
- .get(key, defaultSupplier) ⇒
- .private
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))
var counter = cache.private.get('counter', () => ({ 'times': 0 }));
console.log('Count', counter.times++);
Example (Get a previously set object)
var counter = cache.private.get('counter');
if (counter === null) {
counter = { times: 0 };
cache.private.put('counter', counter);
}
console.log('Count', counter.times++);
openHAB internally makes extensive use of the java.time
package.
openHAB-JS exports the excellent 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 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:
var now = time.ZonedDateTime.now();
var yesterday = time.ZonedDateTime.now().minusHours(24);
var item = items.Kitchen;
console.log("averageSince", item.history.averageSince(yesterday));
actions.Exec.executeCommandLine(time.Duration.ofSeconds(20), 'echo', 'Hello World!');
See JS-Joda for more examples and complete API usage.
There will be times when 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); |
Quantity or QuantityType |
if the unit is time-compatible, added to now |
time.toZDT(item.getItem('MyTimeItem').rawState); , time.toZDT(Quantity('10 min')); |
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; if no timezone is specified, SYSTEM timezone is used |
|
ISO8601 Date/Time String | parsed, depending on the provided data: if no date is passed, today's date; if no time is passed, midnight time | time.toZDT('00:00'); , time.toZDT('2022-12-24'); , time.toZDT('2022-12-24T18:30'); |
RFC String (output from a Java ZonedDateTime.toString() ) |
parsed | time.toZDT('2019-10-12T07:20:50.52Z'); |
"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'); |
ISO 8601 Duration String | added to now |
time.toZDT('PT1H4M6.789S'); |
When a type or string that cannot be handled is encountered, an error is thrown.
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.
var alarm = items.Alarm;
alarm.postUpdate(time.toZDT(alarm).toToday());
Tests whether this time.ZonedDateTime
is before the time passed in timestamp
, tested in various ways:
isBeforeTime
only compares the time portion of both, ignoring the date portionisBeforeDate
only compares the date portion of both, ignoring the time portionisBeforeDateTime
compares both date and time portions
timestamp
can be anything supported by time.toZDT()
.
Examples:
time.toZDT('22:00').isBeforeTime('23:00')
time.toZDT('2022-12-01T12:00Z').isBeforeDateTime('2022-12-02T13:00Z')
Tests whether this time.ZonedDateTime
is after the time passed in timestamp
, tested in various ways:
isAfterTime
only compares the time portion of both, ignoring the date portionisAfterDate
only compares the date portion of both, ignoring the time portionisAfterDateTime
compares both date and time portions
timestamp
can be anything supported by time.toZDT()
.
// Equivalent to items.Sunset
time.toZDT().isAfterTime(items.getItem('Sunset')) // is now after sunset?
time.toZDT().isAfterDateTime('2022-12-01T12:00Z') // is now after 2022-12-01 noon?
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:
time.toZDT().isBetweenTimes('22:00', '05:00') // currently between 11:00 pm and 5:00 am
// Equivalent to items.Sunset
time.toZDT().isBetweenTimes(items.getItem('Sunset'), '11:30 PM') // is now between sunset and 11:30 PM?
// Equivalent to items.StartTime
time.toZDT(items.getItem('StartTime')).isBetweenTimes(time.toZDT(), 'PT1H'); // is the state of StartTime between now and one hour from now
Tests whether this time.ZonedDateTime
is between the passed in start
and end
.
However, the function only compares the date portion of the three, ignoring the time portion.
start
and end
can be anything supported by time.toZDT()
.
Examples:
time.toZDT().isBetweenDates('2022-06-18', '2023-12-24') // currently between 2022-06-18 and 2023-12-24
Tests whether this time.ZonedDateTime
is between the passed in start
and end
.
start
and end
can be anything supported by time.toZDT()
.
Examples:
time.toZDT().isBetweenDateTimes('2022-06-18T22:00Z', '2023-12-24T05:00Z') // currently between 2022-06-18 22:00 and 2023-12-24 05:00
Tests to see if the delta between the time.ZonedDateTime
and the passed in time.ZonedDateTime
is within the passed in time.Duration
.
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?
}
This method on time.ZonedDateTime
returns the milliseconds from now to the passed in time.ZonedDateTime
.
var timestamp = time.ZonedDateTime.now().plusMinutes(5);
console.log(timestamp.getMillisFromNow());
The Quantity
class greatly simplifies Quantity handling by providing unit conversion, comparisons and mathematical operations.
A Quantity consists of a measurement and its Unit of Measurement (UoM), e.g. 5.7 m
(the measurement is 5.7
, the unit is m
meters).
Internally using the openHAB QuantityType
, which relies on javax.measure
, it supports all units and dimensions that openHAB supports.
If your unit is not listed in the UoM docs, it is very likely that it is still supported, e.g. the Angstrom Å for very small lengths (1 Å = 10 nm).
Anywhere a native openHAB QuantityType
is required, the runtime will automatically convert the JS-Quantity
to its Java counterpart.
Quantity(value)
is used without new (it's a factory, not a constructor), pass an amount and a unit to it to create a new Quantity
object:
The argument value
can be a Quantity-compatible Item
, a string, a Quantity
instance or an openHAB Java QuantityType
.
value
strings have the $amount $unit
format and must follow these rules:
$amount
is required with a number provided as string$unit
is optional (unitless quantities are possible) and can have a prefix likem
(milli) orM
(mega)$unit
does not allow whitespaces.$unit
does allow superscript, e.g.²
instead of^2
.$unit
requires the*
between two units to be present, although you usually omit it (which is mathematically seen allowed, but openHAB needs the*
).
Generally, you can expect a unit consisting of two (or more) units to need a *
, e.g. Nm
is N*m
,
Nearly all Units of Measurement (UoM) are expected to work with Quantity
.
ɡₙ
(standard gravity) is known to not work.
// Allowed:
var qty = Quantity('5.75 m');
qty = Quantity('1 N*m');
qty = Quantity('1 m/s');
qty = Quantity('1 m^2/s^2');
qty = Quantity('1 m^2/s^-2'); // negative powers
qty = Quantity('1'); // unitless quantity
qty = Quantity(items.my_uom_item);
// Not allowed:
qty = Quantity('m');
qty = Quantity('1 Nm'); // * is required
qty = Quantity('1 m^2 / s^2'); // whitespaces are not allowed
qty = Quantity('1 m^2 s^2'); // / is required
qty = Quantity('1 m2/s2'); // ^ is required
Note: It is possible to create a unit-less (without unit) Quantity, however there is no advantage over using a number
instead.
It is possible to convert a Quantity
to a new Quantity
with a different unit or to get a Quantity
's amount as integer or float:
var qty = Quantity('10.2 °C');
qty = qty.toUnit('°F');
var intValue = qty.int;
var floatValue = qty.float;
toUnit
returns a new Quantity with the given unit or null
, if conversion to that unit is not possible.
Quantity
provides the following methods for comparison:
equal(value)
⇒boolean
: thisQuantity
equals tovalue
greaterThan(value)
⇒boolean
: thisQuantity
is greater thanvalue
greaterThanOrEqual(value)
⇒boolean
: thisQuantity
is greater than or equal tovalue
lessThan(value)
⇒boolean
: thisQuantity
is less thanvalue
lessThanOrEqual(value)
⇒boolean
: thisQuantity
is less than or equal tovalue
value
can be a string or a Quantity
, for the string the same rules apply as described above.
add(value)
⇒Quantity
:value
can be a Quantity-compatibleItem
, a string or aQuantity
divide(value)
⇒Quantity
:value
can be a Quantity-compatible or NumberItem
, a number, a string or aQuantity
multiply(value)
⇒Quantity
:value
can be a Quantity-compatible or NumberItem
, a number, a string or aQuantity
subtract(value)
⇒Quantity
:value
can be a Quantity-compatibleItem
, a string or aQuantity
For the string the same rules apply as described above.
See openhab-js : Quantity for full API documentation.
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 logger using the log namespace.
var logger = log('my_logger');
//prints "Hello World!"
logger.debug("Hello {}!", "world");
openHAB internally is a Java program. openHAB-JS converts between Java and JavaScript data types and reverse.
See openhab-js : utils for full API documentation.
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 for a convenient way to persist objects.
File based rules can be created in 2 different ways: using JSRule or the Rule Builder.
See openhab-js : rules for full API documentation.
JSRules provides a simple, declarative syntax for defining rules that will be executed based on a trigger condition
var email = "[email protected]"
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: (event) => {
// Equivalent to items.BalconyLights.sendCommand("ON")
items.getItem("BalconyLights").sendCommand("ON");
actions.NotificationAction.sendNotification(email, "Balcony lights are ON");
},
tags: ["Balcony", "Lights"],
id: "BalconyLightsOn"
});
Note: description
, tags
and id
are optional.
Note: You can use the passed event
object to get information about the trigger that triggered the rule.
See Event Object for documentation.
Multiple triggers can be added, some example triggers include:
triggers.ChannelEventTrigger('astro:sun:local:rise#event', 'START');
triggers.ItemStateChangeTrigger('my_item', 'OFF', 'ON');
triggers.ItemStateUpdateTrigger('my_item', 'OFF');
triggers.ItemCommandTrigger('my_item', 'OFF');
triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON');
triggers.GroupStateUpdateTrigger('my_group', 'OFF');
triggers.GroupCommandTrigger('my_group', 'OFF');
triggers.ThingStatusUpdateTrigger('some:thing:uuid','OFFLINE');
triggers.ThingStatusChangeTrigger('some:thing:uuid','ONLINE','OFFLINE');
triggers.SystemStartlevelTrigger(40) // Rules loaded
triggers.SystemStartlevelTrigger(50) // Rule engine started
triggers.SystemStartlevelTrigger(70) // User interfaces started
triggers.SystemStartlevelTrigger(80) // Things initialized
triggers.SystemStartlevelTrigger(100) // Startup Complete
triggers.GenericCronTrigger('0 30 16 * * ? *');
triggers.TimeOfDayTrigger('19:00');
triggers.DateTimeTrigger('MyDateTimeItem');
You can use null
for a trigger parameter to skip its configuration.
See openhab-js : triggers in the API documentation for a full list of all triggers.
The Rule Builder provides a convenient API to write rules in a high-level, readable style using a builder pattern.
Rules are started by calling rules.when()
and can chain together triggers,
conditions and operations in the following pattern:
rules.when().triggerType()...if().conditionType().then().operationType()...build(name, description, tags, id);
Rule are completed by calling .build(name, description, tags, id)
, all parameters are optional and reasonable defaults will be used if omitted.
name
String rule name - defaults generated namedescription
String Rule description - defaults generated descriptiontags
Array of string tag names - defaults empty arrayid
String id - defaults random UUID
A simple example of this would look like:
rules.when().item("F1_Light").changed().then().send("changed").toItem("F2_Light").build("My Rule", "My First Rule");
Operations and conditions can also optionally take functions:
rules.when().item("F1_light").changed().then(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 for further patterns.
when()
or()
.channel(channelName)
Specifies a channel event as a source for the rule to fire..triggered(event)
Trigger on a specific event name
.cron(cronExpression)
Specifies a cron schedule for the rule to fire..timeOfDay(time)
Specifies a time of day inHH:mm
for the rule to fire..item(itemName)
Specifies an Item as the source of changes to trigger a rule..for(duration)
.from(state)
.fromOn()
.fromOff()
.to(state)
.toOn()
.toOff()
.receivedCommand()
.receivedUpdate()
.changed()
.memberOf(groupName)
Specifies a group Item as the source of changes to trigger the rule..for(duration)
.from(state)
.fromOn()
.fromOff()
.to(state)
.toOn()
.toOff()
.receivedCommand()
.receivedUpdate()
.changed()
.system()
Specifies a system event as a source for the rule to fire..ruleEngineStarted()
.rulesLoaded()
.startupComplete()
.thingsInitialized()
.userInterfacesStarted()
.startLevel(level)
.thing(thingName)
Specifies a Thing event as a source for the rule to fire.changed()
updated()
from(state)
to(state)
.dateTime(itemName)
Specifies a DateTime Item whose (optional) date and time schedule the rule to fire..timeOnly()
Only the time of the Item should be compared, the date should be ignored.
Additionally, all the above triggers have the following functions:
.if()
or.if(fn)
-> a rule condition.then()
or.then(fn)
-> a rule operation.or()
-> a rule trigger (chain additional triggers)
if(optionalFunction)
.stateOfItem(itemName)
is(state)
in(state...)
then(optionalFunction)
.build(name, description, tags, id)
.copyAndSendState()
.copyState()
.inGroup(groupName)
.postIt()
.postUpdate(state)
.send(command)
.sendIt()
.sendOff()
.sendOn()
.sendToggle()
// 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();
// Turn on the kitchen light at SUNSET (using the Astro binding)
rules.when().channel('astro:sun:home:set#event').triggered('START').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().timeOfDay('21:00').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');
// 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
rules.when().item('HallLight').changed().to('300,100,100').if(fn).then().sendToggle().toItem('OutsideLight').build();
// Turn on the outdoor lights based on a DateTime Item's time portion
rules.when().dateTime('OutdoorLights_OffTime').timeOnly().then().sendOff().toItem('OutdoorLights').build('Outdoor Lights off');
// 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
rules.when(true).item('HallLight').receivedCommand().then().sendIt().toItem('KitchenLight').build('Hall Light to Kitchen Light');
// When the HallLight is updated to ON, make sure that BedroomLight1 is set to the same state as the BedroomLight2
rules.when(true).item('HallLight').receivedUpdate().then().copyState().fromItem('BedroomLight1').toItem('BedroomLight2').build();
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.
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 some information about that trigger.
This table 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 , Group****Trigger |
Name of Item that triggered event | triggeringItem.name |
groupName |
Group****Trigger |
Name of the group whose member triggered event | N/A |
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 |
cronExpression |
GenericCronTrigger |
Cron expression of the trigger | N/A |
time |
TimeOfDayTrigger |
Time of day value of the trigger | N/A |
eventType |
all except PWMTrigger , PIDTrigger |
Type of event that triggered event (change, command, triggered, update, time) | N/A |
triggerType |
all except PWMTrigger , PIDTrigger |
Type of trigger that triggered event | N/A |
module |
all | (user-defined or auto-generated) name of trigger | N/A |
All properties are typeof string
.
NOTE:
Group****Trigger
s use the equivalent Item****Trigger
as trigger for each member.
Time triggers do not provide any event instance, therefore no property is populated.
See openhab-js : EventObject for full API documentation.
Loading of third party libraries is supported the same way as loading the openHAB JavaScript library:
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.
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, you may also search the forum for details.
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):
-
Create a separate folder for your library outside of
automation/js
, you may also initialize a Git repository. -
Run
npm init
from your newly created folder; at least provide responses for thename
,version
andmain
(e.g.index.js
) fields. -
Create the main file of your library (
index.js
) and add some exports:var someProperty = 'Hello world!'; function someFunction () { console.log('Hello from your personal library!'); } module.exports = { someProperty, someFunction };
-
Tar it up by running
npm pack
from your library's folder. -
Install it by running
npm install <name>-<version>.tgz
from theautomation/js
folder. -
After you've installed it with
npm
, you can continue development of the library insidenode_modules
.
It is also possible to upload your library to npm to share it with other users.
If you want to get some advanced information, you can read this blog post or just google it.
One can access many useful utilities and types using require("@runtime")
, e.g.
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;
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 provided by this library instead.