Using ChannelEventTrigger in UI (JavaScript)

I’m in the slow process of migrating my rules from file based to UI, mostly when I’ve got something to add to an existing rule.

I’ve got a file based JS rule which is triggered by a ChannelEventTrigger (being a “short press” on a Shelly BLU Button). In it, I can access the channel ID by String(event.channelUID). But that doesn’t seem to work in the UI. The documentation also doesn’t mention it.

If I run console.log(event.channelUID), the output is undefined.
If I run console.log(event), the output is shelly:shellyblubutton:38398f8bd741:status#button triggered SHORT_PRESSED.
If I run console.log(typeof event), the output is object.

But how can I access that object?

In the UI, you end up with the raw Java event Object. JavaScript Scripting - Automation | openHAB

This means that the properties are going to more closely map to the Rules DSL Implicit variables. Textual Rules | openHAB

rules/rules.js - openHAB JS is the source code in openhab-js that converts the Java event Object to a JS event Object. This doesn’t get run for UI rules (there’s no way we’ve found yet to intercept the event and replace it like can be done for file based rules). But you can see the code that extracts the data from the Java Object there so you’ll know what to reference in your UI rules..

      case 'org.openhab.core.thing.events.ChannelTriggeredEvent':

        data.channelUID = event.getChannel().toString();

        data.receivedEvent = event.getEvent();

        data.eventType = 'triggered';

        data.triggerType = 'ChannelEventTrigger';

        break;

    }

So you can see that the JS event Object’s channelUID comes from event.getChannel().toString() and the actual event comes from event.getEvent(). But you should be able to just use event.channel and event.event. I know for sure event.event works at least.

There is a utility to dump all the members of an Object in openhab-js: utils.dumpObject()

Mmm, the output of utils.dumpObject(event, true):

13:15:31.698 [INFO ] [g.openhab.automation.openhab-js.utils] - Dumping object...
13:15:31.700 [INFO ] [g.openhab.automation.openhab-js.utils] -   typeof obj = object
13:15:31.702 [INFO ] [g.openhab.automation.openhab-js.utils] -   Java.isJavaObject(obj) = true
13:15:31.705 [INFO ] [g.openhab.automation.openhab-js.utils] -   Java.isType(obj) = false
13:15:31.706 [INFO ] [g.openhab.automation.openhab-js.utils] -   Java.typeName(obj.getClass()) = org.openhab.core.thing.events.ChannelTriggeredEvent

That doesn’t seem to list the properties of the object?

This worked! Thanks!

For this reason I have written a small code. Add this to see all key/value pairs, and, if the key is the name of a function, the result of this function:

Object.keys(this.event).forEach(k => {
    if (k != "equals") {
      var v = this.event[k];
      console.log("event." + k + (typeof v === "function" ? "() = " + v() : " = " + v));
    }
  });

Wonderful:

15:08:40.881 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getEvent() = SHORT_PRESSED
15:08:40.885 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getChannel() = shelly:shellyblubutton:38398f8bd741:status#button
15:08:40.887 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.toString() = shelly:shellyblubutton:38398f8bd741:status#button triggered SHORT_PRESSED
15:08:40.889 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getType() = ChannelTriggeredEvent
15:08:40.891 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getTopic() = openhab/channels/shelly:shellyblubutton:38398f8bd741:status#button/triggered
15:08:40.893 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getPayload() = {"event":"SHORT_PRESSED","channel":"shelly:shellyblubutton:38398f8bd741:status#button"}
15:08:40.895 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.getSource() = null
15:08:40.898 [INFO ] [t.ui.knopjes-blu-button-en-hun-acties] - event.hashCode() = -1886324702

So this.event.getEvent() == event.event. What’s the point of having event.event then? Or maybe more useful: which way to get there is the best way?

  • event.event & String(event.channel) (I only use the latter for .includes())
    or
  • this.event.getEvent() & this.event.toString() (I only use the latter for .includes())

?

Keep in mind that you are looking at a Java Object from JS. event.event is private in the Java class so the only way to access it is through event.getEvent() in Java. JS doesn’t recognize the concept of “private” as Java implements it so in JS you can get the event.event property without going through the getter function.

These sorts of differences between Java and JS is why openhab-js goes to such lengths to present everything to you as a JS Object instead of a Java Object. There are differences between these, often subtle differences, which can trip you up. Unfortunately, in managed rules the event Object is just about the only part that isn’t converted/wrapped as JS.

Which way is best? Both are acceptable. event.event of more JS like and event.getEvent() is more Java like but in this context they are both the same in terms of “goodness”.

event.event is already a String, there is no need to convert it to a String.

Accessing event using this is how you would do it in Nashorn JS. In GraalVM JS there is no need to use this. It only serves to make your code longer without adding anything.

I always use event.event. It’s shorter, clear(ish) and it’s already a String so there’s no need to convert it or call toString().

Except for the following: If you don’t know if an object (like event) exists or not, this command does not lead to an error:

if (this.event === undefined) {...}

but this does lead to an error:

if (event === undefined) {...}

True, if you need to test if event exists, you need to use this. But it’s unnecessary everywhere else.

Testing against the event type is a powerful way to make flexible rules. For example, in my rule templates I use that so that when the rule is triggered manually it runs internal tests to ensure everything is configured correctly. For my MQTT EventBus rule template, I was able to combine the subscribe and publish functions into one rule by seeing how the rule was triggered (if triggered by a Channel event I know it’s a received message, when triggered by an Item I know it’s a message to publish).

But there are cases where event won’t exist (though it’s not super consistent, an issue was recently opened on that) so if you are using the event Object, and your rule has multiple ways to be triggered it’s important to test that it exists before trying to use it.

Most of the time, it’s an error case if the rule is triggered in a way that the event Object doesn’t exist so it’s OK to let it just thorw that exception.

Indeed, but I only did that with event.channel. :wink:

Anyway, thanks for everyone’s input; it was most insightful!

The point remains that utils.dumpObject() doesn’t seem to do what is advertised…?

According to the code, it only dumps the members and functions for JS Objects.

If it’s a Java Object it only tells you the name of the class. You can look that class up in the JavaDocs though and see all that it can do or look at the source code itself. Overview (openHAB Core 5.1.0-SNAPSHOT API)

So you mean that it technically does deliver, however that might not be very user friendly? :slight_smile:

Maybe it would be an idea to implement the code @Oliver2 suggested into the utils.dumpObject() method/function?

You can do it on your own.
Create a helper-rule, say fn_dumpObject with this code:

console.log("Info: Printing object details:");
Object.keys(ctx["obj"]).forEach(k => {
  if (k != "equals") {
    var v = ctx["obj"][k];
    console.log("obj." + k + (typeof v === "function" ? "() = " + v() : " = " + v));
  }
});

and whenever you need to get details on any object in the rule you are working on, call:

rules.runRule("fn_dumpObject",{"obj":nameOfObject});
//e.g.
rules.runRule("fn_dumpObject",{"obj":event});

PRs are welcome. But I suspect that that function was written the way it was for a reason. I didn’t write it so I don’t know what the reason might be.