JRuby OpenHAB Rules System

I should add, to use the RC version (which is the latest available version of 5.0), you need to set your gems to: openhab-jrubyscripting=>=5.0.0.rc.12

Once 5.0 is released, you can then change it to openhab-scripting=~>5.0.0 (note the change from jrubyscriptingscripting

Hey @JimT!

Your last example works fine, after a few small adaptations for the real situation. I already use it for some room! Thanks a lot.

To use the 5.0 lib I only have to change

org.openhab.automation.jrubyscripting:gems=openhab-scripting=~>4.0

to

org.openhab.automation.jrubyscripting:openhab-jrubyscripting=>=5.0.0.rc.12

in the jruby.cfg?

1 Like

Also change require 'openhab' to require 'openhab/dsl'

Is there a possibility to have shared code “global”?

I think this could be interesting for global variables or some methods like my “get_room_code” method. To access it from rules in different files.

Absolutely!

Put them in the rubylib (sub) directory, then require it.

Check the docs File: USAGE — openHAB JRuby

For getting the related items like room etc. You should use the semantic model. In combination with jruby library it is really nice. You won’t need to deduce rooms and related devices from naming patterns any more.

The new version of the library has been released! Announcement thread here.

You can’t really have “global variables” in the traditional manner. Each script file is isolated from one another under the default threading option, so a Ruby $global in one file isn’t visible in another file.

In any case it’s usually not recommended to use a global variable, but if you happen to need one, you could use the Shared Cache feature. It uses the built in mechanism from openHAB, so beware that openHAB will invalidate/remove the stored value when all scripts accessing that key was unloaded (e.g. reloaded?).

I usually use item’s metadata to store a global “flag” that I need in my rules.

Hoping someone can help me please :slight_smile: I recently moved my openhab to docker. I am running the stable release. Recently my JRuby Scripting rules stopped working and I am at a loss as to why.

I have pasted the error from the logs below.

2023-08-10 10:07:27.434 [INFO ] [ort.loader.AbstractScriptFileWatcher] - (Re-)Loading script '/openhab/conf/automation/jsr223/personal/keba.rb'

2023-08-10 10:07:27.812 [WARN ] [ernal.JRubyScriptEngineConfiguration] - Error evaluating `require 'openhab'`

org.jruby.exceptions.LoadError: (LoadError) no such file to load -- openhab

Did you mean?  open3

	at org.jruby.RubyKernel.require(org/jruby/RubyKernel.java:1057) ~[?:?]

	at RUBY.require(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb:85) ~[?:?]

	at RUBY.<main>(<script>:1) ~[?:?]

2023-08-10 10:07:27.827 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script '/openhab/conf/automation/jsr223/personal/keba.rb': Error during evaluation of Ruby in org/jruby/RubyKernel.java at line 1057: (LoadError) no such file to load -- openhab

Did you mean?  open3

2023-08-10 10:07:27.828 [WARN ] [ort.loader.AbstractScriptFileWatcher] - Script loading error, ignoring file '/openhab/conf/automation/jsr223/personal/keba.rb'

If you’ve recently upgraded your jruby helper library to version 5.x, you’ll need to make some changes.

  • Remove the require 'openhab' line at the top of your script. It is no longer required, because the addon now injects the require line by default. If you do want to have the explicit require statement, that’s fine too, because requiring the same file multiple times are simply ignored. In any case the new require line is require 'openhab/dsl'.
  • You will also be affected by other breaking changes from 4.x to 5.x so please read through the v5.0.0 changelog

If you encounter any other issues, feel free to post here again.

Thanks Jim. I downgraded to 4.x at the moment. Will read through the changelog and upgrade later

Could you please share your configuration file. What did you change?

No Problem, I can push you in the right direction.

  1. You will find the extension settings in your user-folder:
    USER/.vscode/extensions/openhab.openhab-1.0.0

2.Within this folder you can open the json-file /meta/openhab.tmLanguage.json

3.Here you should add “rb” to the fileTypes part. Then you can study the file and maybe change some other settings.

After saving the file, you have to restart VSCode, i think. And now the coloring of the text in the rb-files should be DSL-Rules like. And you should have access to the value of an item by howering the mouse pointer over it.

2 Likes

Hello,

I have an issue with simple class. I’m completely new with jRuby.

#file utils.rb
@can_send_pushover_message = true

class PushoverMessenger
  def initialize
    @can_send_pushover_message = true
  end

  def send_pushover_html_message(pushover_message, wait_time_for_next_message)
    if @can_send_pushover_message
      pushover_account = things["pushover:pushover-account:xxxxxxxxxx"]
      pushover_account.sendHtmlMessage(pushover_message, "openHAB")
      @can_send_pushover_message = false
      after wait_time_for_next_message.minutes do
        @can_send_pushover_message = true
      end
    end
  end
end

and i call method:

require "utils.rb" # here is class definition

pushover_messenger = PushoverMessenger.new

rule "test" do
  on_load
  every 10.seconds
  run do
    pushover_messenger.send_pushover_html_message("Hello: <font color='#EDBB99'><b>World</b></font>",15)
  end
end

and when I create a class instance and call method I get error:

undefined local variable or method `things’

What should I add as require or what other trick should I do.

Marcin

things is not available by default inside a class context, only at top level. You either need to call OpenHAB::DSL.things explicitly, or include OpenHAB::DSL into the class to make it available there.

Note also that your @can_send_pushover_message at top level is a different variable than the one inside your class. You don’t (currently) assign to the top level one anywhere, so it will work fine as is. Just warning you if you try to modify it from something not inside the class, and can’t figure out why the class instance isn’t seeing it.

1 Like

This is it. Thank you. Rest of code needs my attention, it is only a draft.

I have an issue using delay. I receive this error: undefined method 'delay' for main:Object (NoMethodError) with the following code snippet:

brightness = 0
while(brightness <= 100)
      brightness = brightness + 1
      prevBrightness = brightness
      light << brightness
      delay 15.seconds
      next if prevBrightness != brightness # User changed brightness in the meantime
end

It should be a simple wakeup light which increases brightness over time and aborts if the user changed the brightness in the meantime.
The script works nice with sleep but I would like to avoid using it.

If I’m not mistaken, delay creates a timer, meaning it schedules the code after it to run later. This is only available in the rule context, so when you’re in another code block, like a loop, it doesn’t exist. If it had been working, I think this rule would have spun out of control anyway, creating tons of new timers.

delay is not meant to be used like that. It is not the same as “sleep” or pause. It is actually an execution block, similar to run and triggered. The link will show you some examples of how to use it properly.

Firstly, in Ruby there are better ways than looping with for, while, do, e.g.:

0.upto(100).each do |brightness|
  # code here to use brightness
end

Secondly, the next if .... above won’t do anything. I think you meant to use break?

Lastly, to implement what you are trying to do, you should use timers instead of some form of sleep.

Something like this perhaps:

rule "Brighten my morning!" do
  every :day, at: "5am"
  run do
    next if light.on? # don't do the gradual thing if light is already on

    level = 0

    after(15.seconds) do |timer|
      next unless light.state.to_i == level # Stop the routine if it got changed manually
      # Note to_i above to round the state to integer in case it has fractions

      level = level + 1
      light << level
      timer.reschedule if level < 100 && !timer.cancelled?
    end
  end
end

Jim
I use UI rules not files so the portion of the rule I would use is below correct?
Can I ask a few questions?

is light an item?
is level a variable?
So we start out checking if the light is on

    next if light.on?

What does next do? Does it require the end
Is this not required in UI rule?
then we set level to 0

 after(15.seconds) do |timer|

ok, we are creating a timer here?

timer.reschedule if level < 100 && !timer.cancelled?

now we are resetting the timer if level isn’t 100 yet or the timer got cancelled? How would the timer have gotten cancelled?

      level = level + 1
      light << level

inside the loop, every 15 seconds the level gets incremented by 1 until we reach 100?
sooo… if the light is not on, we set level (a local variable we just created on the fly?) to 0 then loop every 15 seconds until level reaches 100

Sorry, if you could just break it down a little for us noobs. Some times ruby is so succinct it is hard to follow

Yes to both. You’d need to point light (or rename it) to an actual item.

It’s an early exit from the enclosing block. If you’re using UI rule, you’ll need to replace that with return instead.

Yes.

It’s a paranoid failsafe. It will work even without it, but I have been caught with a runaway self repeating timer that keeps executing / rescheduling itself even after being cancelled.

It’s because you can cancel a timer, and then reschedule it again. So if you happen to already be executing inside your timer handler and you cancel the timer from somewhere else, your code keeps going to the next line and the next line…and eventually reschedule itself thus nullifying your cancel call.

Correct.

level is indeed a local variable there.

I’m just a more experienced noob :smiley: but still a noob. Happy to help if I can.