Rule optimization: Window OPEN reminder

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"
when
	Item testofreturn changed to ON
then
    testofreturn.postUpdate(ON)
    return false; //does not work
    return(false); //does not work
    return(); //does not work
    return //does not work
    testofreturn.postUpdate(OFF)
end

found it:

must be part of an if clause

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

or

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"
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.