Use of Timers within OH 3

I am trying to set up a rule/script within the OH 3 UI which is intended to do the following:

  1. Set room state to occupied when motion sensor state changes to ON.
  2. Set room state to unoccupied 5 minutes after motion sensor state changes to OFF
    Unless it changes back ON during that 5 minute period.

My prefered method would be to use Blockly and/or JavaScript as a means of setting up the control structure I require and adding in the necessary code. However it would appear that setTimeout is not available within the OH environment so if resorted to using Rule DSL and createTimer.

the script I have written is:

val String Occupied = "Occupied"
val String Unoccupied = "Unoccupied"
val String MovingToUnoccupied = "Moving To Unoccupied"

var boolean LoggingEnabled = true

var int TimerPeriod = 2 // Time in minuted

var Timer OccupationTimer 
   
if( StudyMotionSensor1_MotionIntrusion.state == ON ) 
  {
    if(LoggingEnabled) logInfo("Study", "Motion: ON, OccupancyStatus: " + OccupancyStatus.state)
    if(OccupancyStatus.state == MovingToUnoccupied)
    {
      if(LoggingEnabled) logInfo("Study", "Occupancy Delay Timer Cancelled - Motion")
      OccupationTimer.cancel()
    }
    OccupancyStatus.postUpdate(Occupied)
  }
  if (StudyMotionSensor1_MotionIntrusion.state == OFF)
  {
    OccupancyStatus.postUpdate(MovingToUnoccupied)
    if(LoggingEnabled) logInfo("Study", "Occupancy Delay Timer Started")
      OccupationTimer = createTimer(now.plusMinutes((TimerPeriod)),  // Wait TimerPeriod until setting room unoccupied
      [|          
        OccupancyStatus.postUpdate(Unoccupied)                      // No motion then switch light off 
      ])
}

The issue I’ve run up against is that because OccupationTimer is not global it is set to null each time the rule is invoked which leads to the error:

Script execution of rule with UID ‘162f1cdbb3’ failed: cannot invoke method public abstract boolean org.openhab.core.model.script.actions.Timer.cancel() on null

I know I can get around this by using a .rules file as in OH 2 but given the way the OH developement if moving I would prefer to do it via the UI.

So my questions are:

  1. Is there an an alternative to setTimeout in can use in JavaScript within the OH environment. If so could you please point me to where I can find details.
  2. Is there a way of defining global variables for a rule other than defining the rule within a .rule file or creating a dummy item to hold the timerID which something I don’t see as an elegant solution since it will make the model more confusing.

Many thanks in advance for your help

2 Likes

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

Timer in javascript:

I use createTimer in my Javascript rules like it is done in rules dsl.
You just have to import the corresponding java type:

Short example:

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 myTimer;


myTimer = ScriptExecution.createTimer(ZonedDateTime.now().plusSeconds(5), function(){
        logger.info("I was executed 5 seconds too late...");
       myTimer = null;
});
3 Likes

Hi Jerome,

Thank you for the response, it’s much appreciated, I will give it ago when I a chance tomorrow and let you know how I get on.

You could use OH2 style global variables by using OH2 style xxxx.rules text files. But not in the UI based rules entries.

I was struggling with this myself today, and cobbled together bits of knowledge from a number of posts. Much thanks to @rlkoshak for his very helpful posts and knowledge!

This is what I settled on:

var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime = Java.type("java.time.ZonedDateTime");

if (this.timer != undefined) {
  this.timer.cancel();
  this.timer = undefined;
}

function turnOff() {
  events.sendCommand("Bath1_Fan_Switch", OFF);
  this.timer = undefined;
}

this.timer = ScriptExecution.createTimer(ZonedDateTime.now().plusMinutes(60), turnOff);
6 Likes

Is there a reason not to use expire?

There is a reason, although not necessarily a good one, ignorance :grin: I knew the Expire binding didn’t exist in OH3, but didn’t realise it was now an item parameter.

That said, I’m not sure it will do what I want as the device in question can be operated in multiple ways with different time-out requirements, including no time-out. My first thought is that I could use virtual Items to achieve this, but I don’t really want to clutter my model with virtual Items.

Obviously I need to give this further thought and explore the Expire option further.

Many thanks for the pointer and expanding my knowledge a little bit further.

Thanks for the reply. I might got back to rules files, it’s just that I was hoping to be able to use the UI facilities

Indeed id also like to have more conditions on the off.
For example I want to turn on accent light via motion. But if other light sources in the room are on, I do NOT want to turn it off again.
I use node red for stuff like that, but it would be nice to cover it in rules already.

Or in the bathroom I want to dim the light after 3mins and then it off 2mins later, instead of going directly to off.

I use expire for these situations:

  1. For equipments where I want a delayed power off I have a rule that sends an ON command to a item (non-linked to a channel) that expires after some time
  2. In some cases I want a variable expiration (for example, towel radiators shall power OFF 5h in winter and 4h in summer) so I group several “expirable” items in a group
  3. Another rule detects when the expired item (or group) goes to OFF and takes necessary actions

I use this method to:

  1. Switch off TV box 30min after TV is shutoff between midnight and sunrise
  2. Switch off towel radiators 3h/4h/5h after being ON (depending of several factors)
  3. Switch off auxiliary resistance in my solar panel

Expire is good but it should not be combined with channel links because most bindings poll devices and are continuously sending ON commands, so timer never expires. I’ve raised this as an issue in github but so far it has not been actioned.

For “critical power off” situations (such as irrigation) I prefer pulsetime instead, but not all devices support it.

Am I right in thinking for the towel rail example you basically have 3 virtual items (1 each with a 3, 4, or 5hr expire) which act as proxies for the actual towel rail switch Item and you decide which one to use to switch on the towel rail depending on a combination of factors.

I can see how this would be beneficial in some circumstances and will certainly be experimenting with ‘expire’ but I currently envisage, maybe incorrectly, that their are circumstances where I would still want to use my own timer. Eg where circumstances change during the operation of the device which necessitate a different mode of operation, or the timeout needs to change dynamically during operation.

Once again thanks for providing me with an alternative way forward and plenty of food for thought.

Here is an example of the items I’ve used in OH2 (I converted them in OH3, but I think it’s more clear to post an .items file):

Switch FF_MasterBedroom_Power "Toalheiro Suite" <radiator> (FF_MasterBedroom, gPower) ["Switch", "Switchable"] {channel="tplinksmarthome:hs100:suite:switch"}
Group:Switch:OR(ON, OFF)   gMasterBedroom_Power_Timer
Switch MasterBedroom_Power_Timer_3h (gMasterBedroom_Power_Timer) {expire="3h,command=OFF"}
Switch MasterBedroom_Power_Timer_4h (gMasterBedroom_Power_Timer) {expire="4h,command=OFF"}
Switch MasterBedroom_Power_Timer_5h (gMasterBedroom_Power_Timer) {expire="5h,command=OFF"}
Switch MasterBedroom_Power_Timer_6h (gMasterBedroom_Power_Timer) {expire="6h,command=OFF"}

And this is the control rule:

rule "Ligar toalheiro Suite"
when
	Item GF_LivingDining_TV changed from ON to OFF
then
	if (gPresence.state == ON && GlobalSetting.state == "ONW" && now.getHourOfDay() < 4){
		FF_MasterBedroom_Power.sendCommand(ON)
		MasterBedroom_Power_Timer_5h.sendCommand(ON)
    } else if (gPresence.state == ON && GlobalSetting.state == "ONS" && now.getHourOfDay() < 4){
		FF_MasterBedroom_Power.sendCommand(ON)
		MasterBedroom_Power_Timer_4h.sendCommand(ON)
    }
end

rule "Iniciar timer toalheiro Suite"
when
    Item FF_MasterBedroom_Power changed from OFF to ON
then
	if (GlobalSetting.state == "ONW"){
		MasterBedroom_Power_Timer_4h.sendCommand(ON)
    } else if (GlobalSetting.state == "ONS"){
		MasterBedroom_Power_Timer_3h.sendCommand(ON)
    }
end

rule "Desligar toalheiro Suite"
when
    Item gMasterBedroom_Power_Timer changed from ON to OFF
then
	FF_MasterBedroom_Power.sendCommand(OFF)
end

As you point out, the drawback of expire is that time must be predefined. There are times where you need more granularity. In such cases I use either pulsetime (for single events) or createTimer(now.plusSeconds(1), ... (for a chain of events with delay between them).

Should be !== undefined

Not all Items need to be, not should it even be a goal to put all Items into your model.

Some binding’s do this. In my experience most don’t. But everyone has a different set of bindings.

True. But MQTT and TP-Link bindings do it. In my case this represents 85% of the devices that I turn ON/OFF using rules. I think that this is not documented anywhere, hence my suggestion to not use expire with linked items.

I don’t think MQTT does this on its own. But if the device is publishing, the binding will update the item on every message which is as it should be.

Hi Jerome just to let you know I now have the timer working on my system thanks to your example.

Only issue was the logger.info failed: ReferenceError: “logger” is not defined
but I’m not over worried about that.

1 Like

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 Overview (openHAB Core 3.1.0-SNAPSHOT API).

Thank you