JRuby OpenHAB Rules System

For that I’ve already a reusable library in Python and JavaScript that could be used. There isn’t yet a good way to distribute libraries like this so installation means copying files around. But use is pretty simple.

from community.timer_mgr import TimerMgr


tm = new TimerMgr()

...

    # In a Rule, check to see if a Timer exists for this Item. If one
    # exists, log a warning statement that the Item is flapping.
    # Otherwise, set a half second timer and update the Time Item
    # associated with the Item.
    tm.check(event.itemName,
             500,
             lambda: events.postUpdate("{}_Time".format(event.itemName), str(DateTime.now())),
             lambda: my_rule.log.warn("{} is flapping!".format(event.itemName),
             reschedule=True)

...

    # In a Rule, if the door is OPEN, create a timer to go off in 60
    # minutes to post a message to the Alert Item. If it's NIGHT time,
    # reschedule the Timer. If the door is CLOSED, cancel the reminder
    # Timer.
    if items[itemName] == OPEN:
        reminder_timers.check(itemName,
                              "1h",
                              lambda: events.postUpdate("AlertItem", "{} has been open for an hour!".format(itemName)),
                              reschedule=items["vTimeOfDay"] == StringType("NIGHT"))
    else:
        reminder_timers.cancel(itemName)

...

    # Check to see if a Timer exists for the Item.
    if reminder_timers.has_timer(itemName):
        my_rule.log.warn("There already is a timer for {}!".format(itemName))

No need to mess with Maps, that’s handled for you. No need to mess with ZonedDateTime, you can use DateTimeTypes from Items directly or define how far into the future to run the lambdas using the same syntax as Expire (e.g. “5m” is five minutes from now). No need to even mess with the Timers directly. And it supports reschedule and flapping detection (i.e. run a different lambda if the timer already exists).

Yes I actually did read most of the documentation on helper libraries for the python scripting, great stuff.

But looking at the, possibly chosen to reinforce the point, example where python is compared to Ruby i have to say the brevity really appeals to me. And the conversion, the casting, the typing, the units and the formatting: getStateAs(Quantitype).toUnit(:imp:. Frankenstein).floatvalue() and so on, where Ruby just compares the two, done.

I mean yes one can learn what types things are and what they need to be, but if I were to write ten rules then i would need to write all of this stuff over and over, debug it the first time, and remember it, which does not really appeal to me. But I actually don’t think Guido would think it very pythonic to have all of that extra stuff in there, but I am no programmer, and certainty no language designer.

So if i could choose between languages i would go for the one that gets me to my goal in the quickest and least painful manner. I am actually writing some stuff in python for work, but this makes me consider if really i should have gone with Ruby.

I’m not sure that matters all that much. Those who choose to use python and ruby are more of power users than ones just using the UI.

Having the flexibility to move beyond the UI and write rules in several widely known languages is a strong feature. And one that helps separate OpenHab from other HA options.

Plus, they appeal the programmer types which could help attract more developers, maintainers and contributors to OpenHab.

2 Likes

You might think that, but I can assure you there will be lots of users who will be trying to use Ruby in the UIs and disappointed when it doesn’t match the capabilities. And the pool of people on the forum who can help solve the problems will be pretty small since only Ruby people will be able to help. As it is now, I can even help with some Groovy problems without knowing anything about Groovy because the way it interacts with openHAB is consistent with all the other languages. That won’t be the case here.

IDK. I just seems that @broconne put a lot of work to to build a well-thought out - from a Ruby standpoint - new capability to openhab and has been met mainly with negative feedback from “the powers that be”. Everything focused on why it won’t work or is a bad fit or can’t be supported or what he did wrong and so forth. I’d probably be close to just saying “screw it” and walking away.

Scripted rules don’t seem to be first class citizens of openhab anyway. The python scripting didn’t even make it into the core 3.0 release and it’s been around forever. Surely “uniform UI implementation for all scripting languages” is so far down the roadmap it doesn’t make sense to hold this up just for that.

What powers that be? I’m just a user like any one else. I spend most of my time supporting people on the forum. I see trouble ahead in that regard as well as potential trouble getting this added to the core.

Opinions were asked for and I gvae mine. If my advice and fore warnings are not wanted I’ll with hold them. I just would hate all this effort to become either marginalized or not added to OH itself.

I never said it wouldn’t work. Clearly it already works. Is it a bad fit, I think so.

I’ll stop. Just don’t be surprised if there is trouble later on.

My apologies. Perhaps I read more into the “Foundation Member” next to your name than I should have. Your posts sounded very authoritative.

In retrospect my choice of words was poor and implies things I didn’t mean to.

I certainly not trying to quash opinions - just the opposite really.

Doug

Missed that the first time… I need to read slower.

That’s what I thought was happening and why I posted. So I’m very glad you feel that way too and apologize again for the misunderstanding.

To me, this looks pretty nice. I have a reasonably strong Ruby background (a couple of small RoR webapps that I put together that have been working nicely for over a decade, and a familiarity with the language that makes it my first stop for any scripting I need to do. Not a library-writing metaprogramming guru though).
I get the concerns expressed above about things not being done in the OpenHAB way, but I don’t think that is necessarily a bad thing. I have been trying for quite some time to get all my rules the way I want them with limited success. The main reason for my trouble has just been a lack of spare time due to work and family, but the Xtend based rules engine didn’t help, and the underlying Java stuff can get really confusing and ugly very fast. I recently tried the Jython JSR223 rules, and have been disappointed by the amount of OpenHAB/Java based stuff that still shows through. I had really hoped a lot of this would have been abstracted away in Python. I’ve become so frustrated lately that I have been made a significant effort to explore Home Assistant, although I haven’t been a huge fan of their YAML based automations either.
Whenever I have tried writing my rules in OpenHAB I have always had a nagging feeling that it could all be done so much more nicely with a Ruby based DSL that abstracts away all of the issues with types, timers, dates and times, etc. That looks exactly like what you are attempting here. I don’t know what the other efforts at producing a JRuby library are like, but I think you’ve made a very good start. I feel like you could be on to something very worthwhile here that could keep me interested in OpenHAB. I will make an effort to install your library and try to provide some feedback (time permitting).

What about received update? Maybe it could be received_command and received_update. I have absolutely zero knowledge of Ruby, but the syntax in your examples look very appealing. I wonder if you can make it look like `item XXX received command" much like RulesDSL?

Also, from skimming your very detailed docs, I didn’t see a way to postUpdate() to an item.

I hope everyone is having a wonderful holiday. Thank you to everyone who has provided kind words and encouragement for the JRuby scripting library, it is very much appreciated.

I have released version 1.0.0 with an update to support ‘received_command’.

Please do not associate the large jump in version number as any indication of completion or readiness for production. This project follows semver and the jump to 1.x occured because of a breaking change between ‘commanded’ and ‘received_command’.

Changelog
Latest Documentation

I went back and forth on how to handle commands. I initially pushed a version using commanded but as I was building it, I didn’t like it and after I pushed it I changed it to received_command which flowed better.

One other area of concern is writing text based code is not the only way to write rules. If this is going to be fully supportable by openHAB it also needs to support the UI rules approach where all the rule stuff is already handled and the scripting is sued as one option for Conditions (i.e. only run this rule if this script evaluates to true) and Actions (when triggered, if the Conditions are true, run this script).

It’s not clear to me that is going to be the case here which means, if this plug-in is installed users will see “jRuby” listed as an option for these in the UI but won’t actually be able to use it because stuff that is required to support your approach is not available.

@rlkoshak That is a valid concern to have. Nothing I have done should prevent this from working any differently in that context than any other scripting language we have.

  1. The part that would ever go in the core just make JRuby available, prevents the File class from being overwritten and makes the OpenHAB elements that are global, available globally.
  2. User are free to pull in my helper library with the require openhab line, if they don’t they get base JRuby with the changes noted in point 1.
  3. All of the code for sending commands, updating items, etc all works outside of the run block, so if they choose to import the helper library they get that and are not forced to use the run block syntax.

I can’t actually speak to what the maintainers will and will not accept to add to the baseline. But I do fear that the farther you deviate from the other languages in how interact with openHAB and present openHAB concepts I fear the likelihood of acceptance will go down, feelings get hurt and it ends up being a bad experience all the way around.

@rikoshk I appreciate the help and concern. Are we talking about the core maintainers here or the openhab-scripters maintainers? The only thing in the core is making JRuby available. All of my changes/enhancements are to the scripting library, not the core. As far as I know the core doesn’t contain any helper libraries.

One thing that we might want to look into is some additional (utility) functions these libraries add. If such functionality would be implemented in Java and/or added to core all libraries and possible ui rules can also use them and not every helper library would reinvent the wheel. If I have a concern it’s all these helper libraries added lots of features that also need to be maintained. The jruby variant here looks nice though.

@hilbrand This is certainly a valid concern that we have lots of different implementations with different features scattered across the ecosystem. I think there are two ways to evolve this. One is that we first put things into the core and all systems get them. The downside of this approach is that it is (and should be) difficult to add things to the core. It is a large commitment and we are taking a guess that it is worth it, that users will use the new features, etc. The second possibility is we distribute the innovation to the helper libraries and raise up what it is successful to the core. If some of the features here are broadly used (and useful), then it is worth the investment to get it in the core.

It is a balancing act with, in my opinion, no clear answer at this point.

Opinions were asked for and I gvae mine. If my advice and fore warnings are not wanted I’ll with hold them. I just would hate all this effort to become either marginalized or not added to OH itself.

I never said it wouldn’t work. Clearly it already works. Is it a bad fit, I think so.

I’ll stop. Just don’t be surprised if there is trouble later on.

I certainly value your opinion, no one that I have seen spends more time supporting users on this forum than you do. Without your support I am not sure we would have the large user base we have today.

We may have different opinions. However, I believe in the end, we both want the same thing, which is to make OpenHAB as good as possible.

I welcome your continued feedback.

I wonder if you can make it look like `item XXX received command" much like RulesDSL?
@JimT there is an issue open in GitHub to perform the text processing to mimic the RulesDSL. There were be some tradeoffs, you will lose syntax highlighting and other enhancements.

Also, from skimming your very detailed docs, I didn’t see a way to postUpdate() to an item.
@JimT The most recent docs are linked at the very top of it this. You can just use

Item.update <VALUE>

or

Item.postUpdate <Value>

All the java methods that you get in the DSL and in Python for the items exist. The Ruby additions either extend (monkey patch actually) or delegate to the underlying Java objects. I don’t have a lot of test cases to prove it, but most of the standard java/jython syntax should work for the Ruby versions.

Question for the community here…
I am now working on channels and channel triggers.

Does anyone know the best way to test a channel trigger? I need to define a thing and then via my test code have it send channel events on demand. The OpenHAB console can send triggers (so that might work) but I haven’t come across any sort of dummy Thing or test thing I can define to use as the base.

It might not be exactly on demand, but the astro binding could be configured (via offset) to trigger at whatever time you wish. A bit cumbersome perhaps, but that’s the best I can think of.

Perhaps slightly convoluted, but with MQTT Explorer or mqtt.fx it’s very easy to send whatever payload you want to an MQTT broker. With the MQTT Binding setup you could test various triggers. Does require installation and setup of other stuff, though.

@broconne, I tried and succeeded to install the addon, and through ignorance on my part it took me the better part of an evening to get any kind of response out of the openHAB console to show that the bundle had indeed been installed.
If I might suggest a little tweak to the install instructions I would change the first point in the install instructions to:

  1. Install the latest Jruby Scripting Language Addon from here to the folder <openhab_base_dir>/addons/

Then I proceeded to make a simple rule that should run every minute and output something to the log file called test.rb in /openhab/userdata/automation/jsr223/ruby/personal/ with the contents:

require 'OpenHAB'

rule 'Log the rule name every minute' do
  every :minute
  run { logger.info Rule #{name} executed }
end

I confirmed that the bundle has indeed been loaded by looking in the openHAB console, stopping and restarting the bundle named 3.0.0.202011091827│org.openhab.automation.jrubyscripting.

But I am not getting any response in the log files, even when I set the logging level on the bundle to TRACE (I set it back again to DEBUG to keep my sanity when something does start to happen).

I am wondering what happened, possibly it could be an idea to add more debugging output in the bundle definition to enable the user to figure out what the problem is. I have confirmed that the variables RUBYLIB and GEM_HOME point to the two folders specified.
An example could be to confirm that the needed libraries have been found, and that the needed directories are where they are expected. Additionally output in debugging level to indicate that a rule file has been found or found to have changed would be welcome.

Hi @joriskofman -
Oops. I was a little overzealous in my find/replace before I pushed out the docs.

it should be:
require 'openhab'

The capitalization matters. My apologies. I have updated the docs, thanks for catching this and sorry if it cause frustration.

However, you should have been receiving plenty of error messages from Jruby itself saying it couldn’t find ‘OpenHAB’ so something else must be up too.

Enable trace logging for the following:

log:set TRACE jsr223
log:set TRACE org.openhab.core.automation

Once you set that logging to trace and restart openhab you should see a log line like this indicating the Jruby ScriptEngine is loaded:

2020-12-26 16:07:49.335 [DEBUG] [ipt.internal.ScriptEngineManagerImpl] - Initialized a custom ScriptEngineFactory for JSR 223 JRuby Engine (9.2.13.0): supports ruby (jruby 9.2.13.0) with file extensions [rb], names [ruby, jruby], and mimetypes [application/x-ruby]

Also, it may just be the way it was pasted but in the code you provided it is missing the double quotes after logger.info which needs a string passed to it.

It should be

logger.info "Rule #{name} executed" }

Hello Again @broconne, no worries I will keep trying.

So far so good, I got the message from jsr223 that “Bundler” was not installed, which it was then successfully installed. So now it is alive. I also get the option to input jRuby scripts in the scripts section of openHAB, so that also works.
But openHAB still does not find any scripts that are located in the .../personal/ folder.

Failing to find out how to make rules in files work I tried using the scripting functionality in openHAB directly using this I can get rules to run! So that is definitely a success. But I do run into some unexpected (for me at least) behavior when using the scripting functionality.

Scenario 1:
When I create a new script in the openHAB interface and enter some code, pressing the “run now” button, a new line is created under the “rules” section, with the title of the script that was just created.
When I then make changed to the script and deploy it again by pressing the “run now” button a new entry is deployed under the “rules” section, but the old one does not get destroyed. So playing around with the code one can easily end up with many rules that might run at the same time. The only way to destroy these rules is to restart openHAB, as the rules are locked because they are loaded from a file (this is the reason the openHAB interface gives).

Scenario 2:
When I enter the following script:

require 'openhab'

rule 'button pressed event script' do
changed NewItem
  
  run { logger.info("button changed event")}
  while NewItem.truthy?
    delay 1.seconds
    run{ logger.info("button still changed to ON")}
  end
end

Sub scenario 2.1:
Initial state:
NewItem is OFF
Output:
The reponse in the logger is always only “button changed event”, but only for ON commands, not for OFF commands, or changes from ON to OFF.

Sub scenario 2.2:
Initial state:
NewItem is ON
Output:
The rule fails to deploy and is stuck in “running” state until NewItem changed to OFF, but without output to the log files.
Then on changing NewItem to ON the rule runs every second, and the expected output is generated in the log file.
But (the fly in the ointment) when changing NewItem to OFF fails to stop the rule from executing, continuing on as if NewItem is still ON.
Disabling the rule in the “rules” section fails to stop execution
Disabling the script in the “scripts” section fails to stop execution
Only restarting openhab stops execution of the script.

any help greatly appreciated.

@joriskofman Glad we are making progress!

Can you provide the full path of the personal folder you are using?
It should be
<OPENHAB_CONF_DIR>/ automation/jsr223/ruby/personal

The directory monitored is handled by the core, not by the JRuby implementation itself. Are you also using Jython? Do those scripts get loaded, etc?

When I create a new script in the openHAB interface and enter some code, pressing the “run now” button, a new line is created under the “rules” section, with the title of the script that was just created.
When I then make changed to the script and deploy it again by pressing the “run now” button a new entry is deployed under the “rules” section, but the old one does not get destroyed. So playing around with the code one can easily end up with many rules that might run at the same time. The only way to destroy these rules is to restart openHAB, as the rules are locked because they are loaded from a file (this is the reason the openHAB interface gives).

I have not played around with the UI method of creating rules, so I will have to experiment with that. When the rules are file based the core takes care of destroying the rules when files are changed / loaded. The scripting libraries don’t handle that part of it. So without looking at it yet I am not sure creating a rule definition within code in the UI is supported. Based on what @rlkoshak said above I think in the UI you are only supposed to put whatever is in the run block above in your rule code and the triggers would be configured in the UI and not in the code blocks. Again though, I haven’t looked at the rule editing in the UI.

Sub scenario 2.1:
Initial state:
NewItem is OFF
Output:
The reponse in the logger is always only “button changed event”, but only for ON commands, not for OFF commands, or changes from ON to OFF.

OFF commands shouldn’t trigger changed if the switch is already OFF (this is a standard openhab). However, they should trigger from ON to OFF. I added a test to make sure that is the case. Can you take a look here and let me know if I am testing the right thing?

If that doesn’t help with logs set to trace can you go from on to off and provide the logs?

Sub scenario 2.2:

This is something that is not clear and I need to add documentation on. Anything outside of a run or triggered block is executed when the rule is being parsed. The reason it fails to deploy or is “stuck” in running state is because you have a loop that is being processed while while NewItem.truthy? during the rule processing phase not the rule execution phase. That while loops is adding delays and run blocks onto the rule queue.

I have made an issue to clarify the docs around rule processing and rule execution.

I will need to think about the use case you are trying here for the best way to tackle it. Even if you move it inside of a run block it won’t work the way you want it too. Give me a bit to think about it.

I have released version 1.1.0 with an update to support ‘channel’ to provide channel triggers.

Changelog
Latest Documentation

Thanks to those that helped me with suggestions on how to test channel events. It turns out you don’t need a ‘thing’ to test channel events. The openhab:things trigger <channel> <event> will send a trigger to any channel.

1 Like

You too can have that next to your name. All that means is I donate to the openHAB Foundation. If you are in Germany is even considered a charitable gift and can be a tax write-off.

The donations pay for web hosting for the forum, the docs, myopenhab.org, and some of the build services.

The shield next to my pic indicates I’m a moderator on the forum. I do have some authority in that respect but it’s limited to keeping the forum clean and functioning.

Finally, I am also a member of the architectural council as"the voice of the user" since I have so much experience helping users on the forum. But the AC only come into play if there is a contaversy among maintainers they can’t resolve themselves, or a decision that impacts lots of openHAB repos which isn’t the case here.

My opinions stem solely from my opinions and experience helping users on the forum.

Rules are and always be an interaction between your script and openHAB. For the stuff that us built into openHAB that means interacting with Java Classes. If that’s a problem, an approach similar to that taken by HABApp with a separate service interacting with OH through it’s REST API world be more to your liking.

That’s one reason why I suggested this approach above already.

Potentially both. Or perhaps the addhons maintainers and the scripters maintainers (if you plan on submitting your library as part of the helper libraries. But it is not the wrist thing to maintain your own helper libraries. One of of the “unfortunate situations where feelings were hurt” so I’ve a modest library with some stuff in Python that makes managing timer based stuff a lot easier (denounce, time of day, Gatekeeper, etc).

I didn’t realize that theses are all implemented in the helper library. That does make it easier. You could probably create your own add-on for the helper libraries and maintain it on your own. My other concerns are still valid. You’ll basically be on your own for support.