Announcing the initial release of openhab_rules_tools

Edit: changes for Release 1.1.2

I would like to formally announce the release of openhab_rules_tools. Those of you who have followed my postings for awhile will recognize just about everything here, I’ve just made it a bit more formal.

History

These started out as contributions to the Jython Helper Libraries. For various reasons they were never accepted so I moved them to my own repo.

When OH 3 came out there was a delay between when Jython support was established so I rewrote them as Nashorn ECMAScript 5.1.

Now that JS Scripting has been added to OH and the helper library openhab-js included as part of the add-on, I’ve rewritten the library as ECMAScript 11. This allows the library to be published and distributed via npm, vastly simplifying the installation process.

Installation

Dependencies

This library has a dependency on the openhab library. However, because this library comes with the JS Scripting add-on, it is not listed as a dependency. Otherwise when you install this library you’ll get the latest openhab library whether you want it or not.

For now the library assumes that the settings for JS Scripting are at the default which causes openhab-js to automatically be injected into your rules. If you turn that feature off, I don’t know if these libraries will work.

Linux

From the $OH_CONF/automation/js folder run

sudo -u openhab npm i openhab_rules_tools

Look up instructions fro running npm on your specific OS if not running on Linux.

Docs

Lacking at the moment. Look at the files under tests for examples and the code itself for usage details.

Implemented Capabilities

Name What it does
CountdownTimer Class that implements a Timer and updates a passed in Number or Number:Time Item once a second with the amount of time remaining on the timer. Useful for showing how much time is left on a Timer on the UI.
Deferred Class that allows one to schedule a command or update to an Item in the future.
Gatekeeper Schedules a sequence of actions with a time for how long after one action before the next action runs. This can be used to limit how quickly commands are sent to a device or create a schedule (e.g. irrigation).
groupUtils A series of functions that implement some common operations on members of a Group, such as getting a comma separated list of their names. There are also some methods that work on generic Arrays.
LoopingTimer Implements a Timer that keeps calling a function as long as that function doesn’t return null. When the loop should continue, the function must return a when (see timeUtils).
RateLimit Implements a class that ignores events that happen too soon after the most recently processed one. This is useful to filter out alerts that may occur more frequently than you care about (e.g. only remind me the battery is low once a day).
rulesUtils Does not work well in UI scripts. Some functions that will generate a rule triggered by Items with a given tag or given metadata configuration.
TimerMgr Implements all the book keeping required when managing multiple timers in one rule (e.g. one per Item).
timeUtils Mostly Deprecated: most of this functionality is now part of openhab-js. A number of functions useful to using and manipulating times. The most powerful function is toDateTime() which will convert a time.Duration, java.time.Duration, duration string (e.g. ‘3h 5m’), integer number or DecimalType (treated as number of milliseconds), PercentType (treated as seconds), QuantityType (only Number:Time is supported), DateTimeType, and java.time.ZonedDateTime to a time.ZonedDateTime. For all duration whens the duration is added to now. So, you can schedule a timer to go off in five minutes using tm.check(event.itemName, '5m', runme);. All of the parts of openhab_rules_tools that take a time will call this function so it works for RateLimit, Deferred, etc.
testUtils Implements a busy wait sleep as well as an assert function for testing.

To Do

  • docs
  • implement better unit tests
  • migrate some capabilities to openhab-js (rulesUtils.runRule and most of timeUtils have already been migrated)
  • migrate the rule templates to JS Scripting
  • build a script to build the rule templates so I don’t have to manually include library stuff in the template or require users to npm install the library (yet, I may require this eventually). I’m going to require installation of the npm module going foreard.

Testing

Most of the capabilities have their own test script in the test folder. This was written as a UI Script (Settings → Scripts) and throw errors when loaded by OH so I’ve removed the .js extension to prevent them from loading (there is a recent PR that I think makes this no longer necessary).

Because of the one thread per script limitation, despite the efforts I’ve taken to avoid it, you will sometimes see

java.lang.IllegalStateException: Multi threaded access requested by thread Thread[OH-scheduler-26,5,main] but is not allowed for language(s) js.

That’s just a timing issue where two Timers tried to run at the same time. If it persists, you’ll have to adjust the times in the tests to avoid the overlap. I’m making some effort to eliminate these errors by adding code to stagger timers that are scheduled too close together. But I won’t do that until I migrate the rule templates because it depends on the cache.

Rule Templates

You will notice a separate rule_templates folder. For now at least, I don’t want to keep my templates separate from the library so you can see the raw templates in that folder. However, these can only be installed through the marketplace.

If you want to submit updates to the rule templates, here is where to do it.

Where’s all the old stuff?

I’ve created a branch in the repo before-npm. This has the now deprecated Jython and Nashorn implementations. If a GraalVM Python becomes a part of OH I might resurrect the Python libraries. For now, JS Scripting is the only language I’m supporting. If you need access to the deprecated stuff, use that branch.

The latest and greatest is currently in the main branch. I will periodically cut a release when there are important updates to the library.

10 Likes

it’s installing to the current dir, right ? I could add that to openHABian, what do you think a proper dir would be when doing that?

The command needs to be run from $OH_CONF/automation/js and it will install to ./node_modules/openhab_rules_tools so the full path would be $OH_CONF/automation/js/node_modules/openhab_rules_tools. Right now it doesn’t use any libraries outside of openhab but if that changes in the future npm will install those too.

sudo -u openhab mkdir $OH_CONF/automation/js
cd $OH_CONF/automation/js
sudo -u openhab npm i openhab_rules_tools

Note that folder will not exist until the JS Scripting add-on is installed. It doesn’t hurt if it exists before though so it’ll need to be created by openHABian if it doesn’t exist, which will be the case most of the time.

It would be fantastic to have it included as part of openHABian, though I should warn it’s still very much a work in progress. I’m still refactoring and re-implementing stuff that’s written in other languages right now. I’m hoping to move some of these to openhab-js too so that everyone can get to them, but there is a pretty high bar for additions to that, as there should be.

But if this is included, there should probably also be a menu entry to npm install openhab too so users can get the latest version with changes since the last time the add-on was released.

I’m a bit behind but I’ve made it to a relatively stable state so I’ve cut a release 1.1.0 for openhab_rules_tools. You can get this via npm update if you’ve installed it that way previously or you can find the release on github.

This release includes

  • completed refactoring and rewrite in ECMAScript 2021 which is now on the main branch
  • removed deprecated stuff that is no longer supported (they can still be found in the before-npm branch)

I still need to figure out how to generate the docs from the docstrings in the code and beef up some of the other things.

I will update the OP so it’s always the most recent.

@mstormi, it’s probably at a state now where it’s worth consideration for inclusion with openHABian. I can open an issue for discussion if it will help. It would be really awesome to have it available there. :slight_smile:

Hi, this sounds very interesting. I have already used your code as a tutorial and have rewritten some of my old rules. Realy helpful thanks! Now my question. How should I instal when using openhab on docker (running on a raspberry pi). Should I install it in the container, or should I install it from ‘outside’. The later sounds more logical, but just to be sure.

You should have $OH_CONF mounted as a volume into the container. So where ever its mounted from install it that way. I mount a host local folder into the container so I run npm from that folder on the host (not inside the container). Note that npm does not exist inside the container so if you do it from inside the container you’ll have to install npm first.

Thanks! That clears up that.

When I load the openhab_rulew_tools almost doubles the cpu usage. Is that normal?

Shouldn’t be. Unless you use something in a rule they don’t do anything. There might be a little spike when first loading a rule that uses them since there is more code for OH to parse and get ready to run but that should only be brief and when the rule first loads or, if a managed UI rule when the rule first runs.

What causes this error?

2022-06-11 13:45:25.736 [INFO ] [rulesupport.loader.ScriptFileWatcher] - Loading script '/etc/openhab/automation/js/node_modules/openhab_rules_tools/rulesUtils.js'
2022-06-11 13:45:25.831 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: ReferenceError: "osgi" is not defined
        at <js>.:program(rulesUtils.js: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:400) ~[?:?]
        at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:249) ~[java.scripting:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocable.eval(DelegatingScriptEngineWithInvocable.java:56) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocable.eval(InvocationInterceptingScriptEngineWithInvocable.java:79) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocable.eval(DelegatingScriptEngineWithInvocable.java:56) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocable.eval(InvocationInterceptingScriptEngineWithInvocable.java:79) ~[?:?]
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.loadScript(ScriptEngineManagerImpl.java:177) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.createAndLoad(ScriptFileWatcher.java:231) ~[?:?]
        at org.openhab.automation.jsscripting.internal.fs.watch.JSScriptFileWatcher.createAndLoad(JSScriptFileWatcher.java:57) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.importFile(ScriptFileWatcher.java:211) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.lambda$2(ScriptFileWatcher.java:203) ~[?:?]
        at java.util.Optional.ifPresent(Optional.java:183) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.importFileWhenReady(ScriptFileWatcher.java:201) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.checkFiles(ScriptFileWatcher.java:274) ~[?:?]
        at 
org.openhab.core.automation.module.script.rulesupport.loader.ScriptFileWatcher.lambda$8(ScriptFileWatcher.java:296) ~[?:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at java.lang.Thread.run(Thread.java:834) [?:?]
2022-06-11 13:45:25.856 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab/automation/js/node_modules/openhab_rules_tools/rulesUtils.js': org.graalvm.polyglot.PolyglotException: ReferenceError: "osgi" is not defined

Note that for all intents and purposes rulesUtils.js is deprecated. You should use the functions provided by openhab-js rules instead.

If you’ve turned off the automatic imports for JS Scripting than the openhab-js libraries will not be imported for you and openhab-rules-tools depends on those imports being there.osgi is one of the names exposed by openhab-js.

I’ve never seen this error so those are my only ideas what could be wrong. But what ever the problem is, you should probably be using the stuff built into openhab-js anyway. At some point in the not too distant future I will be removing almost everything in this utility.

HI @rlkoshak,

Just tried your library and found a small bug.

I was trying the CountdownTimer function and I kept getting Java errors in the Logs.

I found you still had some hard coded time codes (i.e. “1s”) in your code that was causing the error.
I changed this in the CountdownTimer.js file to be “PT1s” and that fixed the problem.

I’m sure this is because you recently changed to the time.toZDT() function in your code.
I suggest checking the rest of the library in case there’s other instances

Thanks for the heads up. I did recently change from my internal timeUtils to using what comes in openhab-js now. * thought I was thorough but clearly at heart one got by. I’ll need to update my unit tests.

I’ll get it patched tomorrow and I’ll release a new version to npm so the fix gets picked up.

Version 1.1.3 has been published to npm. It includes the fix for the bug in countdownTimer and fixed up unit tests. There is no new features or changes in behavior so I won’t edit the OP.

2 Likes

HI Rich.

I’m not a proper programmer but I know enough to be dangerous.
I think I found another Bug in your countdownTimer Class.

From what I understand with

let timer = new countdownTimer(when,function,Item);

The “function” is supposed to get called after the timer “when” finishes.
It currently gets called immediately but otherwise the item counts down correctly.

Looking at your code I think this.

this.timer = actions.ScriptExecution.createTimer(this.start, func);

should be this. (when I changed the code to this it worked)

this.timer = actions.ScriptExecution.createTimer(this.end, func);

and the below screen shot is for context.

Sorry to keep pointing out errors but i’m sure you’d rather know than not.
I’ve seen a lot of your work through the forums and its very helpful.

I absolutely would like to know rather than letting bugs remain.

I don’t use count down timer in my setup but I do have some unit tests that I though covered testing that the function was called at the same time. Apparently not. I’ll look into this and get it fixed (with a proper unit test) as soon as I can.

Thanks for the heads up!