Use of Timers within OH 3

Ah sorry i have cut out that include in my example.
That will make logger available.

var logger          = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);

Will add it above too.

Many thanks to everyone who replied, in particular @Confectrician and @scotthraban, I now have prototype timers working in the way I envisaged. @garyfree I will also be investigating “expire” further as I can envisage a number of situations where I would find it useful.

One final question, is there a list of available/recomended Javascript includes that can be used with OpenHAB?

Once again many thanks to all.

Any of the few thousand classes that are a part of Java: All Classes (Java SE 11 & JDK 11 )

Most of the relevant stuff from openHAB itself is already included by default. In those rare cases where that is not the case, most of the classes at https://openhab.org/javadoc/latest/.

Thank you

Hey, I have problems with my timer rule.
Maybe someone can help me

I want that an timer starts when two sensors send a status “OFF”.
After 90 seconds should switch off the lights. But when the sensors received a signal “ON” then should the timer break and repeat when the sensors received again status “OFF”

my Code:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime   = Java.type("java.time.ZonedDateTime");

var Bath_Timer;

if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON' || itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON') {
  events.sendCommand('GLight_Bath_Ceiling_Color', itemRegistry.getItem('varColor_Global').getState());
  logger.info('Bewegung im Bad erkannt');
  /* if (Bath_Timer.timer != undefined) {
  Bath_Timer.timer.cancel();
  Bath_Timer.timer = undefined;
}*/
}
if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF' && itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF') {
    Bath_Timer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(45), function(){
       events.sendCommand('GLight_Bath_Ceiling_Color', 'OFF'); 
       Bath_Timer = null;
});
}

A few things I notice:

  1. var Bath_Timer gets recreated every time the rule runs. You need to save that variable so the Timer gets preserved between runs.

    this.Bath_Timer = (this.Bath_Timer === undefined) ? null: this.Bath_Timer;

  2. Why are you pulling the Item from the registry just to get it’s state? items["MotionSensorPresence_Bath1"] will give you the Item’s state.

  3. You probably want to use null instead of undefined and in either case you need to use ===, not ==.

2 Likes

So now it looks good, no errors.

But the timer arn’t brake und restart if some presence are detected over the time. so now the timer starts if he gets the first time signal for no movement

    var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime   = Java.type("java.time.ZonedDateTime");

var Bath_Timer;
this.Bath_Timer = (this.Bath_Timer === undefined) ? null: this.Bath_Timer;

if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON' || itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON') {
  events.sendCommand('GLight_Bath_Ceiling_Color', itemRegistry.getItem('varColor_Global').getState());
  logger.info('Bewegung im Bad erkannt');
  
  if (this.Timer !== undefined) {
  this.Timer.cancel();
  this.Timer = undefined;
}

}
if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF' && itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF') {
    this.Bath_Timer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(45), function(){
       events.sendCommand('GLight_Bath_Ceiling_Color', OFF); 
       this.Bath_Timer.timer = undefined;
});
}

You created the variable this.Bath_Timer but you are cancelling this.Timer

this.Bath_Timer.timer = undefined doesn’t do anything. There already isn’t a this.Bath_Timer.timer. The Timer is stored in this.Bath_Timer.

You should be seeing errors in the logs when this rule tries to run. Knowing what those logs say is critical to helping

I got now this Error

2021-01-05 23:18:21.141 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘6e54d4fea4’ failed: TypeError: null has no such function “cancel” in at line number 13

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime   = Java.type("java.time.ZonedDateTime");

var Bath_Timer;
this.Bath_Timer = (this.Bath_Timer === undefined) ? null: this.Bath_Timer;

if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON' || itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON') {
  events.sendCommand('GLight_Bath_Ceiling_Color', itemRegistry.getItem('varColor_Global').getState());
  logger.info('Bewegung im Bad erkannt');
  
  if (this.Bath_Timer !== undefined) {
  this.Bath_Timer.cancel();
  this.Bath_Timer === undefined;
}

}
if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF' && itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF') {
    this.Bath_Timer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(45), function(){
       events.sendCommand('GLight_Bath_Ceiling_Color', OFF); 
       this.Bath_Timer === undefined;
});
}

I am not sure anymore what’s wrong, I tried since 4 days to running this script

The error is pretty explicit. this.Bath_Timer is null. There is no cancel() method on null. Remember, you’ve initialized this.Bath_Timer to null the first time if it’s not already defined.

There are a number of other problems with the code:

  1. this.Bath_Timer is set to null if undefined or the timer ID when the timer is created. However it is always tested for !== undefined. So all tests will always return true so you will always be cancelling the timer even if it has not started. You should remove the first line of code: “this.Bath_Timer = (this.Bath_Timer …”

  2. It is not clear whether you actually turn the light on as it is either set to explicitly OFF or to the same state as ‘varColor_Global’ so whether or not the light is turned on depends not just on the motion sensors but also the state of another device. So you need to make sure that is in the correct state.

  3. When setting this.Bath_Timer to undefined you should be doing: this.Bath_Timer = undefined; (an asignment) rather than: this.Bath_Timer === undefined; (an equivalance test)

Thank you guys!!! Now it’s properly running without errors, an the timer works now fine! Thanks a lot!

The final code:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime   = Java.type("java.time.ZonedDateTime");

var Bath_Timer;


if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON' || itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'ON') {
  events.sendCommand('GLight_Bath_Ceiling_Color', itemRegistry.getItem('varColor_Global').getState());
  logger.info('Bewegung im Bad erkannt');
  if (this.Bath_Timer !== undefined) {
    this.Bath_Timer.cancel();
    this.Bath_Timer = undefined;
}
}
if (itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF' && itemRegistry.getItem('MotionSensorPresence_Bath1').getState() == 'OFF') {
    this.Bath_Timer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(45), function(){
       events.sendCommand('GLight_Bath_Ceiling_Color', OFF); 
       
});
}
3 Likes

Global variables: I think there is currently no way except of virtual items to hold the information.

I’ve opened an issue for not having global variables for script engines, as the rule DSL has. The issue can be found here: Javascript based rules lack the ability for global variables #2084

And I’ve submitted a pull request to add a global scope for script handled by jsr223 script engines. Maybe, if the maintainers think of it as a good idea, it will eventually get into OH3. Pull request is here: Adds global context variables to action scripts #2089

1 Like

I’ve replied on github, but for the audience here the tl;dr is:

  • JavaScript already works like Rules DSL in this respect. All the “global” variables defined in a .js file is available to all the Rules defined in that .js file, just like in Rules DSL

  • If there is a problem, it that there isn’t (or at least I don’t know it yet) a way to preserve the state of a variable from one run of a Rules DSL Script Action to the next. There is a way to do that with JavaScript (see above). So the problem is Rules DSL Script Actions are less capable compared to JavaScript in Script Actions so it’s Rules DSL that needs to be fixed.

  • There is a ScriptExtension feature that isn’t widely well understood which is the mechanism that would be used to share variables between rules (either those defined in separate files or separate Script Actions/Conditions). And again, this is something that is unavailable in Rules DSL so again, it’s Rules DSL that needs to be fixed, not JavaScript.

My guess (and my reason, till now) because I did not find an example which showed a different way to do so. Thanks!!

2 Likes

@Luemmelpaeppi, two things:

  • You do not need to declare var Bath_Timer;, as that creates a function scoped variable is not actually used anywhere. You are only using the globally scoped this.Bath_Timer variable. If you were to use Bath_Timer, it would contain entirely different values from this.Bath_Timer. It is not affecting the functionality of your script, but it is not needed.

  • You seem to be using the same item name for both the motion sensor checks, I imagine you actually have a second motion sensor in the bath that is not actually being used in the script. MotionSensorPresence_Bath2 perhaps?

What would be the equivalent for DSL timer reschedule? Tried ScriptExecution.reschedule(...) which apparently does not do the trick.

The same as in Rules DSL. You need to call reschedule on the Timer Object itself. this.Bath_Timer.reschedule()

1 Like

I need a timer, too.
Its possible to use only the UI of OH3?

I have create a rule to switch on a light.
I have an Item where i set a Number how long the light is on.

I have to Set the Datetime If the rule “Switch on” runs?
I need the Datetime plus my time aus Trigger for the rule “Switch off”
But i dont know how i can create this Timer AS Trigger for the rule or could i do Something else?

If you can’t do it in a text based rule you can’t do it in the UI either.

Either put it all in the one rule and create a timer as normal, or use an item commanded by the timer to trigger the second rule.

You cannot create a timer without writing some code though, either in a .rules file or in a script action.