Pushover notifications from JavaScript rules

Hi,

I would like to start migrating from DSL to JavaScript rules. I have many rules sending notifications using Pushover, so first requirement/PoC for me would be to be able to send a notification using JS.

Current DSL rule snippet:

val actions = getActions("pushover", "pushover:pushover-account:account")
actions.sendMessage("Body", "Title")

(as documented in Pushover - Bindings | openHAB)

So I’ve read:

So following the Pushsafer example, this is now written like this in JS (not sure why API key is now needed):

let { actions } = require('openhab');
 actions.Pushsafer.pushsafer("<your pushsafer api key>", "Body", "Title", "", "", "", "")

And in DSL like this:

val actions = getActions("pushsafer", "pushsafer:pushsafer-account:account")
actions.sendPushsaferMessage("Body", "Title")

So I’ve tried (file-based):

rules.JSRule({
  name: "Test Pushover",
  description: "My first JS rule",
  triggers: [triggers.ItemStateChangeTrigger('Test', 'OFF', 'ON')],
  execute: data => {
    actions.Pushover.sendMessage('Body', 'Title');
  }
});

Result: Failed to execute rule Test-Pushover-bd382cbc-02d9-4061-8a0e-6b0e5df7c7ce: TypeError: undefined has no such function “sendMessage”: TypeError: undefined has no such function “sendMessage”

Then:
actions.Pushover.pushover.sendMessage('Body', 'Title');

Result: Failed to execute rule Test-Pushover-a7e357c7-b988-4f66-90e4-79c953b328f4: TypeError: Cannot read property “pushover” from undefined: TypeError: Cannot read property “pushover” from undefined

So it seems that Pushover is missing from available actions. Am I right that each binding action needs to be explicitly reimplemented for JavaScript usage, or am I missing something?

Is it possible to use the action with some other syntax from JS?

Best regards,
Jacob Laursen

If you’ve not changed the settings for the JS Scripting addon, this line is not necessary. Furthermore, for now you cannot use let or const in a UI rule because of the way OH reuses the script again on subsequent runs. The second time the rule runs you’ll get an error complaining that actions already exists.

I think the PushSafer example there is wrong. Or to be more precise, it’s for the old 1.x style PushSafer Action which are no longer supported in OH. You’ll want to use the standard way to get an Action provided by a Thing.

I think you’ll want to use:

val action = actions.thingActions("pushsafer", "pushsafer:pushsafer-account:account") 

to get the actions for the PushSafer Thing with that Thing UID.

NOTE: You don’t want to overwrite the actions variable. It’s unfortunate that the binding authors use that variable name in their examples.

From there you can call the functions on action as illustrated in the binding docs.

Thanks @rlkoshak, that got me in the right direction! This worked:

var action = actions.thingActions("pushover", "pushover:pushover-account:account");
action.sendMessage('Message.', 'Title');

Now ready for migration. :slight_smile:

Do you think it would appropriate to add such an example to documentation for each binding providing actions and already having a DSL example? With time this could be considered redundant, but at this moment it might be relevant when having multiple scripting languages?

Best regards,
Jacob Laursen

I don’t think it should be up to the individual binding authors to have to provide examples for each and every supported language and it’s not reasonable to expect the community to update every binding with an Action with examples for all the languages.

Right now, the only truly default language that is usable stand-alone is Rules DSL so that is what I would expect to be used in the examples for the binding docs. Eventually I’d like to see JS Scripting become the default and when that happens we can add or replace the Rules DSL examples with the JS Scripting examples. Until then we need to look at the readme for the automation add-on for how to do stuff like this.

After all, it’s an add-on. It’s optional.

However, the JS Scripting docs need to remove that old and no longer valid example and replace it with the above, along with some text on how this is how to access all Thing provided actions.

1 Like

@rlkoshak - agreed.

Slightly off-topic: Now that I have a working action, I would like to reuse that from a function to I can easily switch between push notification providers for all my rules and have less boiler-plate code.

So I’m trying to read up on how to do this in JS. There’s something called ECMAScript Modules (ESM) supported by GraalVM JavaScript. They should have the extension .mjs. However, it seems this extension is not being watched. Do you know best-practice for reusing functions/methods between JS files - supported by openHAB?

Also tried CommonJS way (I think) with “require(‘notification.js’);” but seems that no matter what I do, I just get: org.graalvm.polyglot.PolyglotException: TypeError: Cannot load CommonJS module: 'notification.js'

Best regards,
Jacob Laursen

Best practice is to create npm modules which will go in $OH_CONF/automation/js/node_modules. Once it’s an npm module, you can require the parts you need in your rule just like any third party library.

But the old style Nashorn load approach documented at OH 3 Examples: Writing and using JavaScript Libraries in MainUI created Rules will still work to inline your code into your rules.

You can also create a rule that gets called by other rules. You can pass data through a triggering Item, or call the rule directly and pass data to the rule as a map. You can also set data in the cache which gets shared between rules.

There are lots of options.

1 Like

Thanks again, placing it in node_modules did the trick. If anyone else new to JS would read this thread, this worked:

$OH_CONF/automation/js/node_modules/notification.js:

exports.send = function(title, body) {
        var action = actions.thingActions('pushover', 'pushover:pushover-account:account');
        action.sendMessage(body, title);
}

$OH_CONF/automation/js/test.js:

var notification = require('notification.js');
[...]
notification.send('Title', 'Body');

Best regards,
Jacob Laursen

I will try to ask here again, because as I added the mail binding , the pushover rule created an error.
Without including system-info or mail-bindung the code was ok.

var pushmessage = actions.thingActions('pushover', 'pushover:pushover-account:xxx');
pushmessage.sendMessage('Z-Wave Dongle is OFFLINE', 'Openhab');
==> /var/log/openhab/openhab.log <==

2022-10-05 09:06:24.046 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:

org.graalvm.polyglot.PolyglotException: TypeError: (intermediate value).thingActions is not a function

	at <js>.:program(<eval>:1) ~[?:?]

	at org.graalvm.polyglot.Context.eval(Context.java:379) ~[?:?]

	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458) ~[?:?]

	at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426) ~[?:?]

	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264) ~[java.scripting:?]

	at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:51) ~[?:?]

	at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:69) ~[?:?]

	at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:51) ~[?:?]

	at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:69) ~[?:?]

	at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.lambda$0(ScriptActionHandler.java:58) ~[?:?]

	at java.util.Optional.ifPresent(Optional.java:183) ~[?:?]

	at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.execute(ScriptActionHandler.java:55) ~[?:?]

	at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1181) ~[?:?]

	at org.openhab.core.automation.internal.RuleEngineImpl.runNow(RuleEngineImpl.java:1033) ~[?:?]

	at org.openhab.core.automation.internal.RuleEngineImpl.runNow(RuleEngineImpl.java:1049) ~[?:?]

	at org.openhab.core.automation.rest.internal.RuleResource.runNow(RuleResource.java:327) ~[?:?]

	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]

	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]

	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

	at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]

	at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:179) ~[bundleFile:3.4.5]

	at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96) ~[bundleFile:3.4.5]

	at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:201) ~[bundleFile:3.4.5]

	at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:104) ~[bundleFile:3.4.5]

	at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59) ~[bundleFile:3.4.5]

	at org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:96) ~[bundleFile:3.4.5]

	at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:265) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:234) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:208) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:160) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:225) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:298) ~[bundleFile:3.4.5]

	at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:217) ~[bundleFile:3.4.5]

	at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) ~[bundleFile:3.1.0]

	at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:273) ~[bundleFile:3.4.5]

	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:550) ~[bundleFile:9.4.46.v20220331]

	at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.doHandle(HttpServiceServletHandler.java:74) ~[bundleFile:?]

	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440) ~[bundleFile:9.4.46.v20220331]

	at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.doHandle(HttpServiceContext.java:294) ~[bundleFile:?]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[bundleFile:9.4.46.v20220331]

	at org.ops4j.pax.web.service.jetty.internal.JettyServerHandlerCollection.handle(JettyServerHandlerCollection.java:90) ~[bundleFile:?]

	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.Server.handle(Server.java:516) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487) ~[bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) [bundleFile:9.4.46.v20220331]

	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) [bundleFile:9.4.46.v20220331]

	at java.lang.Thread.run(Thread.java:829) [?:?]

2022-10-05 09:06:24.063 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'PushOverTest' failed: org.graalvm.polyglot.PolyglotException: TypeError: (intermediate value).thingActions is not a function


Which JavaScript? If it’s JS Scripting, per the docs, you get the action using

actions.Things.getActions('pushover', 'pushover:...');

I think this might be a change from an earlier version of the library. I can find no reference to thingActions anywhere in the library and it’s never been a part of OH core.

Thank‘s a lot Rich, that did the job.
Was is not js scripting (ECMA 21) you were talking about in dec. 21?
Why did the wrong code do the job until I installed the additional add-on (system-info or mail binding) three weeks ago?
Will you please show me the point, where you find the documentation for that, I haven‘t found it?

Yes, but back then the way to get Thing actions was likely different. The helper library has undergone lots of changes since then.

I couldn’t say. Perhaps at that point you got an update to the JS Scripting add-on. Maybe you installed the library separately using npm? Who knows.

https://www.openhab.org/addons/automation/jsscripting/#things-actions which has a link to the raw API docs actions - Documentation which has a couple of examples in addition to the api docs.

FYI, here is the reference: actions - Documentation.
There was a bug in the JavaScript library that broke parts of the actions, but I fixed it two weeks ago and also added the missing JSDoc comment.

To get this fix, the latest helper library version from npm is required.

At the risk of going off topic, when do changes to the helper library become a part of the add-on? Does the SNAPSHOT include the latest from openhab-js or do openhab-js changes get released periodically, or ?

Changes to the helper library become part of the add-on when a new version of the library is released and a new PR to the add-on to upgrade the version and update the README is opened. (Usually I do this.)
The SNAPSHOT therefore includes the latest release of openhab-js, if you want the latest version of the library you need to install it from GitHub (see GitHub - openhab/openhab-js: openHAB JavaScript Library for JavaScript Scripting Automation).
openhab-js releases follow no special cycle, I release depending on the changes and their importance (e.g. a critical bugfix is published ASAP) and before each new openHAB release (so that the openHAB release includes the latest changes of the library).

FYI: The bugfix I was talking about is in the current SNAPSHOT.

I wonder if there is a way to automate some of this and let the library at least follow the milestone releases. Add-ons are handled automatically, I wonder if openhab-js could be handled with the same mechanism.