Array of Timers / Dynamically Instantiated Timers based on triggeringItem name

Hi - I’m using OH 3.3.0.

I’ve been trying to group all of my objects together in order to re-use code by using “Member of changed” in my rule definitions. This is going great! I’m using a combination of lookups via .members.findFirst and other methods to talk to various items dynamically via loops/groups.

I’m now looking for a way to refer to universally declared groups of Timers or the ability to generate them on the fly. If we consider:

var Timer frontLightTimer = null

rule "Lights - Turn off main"
when 
    Item light_Front_Door changed to ON
then
    if (frontLightTimer !== null)
        frontLightTimer.cancel()

    frontLightTimer = createTimer(now.plusMinutes(10), [ |
        light_Front_Door.sendCommand(OFF)
        frontLightTimer = null
    ])
end

This is fine, but as soon as I try to do this code for multiple lights/Timers, I have no way to refer to a specific Timer based on the triggeringItem’s name.

I tried using ArrayLists, but a list of Timers seems to translate to an “Object” and is not functional. I also have no way to add a group of Timers to my .items files, where I could look them up that way. Nor can I look up a variable without a item/group declaration.

Anyone have any knowledge of how to accomplish this? Here’s some ideas of what I’m trying to do:

Array approach:

var List<Timer> lightArray = newArrayList()

rule "Lights - Initialize Lights"
when 
    Member of gLightTimers changed to ON
then
    var vLightName = triggeringItem.name.split("_").get(1)
    var index = gLightTimerIndexes.members.findFirst[ t | t.name == "light_" + vLightName + "_TimerIndex" ] as StringItem
    
    if(index === null)
    {
        var newTimer = createTimer(now.plusMinutes(1), [ |
            logInfo("lights.rules", "{}", "timer ran")
            // newTimer = null // cancel the timer
        ])
        
        lightArray.add(newTimer)
    }
end

variable lookup:

var Timer frontLightTimer = null
var Timer frontGarageLightTimer = null

rule "Lights - Initialize Lights"
when 
    Member of gLightTimers changed to ON
then
    var vLightName = triggeringItem.name.split("_").get(1)
    var vTimer = lookupVariableSomehow(vLightName + "Timer") as Timer
    
    if (vTimer !== null)
        vTimer.cancel()
    
    vTimer = createTimer(now.plusMinutes(1), [ |
        logInfo("lights.rules", "{}", "timer ran")
        // newTimer = null // cancel the timer
    ])
        
end

Thanks for any advice!

Take a look at

Scroll to bottom of that thread – I think it is doing something very similar to what you are attempting. Good luck!

As @JJ_Reynolds links to, use a Map, not a List. You can use the name of the Item related to the Timer as the key.

On JS Scripting, my Announcing the initial release of openhab_rules_tools (installable through openHABian) has a utility called TimerManager which handles the management of multiple timers for you. In a UI rule it would look something like:

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

var timerMgr = privateCache.get('timerMgr', () => new timerMgr.TimerMgr());

var vLightName = event.itemName.split("_").get(1);
timerMgr.check(vLightName, 'PT1M', () => { console.info('timer ran'); }, false, null, ruleUID+'_'+vLightName);

When check is called, if a timer doesn’t exist it creates one. The arguments are:

  • key: unique identifier for the Timer, usually best to use an Item name
  • timeout: anything supported by time.toZDT(), in this case it’s an ISO8601 Duration string for one minute
  • function: the function to call when the timer expires.
  • reschedule: when true, if the timer already exists it will be rescheduled using the timeout
  • flapping function: an optional function to call if check is called and the timer already exists
  • name: a name you can give to the timer so if there is an error in the timer function, you can identify what timer it came from

Thank you @JJ_Reynolds and @rlkoshak! I’ll look into the JS scripting solution as well; thank you for letting me know about those tools. Looks like a great framework!

Here is the full excerpt of my working solution in case anyone else was curious. Just as an aside, you can’t print out the lightTimers Map variable via logInfo, so it makes inspecting it a bit difficult. But, it works!

Switch light_FrontDoor "Front Door Lights" (gLights) { channel="zwave:device:xxx" }
Switch light_FrontDoor_ToBeOn (gLightTimers)

Switch light_GarageOutside "Garage Lights" (gLights) { channel="zwave:device:xxx" }
Switch light_GarageOutside_ToBeOn (gLightTimers)
import java.util.Map

val Map<String, Timer> lightTimers = newHashMap

rule "Lights - Initialize Lights"
when 
    Member of gLightTimers changed to ON
then
    // init
    var vName = triggeringItem.name.split("_").get(1)
    var vTimerName = "timer_" + vName
    var vLight = gLights.members.findFirst[ t | t.name == "light_" + vName ] as SwitchItem
    
    // turn on the light
    if(vLight.state == OFF || vLight.state == NULL)
        vLight.sendCommand(ON)
    
    // turn off this event (so we can call it again to refresh the timer)
    triggeringItem.sendCommand(OFF)
    
    // timers
    lightTimers.get(vTimerName)?.cancel;
    
    lightTimers.put(vTimerName, createTimer(now.plusMinutes(10)) [|
        vLight.sendCommand(OFF)
        lightTimers.remove(vTimerName)
    ] )
end

You can but you have to iterate over it.

lightTimers.keySet.forEach[ key | logInfo('lights.rules', key + ': ' + lightTimers.get(key) ]

I’m not sure that the toString() of a Timer is all that meaningful though.

1 Like