Simple timer for OH3

Hi all, I’ve been unable to get any timers to function in OH3, and I’ve tried through UI creation, DSL rules, and javascript. I think this should be a SUPER EASY thing to do, but searching just returns a million threads from 2019 and earlier than have nothing to do with OH3, and none of the examples if the documentation rules work.

I would like to take a super simple example, of a switch changes from OFF to ON, then I want to turn it off again after say 5 minutes.

The UI created code that just turns the light instantly off is here, but is there not some simple addition like “WaitSeconds(300)” or something you can put in?

  - id: "1"
    configuration:
      itemName: GuestRoomBathFan_Switch
      state: ON
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - id: "2"
    configuration:
      itemName: MasterBathFan_Switch
      command: OFF
    type: core.ItemCommandAction

Hi,
Just use expire meta data for the item.

1 Like

No, you have to use a Script Action and write the delay and commanding of your Item in code.

Which examples do not work? Note, most of the docs are currently file based (e.g. putting the rules in .rules files). These still work pretty much unchanged between OH 2.5 and OH 3. But perhaps you are misunderstanding what a Timer does and how it works. A Timer is a way to schedule some code to execute later. After the timer is created it immediately returns and executes the rest of the rule. Then at the scheduled time it executes that code.

So, using a Script Action using Rules DSL the action would look something like:

createTimer(now.plusMinutes(5), [ | 
    MasterBathFan_Switch.sendCommand(OFF) 
])

Not the similarity to the example in the docs:

            myTimer = createTimer(now.plusMinutes(5), [ |
                logInfo("rules", "Timer activated")
                //Do something...
            ])

However, you don’t even need a Rule for this. You can set the Expire metadata on the GuestRoomBathFan_Switch Item to automatically command the Item to OFF when it’s been ON for five minutes.

Hi this is what I had been trying based on the docs, however it doesn’t do anything. Looks like my mistake was entering things into a javascript rule not a DSL script.

This now works, thanks!!!

  - id: "1"
    configuration:
      itemName: GuestRoomBathFan_Switch
      state: ON
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: |
        createTimer(now.plusMinutes(15), [ | 
        GuestRoomBathFan_Switch.sendCommand(OFF)])
    type: script.ScriptAction

It’s a bit more complicated for JavaScript.

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Expamples");
logger.info("About to test createTimer");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var runme = function(){ logger.info("Timer expired!"); }
var ZonedDateTime = Java.type("java.time.ZonedDateTime");
var now = ZonedDateTime.now();
var timer = SE.createTimer(now.plusSeconds(1), runme);

That brings back horrible memories from university :wink:

Hi,
I tried your example for JavaScript and it worked.
Thanks for that.

Is it possible to delete or reset the timer before it is expired?

You have to save the variable in a way that it doesn’t get rewritten the next time the rule runs. I don’t know how to do that in Rules DSL or Python (if it’s even possible). In JavaScript

this.timer = (this.timer === undefined) ? null : this.timer;
var runme = function() {
    // do stuff
    this.timer = null;
}
if(this.timer != null) {
    this.timer.cancel(); // or reschedule
}
else {
    this.timer = SE.createTimer(...
}

Thanks, this worked. :+1:

May I also ask a JSR223 timer related question:

I am using Ivan’s helper libraries for Python Scripting in OH3.1.

I use a rule at the start of the day to setup timers for all my shutters. That worked in DSL rules, and I am now converting to Python.

I create a list aufrufe where I store all the data: time, which shutter, which percentage. Then I loop through this list to create timers:


	for aufruf in aufrufe:
		rolladenzeiten.log.debug("Timer-Zeit: "+ str(aufruf[0]) + ", rolladen: " + aufruf[1] + ", Stand: " + aufruf[2])	
		timers.append(ScriptExecution.createTimer(aufruf[0], lambda: rolladenTimer(aufruf[1],aufruf[2])))

def rolladenTimer(rolladenName, rolladenStand):
	rolladenzeiten.log.debug("Rolladen " + rolladenName + " wird auf " + rolladenStand + " gefahren!")
	events.sendCommand(ir.getItem("rolladen_"+rolladenName), rolladenStand)

Unfortunately, only the last shutter “Badezimmer” gets all timer events (all timers that have a time that has already passed are fired directly):

2021-09-26 12:53:32.521 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T08:39:12+02:00[Europe/Berlin], rolladen: kueche, Stand: 0

2021-09-26 12:53:32.524 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T19:45:59+02:00[Europe/Berlin], rolladen: kueche, Stand: 100

2021-09-26 12:53:32.527 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T08:47:22+02:00[Europe/Berlin], rolladen: wohnzimmer2, Stand: 0

2021-09-26 12:53:32.530 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T19:46:50+02:00[Europe/Berlin], rolladen: wohnzimmer2, Stand: 100

2021-09-26 12:53:32.532 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T08:32:41+02:00[Europe/Berlin], rolladen: wohnzimmer1b, Stand: 0

2021-09-26 12:53:32.535 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T20:11:31+02:00[Europe/Berlin], rolladen: wohnzimmer1b, Stand: 100

2021-09-26 12:53:32.537 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T08:35:07+02:00[Europe/Berlin], rolladen: wohnzimmer1a, Stand: 0

2021-09-26 12:53:32.540 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T20:14:56+02:00[Europe/Berlin], rolladen: wohnzimmer1a, Stand: 100

2021-09-26 12:53:32.543 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T10:41:37+02:00[Europe/Berlin], rolladen: kinder1, Stand: 0

2021-09-26 12:53:32.545 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T20:08:15+02:00[Europe/Berlin], rolladen: kinder1, Stand: 100

2021-09-26 12:53:32.548 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T10:48:08+02:00[Europe/Berlin], rolladen: badezimmer, Stand: 0

2021-09-26 12:53:32.550 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Timer-Zeit: 2021-09-26T20:08:03+02:00[Europe/Berlin], rolladen: badezimmer, Stand: 100

2021-09-26 12:53:32.631 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

2021-09-26 12:53:32.634 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

2021-09-26 12:53:32.642 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

2021-09-26 12:53:32.644 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

2021-09-26 12:53:32.648 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

2021-09-26 12:53:32.653 [DEBUG] [hon.Jython Stelle Rolladenzeiten ein] - Rolladen badezimmer wird auf 100 gefahren!

Does anyone have a clue what is going on?

It’s a Python lambda problem, Rich and I ran into this when first using timers in loops in Python as well. I can’t remember where we discussed it, but you need to pass the aufruf[1] and aufruf[2] variables into the lambda instead of referencing them directly because they will point to the last thing aufruf was set to before the loop exited.

I think this would work but I’m just typing this on my phone so it might need adjusting:

(lambda x, y: rolladenTimer(x, y))(aufruf[1], aufruf[2])

Yep, what Michael said.

What’s interesting is that JavaScript has a similar problem with anonymous functions. The way to get around that is to use a function generator.

var runmeGenerator = function(var1, var2) {
    return function(){
       // code that uses var1 and var2
    }
}

Thanks Michael and Rich.

In the meanwhile, as I am only a poor programmer and don’t understand what a lambda is, I have found another solution in one of the examples:

My code is now like this:


	for n in zeitenListe:
		
		dieseHeizung = (n.name.split("_"))[1]
		dieserIndex = (n.name.split("tag_"))[1]	
		dieseZeit = ZonedDateTime.parse(heute + "T" + n.state.toString()+"+"+zeitZone)
		
		dieseTemp = float(str(ir.getItem("temp_" + dieseHeizung + "_" + dieserTag + "_" + dieserIndex).state)) - float(dieserUrlaubsAbzug)
		heizungszeiten.log.debug("dieseHeizung: "+ dieseHeizung)
		heizungszeiten.log.debug("dieseZeit: "+ str(dieseZeit))
		heizungszeiten.log.debug("dieseTemp: " + str(dieseTemp))
		
		timers.append(ScriptExecution.createTimerWithArgument(dieseZeit, [dieseZeit, dieseHeizung, dieseTemp], heizungsTimer)) 
   
def heizungsTimer(dieserAufruf):
	heizungszeiten.log.debug("Heizung " + dieserAufruf[1] + " wird auf " + str(dieserAufruf[2]) + " gestellt!")
	events.sendCommand(ir.getItem("heizung_"+dieserAufruf[1] + "_setTemp"), str(dieserAufruf[2]))

This works perfect.