Join/Wait Node: Timeout Feature request

Basic requirement:

  • Turn light on if 1) motion detected, and 2) door closed within 10 seconds.

Note that "motion detected" and "door closed" are from independent sources and can fire multiple times in a row.

  • The flow should complete immediately once the conditions are satisfied

Advanced requirement:

  • Require that "motion" event precedes "door close" event (or vice versa for this example)
  • If "door open" event is fired, reset all tracking. For example, if we are looking for (door close, motion), then (door close, door open, motion) would not trigger the light on event.

How about this? It uses node-red-contrib-simple-gate to allow or stop the door close event through to switch the light on.

[{"id":"a0a4582c.c1a8","type":"inject","z":"bdd7be38.d3b55","name":"Motion event","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":103,"y":1831,"wires":[["fa620c0a.33c2b"]]},{"id":"5921f58c.b4f52c","type":"inject","z":"bdd7be38.d3b55","name":"Door open event","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":115,"y":1918,"wires":[["e71aeb24.9ec5c8"]]},{"id":"ce7ce70b.0381c8","type":"inject","z":"bdd7be38.d3b55","name":"Door close event","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":114,"y":2005,"wires":[["9640704d.fb5aa8"]]},{"id":"fa620c0a.33c2b","type":"trigger","z":"bdd7be38.d3b55","op1":"open","op2":"close","op1type":"str","op2type":"str","duration":"10","extend":true,"units":"s","reset":"","bytopic":"all","name":"","x":520,"y":1832,"wires":[["18683277.e9b3d6"]]},{"id":"a35dc80d.18eb78","type":"debug","z":"bdd7be38.d3b55","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":702,"y":2001,"wires":[]},{"id":"e71aeb24.9ec5c8","type":"change","z":"bdd7be38.d3b55","name":"Reset trigger, close gate","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"},{"t":"set","p":"payload","pt":"msg","to":"close","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":332,"y":1917,"wires":[["fa620c0a.33c2b","18683277.e9b3d6"]]},{"id":"a84ecc10.cf2bf","type":"gate","z":"bdd7be38.d3b55","name":"","controlTopic":"control","defaultState":"closed","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","defaultCmd":"default","persist":false,"x":526,"y":2002,"wires":[["a35dc80d.18eb78"]]},{"id":"18683277.e9b3d6","type":"change","z":"bdd7be38.d3b55","name":"Set control topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":683,"y":1914,"wires":[["a84ecc10.cf2bf"]]},{"id":"9640704d.fb5aa8","type":"change","z":"bdd7be38.d3b55","name":"Light on","rules":[{"t":"set","p":"payload","pt":"msg","to":"light on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":322,"y":2004,"wires":[["a84ecc10.cf2bf"]]}]
2 Likes

I haven't played with node-red-contrib-simple-gate, but I really like this approach @Colin! I made one small edit to your flow - essentially to close the gate immediately after a successful run. This is needed to avoid multiple door close events from firing the light on again.

I'm not sure if there would be any race conditions here or not with the resets and such (at really fast intervals) but it does seem like a cool approach.

This becomes a bit mind stretching if more items are added (e.g., 4 sequential events), but in general, do you agree that this kind of functionality should be baked into the core set of nodes? I feel this is a common situation that is challenging to do.

This becomes a bit mind stretching if more items are added (e.g., 4 sequential events),

Then indeed it will become more complex to manage, I am not sure that it is so "common".

Your original approach (node) was to combine messages based on timeframes and that part does not appear necessary, albeit very useful. Keeping track of a "state" is the key i think, if there were additional events were involved, using XOR's (like the boolean-logic nodes) could perhaps work in some way.

What i like about Colin's flow is that you can "read" what the flow is doing.

I still think that a DSM would be the best way, I haven't used those nodes enough to knock off the solution in 5 minutes. I imagine that @cflurin would come up with a solution in no time at all if he were to have a few minutes to look at it.

1 Like

I haven't read the thread yet but this might help, otherwise I'll have a deaper look.

How can you get another door close event without an open in between?

@cflurin this flow is cool for state tracking, but it doesn't seem to track timing. E.g., both events need to happen within a specific timeframe.

@Colin I realize this is non-ideal, but it has to do with the sensor. Sometimes - esp. depending on speed of movement and sensitivity - the open event may not fire. These sensors are all battery powered, so the latency is non-zero.

Understood. An RBE on the way in might be the easiest way to sanitise that rather than complicating the later logic.

@Colin Yeah, good thought but it's still problematic though -- for instance -- if a door is rapidly open/closed, it will send the "close" event again, but RBE would block it.

Do you mean that if you get a motion event, then miss the door open, then door close that the RBE would hide that? In which case yes I think you are right, the RBE node is not what you want.

On the DSM I think the example posted was not supposed to be a complete solution but a starting point. The timer would need to be added in.

Do you mean that if you get a motion event, then miss the door open, then door close that the RBE would hide that? In which case yes I think you are right, the RBE node is not what you want.

Exactly.

I get that, but after reading over the documentation for it (multiple times), I'm still somewhat confused by how this works or how it would even be possible to modify it to work with a timer. While I understand the code in the example (I think), there are a number of undocumented options. For example:

  • The difference in labeling between states and transition names should be more clear. I don't find this intuitive that idle and opened are state names where as open is a transition name. It gets confusing especially when nearly identical names are chosen for state/transition.
    "states": {
        "idle": {
            "open": "opened",
            "detect": "detected"
        },
    },
  • What is a preState?
  • The description of what these do was not provided: triggerInput , stateOutput , preStateOutput , globalOutput and flowOutput
  • setData and getData examples were not extremely clear, and neither were the timer/watchdog.
  • The use of before/on/after transition isn't entirely obvious. I guess, before/after make sense, but not sure about on.
  • output = true/false seems like an undocumented feature?

Maybe using a trigger and outputting the intermediate states there is some hope but I'm not really sure how it would work.

Here is a first approach corresponding to your specs but I think it needs some more improvements.

[{"id":"2243fd98.bc6bf2","type":"inject","z":"aff5350e.4a1208","name":"Motion true","topic":"motion","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":140,"wires":[["de2779d0.7beb48"]]},{"id":"db2b3f24.f8ab2","type":"inject","z":"aff5350e.4a1208","name":"Door true","topic":"door","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":240,"wires":[["de2779d0.7beb48"]]},{"id":"6d2bb831.b0f508","type":"inject","z":"aff5350e.4a1208","name":"Door false","topic":"door","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":280,"wires":[["de2779d0.7beb48"]]},{"id":"7b3b3552.650bdc","type":"inject","z":"aff5350e.4a1208","name":"Motion false","topic":"motion","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":180,"wires":[["de2779d0.7beb48"]]},{"id":"de2779d0.7beb48","type":"dsm","z":"aff5350e.4a1208","name":"light control","sm_config":"{\n    \"stateOutput\": \"payload\",\n    \"currentState\": \"idle\",\n    \"data\": {\n        \"motion\": \"init\",\n        \"door\": \"init\",\n        \"timeover\": false,\n        \"timeout\": 10\n    },\n    \"methods\": {\n        \"motion\": [\n            \"sm.data.motion = msg.payload;\",\n            \"sm.data.timeover = false;\",\n            \"clearTimeout(timeout.id);\",\n            \"timeout.id = setTimeout(function() {\",\n                \"sm.data.timeover = true;\",\n            \"}, sm.data.timeout * 1000);\"\n        ],\n        \"door\": [\n            \"sm.data.door = msg.payload;\"\n        ],\n        \"onTransition\": [\n            \"if (typeof sm.data.motion !== 'init' && typeof sm.data.door !== 'init') {\",\n                \"if (sm.data.motion && !sm.data.door && !sm.data.timeover) {\",\n                    \"msg.topic = 'light';\",\n                    \"msg.payload = true;\",\n                    \"node.send(msg);\",\n                \"};\",\n            \"};\"\n        ],\n        \"status\": {\n            \"fill\": {\n                \"get\": \"'green';\"\n            },\n            \"shape\": \"dot\",\n            \"text\": {\n                \"get\": \"'motion: ' + sm.data.motion + ' door: ' + sm.data.door;\"\n            }\n        }\n    }\n}\n","x":510,"y":200,"wires":[["2606da5f.12e926"]]},{"id":"2606da5f.12e926","type":"debug","z":"aff5350e.4a1208","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":690,"y":200,"wires":[]}]

In case you missed it the detailed spec is in post 17

I know, the doc should be updated.
I'm working on a new project so a Pull Request for the dsm-documentation is welcome.

Note: there are several examples in the Wiki that might help.

Would consider it if I understood how it works a bit better :slight_smile:
It seems quite powerful once you master all the options.

Interesting. I'm not sure I understand the init state here, but it does seem to work somewhat. I get repeated outputs if motion:true is continually sent though. And, if door:false then motion:true will continue to send events without regard for the elapsed time.

I changed init to null and typeof to boolean. The purpose is to allow evaluation after both inputs are set.

{
    "stateOutput": "payload",
    "currentState": "idle",
    "data": {
        "motion": null,
        "door": null,
        "timeover": false,
        "timeout": 10
    },
    "methods": {
        "motion": [
            "sm.data.motion = msg.payload;",
            "sm.data.timeover = false;",
            "clearTimeout(timeout.id);",
            "timeout.id = setTimeout(function() {",
                "sm.data.timeover = true;",
            "}, sm.data.timeout * 1000);"
        ],
        "door": [
            "sm.data.door = msg.payload;"
        ],
        "onTransition": [
            "if (typeof sm.data.motion === 'boolean' && typeof sm.data.door === 'boolean') {",
                "if (sm.data.motion && !sm.data.door && !sm.data.timeover) {",
                    "msg.topic = 'light';",
                    "msg.payload = true;",
                    "node.send(msg);",
                "};",
            "};"
        ],
        "status": {
            "fill": {
                "get": "'green';"
            },
            "shape": "dot",
            "text": {
                "get": "'motion: ' + sm.data.motion + ' door: ' + sm.data.door;"
            }
        }
    }
}

Regarding the function I'll have a look tomorrow.

@dxdc: my point of view:

  1. You have built a contrib-node because you think you can't do it with the core-nodes, that's ok just use your node.

  2. Howerver I'm quite sure this can be achieved with the core nodes. If you want an one-node solution have a look at the function node or use the dsm node, just start with a simple example. You don't need to understand all the features and options.

  3. I only use a few contrib-nodes (less installtion and most important less maintenance). With the core-nodes (they should remain simple to use) and some contrib-nodes (2 of 8 are my nodes) I can almost do all what I need.

1 Like

I generally agree that simpler is better. There is some functionality that I feel would be really valuable in the context of core nodes though that isn't there. It's "possible" to add it using elaborate function nodes, but at that point, it's cleaner to just make it a separate contrib node for maintenance reasons in my opinion... esp. since you can add unit tests for those.

I think the core nodes would benefit tremendously from enhanced boolean logic gates/state tracking, as well as having that happen within a fix time window, rather than trying to reinvent the wheel. I think these problems are ubiquitous enough that making core nodes with them would be very beneficial.