# JavaScript Scripting

This add-on provides support for JavaScript (ECMAScript 2021+) that can be used as a scripting language within automation rules.

Also included is openhab-js (opens new window), 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.

# Configuration

This add-on includes by default the openhab-js (opens new window) 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

# UI Based Rules

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.

# Adding Triggers

Using the openHAB UI, first create a new rule and set a trigger condition

OpenHAB Rule Configuration

# 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.

OpenHAB Rule Engines

You can now write rules using standard ES6 Javascript along with the included openHAB standard library.

OpenHAB Rule Script

For example, turning a light on:

items.getItem("KitchenLight").sendCommand("ON");
console.log("Kitchen Light State", items.getItem("KitchenLight").state);

Sending a notification

actions.NotificationAction.sendNotification("[email protected]", "Balcony door is open");

Querying the status of a thing

const thingStatusInfo = actions.Things.getThingStatusInfo("zwave:serial_zstick:512");
console.log("Thing status",thingStatusInfo.getStatus());

See openhab-js (opens new window) for a complete list of functionality

# Scripting Basics

The openHAB JSScripting runtime attempts to provide a familiar environment to Javascript developers.

# Require

Scripts may include standard NPM based libraries by using CommonJS require. The library search path will look in 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 TRACE level, but can be configured using the console logging commands.

log:set DEBUG org.openhab.automation.script

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 (opens new window) for more information about console logging.

# setTimeout

The global setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires.

var timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);
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 (opens new window) for more information about setTimeout.

# setInterval

The setInterval() method repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.

var intervalID = setInterval(func, [delay, arg1, arg2, ...]);
var intervalID = setInterval(function[, delay]);

The global clearInterval() method cancels a timed, repeating action which was previously established by a call to setInterval().

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 namespace as well as ScriptLoaded and 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 (opens new window) for more information about setInterval.

# ScriptLoaded

For file based scripts, this function will be called if found when the script is loaded.

scriptLoaded = function () {
    console.log("script loaded");
    loadedDate = Date.now();
}

# ScriptUnLoaded

For file based scripts, this function will be called if found when the script is unloaded.

scriptUnloaded = function () {
    console.log("script unloaded");
    //clean up rouge timers
    clearInterval(timer);
}

# Paths

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.

# Standard Library

Full documentation for the openHAB JavaScript library can be found at openhab-js (opens new window)

# Items

The items namespace allows interactions with openHAB items.

See openhab-js : items (opens new window) for full API documentation

  • items : object
    • .getItem(name, nullIfMissing) ⇒ Item
    • .getItemsByTag(...tagNames) ⇒ Array.<Item>
    • .createItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction], [itemMetadata])
    • .addItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction])
    • .removeItem(itemOrItemName) ⇒ Boolean
    • .replaceItem(itemName, [itemType], [category], [groups], [label], [tags], [giBaseType], [groupFunction])
    • .safeItemName(s) ⇒ String
const item = items.getItem("KitchenLight");
console.log("Kitchen Light State", item.state);

Calling getItem(...) returns an Item object with the following properties:

  • Item : object
    • .type ⇒ String
    • .name ⇒ String
    • .label ⇒ String
    • .history ⇒ ItemHistory
    • .state ⇒ String
    • .rawState ⇒ HostState
    • .members ⇒ Array.<Item>
    • .descendents ⇒ Array.<Item>
    • .isUninitialized ⇒ Boolean
    • .tags ⇒ Array.<String>
    • .getMetadataValue(namespace) ⇒ String
    • .updateMetadataValue(namespace, value) ⇒ String
    • .upsertMetadataValue(namespace, value) ⇒ Boolean
    • .updateMetadataValues(namespaceToValues)
    • .sendCommand(value)
    • .sendCommandIfDifferent(value) ⇒ Boolean
    • .postUpdate(value)
    • .addGroups(...groupNamesOrItems)
    • .removeGroups(...groupNamesOrItems)
    • .addTags(...tagNames)
    • .removeTags(...tagNames)
const item = items.getItem("KitchenLight");
//send a ON command
item.sendCommand("ON");
//Post an update
item.postUpdate("OFF");
//Get state
console.log("KitchenLight state", item.state)

calling item.history... returns a ItemHistory object with the following functions:

Note serviceId is optional, if omitted, the default persistance service will be used.

  • ItemHistory : object
    • .averageSince(timestamp, serviceId) ⇒ Number
    • .changedSince(timestamp, serviceId) ⇒ Number
    • .deltaSince(timestamp, serviceId) ⇒ Number
    • .deviationSince(timestamp, serviceId) ⇒ Number
    • .evolutionRate(timestamp, serviceId) ⇒ Number
    • .historicState(timestamp, serviceId) ⇒ state
    • .lastUpdate(serviceId) ⇒ Date
    • .latestState(serviceId) ⇒ state
    • .maximumSince(timestamp,serviceId) ⇒ state
    • .minimumSince(timestamp,serviceId) ⇒ state
    • .persist(serviceId)
    • .previousState(skipEqual,serviceId) ⇒ state
    • .sumSince(timestamp, serviceId) ⇒ Number
    • .updatedSince(timestamp, serviceId) ⇒ Boolean
    • .varianceSince(timestamp,serviceId) ⇒ state
var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
var item = items.getItem("KitchenDimmer");
console.log("KitchenDimmer averageSince", item.history.averageSince(yesterday));

# Actions

The actions namespace allows interactions with openHAB actions. The following are a list of standard actions.

Additional actions provided by user installed addons can be accessed using their common name on the actions name space (example: actions.Pushsafer.pushsafer(...))

See openhab-js : actions (opens new window) for full API documentation and additional actions.

# Audio Actions

See openhab-js : actions.Audio (opens new window) for complete documentation

# BusEvent

See openhab-js : actions.BusEvent (opens new window) for complete documentation

# Ephemeris Actions

See openhab-js : actions.Ephemeris (opens new window) 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 (opens new window) as well as the Ephemeris JavaDoc (opens new window).

// Example
let weekend = actions.Ephemeris.isWeekend();

# Exec Actions

See openhab-js : actions.Exec (opens new window) for complete documentation

Execute a command line.


// Execute command line.
actions.Exec.executeCommandLine('echo', 'Hello World!');

// Execute command line with timeout.
let Duration = Java.type('java.time.Duration');
actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hello World!');

// Get response from command line.
let response = actions.Exec.executeCommandLine('echo', 'Hello World!');

// Get response from command line with timeout.
response = actions.Exec.executeCommandLine(Duration.ofSeconds(20), 'echo', 'Hello World!');

# HTTP Actions

See openhab-js : actions.HTTP (opens new window) for complete documentation

// Example GET Request
var response = actions.HTTP.sendHttpGetRequest('<url>');

Replace <url> with the request url.

# ScriptExecution Actions

See openhab-js : actions.ScriptExecution (opens new window) for complete documentation

let now = time.ZonedDateTime.now();

// Function to run when the timer goes off.
function timerOver () {
  logger.info('The timer is over.');
}

// Create the Timer.
this.myTimer = actions.ScriptExecution.createTimer(now.plusSeconds(10), timerOver);

// Cancel the timer.
this.myTimer.cancel();

// Check whether the timer is active. Returns true if the timer is active and will be executed as scheduled.
let active = this.myTimer.isActive();

// Reschedule the timer.
this.myTimer.reschedule(now.plusSeconds(5));

# Semantics Actions

See openhab-js : actions.Semantics (opens new window) for complete documentation

# Things Actions

See openhab-js : actions.Things (opens new window) for complete documentation

# Voice Actions

See openhab-js : actions.Voice (opens new window) for complete documentation

# Cloud Notification Actions

(optional action if openhab-cloud is installed)

Notification actions may be placed in Rules to send alerts to mobile devices registered with an openHAB Cloud instance (opens new window) such as myopenHAB.org (opens new window).

For available actions have a look at the Cloud Notification Actions Docs (opens new window).

// 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.

# 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.

See openhab-js : cache (opens new window) for full API documentation

  • cache : object
    • .get(key, defaultSupplier) ⇒ Object | null
    • .put(key, value) ⇒ Previous Object | null
    • .remove(key) ⇒ Previous Object | null

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

let counter = cache.get("counter", () => ({ "times": 0 }));
console.log("Count",counter.times++);

Example (Get a previously set object)

let counter = cache.get("counter");
if(counter == null){
     counter = {times: 0};
     cache.put("counter", counter);
}
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

let logger = log('my_logger');

//prints "Hello World!"
logger.debug("Hello {}!", "world");

# Time

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 that a native Java ZonedDateTime or Duration is required, the runtime will automatically convert a JS-Joda ZonedDateTime or Duration to its Java counterpart.

Examples:

var now = time.ZonedDateTime.now();
var yesterday = time.ZonedDateTime.now().minusHours(24);

var item = items.getItem("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.

# File Based Rules

The JSScripting 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 connivent way to persist objects.

File based rules can be created in 2 different ways: using JSRule or the Rule Builder.

See openhab-js : rules (opens new window) for full API documentation

# JSRule

JSRules provides a simple, declarative syntax for defining rules that will be executed based on a trigger condition

const 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: data => {
    items.getItem("BalconyLights").sendCommand("ON");
    actions.NotificationAction.sendNotification(email, "Balcony lights are ON");
  }
});

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')

See openhab-js : triggers (opens new window) in the API documentation for a full list of all triggers.

# Rule Builder

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);

Rule are completed by calling .build(name,description) , if name or description are omitted, a generated value will be used.

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");

see Examples for further patterns

# Rule Builder Triggers

  • 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.
    • .item(itemName) Specifies an item as the source of changes to trigger a rule.
      • .for(duration)
      • .from(state)
      • .to(state)
      • .fromOff()
      • .toOn()
      • .receivedCommand()
      • .receivedUpdate()
    • .memberOf(groupName)
      • .for(duration)
      • .from(state)
      • .to(state)
      • .fromOff()
      • .toOn()
      • .receivedCommand()
      • .receivedUpdate()
    • .system()
      • .ruleEngineStarted()
      • .rulesLoaded()
      • .startupComplete()
      • .thingsInitialized()
      • .userInterfacesStarted()
      • .startLevel(level)
    • .thing(thingName)
      • changed()
      • updated()
      • from(state)
      • to(state)

Additionally all the above triggers have the following functions:

# Rule Builder Conditions

  • if(optionalFunction)
    • .stateOfItem(state)

# Rule Builder Operations

  • then(optionalFunction)
    • .build(name, description)
    • .copyAndSendState()
    • .copyState()
    • .inGroup(groupName)
    • .postIt()
    • .postUpdate(state)
    • .send(command)
    • .sendIt()
    • .sendOff()
    • .sendOn()
    • .sendToggle()

# Rule Builder Examples

//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
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
rules.when().cron("0 0 21 * * ?").then().sendOff().toItem("KitchenLight").build("9PM Rule", "turn off the kitchen light
at 9PM");

//set the colour of the hall light to pink at 9PM
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");

//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();

//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().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
rules.when().item('HallLight').receivedUpdate().then().copyState().fromItem('BedroomLight1').toItem('BedroomLight2').build();