How to create a recursive Timer in OpenHab3's DSL Rule

I wanted a DSL Script to send a notification email if a door was still opened after several checks.

There are various options to achieve this purpose.

Click to Read More

A simple option could have been to use a Switch Item with an Expire Property. When the door opens, set that Switch Item to 'ON' and initialize a Number Item to be count how many checks will be done. When the Switch Item expires, execute a script which checks if the door is still opened. If it is not, that script ends. Otherwise, it increases the Item Number. If that Item Number is lower than the maximum checks to be done (your threshold), Set back the Switch Item to 'ON'. Otherwise, send the notification email.

Another option (just for fun) relies on a DSL Script with a lamdba expression which creates a Timer calling itself recursively until the door is closed or your threshold is reached. That DLS Script must be called when the Door opens.

Here after a Number Item SensorGarageDoor_Duration is used to count the number of checks: The Item SensorGarageDoor_OpenClose is the state of the Sensor on the Door.

val int treshold = 3
var int attempt = 0

// Define a Lambda Expression to check if the Door is open and if not,
// call itself after 15 minutes to check again until a treshold of calls is reached
val Functions$Function1 Monitor = [	Functions$Function1 recursive |
	if (SensorGarageDoor_OpenClose.state == OPEN) {
		logInfo("default.rules", "Monitor Opened Door")
		createTimerWithArgument(now.plusMinutes(15), recursive, [ Functions$Function1 callMonitor |
				var int duration = (SensorGarageDoor_Duration.state as Number).intValue + 1
				SensorGarageDoor_Duration.sendCommand(duration)
				logInfo("default.rules", "Garage Opened since " + duration.toString + " cycle(s)")
				if (duration == treshold) {
					logInfo("default.rules", "Notify by email that Garage still Opened after " + duration.toString + " cycles")
					val mailActions = getActions("mail", "your smtp binding")
					val success = mailActions.sendMail("your from", "your subject", "your message")
				} else if (duration < treshold) {
					callMonitor.apply(callMonitor)
				}
			])
	} else {
		logInfo("default.rules", "Stop monitoring Door")
	}
	true]
	
if (SensorGarageDoor_Duration.state != NULL) {
	attempt = (SensorGarageDoor_Duration.state as Number).intValue
} else {
	attempt = treshold
}

if (attempt >= treshold) {
	logInfo("default.rules", "Request for monitoring Opened Door")
	SensorGarageDoor_Duration.sendCommand(0)
	Monitor.apply(Monitor)
} else {
	logInfo("default.rules", "New request for monitoring Opened Door ignored")
}

One of the trick is to pass the Lambda Expression as parameter to the Timer (Created with createTimerWithArgument). Indeed, a Timer may not call a "Global" Lambda Expression. To get a Lambda Expression as parameter, this parameter must be defined as Functions$FunctionX (where X is the amount of parameters of the Lambda Expression). In my sample above, my Lambda as one parameter which is itself, to be passed to the Trigger (So, it is typed Functions$Function1).

You must type explicitly the parameters as Functions$Function1 to be able to use the method "apply()".

Leave a Reply

Your email address will not be published. Required fields are marked *