Node Red Flow issue

I had a flow which the community help me get going - the post is here:

Any two of multiple triggers, within a time? - Third party integrations / Node-RED - Home Assistant Community (home-assistant.io)

It’s now stopped working after some of the sensors were removed. I’ve stripped it back to two sensors but i still get an error in the debugs; i cant trigger it at the moment but i think it was ‘entity ID can’t be empty’ or something very similar.

Hopefully i’ve managed to paste the current code below, can anyone give me a pointer?

[
    {
        "id": "fbaea1aaab14874b",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "7c20d6964e467237",
        "type": "server-state-changed",
        "z": "fbaea1aaab14874b",
        "name": "sensors",
        "server": "80ed050d.56b658",
        "version": 5,
        "outputs": 2,
        "exposeAsEntityConfig": "",
        "entityId": [
            "binary_sensor.7wr_kitchen_doors_motion",
            "binary_sensor.7wr_rear_doorbell_motion"
        ],
        "entityIdType": "list",
        "outputInitially": false,
        "stateType": "str",
        "ifState": "on",
        "ifStateType": "str",
        "ifStateOperator": "is",
        "outputOnlyOnStateChange": true,
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "ignorePrevStateNull": false,
        "ignorePrevStateUnknown": false,
        "ignorePrevStateUnavailable": false,
        "ignoreCurrentStateUnknown": false,
        "ignoreCurrentStateUnavailable": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "entityidfilter",
                "valueType": "config"
            }
        ],
        "x": 110,
        "y": 200,
        "wires": [
            [
                "ca42ab738f030c2b"
            ],
            []
        ]
    },
    {
        "id": "ca42ab738f030c2b",
        "type": "change",
        "z": "fbaea1aaab14874b",
        "name": "set entity ids",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"entity_id\": payload ~> $join(\",\")}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 270,
        "y": 200,
        "wires": [
            [
                "8a6d4e80ebf31f28",
                "b7c39c3a32076d04"
            ]
        ]
    },
    {
        "id": "8a6d4e80ebf31f28",
        "type": "countdown",
        "z": "fbaea1aaab14874b",
        "name": "",
        "topic": "",
        "payloadTimerStart": "true",
        "payloadTimerStartType": "bool",
        "payloadTimerStop": "false",
        "payloadTimerStopType": "bool",
        "timer": "320",
        "resetWhileRunning": true,
        "setTimeToNewWhileRunning": true,
        "startCountdownOnControlMessage": false,
        "minuteCounter": false,
        "x": 490,
        "y": 200,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "fa5cc73317dc1d3f",
        "type": "api-get-history",
        "z": "fbaea1aaab14874b",
        "name": "Get 20s History",
        "server": "80ed050d.56b658",
        "version": 1,
        "startDate": "",
        "endDate": "",
        "entityId": "",
        "entityIdType": "equals",
        "useRelativeTime": true,
        "relativeTime": "320 seconds",
        "flatten": false,
        "outputType": "array",
        "outputLocationType": "msg",
        "outputLocation": "payload",
        "x": 560,
        "y": 260,
        "wires": [
            [
                "ecfdfa6d1022e9bd"
            ]
        ]
    },
    {
        "id": "b7c39c3a32076d04",
        "type": "delay",
        "z": "fbaea1aaab14874b",
        "name": "",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 400,
        "y": 320,
        "wires": [
            [
                "fa5cc73317dc1d3f"
            ]
        ]
    },
    {
        "id": "ecfdfa6d1022e9bd",
        "type": "function",
        "z": "fbaea1aaab14874b",
        "name": "check sensor states >1",
        "func": "// loop through the different sensors\nconst activeSensors = msg.payload.map((sensor) => {\n    // check if atleast one history has the state \"on\"\n    return sensor.some((entity) => entity.state === \"on\");\n// count how many sensors had an \"on\" state\n}).filter((sensor) => sensor === true).length > 1;\n\n// if activeSensors is true than two sensor were active in the time window\nif(activeSensors) {\n    return msg;\n}\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 810,
        "y": 320,
        "wires": [
            [
                "6b85147702262cc5",
                "ea74f27aa182f0ec"
            ]
        ]
    },
    {
        "id": "6b85147702262cc5",
        "type": "time-range-switch",
        "z": "fbaea1aaab14874b",
        "name": "",
        "lat": "",
        "lon": "",
        "startTime": "22:00",
        "endTime": "06:00",
        "startOffset": 0,
        "endOffset": 0,
        "x": 1070,
        "y": 320,
        "wires": [
            [
                "bb763592ce0872a1"
            ],
            []
        ]
    },
    {
        "id": "bb763592ce0872a1",
        "type": "api-call-service",
        "z": "fbaea1aaab14874b",
        "name": "CRITICAL NOTIFICATION ",
        "server": "80ed050d.56b658",
        "version": 5,
        "debugenabled": true,
        "domain": "notify",
        "service": "mobile_app_ians_iphone_2",
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "data": "{\"title\":\"Garden\",\"message\":\"Garden Motion\",\"data\":{\"push\":{\"sound\":{\"name\":\"default\",\"critical\":1,\"volume\":0.7}}}}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 1340,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "ea74f27aa182f0ec",
        "type": "debug",
        "z": "fbaea1aaab14874b",
        "name": "debug 104",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1250,
        "y": 440,
        "wires": []
    },
    {
        "id": "80ed050d.56b658",
        "type": "server",
        "name": "Home Assistant",
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "",
        "connectionDelay": false,
        "cacheJson": false,
        "heartbeat": false,
        "heartbeatInterval": "",
        "statusSeparator": "",
        "enableGlobalContextStore": false
    }
]

You are building a list of entities in your change node, using the event: state node’s configuration.

{"entity_id": payload ~> $join(",")}

This places the value required into the field entity_id, for later use in the get history node. The input field name in the get history node has changed to entityId, so I am guessing that your change node code needs to become

{"entityId": payload ~> $join(",")}

I think i have corrected it, but it still throws an error from the 20s history node:

image

[
    {
        "id": "0ee7353257d238a7",
        "type": "api-call-service",
        "z": "aa491b0aae0bc8c2",
        "name": "CRITICAL NOTIFICATION ",
        "server": "80ed050d.56b658",
        "version": 5,
        "debugenabled": true,
        "domain": "notify",
        "service": "mobile_app_ians_iphone_2",
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "data": "{\"title\":\"Garden\",\"message\":\"Garden Motion\",\"data\":{\"push\":{\"sound\":{\"name\":\"default\",\"critical\":1,\"volume\":0.7}}}}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 1200,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "5beca34fe8247fd8",
        "type": "api-get-history",
        "z": "aa491b0aae0bc8c2",
        "name": "Get 20s History",
        "server": "80ed050d.56b658",
        "version": 1,
        "startDate": "",
        "endDate": "",
        "entityId": "",
        "entityIdType": "equals",
        "useRelativeTime": true,
        "relativeTime": "320 seconds",
        "flatten": false,
        "outputType": "array",
        "outputLocationType": "msg",
        "outputLocation": "payload",
        "x": 420,
        "y": 200,
        "wires": [
            [
                "75f51e82196c9008"
            ]
        ]
    },
    {
        "id": "75f51e82196c9008",
        "type": "function",
        "z": "aa491b0aae0bc8c2",
        "name": "check sensor states >1",
        "func": "// loop through the different sensors\nconst activeSensors = msg.payload.map((sensor) => {\n    // check if atleast one history has the state \"on\"\n    return sensor.some((entity) => entity.state === \"on\");\n// count how many sensors had an \"on\" state\n}).filter((sensor) => sensor === true).length > 1;\n\n// if activeSensors is true than two sensor were active in the time window\nif(activeSensors) {\n    return msg;\n}\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 670,
        "y": 260,
        "wires": [
            [
                "f8dec7cdd712bf07",
                "ea3fa88162a3c432"
            ]
        ]
    },
    {
        "id": "ea3fa88162a3c432",
        "type": "debug",
        "z": "aa491b0aae0bc8c2",
        "name": "debug 104",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 890,
        "y": 400,
        "wires": []
    },
    {
        "id": "bbf13142749655ca",
        "type": "change",
        "z": "aa491b0aae0bc8c2",
        "name": "set entity ids",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"entityId\": payload ~> $join(\",\")}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 130,
        "y": 140,
        "wires": [
            [
                "77698ebc63e804b1",
                "0b190e37380c90fd"
            ]
        ]
    },
    {
        "id": "77698ebc63e804b1",
        "type": "countdown",
        "z": "aa491b0aae0bc8c2",
        "name": "",
        "topic": "",
        "payloadTimerStart": "true",
        "payloadTimerStartType": "bool",
        "payloadTimerStop": "false",
        "payloadTimerStopType": "bool",
        "timer": "320",
        "resetWhileRunning": true,
        "setTimeToNewWhileRunning": true,
        "startCountdownOnControlMessage": false,
        "minuteCounter": false,
        "x": 350,
        "y": 140,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "0b190e37380c90fd",
        "type": "delay",
        "z": "aa491b0aae0bc8c2",
        "name": "",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 260,
        "y": 260,
        "wires": [
            [
                "5beca34fe8247fd8"
            ]
        ]
    },
    {
        "id": "f8dec7cdd712bf07",
        "type": "time-range-switch",
        "z": "aa491b0aae0bc8c2",
        "name": "",
        "lat": "",
        "lon": "",
        "startTime": "22:00",
        "endTime": "06:00",
        "startOffset": 0,
        "endOffset": 0,
        "x": 930,
        "y": 260,
        "wires": [
            [
                "0ee7353257d238a7"
            ],
            []
        ]
    },
    {
        "id": "80ed050d.56b658",
        "type": "server",
        "name": "Home Assistant",
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "",
        "connectionDelay": false,
        "cacheJson": false,
        "heartbeat": false,
        "heartbeatInterval": "",
        "statusSeparator": "",
        "enableGlobalContextStore": false
    }
]

Change the output of the change node ‘set entity id’s’ from msg.payload to msg.payload.entityId

image

Ah yes:

It is the events: state node - the config setting has also changed to entityId.

Output properties:

set msg.payload to config.entityId

The rest is OK and works for me.

Hmm, I am still getting an error. I’m away for the weekend so if gentlemen you will bear with me I will double check and test again next week and report back.

I’m with Lady Grantham, what is a weekend?

All tested again, and working.
Refactored - moved your change node JSONata into the event: state node, and changed your function node for JSONata in a change node. The output of the last change node is now a count of the entities that have an “on” state in each history, ie will only be 2 if both entities have been “on” in the past xxx seconds. You can use a switch node to onward route if payload = 2…

I have fully tested this with two of my own binary sensors (it works) and have back copied your sensor names into the event node and provided a scrubbed export for you.

[{"id":"cb27044705bd5113","type":"server-state-changed","z":"fbaea1aaab14874b","name":"sensors","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":["binary_sensor.7wr_kitchen_doors_motion","binary_sensor.7wr_rear_doorbell_motion"],"entityIdType":"list","outputInitially":false,"stateType":"str","ifState":"on","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"entityId","valueType":"config"},{"property":"payload","propertyType":"msg","value":"{\"entityId\": $join(payload, \",\")}","valueType":"jsonata"}],"x":250,"y":720,"wires":[["3d82f207bb6ed24c"],[]]},{"id":"3d82f207bb6ed24c","type":"delay","z":"fbaea1aaab14874b","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":400,"y":720,"wires":[["6af156368ab384f0"]]},{"id":"6af156368ab384f0","type":"api-get-history","z":"fbaea1aaab14874b","name":"Get 20s History","server":"","version":1,"startDate":"","endDate":"","entityId":"","entityIdType":"equals","useRelativeTime":true,"relativeTime":"320 seconds","flatten":false,"outputType":"array","outputLocationType":"msg","outputLocation":"payload","x":560,"y":720,"wires":[["b67069d3712bcda1"]]},{"id":"b67069d3712bcda1","type":"change","z":"fbaea1aaab14874b","name":"Count Entities with \"on\" state","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.(\"on\" in $.state)[$=true]~>$count()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":720,"wires":[[]]}]

Fingers crossed!

I think that has cracked it - I am very grateful for your help!

I’m no expert in the code, but the only bit i cant see is where there is a check on two entities being active (must be in there somewhere as it seems to be working as intended!). I can’t see it in the 'count entities with ‘on’ state) and nor in the time range node. Its in there somewhere though right?!

The flow is triggered by an events: state node, which can hold a list of entities to check, and therefore sends a message when any one of these entities has a state that goes to “on” (“on” and not “off” as there is a conditional test in there too).

There is a bit of JSONata code in the output properties that lifts the config settings into msg.payload, then forms the correct entity-id list (from the node settings) as input to go into the history node.

The history node then uses this input entity list to read the history of all entities in the list. Since this is an array of entities, the history output will be an array of the histories, each of which (history for each entity) is itself an array including the state values during the period of time selected by the history node.

We already know that at least one of the entities has had a state of “on” during the history (it triggered the events: state node just 5 seconds ago…) - we just need to see if the other entity was also “on” during the history period.

The easiest (?) way to do this is to look at each entity, look at the history array of states, and look to see if “on” is in this array. JSONata is certainly obscure and not easy to work with, but very powerful and concise.

payload.("on" in $.state)[$=true]~>$count()

The JSONata code takes the history output in msg.payload (this is an array of entities)

Then .( ) goes through each entity - this is the mapping or iteration operator.

("on" in $.state) looks at the entity (in the list) and the history field “state”, which is itself an array of the state values, and the expression will return ‘true’ if there is at least one “on” state in there.

At this point we have
[true, false] or [true, true]

The [$=true] is a filter, and reduces the array to only those values where the test expression is true, ie where true=true.

This leaves us with [true] or [true, true]

Then the function chaining operator ~> uses $count() to return the size of the array, which will be either 1 (only one device went on during the history period) or 2 (both devices were on at some point during the history period).

So we have a count of how many devices (in a list of devices) had a state of “on” at some point during the history sample time period.

This may not be quite what you wanted - certainly it will not show that both devices were on at the same time during a point in the history period. That requires a lot more code…

But if it is working for you then I suggest you just use it for now!

Thanks for the explanation - i follow the logic, but for sure I could never have scripted it.

Only one question. It does exactly what I want in that i am looking for ‘two in a period of time’ rather than ‘two at once during the period’ (it is effectively an overnight double alarm trigger so i don’t get woken up in the night by one sensor false detecting - there is less chance of two false detecting within the same period). However the actual sensor pool is more like 8-10 devices, i just chopped it down to two to test it when it stopped working. I can’t quite read from the code if the change node is looking for >1 or is looking for ‘exactly 2’ in order to pass the message on. Apologies as i could/should have explained that right at the start…

In the spirit of trying to understand, i think i can see partly what will happen. The array would be [true] for one sensor having been active in the period, [true,true] for two, [true,true,true] for three and so on.

I can then see the count function adding that up - i just can’t see where currently the decision is made to ‘pass forward’ the message if it =2, so i cant quite see what would happen if it =3. All would work ok it ‘not 0 or 1’ or >1 i guess.

JSONata is an acquired taste, and I have devoted a lot of time to learning this stuff…

Yup - the output should work nicely for a number N of entities, and you should get msg.playload as a count ‘n’ being from 1 to N. I just worked on this part of the flow and did not specifically put in a switch node - it should be easy to use a switch for your test as something like

payload > 1

or payload >=2 if you wanted to add a bit more leeway against false alarms.

If you wanted to cut out a need for a switch node, then you could indeed do the test against the count in the JSONata expression directly, but that would still end up as a msg.payload as, for example ‘true’ or ‘false’ and you would still need a switch node.

That is really helpful for me to understand what is happening in the code - I appreciate your efforts here and have tried to learn a little from it. I agree that the JSON is not easy to follow!