Rule optimization: Window OPEN reminder

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"
when
    Time cron "0 */5 * * * ? *"     	         
then
	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,  item.name.split("_").get(0), minutes.toString)
        }  
	})   
	if (msg != "") 
	{
		msg = String::format("*Fenster offen*\n%s", msg)
	    logInfo(logger, msg)
    	sendTelegram("openHAB", msg)
	}
end 
1 Like

@rlkoshak
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”}

thanks

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) {

I use duiffie´s rule and it work well. Is just extended it a little bit for my needs, but I´m still not familiar with javascript. Here is my code:

import java.util.Map

val Map<String, Timer> OpenWindowTimers = newHashMap

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

	var String myTimerKey = windowItem.name.toString
	var String season_name = transform("MAP", "astro.map", Jahreszeit.state.toString)
	var winopentime = 0
	switch Jahreszeit {
		case Jahreszeit.state == "SPRING"	:	winopentime = 15
		case Jahreszeit.state == "SUMMER"	:	winopentime = 30
		case Jahreszeit.state == "AUTUMN"	:	winopentime = 10
		case Jahreszeit.state == "WINTER"	:	winopentime = 5
		default								:	winopentime = 5  //just in case if no season is set
	}
	
	logInfo("Fenster-Check", "Jahreszeit ist " + season_name )
	if (windowItem.state == CLOSED) {
		if (timerMap.get(myTimerKey) !== null) timerMap.get(myTimerKey).cancel()
	} else if (windowItem.state == OPEN) {
	timerMap.put(myTimerKey, createTimer(now.plusMinutes(winopentime)) [|
		timerMap.put(myTimerKey, null)

		val String shortName = transform("MAP", "windowShortName.map", windowItem.name.toString)
		val String longName = transform("MAP", "windowLongName.map", windowItem.name.toString)

		logInfo("Fenster-Check", shortName + " ist seit "+ winopentime +" Min. offen")
		sendBroadcastNotification(longName + " ist seit "+ winopentime +" Min. offen")
		echodotWZ_reminder.sendCommand(longName + " ist seit "+ winopentime +" Minuten offen")
	])
	}
	true
	]



rule "Fenster-Check"
when
	Item gWindoors received update
then
	logInfo("Fenster-Check", "Fenster/Tür hat sich verändert")
	Thread::sleep(500) // this gives the persistence service time to store the last update
	val lastUpdatedWindowItem = gWindoors.members.filter[s|s.lastUpdate("mapdb") !== null].sortBy[lastUpdate("mapdb")].last as ContactItem
	checkOpenWindow.apply(lastUpdatedWindowItem, OpenWindowTimers)
end

What i´ve done is simple, I just use the astro binding to check which season it is and depending on that, set a specific amount of minutes for the timer.

What I still want is a further extension to recheck if a window is still open after the first timer expired. I know there´s a command to reschedule a timer: https://docs.openhab.org/addons/actions.html#timers

Could someone assist to make this possible?

I monitore my windows status and send a message to alexa when a window is opened for longer than 15 minutes.
My rule looks like this:

rule "Window open while heat is on"

when
    Item gEGContacts changed to OPEN or
    Item gOGContacts changed to OPEN or
    Item gDBContacts changed to OPEN
then

if(EG_Vi_hkpump.state == ON){
    if(stopMotionTimer === null && gEGContacts.state == OPEN && gOGContacts.state == OPEN && gDBContacts.state == OPEN){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Erd, Ober und Dachgeschoss.')
        stopMotionTimer = null
        ]
    }
    else if(stopMotionTimer === null && gEGContacts.state == CLOSED && gOGContacts.state == OPEN && gDBContacts.state == OPEN){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Ober und Dachgeschoss.')
        stopMotionTimer = null
        ]
    }
        else if(stopMotionTimer === null && gEGContacts.state == OPEN && gOGContacts.state == CLOSED && gDBContacts.state == OPEN){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Erd und Dachgeschoss.')
        stopMotionTimer = null
        ]
    }
        else if(stopMotionTimer === null && gEGContacts.state == OPEN && gOGContacts.state == OPEN && gDBContacts.state == CLOSED){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Erd und Obergeschoss.')
        stopMotionTimer = null
        ]
    }
        else if(stopMotionTimer === null && gEGContacts.state == OPEN && gOGContacts.state == CLOSED && gDBContacts.state == CLOSED){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Erdgeschoss.')
        stopMotionTimer = null
        ]
    }
        else if(stopMotionTimer === null && gEGContacts.state == CLOSED && gOGContacts.state == OPEN && gDBContacts.state == CLOSED){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Obergeschoss.')
        stopMotionTimer = null
        ]
    }
        else if(stopMotionTimer === null && gEGContacts.state == CLOSED && gOGContacts.state == CLOSED && gDBContacts.state == OPEN){
        stopMotionTimer = createTimer(now.plusMinutes(15)) [|
        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im Dachgeschoss.')
        stopMotionTimer = null
        ]
    }
}

But the problem is, that when a window is closed while the timer is running, alexa still brings the message that a window is open after 15 minutes. So I need a second check, if a window is still open after 15 minutes and I don’t know how to do it.
An other problem is, that I would like to put alexas volume to a certain value before the window message is played. After the message I would like to restore the previous volume.
And Ideas?
By the way - I am a noob learning :wink:

This would have been better posted as a separate new posting.

OK, I see a whole lot of duplicated code here. Let’s see if we can make is simpler and then we can address the problem (which is that you need to cancel the Timer when the window closes.

First, put all your Contacts into a Group, I’ll call it Windows. Define is as Group:Windows:OR(OPEN, CLOSED). The state of Windows will be OPEN if any one of its members is OPEN, CLOSED otherwise.

Next change the Rule to trigger with Member of Windows. But, you probably want an alert if the heat turns on and a window is already OPEN so let’s trigger on the heat pump as well.

If the pump isn’t on, just exit the Rule. If there is already a Timer set, exit the Rule.

var Timer stopMotionTimer = null

rule "Window open while heat is on"
when
    Member of Windows changed to OPEN or
    Item EG_Vi_hkpump changed to ON
then
    if(EG_Vi_hkpump.state != ON) return;
    if(stopMotionTimer !== null) return;

    // Set the timer
    stopMotionTimer = createTimer(now.plusMinutes(15), [ |
        // Generate a list of all the open windows
        var openWins = Windows.members.filter[ w | w.state == OPEN ].map[ transform("MAP", "windows.map", name) ].reduce[ msg, winName | msg = msg + ", " + winName ] // get list of windows
        openWins = openWins.replaceFirst(", ", "") // delete the first comma
        openWins = openWins.replaceLast(", ", "und ") // replace the last comma with "und"

        Echo_TTS.sendCommand("Die Heizung ist an. Bitte Fenster schließen im " + openWins + ".")
        stopMotionTimer = null
    ])

end

Theory of operation: When a Window opens or the heat turns ON, set a timer if one doesn’t already exist. When the Timer goes off, construct a list of all the open Windows and TTS the list.

Now what if the heat turns off or all the windows close?

rule "Windows closed"
when
    Item Windows changed to CLOSED or // happens when all windows are closed
    Item  EG_Vi_hkpump changed to OFF
then
    stopMotionTimer?.cancel // if stopMotionTimer isn't null, cancel it.
end

Theory of operation: If all the windows close or the heat turns off, cancel the timer if it is running.

1 Like

Hello,
thank you so much for your help.
I already have a group for all windows. It is

Group:Contact:OR(OPEN,CLOSED) gHContacts "Fensterkontakte Haus [(%d)]" <window>

An example for a window item:

Contact EG_wo_fe_re1  "Wohnen Rechts Auf [MAP(de.map):%s]" <window> (gEGContactsOffen, gEGContacts) { channel="knx:device:bridge:Tasterschnittstellen:EG_wo_fe_re1" } 
Contact EG_wo_fe_re2  "Wohnen Rechts Kippe [MAP(de.map):%s]" <window> (gEGContactsKippe, gEGContacts) { channel="knx:device:bridge:Tasterschnittstellen:EG_wo_fe_re2" } 
String EG_wo_fe_re "Wohnen Rechts [MAP(de.map):%s]" <contact> (gHFenster, gEG, gEGFenster, gWO, gHContacts)

And the logfile shows an error:

019-02-25 20:45:51.532 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
Assignment to final parameter
2019-02-25 20:45:51.731 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rules.rules'
2019-02-25 20:45:51.742 [WARN ] [mmon.WrappedScheduledExecutorService] - Scheduled runnable ended with an exception: 
java.lang.NullPointerException: null
	at org.eclipse.smarthome.model.rule.runtime.internal.engine.RuleContextHelper.getContext(RuleContextHelper.java:68) ~[?:?]
	at org.eclipse.smarthome.model.rule.runtime.internal.engine.RuleEngineImpl.lambda$2(RuleEngineImpl.java:339) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:?]
	at java.lang.Thread.run(Thread.java:748) [?:?]
2019-02-25 20:46:35.456 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
Assignment to final parameter
2019-02-25 20:46:35.594 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rules.rules'
2019-02-25 20:48:20.193 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2019-02-25T20:48:20.056+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  var openWins
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XBinaryOperationImplCustom>)
  <null>.stopMotionTimer = <XNullLiteralImplCustom>
} ] threw an unhandled Exception: 
java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature param msg
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy155.apply(Unknown Source) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IteratorExtensions.reduce(IteratorExtensions.java:647) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IterableExtensions.reduce(IterableExtensions.java:545) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1086) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1061) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1047) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:992) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:772) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:220) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:827) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:264) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy150.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
2019-02-25 20:48:20.308 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2019-02-25T20:48:20.056+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  var openWins
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XBinaryOperationImplCustom>)
  <null>.stopMotionTimer = <XNullLiteralImplCustom>
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
Caused by: java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature param msg
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy155.apply(Unknown Source) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IteratorExtensions.reduce(IteratorExtensions.java:647) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IterableExtensions.reduce(IterableExtensions.java:545) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1086) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1061) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1047) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:992) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:772) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:220) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:827) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:264) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy150.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more
2019-02-25 20:50:32.060 [INFO ] [.smarthome.model.script.system.rules] - Uptime updated to 1 Std. / 52 Min.

Your rule adjusted to my group:

var Timer stopMotionTimer = null

rule "Window open while heat is on"
when
    Member of gHContacts changed to OPEN or
    Item EG_Vi_hkpump changed to ON
then
    if(EG_Vi_hkpump.state != ON) return;
    if(stopMotionTimer !== null) return;

    // Set the timer
    stopMotionTimer = createTimer(now.plusMinutes(1), [ |
        // Generate a list of all the open windows
        var openWins = gHContacts.members.filter[ w | w.state == OPEN ].map[ transform("MAP", "windows.map", name) ].reduce[ msg, winName | msg = msg + ", " + winName ] // get list of windows
        openWins = openWins.replaceFirst(", ", "") // delete the first comma
        openWins = openWins.replaceLast(", ", "und ") // replace the last comma with "und"

        Echo_TTS.sendCommand("Die Heizung ist an. Bitte Fenster schließen im " + openWins + ".")
        stopMotionTimer = null
    ])

end

There is an exception being thrown from inside the Timer.

I just typed in the code above so there is likely a typo.

Add some logging so you can figure out which line is throwing the error.

I found some typos like

stopMotionTimer = createTimer(now.plusMinutes(1)

Shoulld be

stopMotionTimer = createTimer(now.plusMinutes(1))

right?
Still some errors in the openhab.log

2019-02-25 22:21:53.230 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
Assignment to final parameter

The rule looks like this now:

rule "Window open while heat is on"
when
    Member of gHContacts changed to OPEN or
    Item EG_Vi_hkpump changed to ON
then
    if(EG_Vi_hkpump.state != ON) return;
    if(stopMotionTimer !== null) return;

    // Set the timer
    stopMotionTimer = createTimer(now.plusMinutes(1)) [|
        // Generate a list of all the open windows
        var openWins = gHContacts.members.filter[ w | w.state == OPEN ].map[ transform("MAP", "windows.map", name) ].reduce[ msg, winName | msg = msg + ", " + winName ] // get list of windows
        openWins = openWins.replaceFirst(", ", "") // delete the first comma
        openWins = openWins.replaceLast(", ", "und ") // replace the last comma with "und"

        Echo_TTS.sendCommand("Die Heizung ist an. Bitte Fenster schließen im " + openWins + ".")
        stopMotionTimer = null
    ]

end

rule "Windows closed"
when
    Item gHContacts changed to CLOSED or // happens when all windows are closed
    Item  EG_Vi_hkpump changed to OFF
then
    stopMotionTimer?.cancel // if stopMotionTimer isn't null, cancel it.
end

When a window is open, this happens:

2019-02-25 22:26:44.382 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rules.rules'
2019-02-25 22:28:17.953 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2019-02-25T22:28:17.901+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  var openWins
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XBinaryOperationImplCustom>)
  <null>.stopMotionTimer = <XNullLiteralImplCustom>
} ] threw an unhandled Exception: 
java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature param msg
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy155.apply(Unknown Source) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IteratorExtensions.reduce(IteratorExtensions.java:647) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IterableExtensions.reduce(IterableExtensions.java:545) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1086) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1061) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1047) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:992) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:772) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:220) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:827) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:264) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy150.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
2019-02-25 22:28:18.044 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2019-02-25T22:28:17.901+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  var openWins
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <null>.openWins = <XMemberFeatureCallImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XBinaryOperationImplCustom>)
  <null>.stopMotionTimer = <XNullLiteralImplCustom>
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
Caused by: java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature param msg
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy155.apply(Unknown Source) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IteratorExtensions.reduce(IteratorExtensions.java:647) ~[?:?]
	at org.eclipse.xtext.xbase.lib.IterableExtensions.reduce(IterableExtensions.java:545) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1086) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1061) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1047) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:992) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:151) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:772) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:220) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:827) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:264) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy150.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more

No, that isn’t a typo.

There is a feature of the language that I absolutely hate because it causes a lot of confusion.

createTimer takes two arguments, a DateTime and a lambda. But the language let’s you define the second argument outside the parens if and only if the last argument is a lambda.

For those who are not familiar with the language, it looks like you are somehow magically associating the lambda with the Timer by putting it after the call.

createTimer(now.plusMinutes(1), [ |
    // some code
])

is a more correct reflection of what is actually going on. So I always put the lambda inside the parens and encourage others who post to the forum to do the same.

How did you declare stopMotionTimer? Is it a val? It needs to be a var.

There is nothing in this code that would generate that error unless stopMotionTimer is a val instead of a var.

Did you create windows.map? You need to create it with a mapping between the Item names and the name you want to include in your spoken message. See Design Pattern: Human Readable Names in Messages.

Okay, lets start with the easy one.
The timer is var:

var Timer stopMotionTimer = null

So this is not the reason.
When I leave the code like this:

createTimer(now.plusMinutes(1), [ |
    // some code
])

I get this errors in the log:

2019-02-25 23:27:00.159 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'rules.rules' has errors, therefore ignoring it: [124,54]: mismatched input ',' expecting 'end'

Putting the rule to this:

createTimer(now.plusMinutes(1) [ |
    // some code
])

Gives me:

2019-02-25 23:31:23.767 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
Assignment to final parameter
2019-02-25 23:31:23.777 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'rules.rules'

You are right - I missed the windows map. I will do it and post if it helps.

I made a “windows.map” like this:

EG_wo_fe_li=Wohnen Links
EG_wo_fe_re=Wohnen Rechts
EG_er_fe_li=Erker Links
EG_er_fe_re=Erker Rechts
EG_es_fe_li=Essen Links
EG_es_fe_li3=Essen Links, linker Flügel
EG_es_fe_re=Essen Rechts
EG_es_fe_re3=Essen Rechts, linker Flügel
EG_ko_fe1=Kochen, rechter Flügel
EG_ko_fe2=Kochen, linker Flügel
EG_wc_fe=Gäste WC
OG_Mi_fe_li=Mika links
OG_Mi_fe_re=Mika rechts
OG_Jo_fe_li=Joel links
OG_Jo_fe_re=Joel rechts
OG_Sc_fe_li1=Schlafen links, rechter Flügel
OG_Sc_fe_re=Schlafen rechts
OG_Sc_fe_li2=Schlafen links
OG_Hw_fe=Hauswirtschaftsraum
OG_Ba_fe_li=Bad links
OG_Ba_fe_re=Bad rechts
DB_fe_bu=Büro
DB_ve=Büro Velux
DB_fe_te=Dachboden Technik

Left hand the items and right hand the names to be spoken.
Still get an error on the log:

2019-02-25 23:47:29.455 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
Assignment to final parameter
2019-02-25 23:47:29.704 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rules.rules'
2019-02-25 23:48:59.190 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Window open NEW': An error occurred during the script execution: index=1, size=1

When not even the map works - I think it’s time for bed…

Darn it, I always get this wrong.

.reduce[ msg, winName | msg + ", " + winName ]

We don’t assign to msg directly in the body of the lambda. The result of the operation is automatically assigned to msg.

1 Like

Thats it! Thank you!
Regarding the pump I need to make an adjustment.
Actually it is like:

rule "Windows closed NEW"
when
    Item gHContacts changed to CLOSED or// happens when all windows are closed
    Item EG_Vi_hkpump changed to OFF
then
    stopMotionTimer?.cancel // if stopMotionTimer isn't null, cancel it.
end

This would stop the timer every time, the pump stopps. Unfortunately this happens also for heating our drinking water.
So it needs to be like:

rule "Window open NEW"
when
    Member of gHContacts changed to OPEN
then
    if(EG_Vi_hkpump.state != OFF) return;
    if(stopMotionTimer !== null) return;

    // Set the timer
    stopMotionTimer = createTimer(now.plusMinutes(1) [ |
        // Generate a list of all the open windows
        var openWins = gHContacts.members.filter[ w | w.state == OPEN ].map[ transform("MAP", "windows.map", name) ].reduce[ msg, winName | msg + ", " + winName ] // get list of windows
        openWins = openWins.replaceFirst(", ", "") // delete the first comma
        openWins = openWins.replaceLast(", ", "und ") // replace the last comma with "und"

        Echo_TTS.sendCommand("Die Heizung ist an. Bitte Fenster schließen im " + openWins + ".")
        stopMotionTimer = null
    ])

end

rule "Windows closed NEW"
when
    Item gHContacts changed to CLOSED // happens when all windows are closed
then
    if(EG_Vi_hkpump.state != OFF) return;
    stopMotionTimer?.cancel // if stopMotionTimer isn't null, cancel it.
end

I hope, this will skip the rule when the pump is OFF, right?

Do you think it is possible to adjust the Windows closed rule the way alexa tells me after a minute after the last window was closed, which windows are still open?
Like my wife closed 3 windows but forgot one in the attic :wink:
By the way - how can I ask which windows are open? I always get the answer Window xyz does not support this or something like this.

Yes.

I can’t help with Alexa stuff. I don’t use it.

But for your first use case you would need a new Rule that triggers when Member of gHContacts changed to CLOSED. Set a Timer there to send your alert using the same approach as “Window open NEW”

I’m using a switch to trigger a rule to build the sentence Alexa should say and have her speak it via Alexa control binding. I have a second function “long press” on a light switch near the main entrance so I can trigger it before leaving the house to check if all Windows are closed. I also can ask Alexa “did I close the windows”, but there’s no variation allowed. I can’t ask “are the windows closed”. For that I would need a second routine with exactly these words.

Hey guys, I am sorry but the rule does not work.
Alexa is not saying anything although there is no error in the log.
So Maybe there is some error in the definition of the items?
Lets look on one window - for example DB_ve

Group:Contact:OR(OPEN,CLOSED) gHContacts "Fensterkontakte Haus [(%d)]" <window>
Contact DB_ve  "Dachboden Velux [MAP(de.map):%s]" (gHFenster, gDB, gDBFenster, gBU, gHContacts, gDBContacts) { channel="knx:device:bridge:Tasterschnittstellen:DB_ve" } 

The “de.map” is like this:

CLOSED=zu
OPEN=offen
NULL=undefiniert

The definition of the pump is:

Switch EG_Vi_hkpump "Heizkreispumpe [%s]" <power> (gVitogate) { channel="knx:device:bridge:Vitogate:EG_Vi_hkpump" }

The rule is acually like this:

rule "Window open NEW"
when
    Member of gHContacts changed to OPEN
then
    if(EG_Vi_hkpump.state == OFF) return;
    if(stopMotionTimer === null) return;

    // Set the timer
    stopMotionTimer = createTimer(now.plusMinutes(1) [ |
        // Generate a list of all the open windows
        var openWins = gHContacts.members.filter[ w | w.state == OPEN ].map[ transform("MAP", "windows.map", name) ].reduce[ msg, winName | msg + ", " + winName ] // get list of windows
        openWins = openWins.replaceFirst(", ", "") // delete the first comma
        openWins = openWins.replaceLast(", ", "und ") // replace the last comma with "und"

        Echo_TTS.sendCommand('Die Heizung ist an. Bitte Fenster schließen im ' + openWins + '.')
        stopMotionTimer = null
    ])

end

rule "Windows closed NEW"
when
    Item gHContacts changed to CLOSED // happens when all windows are closed
then
    if(EG_Vi_hkpump.state == OFF) return;
    stopMotionTimer?.cancel // if stopMotionTimer isn't null, cancel it.
end

I changed the rule a little bit from:

if(EG_Vi_hkpump.state != OFF) return;

to

if(EG_Vi_hkpump.state == OFF) return;

because I had the impression, that this is the reason why nothing comes. Furthermore my old rule which worked was defined like this:

if(EG_Vi_hkpump.state == ON){
    if(stopMotionTimer === null && gEGContacts.state == OPEN &&.....

But still no luck.
Any ideas? Maybe something with this %d and %s in my item definition where I still don’t know what the difference is?

this needs to be !==. With it being === that means stopMotionTimer never gets set to anything besides null and the Rule never runs.

This is deliberate. This line means that any time a window opens when the heat pump is not running there will be no alert.

What you had before was “only do something if EG_Vi_hkpump.state is ON”. This is “only do something if EG_Vi_hkpump.state is not OFF”. Hopefully you can see these mean the same thing.