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
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!
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