Rule optimization: Window OPEN reminder


I have 11 windows that I want to monitor.
Here is a part of the rule I would apply.
The rule is repeated 11 times (for every window one rule).
Every window item is a contact and the item Sommer is a switch for “Summer-Mode”.
After 30 Min. I will get a notification over push notification and Alexa reminder.

  /* ------------------------------------------------------------------------ */
 /* Variablen für Timer
/ ------------------------------------------------------------------------- */

var Timer 	DGBadFenster_Timer = null
var Timer 	DGFlurFenster_Timer = null
var Timer 	DGStudioFensterLINKS_Timer = null
var Timer 	DGStudioFensterRECHTS_Timer = null
var Timer 	OGKinderFenster_Timer = null
var Timer 	OGSchlafTuer_Timer = null
var Timer 	OGBadFenster_Timer = null
var Timer 	OGSportFenster_Timer = null
var Timer 	EGWZTuer_Timer = null
var Timer 	EGKuecheFenster_Timer = null
var Timer 	EGWCFenster_Timer = null

  /* ------------------------------------------------------------------------ */
 /* Fenster DG Bad 30 Min. offen
/ ------------------------------------------------------------------------- */

rule "Fenster DG Bad 30 Min. offen"
	Item DGBadFenster changed
	if (DGBadFenster.state==CLOSED) {
		if (DGBadFenster_Timer!=null) {
	else if ((DGBadFenster.state==OPEN) && (Sommer.state!=ON)) {
		if (DGBadFenster_Timer==null) {
			DGBadFenster_Timer=createTimer(now.plusMinutes(30)) [|
				logInfo("Rules", "Fenster DG Bad 30 Min. offen")
				sendNotification("EMAIL-ADRESS", "Fenster DG Bad seit 30 Min. offen")
				EchoDotWZ_Remind.sendCommand("Fenster im Dachgeschoss Bad ist seit 30 Minuten offen")

  /* ------------------------------------------------------------------------ */
 /* Fenster DG Flur 30 Min. offen
/ ------------------------------------------------------------------------- */

rule "Fenster DG Flur 30 Min. offen"
	Item DGFlurFenster changed
	if (DGFlurFenster.state==CLOSED) {
		if (DGFlurFenster_Timer!=null) {
	else if ((DGFlurFenster.state==OPEN) && (Sommer.state!=ON)) {
		if (DGFlurFenster_Timer==null) {
			DGFlurFenster_Timer=createTimer(now.plusMinutes(30)) [|
				logInfo("Rules", "Fenster DG Flur 30 Min. offen")
				sendNotification("EMAIL-ADRESS", "Fenster DG Flur seit 30 Min. offen")
				EchoDotWZ_Remind.sendCommand("Fenster im Dachgeschoss Flur ist seit 30 Minuten offen")


Do you have any idea how I could optimize this?

To start with, I’d try something like this:

put all your windows that you want a notification for in a group
items file:

Group:Contact:OR(OPEN,CLOSED)    allWindows

Contact DGBadFenster (allWindows)
Contact DGFlurFenster (allWindows)

persist that group with, for example, mapdb

allWindows* : strategy = everyChange,restoreOnStartup

add 2 map-files to your transform folder.

DGBadFenster=DG Bad
DGFlurFenster=DG Flur

DGBadFenster=Dachgeschoss Bad
DGFlurFenster=Dachgeschoss Flur

and finally in your rules, do:

import java.util.Map

val Map<String, Timer> OpenWindowTimers = newHashMap

val Functions$Function2<ContactItem, Map<String, Timer>, Boolean> checkOpenWindow = [
  timerMap |

  val String myTimerKey =

  if (windowItem.state == CLOSED) {
    if (timerMap.get(myTimerKey) !== null) timerMap.get(myTimerKey).cancel()
  } else if ((windowItem.state == OPEN) && (Sommer.state != ON)) {
    timerMap.put(myTimerKey, createTimer(now.plusMinutes(30)) [|
      timerMap.put(myTimerKey, null)

      val String shortName = transform("MAP", "",
      val String longName = transform("MAP", "",

      logInfo("Rules", "Fenster " + shortName + " 30 Min. offen")
      sendNotification("EMAIL-ADRESS", "Fenster " + shortName + " seit 30 Min. offen")
      EchoDotWZ_Remind.sendCommand("Fenster im " + longName + " ist seit 30 Minuten offen")


rule "Fenster check"
  Item allWindows received update
  Thread::sleep(100) // this gives the persistence service time to store the last update

  val lastUpdatedWindowItem = allWindows.members.filter[s|s.lastUpdate("mapdb") !== null].sortBy[lastUpdate("mapdb")].last as ContactItem

  checkOpenWindow.apply(lastUpdatedWindowItem, OpenWindowTimers)

now you don’t need to add a rule for every window. just add all your windows to the allWindows group and their corresponding (short and long) names to the mapping-files and you’re all set


wow, thanks a lot.

One additional question:
Is it possible to use as persitence rrd4j?
Because today I used rdd4j persitence and I would be reluctant to add another persistence.

It does not make a lot of sense to use rrd4j for this solution, as you only need the last item state persisted (no history). I use rrd4j only for charting purposes. According to the openHAB rrd4j docs it even seems impossible:

NOTE: rrd4j is for storing **numerical data only**. Attempting to use rrd4j to store complex datatypes (eg. for restore-on-startup) will not work.

I’m running MapDB and rrd4j on my raspi3. MapDB is used to store the last state of ALL items, rrd4j stores ONLY selected numeric items.
The system is running without any problems.


whit the help of this Thread from @rlkoshak i build this:

Contact     FHT_Bad_Fenster 			            "Bad Fenster[MAP(]"  				                <iftswindow1w>          (FHT, FHTfenster)
DateTime    FHT_Bad_Fenster_LastUpdate              "Bad Fenster letztes Update [%1$tm.%1$td.%1$tY %1$tH:%1$tM]"            <itimeclock>            (FHT, FHTfensterUpdate)
Switch      FHT_Bad_Fenster_Timer                   "Bad Fenster Timer"                                                     <itimetimer>            (FHT, FHTfensterTimer)   {expire="15m,command=OFF" }
Number      FHT_Bad_Fenster_offenZeit               "Bad Fenster offen Zeit[%s]"                                            <itimetimer>            (FHT, FHToffenZeit)
Number      FHT_Bad_Fenster_maxoffenZeit            "Bad Fenster max öffnungs Zeit[%s]"                                     <itimetimer>            (FHT, FHTmaxoffenZeit)

rule "Fensterüberwachung"
    Item FHT_Bad_Fenster changed or
    Item FHT_Buero_Fenster_Hof changed or
    Item FHT_Buero_Fenster_Strasse changed or
    Item FHT_Wohnzimmer_Fenster_Tur changed or
	Item FHT_Wohnzimmer_Fenster_Bal changed or
	Item FHT_Wohnzimmer_Fenster_Hof changed or
	Item FHT_Schlafzimmer_Fenster changed or
    Item FHT_Kueche_Fenster changed

    val logname = 'FENSTERÜBERWACHUNG'


    val fenster = FHTfenster.members.filter[s|s.lastUpdate("mapdb") !== null].sortBy[lastUpdate("mapdb")].last as ContactItem 
    val timer = FHTfensterTimer.members.filter[t | =="_Timer"].head as SwitchItem

    val lastUpdate = FHTfensterUpdate.members.filter[dt | == + "_LastUpdate"].head as DateTimeItem
    lastUpdate.postUpdate(new DateTimeType)

    val StringBuilder msg = new StringBuilder
    msg.append = transform("MAP", "",

    if(fenster.state == OPEN) {
        msg.append(" wurde geöffnet")
    else {
        sendCommand("_offenZeit", "0")
         msg.append(" wurde geschlossen")
    var alert = false
    if(Tageszeit.state.toString == "Nacht" && fenster.state == OPEN){
        alert = true
        msg.append(" und es ist Nacht")
    if(Anwesend.state == OFF){
        alert = true
        msg.append(" und niemand Zuhause")
    logInfo(logname, msg.toString)
    if(Anwesend.state == OFF){
        sendNotification("xxx", msg.toString)

rule "Fenster Timer ausgelaufen"
    Item FHT_Bad_Fenster_Timer received command OFF or
    Item FHT_Buero_Fenster_Hof_Timer received command OFF or
    Item FHT_Buero_Fenster_Strasse_Timer received command OFF or
    Item FHT_Wohnzimmer_Fenster_Tur_Timer received command OFF or
	Item FHT_Wohnzimmer_Fenster_Bal_Timer received command OFF or
	Item FHT_Kueche_Fenster_Timer received command OFF or
	Item FHT_Schlafzimmer_Fenster_Timer received command OFF or
    Item FHT_Wohnzimmer_Fenster_Hof_Timer received command OFF

    val logname = 'FENSTERALARM'


    val timer = FHTfensterTimer.members.filter[t|t.lastUpdate("mapdb") !== null].sortBy[lastUpdate("mapdb")].last as SwitchItem
    var fensterName ="_Timer").get(0)
    val fenster = FHTfenster.members.filter[z | == fensterName].head as ContactItem
    var offenZeit = FHToffenZeit.members.filter[o | =="_offenZeit"].head as NumberItem
    val maxoffenZeit = FHTmaxoffenZeit.members.filter[m | =="_maxoffenZeit"].head as NumberItem
    var erlaubt = maxoffenZeit.state as Number
    var dauer = offenZeit.state as Number

    val StringBuilder msg = new StringBuilder
    msg.append = transform("MAP", "",

    if(fenster.state == OPEN) {
        dauer = dauer + erlaubt
        sendCommand(offenZeit, dauer)
        if( dauer == erlaubt ) {
            msg.append(" schon ")
            msg.append(" Minuten offen!")
        } else {
            msg.append(" immer noch offen! (")
        logInfo(logname, msg.toString)
        if( Anwesend.state == OFF && dauer == erlaubt ) {
           sendNotification("xxx", msg.toString)
           sendNotification("xxx", msg.toString)
        if( Virtuell_x1_Zuhause.state == ON ) {
            sendNotification("xxx", msg.toString)
        if( Virtuell_x2_Zuhause.state == ON ) {
            sendNotification("xxx", msg.toString)
        sendCommand(timer, ON)

After a couple of days it seems to work :slight_smile:

i hope it helps somebody



With the addition of the triggeringItem implicit variable, you no longer need to use the persistence hack to get the Item that triggered the Rule. Replace the first two lines after the val logname ... with:

val fenster = triggeringItem

Do you have any insight on the state of triggeringItem working with groups ?

I’ve successfully used jython logic to surface the triggering item from a group via a virtual item “interface”. Sorta-like a pass-by-reference (VERY loosely). It works but does create more (but simpler) maintenance points.

In work but not yet available. There will be a new trigger type to populate triggeringItem with the member that triggered the update to the Group. I couldn’t find the issue but I’m subscribed to it and don’t remember seeing that it has closed.

Once it is done we will have to wait for a new release of ESH to be merged with OH.

FWIW, here’s how I monitor my garage (if open for 10 mins)… It sends me a text message, and HABPanel speaker will say "Warning: Garage remained open."

val ruleForGarageInSeconds = 60 * 10 // 10 minutes
var Timer alarmReminderTimer = null
var DateTime garageLastOpened = null
val String emailRecipients = ""

rule "Send Reminder for Garage - OPEN"
	Item alarmContactZone23GarageDoorDriveway changed to OPEN

	if (alarmReminderTimer == null) {
		garageLastOpened = now;
		logInfo("GarageCheck-isOpen", "Garage Opened @ " + garageLastOpened.toString("MM/dd/yy HH:mm:ss a") +
				" . Will check if still open in " + ruleForGarageInSeconds + " seconds."
		alarmReminderTimer = createTimer(now.plusSeconds(ruleForGarageInSeconds), [|
			// Check every X seconds if OPEN
			// Garage is still open
			if (alarmContactZone23GarageDoorDriveway.state == OPEN) {
				postUpdate(hp_s_voicespeaker, "Warning: Garage remained open.");
					"Warning: Garage remained open.", 
					"Warning: Garage is still open after " + ruleForGarageInSeconds + "seconds.\r\n"
						+ "Garage Opened @ " + garageLastOpened.toString("MM/dd/yy HH:mm:ss a") + ".\r\n"
						+ "Will re-check and re-send reminder after "+ ruleForGarageInSeconds + " seconds.");
				logInfo("GarageCheck-isOpen","Garage still open after " + ruleForGarageInSeconds + " seconds. Sending reminder.");
			// Garage is still closed now			
			else {
				if (alarmReminderTimer != null) {
				alarmReminderTimer = null				
	} else {

rule "Send Reminder for Garage - CLOSED" 
	Item alarmContactZone23GarageDoorDriveway changed to CLOSED
	if (alarmReminderTimer != null)
	alarmReminderTimer = null;
	logInfo("GarageCheck-isClose","Garage closed. Timer stopped.");

Does this work? I’ve a vague recollection that I tried to reschedule a Timer from inside itself and it not working or not working consistently because you cannot reschedule a Timer that has expired and if you are inside the Timer body the Timer is by definition expired. It is one of the reasons I wrote Design Pattern: Recursive Timers. Maybe it works better now or the behavior changed. This would have been back in the 1.8 days I think.

This can be replaced with


The ? operator will cause the line to simply be skipped if alarmReminderTimer is null. It is one of the nice pieces of a syntactic sugar the Xtext language added that I actually like.

Since we are posting examples :wink:

I use Design Pattern: Expire Binding Based Timers, Design Pattern: Associated Items, Design Pattern: Human Readable Names in Messages, and Design Pattern: Separation of Behaviors (for the alerts) to make the rule generic for all my doors:


Group:Contact:OR(OPEN,CLOSED) gDoorSensors "The doors are [MAP(]"

Group:Number:SUM gDoorCounts "Open Doors [%d]"

Contact vFrontDoor "Front Door is [MAP(]"
  <door> (gDoorSensors,gDoorCounts)
  { mqtt="<[mosquitto:entry_sensors/main/front_door:state:default]" }

Switch vFrontDoor_Timer
  (gDoorsTimers, gResetExpire)
  { expire="1h,command=OFF" }

DateTime vFrontDoor_LastUpdate "Front Door [%1$tm/%1$td %1$tH:%1$tM]"
  <time> (gDoorsLast)

// these three Items for remaining doors and windows


rule "Keep track of the last time a door was opened or closed"
  Item vGarageOpener1 changed from CLOSED to OPEN or
  Item vGarageOpener1 changed from OPEN to CLOSED or
  Item vGarageOpener2 changed from CLOSED to OPEN or
  Item vGarageOpener2 changed from OPEN to CLOSED or
  Item vFrontDoor changed from CLOSED to OPEN or
  Item vFrontDoor changed from OPEN to CLOSED or
  Item vBackDoor changed from CLOSED to OPEN or
  Item vBackDoor changed from OPEN to CLOSED or
  Item vGarageDoor changed from CLOSED to OPEN or
  Item vGarageDoor changed from OPEN to CLOSED
  val door = triggeringItem as ContactItem

  // Update LastUpdate
  val last = gDoorsLast.members.filter[dt | =="_LastUpdate"].head as DateTimeItem // Associated Items DP
  last.postUpdate(new DateTimeType)

  // Set/cancel the Timer
  if(door.state == OPEN) sendCommand("_Timer", "ON") // Associated Items DP, Expire Based Timers DP
  else postUpdate("_Timer", "OFF") // Associated Items DP, Expired Based Timers DP

  // Log and alert
  val StringBuilder msg = new StringBuilder
  val doorName = transform("MAP", "", // Human Readable Names in Messages

  msg.append(" was")
  msg.append(if(door.state == OPEN) " opened" else " closed")

  var alert = false
  if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED"){ // Time of Day DP
        alert = true
        msg.append(" and it is night")

  if(vPresent.state == OFF) { // Generic Based Presence Example
        alert = true
        msg.append(" and no one is home")

    // create Timer and wait before sending alert (I've a bad sensor, this prevents a barrage of alerts when it starts flapping)
    if(flappingTimers.get( === null) {
      flappingTimers.put(, createTimer(now.plusSeconds(1), [|
        aAlert.sendCommand(msg.toString) // Separation of Behaviors DP
        flappingTimers.put(, null)
    // Flapping, cancel the timer before the alert can be sent
    else {
      flappingTimers.put(, null)
      logWarn(logName, + " is flapping!")
  else logInfo(logName, msg.toString)


// Expire Based Timers DP
rule "Timer expired for a door"
  Item vGarageOpener1_Timer received command OFF or
  Item vGarageOpener2_Timer received command OFF or
  Item vFrontDoor_Timer received command OFF or
  Item vBackDoor_Timer received command OFF or
  Item vGarageDoor_Timer received command OFF
  val doorName = transform("MAP", "", // Human Readable Names in Messages DP

  aAlert.sendCommand(doorName + " has been open for over an hour") // Separation of Behaviors DP

  if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") { // Time of Day DP
        triggeringItem.sendCommand(ON) // reschedule the timer, Expire Based Timer DP

Once I get back to the AIY, I’ll update my aAlert rule to also announce the message on the speaker.

I actually like this set of Rules because it illustrates how the DPs are not intended to be used in isolation. They can and should be used together. If you include the Generic Presence Detection tutorial, the above two rules use seven separate tutorials and examples (if you include the Design Pattern: Working with Groups in Rules which is really just a generic application of the way Groups are used in the other DPs).

The theory of operation is when a door changes state I update a DateTime Item to store when the last time the door was opened/closed.

If the door is opened I start a Timer. If the door is closed, I cancel the Timer.

The rest of the main rule is constructing and sending an alert message. I use a map file to convert the Item name into a more friendly name for the message, indicate whether the door was opened or closed, and then add additional warnings if it is night and/or no one is home.

If it is night or no one is home I want an alert. I’ve a bad door sensor that periodically goes nuts flapping between open and closed several times a second and it gets worse in the cold. So I added a little anti-flapping Timer code to wait a second before sending the alert. If the door changes state while this antiflapping timer is active we throw out the alert. Otherwise we send on the alert using the Separation of Behaviors DP.

Remember that Timer we set? When it goes OFF this second rule triggers where we again use the map to get a nicer version of the Item name and send an alert that the door has been open too long. If it is NIGHT or BED time, we reschedule the Timer so we get the alert repeatedly until the door closes or MORNING comes.

I’ve posted this before which is probably why it looks very similar to Chris’s.


Yeah I’m not 100% with xtend but ?. is actually common even for .Net and typescript. Looks like they are extending the language to match more popular ones. What I want them to add is the consistency of the " ; " as I am used to end everything with it. Lol. I often make that habit in python too hehehe

Out of curiosity, why not just trigger the rule based in group itself.

Working 100% .here’s the last text I got from it:

Warning: Garage is still open after 600seconds.

Garage Opened @ 01/26/18 07:41:38 AM.

Will re-check and re-send reminder after 600 seconds.

^direct copy paste from cellphone (yes I’m replying on my phone lol. Waiting in d parking lot for my son to come out from school hehehe).

Just a fyi… google assistants (even AIY) are also Chromecast, so you can use the Chromecast binding also. You can pipe text to a vlc and use that to stream it to the AIY. Or just send this command to the AIY:
repeat after me, ....the text...

Well, it doesn’t hurt to have them in the Rules DSL. But the only time it is required is if you want to use return false; to exit early.

There are several reasons, some of which will go away in the not too distant future.

There are only two ways to trigger a Rule using a Group and have the Rule trigger when members of the Group change or receive an update: changed or received update.

One can use changed if the Group’s aggregation function is properly configured so when the members of the Group change state the Group changes state in a detectable way which is usually possible using SUM. And this would probably work in this case but see below for why I don’t.

If one uses received update the Rule will trigger any time any member of the Group receives and update. However, as a side effect of how the Group’s state is calculated, the Rule will actually trigger N-1 times for that one update where N is the number of members of the Group. This would probably work here as well but it will take a lot of extra code to deal with the multiple triggers to avoid getting lots of alerts for one update to a member. Also, since the Rule triggers for all updates it means the Rule will trigger when I restoreOnStartup the members of the Group as the Items goe from NULL to what ever their restored states are.

So let’s assume I used changed with a sane aggregation function like SUM. Then I have to figure out which Item actually changed. There are two ways I can handle this. The first is to just generate an alert/set Timers, etc for everything that is OPEN and ignore which Item triggered the Rule. I use this approach in lots of places and it does work successfully. However, in this case it will take a lot more code to implement, more than the extra lines caused by listing all the Items as separate triggers.

The second is to use the Persistence LastUpdate hack (see the Working with Groups in Rules for details). That hack requires Persistence and a Thread:sleep at the front to give persistence a chance to save the latest states. It’s only two line of code so it isn’t that big of a deal, but I really don’t like the dependency on timing.

So, to ensure that the Rule only triggers once per Item change and to avoid needing to use the persistence hack and instead use the triggeringItem implicit variable I need to list each Item separately as triggers to the Rule.

Soon there will be introduced a new Rule trigger that we can use on Groups that will populate triggeringItem with the member of the Group that caused the Group to change or update. At that point I can use that new trigger with the Group without a lot of extra work.

About 90% of my posts, including this one, is from my phone. :slight_smile:

I can’t get it to show up as a device I can cast to. That was one of my first ideas but I dropped it as not supported. Maybe I need to look into it more. Searching online reveals mixed results with some saying it works and some saying it used to work but is no longer supported.

For now I’m going down the MQTT text message to send the announcement to the AIY. I may go back and try chromecast again.

1 Like

Does this really works? I tried it and it gives me an error.
Void function cannot return a value

If I use only return(); same

rule "test of return"
	Item testofreturn changed to ON
    return false; //does not work
    return(false); //does not work
    return(); //does not work
    return //does not work

found it:

must be part of an if clause

if (testofreturn.state == testofreturn.state) {return}


if (testofreturn.state == testofreturn.state) return;

The syntax jumped around a few months back and I must have misremembered where it ended up. It would be just return;

It isn’t that it is part of an if clause that lets it work in your examples, it is that it has no argument and the semicolon.

I have a different approach. I just trigger a check rule by cron, every few minutes. I don’t check for summer, or do an amazon reminder but that could easily done. If a window is open for some time, i send a telegram message. All Window contacts are in the group gfk.

rule "fenster offen"
    Time cron "0 */5 * * * ? *"     	         
	var String msg = ""
    gfk.members.filter[t|t.lastUpdate("mapdb") !== null].filter(s| s.state.toString == "OPEN").forEach(item | 
        var Number minutes = (now.millis - item.lastUpdate.millis) / 60000
        if (minutes > 10) {
		   msg = String::format("%s - %s: seit %s minuten.\n", msg,"_").get(0), minutes.toString)
	if (msg != "") 
		msg = String::format("*Fenster offen*\n%s", msg)
	    logInfo(logger, msg)
    	sendTelegram("openHAB", msg)
1 Like

return; does not work too, gives an error in the next line of code.

But with an if clause it is working for me.
if(1 == 1) return;

As written here

it makes no sense to return without an if clause, and I think this is right.

I have troubles with the rules found in the first post:

else if ((Door.state == OPEN)) && (Sommer.state!=ON)) {

I replaced it with
else if ((Door.state == OPEN)) && (Temperature.state < 15)) {

but I get the error
2018-02-19 18:24:20.900 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model ‘fensteroffencheck.rules’ has errors, therefore ignoring it: [14,36]: no viable alternative at input ‘&&’

[14,75]: no viable alternative at input ‘15’

[14,78]: mismatched input ‘)’ expecting ‘]’

[25,3]: missing EOF at ‘}’

I just want to shoot the rule if any door is open AND outside temperature is below 15 deg celsius

item Door is a group with all windows and doors
item Temperature is a number item from the weather binding:
Number Temperature “Temperature [%.2f °C]” {weather=“locationId=home, type=temperature, property=current”}


else if ((Door.state == OPEN)) && (Temperature.state < 15)) {

Should be

else if ((Door.state == OPEN) && (Temperature.state < 15)) {

Half of those parens are unnecessary

else if (Door.state == OPEN && Temperature.state < 15) {