Monoflop Switch (encapsulated state, reusable, multiple instances)

While trying to automate some of my outside lights with the input of motion sensors more intelligently (behave differently, depending on where the motion started) I noticed that I would need some common behavior repeated multiple times. Most notably a way to route messages differently depending on the state of a triggered timer (monoflop that controls a switch).

In more general terms my goal is to have a group of multiple nodes share some state, but at the same time I also want to restrict the visibility of that state (encapsulation) to be able to instantiate multiple copies of the same group on the same flow without having to manually rename a lot of flow context variables in a lot of places (because this is error prone). I want to be able to just drop a self-contained stateful component (preconfigured group of nodes) onto any flow any number of times and have it all work immediately an not interfere with each other.

Unfortunately subflows would be suboptimal for this purpose because a node can only have one input, this also applies to subflows. Additionally the debugging capabilities with subflows are severely limited, you cannot observe its internal flow and node contexts from the outside.

So I came up with the idea of grouping as a containment and a way to have encapsulated state for each group instance:

The importable serialization for the above screenshot is:

[{"id":"dc569e29d1c482ee","type":"inject","z":"f9b0024d877809a7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":600,"wires":[["95cbc23478929bf0"]]},{"id":"75716020b3950241","type":"debug","z":"f9b0024d877809a7","name":"debug 13","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":660,"wires":[]},{"id":"f0f8df0f875a85d8","type":"inject","z":"f9b0024d877809a7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":680,"wires":[["ea921ff95069e30b"]]},{"id":"35fa30db7e81e597","type":"group","z":"f9b0024d877809a7","name":"Monoflop Switch","style":{"label":true},"nodes":["95cbc23478929bf0","ea921ff95069e30b","fdfafaa2caed2040","e0e771cacb3bea55","aec2b1008d74c32b","be0b629e64af9be6"],"x":334,"y":539,"w":312,"h":187},{"id":"95cbc23478929bf0","type":"trigger","z":"f9b0024d877809a7","g":"35fa30db7e81e597","name":"","op1":"","op2":"","op1type":"pay","op2type":"payl","duration":"6","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":420,"y":600,"wires":[["fdfafaa2caed2040"],["e0e771cacb3bea55"]]},{"id":"ea921ff95069e30b","type":"switch","z":"f9b0024d877809a7","g":"35fa30db7e81e597","name":"","property":"${NR_GROUP_ID}","propertyType":"flow","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":410,"y":680,"wires":[["aec2b1008d74c32b"],["be0b629e64af9be6"]]},{"id":"fdfafaa2caed2040","type":"change","z":"f9b0024d877809a7","g":"35fa30db7e81e597","name":"true","rules":[{"t":"set","p":"${NR_GROUP_ID}","pt":"flow","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":580,"wires":[[]]},{"id":"e0e771cacb3bea55","type":"change","z":"f9b0024d877809a7","g":"35fa30db7e81e597","name":"false","rules":[{"t":"set","p":"${NR_GROUP_ID}","pt":"flow","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":620,"wires":[[]]},{"id":"aec2b1008d74c32b","type":"junction","z":"f9b0024d877809a7","g":"35fa30db7e81e597","x":620,"y":660,"wires":[["75716020b3950241"]]},{"id":"be0b629e64af9be6","type":"junction","z":"f9b0024d877809a7","g":"35fa30db7e81e597","x":620,"y":700,"wires":[[]]}]

The trick here is from inside a group you can access the environment variable NR_GROUP_ID which contains a unique ID which you can then use as the name for a flow context variable to have a private place to store state that is easily accessible for all nodes inside the group and impossible to conflict with other copies of the same group or other groups or anything else on the same flow.

Here is how I configured the change nodes:
node-change

And the switch node:
node-switch

It creates a flow variable with a cryptic name (which is the group ID):
flow-variable

If you need multiple of these monoflop-switches (or similar self-contained stateful components) you can just copy-paste the entire group without reconfiguring anything and without needing to rename flow variables.

Because I am so excited about this actually really working I thought I must immediately share it with the community, so maybe someone else can also make use of this encapsulation technique.