How to use a rule function for timer

I have the below code which is a repeated for Door1 , just trying to figure out how to convert most of this into a function?
Can I pass Timer variable into function?

rule "check Garage door2 left open"
when
        Item GarageStateDoor2 changed
then
        var door="Door2"
        if (GarageDoorTimer2 !== null) {
                GarageDoorTimer2.cancel()
                }
        if (GarageStateDoor2.state == "OPEN") {
                doorOpenTime2 = now.millis
                GarageDoorTimer2 = createTimer(now.plusMinutes(GarageOpenMax)) [|
                        var open_duration = ((now.millis - doorOpenTime2) / 60000)
			var msg = "Garage open "+ door  + " for " + open_duration + " mins"
			sendMail(email,"Garage Open " + door, msg)
			logInfo("garage.rules",msg  )
                        ]
        }
end

I’ve done this. maybe you can reuse it.

What you need:
items:

// This item stores the length of the timer tiem in sec.
Number xxx_TimerLength

// This item is set to a given state if timer end
Switch   xxxItemToManipulate

Function:

import java.util.HashMap

var HashMap<String, Timer> 		tTimersWithCancel = newHashMap()

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters:
// relatedItem = Item which should be changed if timer ends
// tTimersWithCancel = pointer to the timer variable
// timerlengthinseconds = length of timer in sec.
//   					  0 = timer should end before normal end but end action is executed
//  					 -1 = timer should end before normal end but end action is not executed
// newitemstate = State of the item after times end (end action)
//
val Procedures$Procedure4<GenericItem, HashMap<String, Timer>, Integer, String> startTimerWithCancel=[ relatedItem, tTimersWithCancel, timerlengthinseconds, newitemstate |
	// timer should end before normal end but end action is executed
	if(timerlengthinseconds == 0)
	{
		if(tTimersWithCancel.get(relatedItem.name) !== null)
			tTimersWithCancel.get(relatedItem.name).reschedule(now)
		return;
	}
	
	// timer should end before normal end but end action is not executed
	if(timerlengthinseconds < 0)
	{
		if(tTimersWithCancel.get(relatedItem.name) !== null)
		{
			tTimersWithCancel.get(relatedItem.name).cancel()
			tTimersWithCancel.put(relatedItem.name, null)
			tTimersWithCancel.remove(relatedItem.name)
			logInfo("Logger", "TimerWithCancel for Item " + relatedItem.label + " canceled. No action executed.")
		}
		return;
	}
	
	// Timer started
	// wait a little bit, if two timers end on the same time not all of them are execute the commands
	Thread::sleep(100)

	// create the timer and do the action at the end of the timer
	if(tTimersWithCancel.get(relatedItem.name) === null || tTimersWithCancel.get(relatedItem.name).hasTerminated)
	{
		logInfo("Logger", "TimerWithCancel for Item " + relatedItem.label + " started with " + timerlengthinseconds + " sec.")
		tTimersWithCancel.put(relatedItem.name, createTimer(now.plusSeconds(timerlengthinseconds)) [|
			tTimersWithCancel.get(relatedItem.name).cancel()
			tTimersWithCancel.put(relatedItem.name, null)
			tTimersWithCancel.remove(relatedItem.name)
			logInfo("Logger", "TimerWithCancel for Item " + relatedItem.label + " reached end. Action " + newitemstate.toString + " executed.")
			relatedItem.sendCommand(newitemstate.toString)
		])
	}

	// timer is running, just reschedule with full value of time
	else
	{
		logInfo("Logger", "TimerWithCancel for Item " + relatedItem.label + " rescheduled with " + timerlengthinseconds + " sec.")
		tTimersWithCancel.get(relatedItem.name).reschedule(now.plusSeconds(timerlengthinseconds))
	}
]

How to use it in a rule:
Keep in mind, rule and function must be in the same rule-file

// Start the timer and set Item to OFF at timer end
startTimerWithCancel.apply(xxxItemToManipulate, tTimersWithCancel, (xxx_TimerLength.state as DecimalType).intValue, "OFF")

// If you want to end timer before time runs out and Item should be changed
startTimerWithCancel.apply(xxxItemToManipulate, tTimersWithCancel, 0, "OFF")

// If you want to end timer before time runs out and Item should not be changed
startTimerWithCancel.apply(xxxItemToManipulate, tTimersWithCancel, -1, "OFF")

Maybe this will help you. I have the same use case. I use a proxy item to trigger additional actons if timer runs out. This will keep the function universal.

Example for my fire alert timer

rule "Fire alert proxy"
	when
		Item swSmokeAlertProxy changed
	then
		Thread::sleep(100)
		
		if(swSmokeAlertProxy.state == ON)
		{
			SmokeSensor01.sendCommand(ON)
			SmokeSensor02.sendCommand(ON)			
			startTimerWithCancel.apply(swSmokeAlertProxy, tTimersWithCancel, (nuSmokeAlarmAlertTime.state as DecimalType).intValue, "OFF")
		}
		else
		{
			startTimerWithCancel.apply(swSmokeAlertProxy, tTimersWithCancel, -1, "OFF")
			logInfo("Logger", "Feueralarm beendet")
			
			SmokeSensor01.sendCommand(OFF)
			SmokeSensor02.sendCommand(OFF)
		}
end

I have a different code for escalation timer too. Escalation timer will do some escalation, if timer ends and the status it not the status which is expected. This function will check in a loop if the status is not the wished one and reduce the timer interval to half one every run. This will increase the message interval for maybe 1h to 30min and the to 15 min. At the end, if it is not solved, the functions gives up :wink:

Any improvements are welcome

As proven, sometimes it’s ridiculously complex to reduce code duplication.
At least if you don’t own much more than two garage doors, I would copy the code :wink:

Thanks guys, I think I will see how I go with duplication in the first instance its a bit simpler, but good to know this if I need to use it in the future if my script gets more complex.