Avoid state flickering with JRuby scripts

I don’t use jRuby so this is just a guess, but I suspect the for: 1.seconds part only creates 1 timer for the whole rule instead of one timer per member. All the proxy Items change, the rule gets triggered on each of them and then waits for one second after the last proxy Item’s change triggered the rule and runs the rule only for that last proxy Item.

I don’t think you are going to avoid managing a separate timer for each Item, no matter the language. I’ve written a library of JS Scripting that includes a timerMgr for just such a situation. I’d be surprised if there isn’t something similar for jRuby.

For completeness, using my library the rule would look something like this as a text based rule:

const {timerMgr} = require('openhab_rules_tools');

rules.when().memberOf('gJalousien_Proxy').changed().then(event => {
  const timers = cache.private.get('timers', () => new timerMgr.TimerMgr());
  timers.check(event.itemName, 'PT1S', () => {
    const jalousie_proxy = event.itemName;
    const jalousie_name = jalousie_proxy.replace('_Proxy', '');
    items[jalousie_name].sendCommand(event.newState.toString());
    console.info("Jalousie verfahren auf neue Position: " + jalousie_name + ': ' + event.newState);
  });
}).build('Jalousie verfahren', 'description');

That uses the private cache to store the timers. That call to check will create a timer for one second if one for event.itemName doesn’t already exist, or reschedule the timer if one does already exist. After the one second, the timer goes off and calls the function that commands the Item and logs and then cleans up the timer.

Since this is file based you could probably put the timers variable as a global instead of in the cache but I mostly use UI rules so generally use the cache. As a UI script action it would look like:

const {timerMgr} = require('openhab_rules_tools');
const timers = cache.private.get('timers', () => new timerMgr.TimerMgr());

timers.check(event.itemName, 'PT1S', () => {
  const jalousie_proxy = event.itemName;
  const jalousie_name = jalousie_proxy.replace('_Proxy', '');
  items[jalousie_name].sendCommand(event.newState.toString());
  console.info("Jalousie verfahren auf neue Position: " + jalousie_name + ': ' + event.newState);
});

Thank you for the explanation. It makes sense that the timer is bound to the rule and not the members.

Coming to your example: that looks indeed quite handy! So the whole timer management is happening implicitely?
I do not have experience with JS Scripting rules yet. Do I put your first example into a “rule.js” file and put it under ./automation/js folder and that’s it? And of course install the JavaScript Scripting addon before. That’s all then?

@jabra_the_hut, in jruby when you do changed GroupItem.members, for: 1.second, it should keep track of each individual member separately, so if you have 17 members, it will keep track each of the 17 separately, as you would expect.

I tested this scenario just to be sure, on the latest jruby helper library version 5.5.0. Are you using v4.x or v5.x? Although I believe it should work the same way in 4.x, I just can’t test it.

Something else is not working the way you might think it is.

If your openhab.event.ItemStateUpdatedEvent (or whatever is the equivalent in openhab 3.x) logging is enabled, see if all those items received the state changed.

On a separate note, I think you can completely remove using proxy for this situation, if the whole purpose of proxy is to prevent too many commands being sent in rapid succession. One way I can think of is using a custom profile and perform a debounce in the profile. The Jruby library has a handy method to do this too.

I guess you could say that from the end user perspective. It’s very explicit in the library code.

Yes

No, you’d also need to install openhab_rules_tools.

I think it’s openhab.event.ItemStateEvent.

Does your question refer to the Ruby Gems? There I had openhab-scripting=~>4.0. Changing it to openhab-scripting=~>5.0I cannot get the script to work.
At Require Scripts I have openhab/dsl and my script begins with require 'openhab'.
However, when the rule gets executed I receive in the logs:

2023-08-11 11:31:29.180 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/openhab/conf/automation/jsr223/Jalousien.rb': Error during evaluation of Ruby in org/jruby/RubyKernel.java at line 1017: (LoadError) no such file to load -- openhab

I run openHAB 3.4.5 insider a Docker Container.

Yes, I was referring to the ruby gem.

If you have other scripts already written and working with version 4.x, do not change it to 5.x at this point, until you’ve gone through the v5 changelog and adjust for the breaking changes. Upgrading to 5.x is not necessary to solve this problem, because I believe the 4.x works the same way for changed member item, if I remember correctly.

If on the other hand, you don’t have any other established jruby scripts by all means, start using v5 because that’s where the current development is. In that case, if you are using v5.x, just remove the require 'openhab' line at the top.

The jruby helper library (gem) v5.x is compatible with openhab 3.4


I am simply wondering whether all your 17 items received a state change, because if they did, your rule should work as you intended.

This is my first rule to be developed in JRuby, so I am fine starting with 5.x. :slight_smile:

Removing the require directive the script execution fails due to the “find” command:

2023-08-11 11:31:13.590 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
org.jruby.exceptions.NoMethodError: (NoMethodError) undefined method `find' for #<OpenHAB::Core::Items::Proxy:0x1a9949a>
	at RUBY.method_missing(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:87) ~[?:?]
	at RUBY.<main>(/openhab/conf/automation/jsr223/Jalousien.rb:9) ~[?:?]
(...)

You wrote above that the Proxy item is not necessary for this usecase. How can that be? I have “wired” the proxy items to the Homekit addon, so that whenever the user changes the desired position of one or multiple rolling shutters inside the “Home” app the proxy item’s state changes. This gets recognized by this rule here and the rule sends it to the actual thing.

Removing the proxy item would mean the rule would fire when the thing’s item was already changed which would mean that the blinds already move when we enter the rule. Isn’t it?

This is due to the difference between 4.x and 5.x.

Your code:

    jalousie_thing = gJalousien.find { |item| item.name.include? jalousie_name }

Needs to change to:

    jalousie_thing = gJalousien.members.find { |item| item.name.include? jalousie_name }

But it is not necessary to use find. It could simply just be:

items[jalousie_name]&.command jalousie_proxy.state

Using item profile on the actual item itself, you can intercept the commands and not pass them through to the thing (shelly binding?) when commands are received too frequently.

Two things that will help you achieve this:

  1. Creating the profile in jruby
  2. Wrapping the callback.handle_command with jruby’s debounce_for so that it only handles the command when there’s at least X amount of time (e.g. 1.second) idle.

Yes, it’s the Shelly binding. OK, now with your tweaks it works. A timer gets created for each roller shutter. But still it does not perfectly. This time it seems to be due to the way how Homekit handles roller shutters.
First the items receive the desired position of 80% and while being closed homekit sends out 100% so the shutters end up being fully closed. Same with bringing it from 100% to 80%. In the middle of the movement of the blinds homekit sends out 0% from nowhere and the blinds are opened in the end. My logs look like this:

2023-08-11 14:03:58.931 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_Ost: 80%
2023-08-11 14:03:58.952 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 80%
(...)

2023-08-11 14:04:02.692 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 100%
2023-08-11 14:04:02.799 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_Ost: 100%
(...)
Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 80%
2023-08-11 14:04:28.913 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 80%
2023-08-11 14:04:28.934 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 0%
2023-08-11 14:04:30.426 [INFO ] [n.jrubyscripting.rule.Jalousien.rb:1] - Jalousie verfahren auf neue Position: va_Jalousie_SollPos_Wohnzimmer_KuecheOst: 0%

This will probably be solved with those profiles you recommend so I will read into it.

If that gets sent from “somewhere”, then your rule will simply pass that through. Even when using a profile, unless you implement some sort of logic to filter that out, but how would you know what’s a real and what’s an undesired command? You’d need to figure out what’s sending that extra command and stop it from doing that.

The profile method (untested), it’s super simple:

Rollershutter               va_Jalousie_SollPos_Wohnzimmer_KuecheNord
                            "Küche Nord"
                            <rollershutter>
							(gJalousien)
                            ["Blinds"]
                            { channel="shelly:shelly25-roller:<id>:roller#control" [profile="ruby:jalousie"] }                     

create a jruby file:

profile(:jalousie) do |event, callback:, command:, item:|
  next true if event != :command_from_item

  debounce_for(1.second, id: item) do
    callback.handle_command(command)    
  end
  false
end

You don’t need the proxy, nor the groups, nor the rule.

1 Like

I wonder how the binding knows. :thinking:
I think it has something to do with the „closing“ state in which the shutters are while travelling down. Without using proxies this is not an issue.

I will try it with your example and give feedback. Thank you for that so long! :pray:t2:

Do I need to install something additionally?
After having stored the script inside a *.rb file I get

2023-08-11 21:21:55.321 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/openhab/conf/automation/jsr223/Jalousien.rb': org.jruby.exceptions.SyntaxError: (SyntaxError) /openhab/conf/automation/jsr223/Jalousien.rb:17: syntax error, unexpected ')'
...bounce_for(id: item, 1.second) do
                             ^

Sorry, wrong argument order on debounce_for. Also next should be used instead of return. I have updated the original post with the corrections.

Ok, the script is now loading. However, it does not seem to have any effect :flushed:

I do not get any errors but the system behaves as if this script would not exist. State changes are immediately propagated to the shellys resulting in a fast relay clicking at the shelly.

I equipped each thing item with the ruby:jalousie profile (and homekit=WindowCovering)

@jabra_the_hut I’ve made another mistake (told ya I haven’t tested it) in my post.

The mistake was how to specify the profile. It should be enclosed in a square bracket, right after the channel= definition, and should not have been separated with a comma.

So the syntax should’ve been:

{ channel="shelly:shelly25-roller:<id>:roller#control" [profile="ruby:jalousie"], homekit="WindowCovering" } 

I’ve also updated the original post with the correct syntax.

All good. It‘s just me who feels super stupid as I currently fully rely on every single line of code from you. ^^

I updated the items. Still the same as this morning. No effect. Shellys immediately receive the new target position and click around.

OK try two things:

  1. Add a log at the top of the profile code
profile(:jalousie) do |event, callback:, command:, item:|
  logger.info("Ruby profile #{event}: command: #{command} -> #{item.name}")

  next true if event != :command_from_item

  debounce_for(1.second, id: item) do
    callback.handle_command(command)    
  end
  false
end
  1. If you can’t see any log, try restarting openhab. Sometimes it doesn’t reload the changes on .things file. I realised that this is in an .items file but worth a try. A less severe step would be to comment out the whole item (to remove the item), then uncomment it to reload it.

As we say in German: Reboot tut gut / reboot does good.

Yup, it now works as expected. :tada::partying_face:
All blinds wait a second and then move. No annoying relay clicking anymore. I can see the log messages.

Thank you very much for your support. Ruby scripts seem quite impressive although not easy to understand when you did 2 years of RuleDSL only. I shall dig deeper now in this.
Thanks again!

1 Like

LOL :slight_smile:

I didn’t know any Ruby, 2 years ago. It’s a great language and I love it. I’d suggest going through this page if you haven’t already done so: File: Ruby Basics — openHAB JRuby

1 Like