Struggling migrating of JS rules from openHAB 3 to 4

  • Raspberry Pi 5 Model B Rev 1.0, 8GB, SD Card 128GB
  • Debian GNU/Linux 12.5 (bookworm)
  • Linux openhabian 6.1.0-rpi7-rpi-2712 #1 SMP PREEMPT Debian 1:6.1.63-1+rpt1 (2023-11-24) aarch64 GNU/Linux
  • openjdk 17.0.11 2024-04-16
  • OpenJDK Runtime Environment (build 17.0.11+9-Debian-1deb12u1)
  • OpenJDK 64-Bit Server VM (build 17.0.11+9-Debian-1deb12u1, mixed mode)
  • openHAB 4.1.2 - Release Build

Recently I migrated my system from openHAB3 running on a RPi3+ to openHAB4 on a RPi5. While most of the bindings are working properly I really struggle to get the JS rule run on the new version.
The easy task was to modify the item handling (infortunately I did not find a list of these changes in the internet):

itemRegistry.getItem("someItemName").getState(); => items.someItemName.state;
events.sendCommand(someItem, ON); => someItem.sendCommand('ON');
if (someItem.getState() === ON) => if (someitem.state == 'ON');

For some statements that worked on openHAB3 I could not find a working solution:

  1. PointType
    var HomeLocation = new PointType(new DecimalType(21.12345), new DecimalType(10.853317)); => var HomeLocation = new PointType(21.12345, 10.853317);
    Gives: [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘rule name’ failed: org.graalvm.polyglot.PolyglotException: ReferenceError: “PointType” is not defined)

  2. Group Item Members
    var MemberList = Java.from(itemRegistry.getItem(groupItemName).members); => var MemberList = items.groupItemName.members;
    MemberList is empty though the item ‘groupItem’ is an item of type Group with 3 Direct Group Members.

    var N = items.NetworkDevicesOnline.size;
    var O = items.NetworkDevicesOnline.members.filter(i => i.state == ‘ON’).size;
    Both N and O are reported as undefined.

Another problem I’m facing after the version change is that the oh-input-card I use to enter timestamp items (string type item, semantic class Point, semantic property Timestamp).
I use the oh-input-card as Default LIst Item Widget and as Default Standalone Widget.
Wherever the item is displayed it just shows “–:–”. The time can be either typed in or selected by time widget, but when I click the checkmark the time disappears and “–:–” is shown again.

Something I’m not really sure of is the syntax differences between the JS rule, the Widgets Expression Tester and the YAML code.
This makes searching the internet really frustrating due to these syntax differences and the changes of the syntax in different versions.
I worked my whole life as successful C++ programmer but Java Script makes me mad.

I’m sure there are people that are able to help me out of my misery. Any hint that helps me to sucessfully work with this great system is thoroghly appreciated.
Thanks a lot, Norbert

You can add the Nashhorn addon and just use the rules like before and then rewrite them later when you are more familiar? That’s what I did and I just swapped them over on at a time.

Then at the bottom of the rule there is an up arrow thing ^ click that and select the older ecmascript and it “should” work.

Assuming you are doing all this in the UI.

Then when you have swapped them over you can remove the nashorn addon.

2 Likes

Definitely follow @ubeaut’s advice. Nashorn JS and JS Scripting (aka GraalVM JS) are not the same. JS Scripting is not merely a slightly updated version of Nashorn JS. They really are two totally separate and independent rule engines. Converting from Nashorn JS to JS Scripting should be approached the same as you would for converting Rules DSL to JS Scripting.

While it is possible to make Nashorn JS Rules run on JS Scripting with minor additions, you are really better off rewriting the rules as JS Scripting is going to make your rules way shorter and way easier to read and understand. If you are only going to do minimal changes to make your Nashorn JS Rules compatible with JS Scripting, you may as well just keep them running with Nashorn JS and save yourself the work of rewriting.

JS Scripting doesn’t import all the openHAB types like Nashorn JS does. In fact, the helper library for JS Scripting goes to great lengths to ensure that you are always dealing with native JS classes and objects and almost never need to work with the “raw” Java Classes and Objects.

But in those rare cases where you do need to use a raw Java class, you need to import it manually. You can do it one of two ways:

var { PointType, DecimalType} = require('@runtime');

The @runtime module is basically your Nashorn JS context. You can individually import stuff from that context, as shown here, or you can import the whole thing and reference parts of it.

var runtime = require('@runtime');
...
runtime.itemRegistry.getItem('someItemName').getState();
...
var HomeLocation = new runtime.PointType(new runtime.DecimalType....

Note, when working in JS Scripting, often times it’s better to stick with Strings. So this line would probably best be written as

var HomeLocation = new runtime.PointType('21.12345, 10.853317');

More information is required. But given this ha;lf Nashorn/half JS Scripting approach it’s really challenging to determine what’s what.

Of course, thats not valid JS. items.NetworkDevicesOnline is a JS Scripting Item Object and that class doesn’t provide a size property.

Similarly, JS Arrays do not provide a size property. But it does provide a length property. Array: length - JavaScript | MDN.

This is one of the reasons that JS Scripting tries so hard to make it so you don’t work with the raw Java classes and Objects. Java and JavaScript implement common stuff differently and JS Scripting wants to provide as pure of a JS environment as possible. So it converts the raw Java stuff to their JS equivalents.

Nashorn worked with the raw Java Lists, ItemType, and State classes and objects so you always had to know whether you were dealing with a Java object or a JavaScript object and know what to do in each case. In JS Scripting you can always assume you’re going to get a JS object that works with the standards of the JS language.

We need more information including verification that the Item state is actually updated and not NULL or UNDEF and the YAML for the widgets as configured.

There is very little overlap. Expressions do use the standard JS ternanry operator ((condition) ? true : false) and a few other standard JS capabilities but overall you should treat the two as being completely different and independent environments.

For rules see JavaScript Scripting - Automation | openHAB. This is the reference for JS Scripting rules, includes many examples, and if very complete and comprehensive.

For widget expressions see Building Pages - Components & Widgets | openHAB and Creating Personal Widgets | openHAB.

So far the stuff you are struggling with is the API between JS and openHAB itself. You are not yet fighting JavaScript as a language. Even in C++, trying to use a poorly understood or misunderstood library would lead to frustration.

tl;dr: You have three options:

  1. If you are happy with your rules as is, save yourself some time and pain and just install the Nashorn JS add-on and set the mime type of your rules to use that. Then you need change nothing.

  2. If you want to use the new JS Scripting rules engine, once you complete 1, reimplement your rules one by one using JS Scripting. Use the link to the reference above for everything you need to do. Treat this as a complete rewrite though, this isn’t just updating a few things here and there.

  3. Instead of rewriting in JS Scripting, you have a whole host of other language options including Blockly, Python, Jython, jRuby, and Groovy. Since you have to rewrite the rules anyway you may as well open your options.

Just giving you some ideas of how it would look like in JRuby:

Nashorn JS JRuby
itemRegistry.getItem("someItemName").getState(); someItemName.state
events.sendCommand(someItem, ON); someItem.on
if (someItem.getState() === ON) if someItem.on?

See item state
var MemberList = Java.from(itemRegistry.getItem(groupItemName).members); member_list = groupItemName.members
var HomeLocation = new PointType(new DecimalType(21.12345), new DecimalType(10.853317)); home_location = PointType.new(21.12345, 10.853317)

The number of members:

NetworkDevicesOnline.members.size # The number of members
NetworkDevicesOnline.members.select(&:on?).size # The number of members that are in the ON state

See:

If you post some of your actual rules, I’ll be happy to convert them for you into jruby to get you started.

Thanks for your extensive answers. I’m about trying the proposed solutions.