Replicating Rules DSL "receivedCommand" in JSScripting

Hello everyone,

I finally started migrating my old openHAB 2.5 installation to the latest Milestone build of openHAB 3. I’m currently running openHAB in a docker container pulled from openhab/openhab:milestone. Docker is running on a Raspberry Pi 3 Model B Rev 1.2 with the OS Debian GNU/Linux 11 (bullseye).

I have multiple rules from openHAB 2.5, using the Rules DSL, that access the implicit variable receivedCommand. I’m trying to replicate that behavior using file-based Javascript rules with the JSScripting Addon and can’t get it to work. Researching this forum I stumbled upon this thread which mentioned the event.itemCommand statement should provide the same functionality.

I’ve tried testing this with a very simple rule:

var {event, log, rules, triggers} = require("openhab")

let logger = log("tests");

rules.JSRule({
    name: "[TESTING] Test Switch Received Command",
    description: "Log the item name, state and received command",
    triggers: [
        triggers.ItemCommandTrigger("testSwitch")
    ],
    execute: data => {
        let cmd = event.itemCommand;
    }
});

Unfortunately this rule only produces the following error messages when I flip testSwitch in a sitemap:

Failed to execute rule '-TESTING--Test-Switch-Received-Command-5e7c8d4a-5f10-4e4b-b988-ff9b2f8f43b4': Fail to execute action: 1

Since the only execution statement of the rule is a variable assigment from event.itemCommand I assume that statement is not working as expected.

For anyone wondering why I’m requiring openhab, it’s because I have configured JSScripting to not use the built-in variables via the Main UI.

Any help would be appreciated. I’m also interested in replicating the triggeringItem functionality from the rules dsl. Apparently this should work using event.itemName or event.item. Unfortunately using those statement produce the same error message as above.

This requires a little bit of understanding how that execute part works.

First, what you are passing to JSRule is a dictionary and execute is the key for the function that gets called when the rule is triggered. OH expects this function to take one argument which will be the “event” that triggered the rule.

data => { is a simplified way to define a function with one argument. So

execute: data -> {
    let cmd = event.itemCommand;
}

is the same as

execute: function(data) {
    let cmd = event.itemCommand
}

Perhaps now the problem is apparent. You’ve name the argument to the function data, not event. So either change to

execute: event => {

or

    let cmd = data.itemCommand;

Thank you for your help, that was indeed a problem. I have changed the names accordingly, but I’m running into a different problem when trying to display the received command in a log message. My rule now looks like this:

var {log, rules, triggers} = require("openhab");

let logger = log("tests");

rules.JSRule({
    name: "[TESTING] Test Switch Received Command",
    description: "Log the item name, state and received command",
    triggers: [triggers.ItemCommandTrigger("testSwitch")],
    execute: event => {
        let cmd = event.itemCommand;
        logger.info("{}", cmd)
    }
});

Now I get the following error message:

TypeError: Cannot read property "message" from undefined

If I replace

logger.info("{}", cmd);

with

logger.info(cmd);

the error message changes to:

TypeError: undefined has no such function "toString"

Without calling the logger the rule works. So it seems like the variable names are correct now, but the event.itemCommand; is not returning an actual value. Any ideas?

EDIT:
I also tried logger.info("{}", event); which did not cause an error, but isn’t really helpful as the output is [object Object].

Have you changed the setting for JS Scripting in MainUI so it doesn’t import anything by default? If not, you don’t need that first line.

var {log, rules, triggers} = require("openhab");

If so, make that line a const instead of a var.

That’s not related to the error, but something to note.

The error is because event.itemCommand is undefined.

I don’t use file based rules so it’s possible that openhab-js does stuff to the event. But I believe console.log will dump the whole Object to the log showing the parts.

console.log(cmd)

If not, utils.dumpObject(cmd) will do it for you. You’ll have to add utils to your imports if it turns out you do still need them.

That will tell you what’s in event.

Thanks for your help. utils.dumObject() helped me find the solution. It seems like file-based rules handle the event object differently then rules defined using the Main UI editor. Be aware I only tested rules written in application/javascript;version=ECMAScript-2021.

In file-based rules it is a simple JavaScript Object, in UI rules it is an instance of a Java class. Using the built-in Javascript method Object.keys(event) let me iterate over all keys of these objects to log them and their values. I have done that in both file-based and UI rules.

Main UI Rule

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: testSwitch
    type: core.ItemCommandTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: |
        let logger = log("rule_from_ui");
        logger.info("logging event from ui rule");
        const event_keys = Object.keys(event);
        for (let i = 0; i < event_keys.length; i++) {
          const key = event_keys[i];
          logger.info("{}: {}", key, event[key]);
        };
    type: script.ScriptAction

The above rule reveals the following structure of the event object:

{
  "getItemCommand": "org.openhab.core.items.events.ItemCommandEvent.getItemCommand",
  "toString": "org.openhab.core.items.events.ItemCommandEvent.toString",
  "getType": "org.openhab.core.items.events.ItemCommandEvent.getType",
  "getItemName": "org.openhab.core.items.events.ItemCommandEvent.getItemName",
  "getTopic": "org.openhab.core.items.events.ItemCommandEvent.getTopic",
  "getPayload": "org.openhab.core.items.events.ItemCommandEvent.getPayload",
  "getSource": "org.openhab.core.items.events.ItemCommandEvent.getSource",
  "equals": "org.openhab.core.items.events.ItemCommandEvent.equals",
  "hashCode": "org.openhab.core.items.events.ItemCommandEvent.hashCode",
}

To get the received command the following statement is needed:

const cmd = event.getItemCommand();

File Based Rule

let logger = log("tests");

rules.JSRule({
    name: "[TESTING] Test Switch Received Command",
    description: "Log the item name, state and received command",
    triggers: [triggers.ItemCommandTrigger("testSwitch")],
    execute: event => {
        logger.info("logging event from file based rule");
        const event_keys = Object.keys(event);
        for (let i = 0; i < event_keys.length; i++) {
            const key = event_keys[i];
            logger.info("{}: {}", key, event[key]);
        };
    }
});

The above rule reveals the following, slightly different, structure of the event object:

{
  "eventType": "command",
  "triggerType": "ItemCommandTrigger",
  "receivedCommand": "ON",
  "oldState": "null",
  "newState": "null",
  "itemName": "testSwitch",
  "module": "e2960f39-b513-4144-aadd-2a0eea8e74aa"
}

To get the received command the following statement is needed:

const cmd = event.receivedCommand;

I find this to be a bit confusing. I would expect the Javascript to produce the same results, independent of where it’s written. I also think it’d be a good idea to expand the JSRule documentation to include a more detailed explanation of what the triggerConfig expected by the execute key actually is. It does say callback, but I think it’d be advantageous to explain that the callback has to expect exactly one argument which will contain information about the event triggering the rule. This could maybe help people avoiding the initial problem I had. Unfortunately I wasn’t able to find such a description. If it exists, maybe just add a link as a reply for future reference.

2 Likes

Valentin, great job tracking down your issue!!!
The documentation is all generated and maintained by the users of openHAB. You can suggest an edit to any of it and I’m sure your suggestion will be excepted and appreciated. If you don’t have time, link the correct page and I’ll try my best to make the addition although I’m still learning js scripting

Thanks, I’m still quite new to engaging in this community, where would be the best place to suggest such an edit? I’d be glad to do it.

You don’t have to use the event object at all to get the command that triggered a receivedCommand rule in the UI rules. The variable command is also injected into the scope (but it is an OH command type and not a string). For example:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.dummyTest');
logger.info(utils.dumpObject(command));
logger.info(command.toString());

results in:

2022-04-02 10:12:04.034 [INFO ] [org.openhab.automation.script.utils ] - Dumping object...
2022-04-02 10:12:04.035 [INFO ] [org.openhab.automation.script.utils ] -   typeof obj = object
2022-04-02 10:12:04.035 [INFO ] [org.openhab.automation.script.utils ] -   Java.isJavaObject(obj) = true
2022-04-02 10:12:04.036 [INFO ] [org.openhab.automation.script.utils ] -   Java.isType(obj) = false
2022-04-02 10:12:04.045 [INFO ] [org.openhab.automation.script.utils ] -   Java.typeName(obj.class) = org.openhab.core.library.types.OnOffType
2022-04-02 10:12:04.067 [INFO ] [org.openhab.automation.script.utils ] - getAllPropertyNames(obj) = [object Array]
2022-04-02 10:05:08.725 [INFO ] [org.openhab.rule.dummyTest          ] - ON

When my testing switch is turned on.

If I recall there are other such direct variables for the other trigger types. newState for state changed triggers, etc.

At the bottom of each page in the documentation, there is a link to the github page where you can suggest edits. It looks like this

Screenshot from 2022-04-02 14-48-57

In the file based rules, the everything is JavaScript and the openhab-js library has control over the full path from trigger to the calling of your function. In the UI, openhab-js doesn’t come into play until after the rule has already triggered and the the function that runs when the rule is triggered is called.

Work is being done to make the two work more similarly but they will never be exactly the same in all aspects because the way they are written and invoked is very different between the two.

And unfortunately few of the developers working on this part of OH use UI rules day to day. So often an assumption or decision will be made to support one that doesn’t work well in the other.

@vlntnwbr Hello Valentin, I think there is no more need to do so, as I have already submitted some docs for the event payload.
See GitHub - openhab/openhab-js: openHAB JavaScript Library for JavaScript Scripting Automation.

Most parts of the JS Scripting documentation are copied from the openhab-js README.
Currently (I think so) the better place to look for docs is the openhab-js repo as the development happens there and its README is much more recent.

I think someone is working on a solution to automatically pull the docs from openhab-js into the JS Scripting docs, but I have no idea how the progress is there.

Yes you are totally right, for JSRule it seems that https://github.com/openhab/openhab-js/blob/main/rules/rules.js#L327 is used to „format“ the event payload.

1 Like