You can use some of the basic node-red functionality to achieve this very easily. No function nodes or nodes in addition to the core node needed. First you have 2 message streams you want to join. The messages from one contact and the messages from the other contact. You can use a join node to get both states into one message. There is an easy tutorial for this in the docs here: https://cookbook.nodered.org/basic/join-streams
When you have both properties in one msg object you can use a switch node to route based on those properties: https://cookbook.nodered.org/basic/route-on-property
How can we apply this?
Ok so what you want is to have and and or functionality. You want to do something when both are open so and. You can achieve this by putting two switch nodes in series each checking for one of the contact properties in the msg. So the msg only passes through if both are open.
You can use this to trigger a trigger node that will do something after a certain amount of time.
As you only want this to happen if both windows are still open we can use the handy functionality of a trigger node that it can be reset if a certain message is send to it.
So we want to reset it if one of the two gets closed in the meantime so that it doesn’t trigger in that case.
To make an or like this we can put the switch nodes checking for closed in parallel so or instead of putting them in series like in our open check. We than use a change node to set the msg to the reset property of the trigger node to stop it should either of the windows close.
Here is an example flow:
[{"id":"7301bcba.095b64","type":"inject","z":"5d6b1a8.91496e4","name":"","topic":"window1","payload":"open","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":380,"wires":[["e892d67d.5a62e8"]]},{"id":"50460100.97af6","type":"inject","z":"5d6b1a8.91496e4","name":"","topic":"window1","payload":"closed","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":440,"wires":[["e892d67d.5a62e8"]]},{"id":"b809a435.3ed638","type":"inject","z":"5d6b1a8.91496e4","name":"","topic":"window2","payload":"open","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":500,"wires":[["e892d67d.5a62e8"]]},{"id":"8670836c.73e028","type":"inject","z":"5d6b1a8.91496e4","name":"","topic":"window2","payload":"closed","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":560,"wires":[["e892d67d.5a62e8"]]},{"id":"e892d67d.5a62e8","type":"join","z":"5d6b1a8.91496e4","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":390,"y":460,"wires":[["3065c49f.404b94","2f9e5d6b.2e64ea","8b76a07c.8a1f1"]]},{"id":"3065c49f.404b94","type":"switch","z":"5d6b1a8.91496e4","name":"window1 open?","property":"payload.window1","propertyType":"msg","rules":[{"t":"eq","v":"open","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":580,"y":460,"wires":[["fac482a2.a471b"]]},{"id":"8b76a07c.8a1f1","type":"switch","z":"5d6b1a8.91496e4","name":"window2 closed?","property":"payload.window2","propertyType":"msg","rules":[{"t":"eq","v":"closed","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":590,"y":400,"wires":[["2833614b.60c87e"]]},{"id":"2f9e5d6b.2e64ea","type":"switch","z":"5d6b1a8.91496e4","name":"window1 closed?","property":"payload.window1","propertyType":"msg","rules":[{"t":"eq","v":"closed","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":590,"y":340,"wires":[["2833614b.60c87e"]]},{"id":"2833614b.60c87e","type":"change","z":"5d6b1a8.91496e4","name":"stop","rules":[{"t":"set","p":"payload","pt":"msg","to":"stop","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":400,"wires":[["7972b709.bb5ad"]]},{"id":"fac482a2.a471b","type":"switch","z":"5d6b1a8.91496e4","name":"window2 open?","property":"payload.window2","propertyType":"msg","rules":[{"t":"eq","v":"open","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":780,"y":460,"wires":[["7972b709.bb5ad"]]},{"id":"7972b709.bb5ad","type":"trigger","z":"5d6b1a8.91496e4","op1":"both windows open","op2":"both windows still open","op1type":"str","op2type":"str","duration":"10","extend":false,"units":"s","reset":"stop","bytopic":"all","name":"","x":990,"y":460,"wires":[["835e2cdf.59e948"]]},{"id":"835e2cdf.59e948","type":"debug","z":"5d6b1a8.91496e4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1170,"y":460,"wires":[]}]
It uses injects to simulate the contact inputs and sends one message if both are open and than after 10 seconds another on if they are still open. If one of them closes within the 10 second timeframe no msg is send to the debug.
I hope this helps you.
Johannes