OH 3 Examples: Writing and using JavaScript Libraries in MainUI created Rules

i will double check!

i just redeployed my container and it worked with you advice with brackets

RaspberryPi OS64 will be released soon - so this could be the perfect timing for a transition to GraalVM for openHAB 3. As i read it should be possible to support GraalJS at Nashorn - so it could be possible to create all scripts and rules in the latest JS Version ES6 that has a lot of benefits. Later it would also be possible to use Python 3.x instead of the old and unsupported 2.x.
Does anybody have more info on using graaljs and ES6 in openHAB3?

@fuslwusl I believe this is what jsscripting automation bundle offers JavaScript Scripting - Automation | openHAB

It should be easy as just installing the bundle but have not tried personally…

Hi @rlkoshak thank you for this writeup. It has helped me remove dozens of duplicates in my code so it can be a bit more DRY. :+1:

Although I’m still somewhat disenchanted from all the verbosity we need in our ECMAscripts, such as (1) downgrading scripts from other projects to 2009 ES5 even when running the latest version of openHAB, (2) needing to import a special logger rather than just extending console.log as in other projects in every script, or (3) requiring 6(!) commands just to run another script as defined in the UI. These are my first annoyances as a brand new user, so it feels a bit like sharp edges that could have been sanded by now.

It doesn’t help that pretty much all documentation, tutorials, community posts, blogs and youtube videos are made for OH 2 or are retrofitted. It is as if all OH 3 users are former OH 2 users and nothing was written with fresh OH 3 users and ECMAscript in mind. Because if you start from scratch in OH3, you never get to write those OH 2 things or create those files. So these examples in the community are really helpful. :+1:

Anyway, the 4th thing I wanted to do as a noob is send a notification to my phone using a self-hosted service such as Matrix (because no one should send messages about presence to clouds hosted by people you don’t know), and again this was not easy because I couldn’t find a plugin for this. :sweat_smile: So I thought I’d share my first little library script that helps sending notifications from Matrix/Synapse so that it may help someone.

/home/openhab/conf/automation/com.redsandro.openhab/matrix.js

(function(self) {
  'use strict';
  self.sendMatrixMsg = function(room, msg) {
    var HTTP     = Java.type("org.openhab.core.model.script.actions.HTTP")
    var rooms    = {
      house        : '!<roomId>:<host>',
      person1      : '!<roomId>:<host>',
      person2      : '!<roomId>:<host>',
      person3      : '!<roomId>:<host>'
    }
    var roomId   = rooms[room] || rooms['echo']
    var token    = '<access_token>'
    var url      = ['https://<host>/_matrix/client/r0/rooms/', roomId, '/send/m.room.message?access_token=', token].join('')
    var payload  = {
      msgtype: 'm.text',
      body: msg
    }

    HTTP.sendHttpPostRequest(url, "application/json", JSON.stringify(payload), 3000)
  }
})(this)

To use in a script:

var ohConf = java.lang.System.getProperty("openhab.conf")
load(ohConf + '/automation/com.redsandro.openhab/matrix.js')
this.sendMatrixMsg('home', "I'll turn off the heating in the bathroom.")

Not everyone likes Matrix. In my opinion, a broad range of notification services should really be built in using a single dedicated library like Apprise or Shoutrrr. I’ve opened a feature suggestion, so give it a thumbs up if you think this is a good idea. :slightly_smiling_face:

Well, the above is for Nashorn JS which is:

  • what comes with Java 11
  • hasn’t really been touched in many years by the Java maintainers
  • is deprecated and is completely gone in Java versions past Java 11

If you are getting started now, you should use the JSScripting add-on which is the latest ECMAScript, comes with a helper library (instead of needing to install one separately) that makes it easier to interact with OH, supports npm to install libraries, and soon (I think there’s a PR) will support console.

Nashorn is and has been a dead end for quite a while. Not a lot has been written on it because doing so will take a huge amount of effort for something that will be gone in a year or two anyway. Now that we have the JSScripting which I and others will push to make installed by default in OH 3 I plan on writing some more and more official docs for it.

This and other tutorials I’ve written were mostly intended to be stop gaps until a more permanent solution was implemented. Nashorn is definitely going away when OH moves off of Java 11. Jython will break at some point and won’t be fixable since it’s all but abandoned by upstream (and it’s Python 2.7 which is end of life). jRuby is only now starting to pay any attention at all to UI rules. Rules DSL is not fully capable. Blockly is great but itself renders to Nashorn and needs to be updated to render to something else. JRules do not and never will support the UI. Java Scripting does support the UI but is only available in the Marketplace.

The only candidate right now to become a default rules language is JSScripting. So over the past couple of months a ton of work has been put into it to make it suitable for writing in .js files and for creating rules in the UI in a way that will still support third party libraries from npm without placing too much of a burden on those writing Script Actions and Script Conditions in the UI.

Yes you do. You can write all that stuff in the same files in the same ways in OH 3. All the UI stuff in OH are new capabilities that were added in addition to what was supported in OH 2, not in place of.

It will be much easier for others to find if you post it as a separate thread under Tutorials & Examples Solutions. Not only will it be easier to find but it will be easier for you to help others who want to use it and for you to post updates and such.

If you decide to jump to the bleeding edge a little bit, see Migrate from Nashorn to JSScripting for UI Rules, GitHub - openhab/openhab-js: openHAB JavaScript Library for JavaScript Scripting Automation, and most of the work being done is covered at GraalVM JavaScript replaces Nashorn, breaking Blockly and existing Nashorn rules · Issue #2433 · openhab/openhab-core · GitHub with links to three outstanding PRs that I’m waiting for before I jump head first into building some proper docs.

You might consider turning this into a Rule Template. Then a user can install it from the Marketplace just like an add-on. With JSScripting (or even Blockly) it’s easy to call a rule from another rule and you can pass data to the called rules. So a user could install the template, instantiate it, and then call it using a one liner from the helper library.

Alternatively it could be distributed as an npm module with JSScripting and users can install it using npm.

As for this, if you intend this to be built into the core then you filed the issue in the wrong place. It would need to be filed in the openhab-core repo. However, I don’t think this is something suitable for core and implementing it as an add-on is the correct approach.

Apprise isn’t an option for an OH add-on because it’s written in Python. Shoutrrr isn’t an option for an OH add-on because it’s written in Go. OH is written in Java and OH add-ons are written in Java. Obviously one could run these as a separate service with an API and an OH can interact with that separate service via an add-on. That’s how MQTT works. But these cannot be embedded into OH itself.

As for your comments in the issue, OH takes an agnostic position re cloud services vise self hosted services. We neither encourage nor discourage either approach.

But, as with everything in an all volunteer openSource project, having a good idea isn’t enough. Someone has to volunteer their time and effort to implement it so it’s pretty rare that a mere feature request ever goes anywhere unless it’s backed up by a PR. So if you feel strongly about this feature you’ll probably need to code yourself or find someone to code it for you.

This has already been merged and will be in the 3.2 release

See GitHub - openhab/openhab-js: openHAB JavaScript Library for JavaScript Scripting Automation

This library is shipping by default in JSScripting for 3.2 (do out in the next couple weeks!) , so out of the box you will have console logging, setTimer and setInterval support as well as the standard openhab-js library, so easy access to items, actions and rules.

We are still working on cleaning up the documentation

8 Likes

Good to know. I will look this up. There is still time to rename it to ESScripting to emphasize the ES compatibility and distinct from Microsoft’s JScript. :wink:

You can write all that stuff in the same files in the same ways in OH 3. All the UI stuff in OH are new capabilities that were added in addition to what was supported in OH 2, not in place of.

I spoke too strongly. There is a code tab (which I discovered later) indeed. What I am trying to say, I guess, is that if you just use OH3 for the first time without prior experience, you will automatically take the UI approach, and when you go more advanced, you will automatically pick the widely known ECMAscript over some domain specific language.

a ton of work has been put into it to make it suitable for writing in .js files

I’m excited to try this out.

Someone has to volunteer their time [so] if you feel strongly about this feature you’ll probably need to code yourself

I just observed that people have been reinventing the wheel when I thought there were suitable dedicated libraries for that. But I didn’t take into account that the libraries need to be Java.

OH is written in Java and OH add-ons are written in Java. [Apprise and Shoutrrr] cannot be embedded into OH itself.

I’ve learned a lot of new things from your post today, and my initial thoughts are partially obsoleted by new ones.

Just for my understanding, in the future without Java 11 and with JSScripting, add-ons wouldn’t be able to be written in ES as an npm module, correct? Though you can install npm modules in (future) scripts?

Since ES is a lot more accessible than Java, I think an ecosystem may develop of ES add-ons that bypass the traditional add-on capability because they can only work in ES scripts, which is probably acceptable to many people including myself.

Although it may be interesting to find a way to utilize the future default ES support allow npm modules following a very specific API to run as plugins for the sole reason that it may attract more developers. I know, if I feel strongly … just thinking out loud.

Probably not since as an add-on it’s actually been around and part of OH since OH 3.1. I don’t know that many would be confused by that though since there isn’t anything MS related in OH.

openHAB is written in Java.
Add-ons are written in Java.
Through an add-on (again written in Java) the ability for end users to write rules using alternative languages, one of which is JSScripting which enables the use of GraalVM JavaScript to write rules.

Add-ons are not and never have been written in any language other than Java.

Given that JSScripting only works for writing rules, it will be somewhat limited in what it can do. For example, you can’t set up a service with an API, you are limited in how you can do multithreaded stuff, etc. So it’s probably not going to be a pleasant experience to write a full integration with a technology using just JSScripting. It might be possible in some cases but not in all.

I’m also frown a little bit on this approach because implementing the equivalent to add-ons in this way is going to fragment OH and make it really hard to use. For example, let’s say you coded up an ECMA “add-on” that integrates Matter using the JSScripting add-ons. What about all the jRuby enthusiasts? Now they have to manage their Ruby stuff using gems and now they have to pull in npm too. What about the average joe user who’s only exposure and use of OH is through the UI and what’s the default. Now they have to go to the command line, install node/npm to get Matter support and now they have two ways they have to know and understand to install “add-ons”.

I’ve been tilting at this windmill for a long time. Unfortunately the people who are enthusiastic enough to create an add-on to support a language never give a thought to how that will impact OH over all. It wasn’t until I created that issue to explicitly break things when the JSScripting add-on is installed (since it broke things silently) that anyone seriously looked at what it means to try to use this add-on to build rules in the UI (which is how the bulk of our users will be writing rules).

I’m much more in favor of trying to create and maintain a consistent experience for OH users than I am in sticking to any one languages purity and development stack. So if it’s a choice between enabling/encouraging the writing of add-ons in another language but then introducing yet another way to do something or saying “too bad” to the language enthusiasts, I’m going to say “too bad” every time.

But code speaks louder than “helping new users” experience every time. I’m encouraged that finally I’m being heard on the JSScripting front and we are finding what I think it a pretty nice compromise between language purity and support for stuff like NPM and support for a reasonable and consistent experience for our least technical users.

Obviously, encouraging developers is worth while, but not at the expense of the less technical end users. Developers can largely take care of themselves but the non-technical end users cannot.

1 Like

Gotcha. :+1: Although to be fair, the way I pictured theoretical support for ES12, add-ons would be run using node.js, and there really is a lot you can do because node_modules can platform-independently compile binaries upon installation. So it’s only limited if the intended implementation is.

I’m also frown a little bit on this approach because [this would] fragment OH

Yeah you’re probably right. Statistics show that W3 and Node.js have popularized ECMAScript to new heights and it arguably makes more popular sense to support ES rather than Ruby, but it would fragment OH even more. On the other hand, is it sustainable to keep things the way they are?

I’m encouraged that finally I’m being heard on the JSScripting front and we are finding what I think it a pretty nice compromise

I’m happy to hear that. :+1: And I can’t help but notice in your writing style (here and elsewhere) that it has not been easy to get the relevant people to agree. I didn’t know OH development process was so laborious. That must make it extra tiresome to listen to new users and their ideas.

I don’t know that many would be confused [with JScript] since there isn’t anything MS related in OH.

You are free not to care and it’s definitely not important to waste your time on. But you’re making an odd argument. Do you think an ES12 add-on called “Pythonious Scripter” wouldn’t be confusing because there is no Python in OH?

Allow me to make this point one more time, because I’d prefer if you acknowledge the plausibility of one thing even though you don’t feel strongly about it. There are three name choices to guerilla-market this new development with, and they are JS (capitalized) which for the senior javascript developer rings back to 1996, there is the slightly better js (lowecase) which is a throwback to the <= 2009 var days, and then there is ES which is distinctly used for the new and modern implementations from 2015 onwards. Think jslint vs eslint. The former is all but obsolete. It’s better to sound modern and forward when OH is currently known for the outdated scripting engine.

If it’s running in node.js, it’s not running in openHAB and is not an add-on nor does it have anything to do with openHAB rules either. It would interact with openHAB like any of the many similar external services do (e.g. zigbee2mqtt).

It’s sustainable as long as there are volunteers to the openHAB project willing to donate their time building and maintaining it. We’re not going to say “sorry, Ruby isn’t popular enough, we’re not going to accept your add-on to support it despite all the hard work you’ve done”.

We are not keeping things the way they are, but pissing off a bunch of developers who have already donated hundreds of hours to the OH project isn’t exactly going to attract lots of developer either.

And again, this is for rules development. When X event happens do Y. It’s all about working with and in openHAB’s framework.

It’s not so much getting them to agree. It’s been a struggle to get them to care. They’ve scratched their itch and let the rest of OH users fend for themselves.

Most of the work of any open source project that involves more than a couple of developers is going to be primarily focused on communication, coordination, and compromise. The coding is almost secondary. It’s even more pronounced when you don’t have any company leading or sponsoring developers to work on it. openHAB has no employees nor dedicated developers. It is 100% volunteer effort. And you can’t tell a volunteer what they can and can’t work on. They are going to work on what they want and the openHAB project is genuinely grateful for the efforts and contributions in any and all forms.

But you can’t really have a strong and well defined roadmap or anything like that. What gets worked on is what ever the devs decide to work on. That’s also why feature requests and add-on requests usually don’t go anywhere. It’s not enough to have a good idea. You have to have someone willing to donate their time to build it. There really are not all that many new ideas out there so often, if there were a contributor to OH who was willing and able to work on that idea, it would have already been implemented.

You are free to file an issue on the add-on to request the name change. But it will frankly be a pretty significant and disruptive change over all and it will break a lot of things. Had this suggestion came along two to three years ago when this add-on was first being developed or even a year ago when it was accepted as part of OH that would be one thing. At this point it would be a big breaking change of a nature that would probably have to wait for OH 4. But I’m not the maintainers. They may think it’s worth all the disruption and impact to end users to sound “modern”.

Gotcha. If one thinks it’s valuable, one should create a PR.

Okay, that’s a better and more satisfying argument. :+1: You are right, someone probably didn’t give the name too much thought 3 years ago and it’s not worth the trouble changing it so late in the game.

I previously ignored a similar statement because I thought it was not an argument, but since you come back to it, as my last lengthy contribution to this thread I’ll try to help you move past that way of thinking:

Add-ons are not and never have been written in any language other than Java.

I know that. Hence I was explicitly talking about a future version. Discussing ideas for future development usually involves a bigger or lesser degree of expanding from where you are to where you want to be. You indicated a ton of work has been done towards making ES12 suitable to become a default OH capability. Extrapolating from this, I assumed it was not a stretch to think V8/node.js for internals were on the table, perhaps even secretly within existing enthusiast volunteer wishful consensus, either as a separate component using a specific API, or using something like J2V8. You can even run the node.js runtime inside the JVM runtime apparently although I wouldn’t recommend it.

Stating the current status quo of something is not a good argument. In the year 927 king Athelstan did not say: “This is not and never has been the Kingdom of England!”

If one want to write a completely separate capability, that’s one thing. It’s been done before (see HABApp which is a wholly separate process which interacts with OH through OH’s REST API. That’s perfectly acceptable and there is lots of precedent for that sort of thing for both Rules (Node Red, HABApp, etc.) and “bindings” (zigbee2mqtt, zwave-js, sensor_reporter, any2mqtt, etc). But these are all separate services created and maintained by separate projects. They are not part of openHAB and therefore they do not implement openHAB rules nor bindings. But they very much can enable OH to connect to other technologies and devices and/or code home automation behaviors.

Unless and until someone decides to rewrite OH in node.js (at which point is it even OH any more or something new?), or takes the path of embedding node.js so it runs inside the JVM (which you advise against and I would agree and I’m not even sure it would work with OH’s existing architecture), OH simply will never be able to write OH add-ons using node.js. It is not technically feasible. It has nothing to do with the status quo.

However, if one wants to run something along side of OH written in node.js that is perfectly fine, reasonable, supported, and has precedent. But that wouldn’t be part of OH either. And this approach does come with some limitations and extra complexities that will make OH harder to use for less technical users when they want to use those features. And frankly, those are the users I care about most and who I advocate for the most. Those users just want to write some light scripted behavior in MainUI. If you tell them they need to install something separate and pull down libraries using npm or gems or pip or the like you’ve already lost them. All they care about is that they want their lights to come on at dusk and the like.

@rlkoshak ok gotcha. Thank you for taking the time to respond to my inquiries. I appreciate it. Apologies for steering away from the original topic.

No worries and I feel like I need to say the following as well. I do not speak for openHAB nor the openHAB foundation. Everything I’ve said is based on experience and personal opinion.

2 Likes

Hi Rick,

Thanks for the great article! I’ve already read it a couple of months ago, but got back to it only now. I’ve started with the OH update to 3.2, so now I have the GraalVM engine. Can you please advice me, what is the trick in this new engine to define a library function? The code which is written in this post is not working there.

Thanks,
Attila

Not easily. You’ve two choices really.

  1. Do it just like the above to essentially include the contents of the library into your rule.

  2. Follow any tutorial out there for how to build an npm module.

2 is the standard way to do it and will be more flexible and sharable in the long run. But it’s more involved so may be more work than you may want to do. If you want to go that rout look for just about any tutorial on Google and you should be OK. The modules go in $OH_CONF/automation/js/node_modules/<your folder>. For example, I’ve got a personal folder there where I’ve put a few of my personal libraries. I’m working on an openhab_rules_tools module which I hope to push to npm and eventually integrate some of them into the helper library that comes with JS Scripting.

Maybe once I figure the ins and out of this approach I’ll write a tutorial but I’m still learning it myself.

1 Like

Thanks for the quick answer Rich. Yeah, would be good to have the simplicity as in the old version, but will give a shot to the npm package as I do not want to duplicate the code. Thanks!

Great tutorial on using JavaScript libraries in MainUI-created rules! I found the examples particularly helpful for understanding how to structure and load libraries effectively. One question: do you have any tips for debugging libraries when something goes wrong? Also, have there been any updates or changes to this approach with newer openHAB versions? Thanks for sharing this valuable resource!

It depends on how independent the library is from OH. If it’s generic JS, then any JS focused testing approach/suite will work just fine. If it requires some interaction with OH you’ll need to either mock up the OH interactions (when using a testing suite) or test while connected to OH.

Personally I’m not doing anything that is complex enough so simply exercising the code in an OH rule is sufficient for my testing needs

Kind of. Everything discussed above is for the old Nashorn JS Add-on which is ECMAScript 5.1. If you want to use a much more recent version of JS you should use the JS Scripting add-on. This supports npm modules for libraries. See JavaScript Scripting - Automation | openHAB.

Importing is also different using require instead of include.

Also, there is now the cache to save variables instead of using this as demonstrated above.

Note that all of the examples above have been rewritten in the newer ECMAScript and are available in openhab_rules_tools which you can install using openhabian-config or through npm (instructions on the link).

With the newer JS Scripting the Offline Alert example would look something like:

triggers:
  - id: "1"
    configuration:
      time: 08:00
    type: timer.TimeOfDayTrigger
conditions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: ServiceStatuses
      state: ON
      operator: "!="
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: >-
        var {utils} = require('rlk_personal');


        var nullItems = utils.getNames("ServiceStatuses", function(i) { return i.state.class == UnDefType.class; });

        console.info(nullItems);


        var offItems = utils.getNames("ServiceStatuses", function(i) { return i.state == OFF; });

        console.info(offItems);


        var msg = "";


        if(nullItems.length > 0) {
          msg = "The following sensors are in an unknown state: " + nullItems;
        }

        if(offItems.length > 0) {
          if(msg.length > 0) msg += "\n";
          msg += "The following sensors are known to be offline: " + offItems;
        }


        utils.sendInfo(msg);
    type: script.ScriptAction

The reimplementation of utils (which is part of a node module I created called rlk_personal:

exports.sendAlert = function(message, logger) {
  var logger = (logger) ? logger : log('sendAlert');
  logger.warn('ALERT: ' + message);
  actions.NotificationAction.sendBroadcastNotification(message, 'alarm', 'alert');
}

exports.sendInfo = function(message, logger) {
  var logger =  (logger) ? logger : log('sendInfo');
  logger.info('INFO: ' + message);
}

exports.isNight = function() {
  const currToD = items.getItem('TimeOfDay').state;
  return currToD == 'NIGHT' || currToD == 'BED';
}

exports.isAway = function() {
  return exports.isNight() || items.getItem('Presence').state != 'ON';
}

exports.getNames = function(group, filterFunc) {
  return items.getItem(group.name || group).members
                             .filter(filterFunc)
                             .map(s => (s.getMetadata()['name']) ? s.getMetadata()['name'].value : s.label)
                             .join(', ');
}

You will notice that the code is much shorter, is a lot less work to set up thanks to the helper library that comes with the add-on, and uses pure JS Classes, Objects, and idioms everywhere instead of needing to jump back and forth between JS and Java as shown in the original post. You can especially see this in the getNames function where the original uses Java’s streams API to filter the Java List as opposed to the new verison that uses all JavaScript Array manipulation and features.