Node-Red Flow Samples/Sharing

I learned a lot today, thanks again for the tips, @aaiyar and @rakeshg .

I am actually trying to do something more specific than this, but here's the proof of concept that I worked up. It's a generic switch, where there's a dropdown populated with devices of capability.switch. There's a single toggle that acts on whichever you select.

I have been struggling with making some of my logic in node-red dynamic with respect to Hubitat devices, because knowing the deviceId (DNI) is sort of cumbersome. This gets me past that, plus looks good on a dashboard, to boot.

I'd appreciate any tips if I'm doing brute force things that have a more elegant/cleaner way.

2 Likes

Don't see much that can/should change. One possible thing is that instead of a switch node (switch), use a change node to set msg.command to msg.payload coming from the ui_switch node. I think the ui_switch has "on"/"off" states only, so should match msg.command attributes.

The other thing is that you could possibly load the devices/all at NR startup (unless there is a need to populate it repeatedly) and/or if you see a hubStartup event. The use the ui_control node ( on the flow - connect event only) to populate msg.options and the device selection node.

Many ways to skin the cat! Have fun...

2 Likes

Thanks for the tips!

The value from the UI switch is true/false. I didn't see a shorter way to get that to on/off than the change node. Is there one?

You read my mind on refreshing devices. I did it that way so that anything new I added on the HE side would be populated. Unless the event of adding a device would be injected by any of the HE nodes (to cause the logic after it to flow), but I wasn't sure on that one.

2 Likes

I think that value can be on/off. For example, in this sequence:

Screen Shot 2022-08-14 at 11.22.06 AM

The switch node is configured as:

and the change node is configured as:

3 Likes

Perfect, the switch node configuration is the piece I was missing.

2 Likes

Wanted to share a subflow I developed thanks to @stephen_nutt's desire to convert a sequence of button presses to a different button press...

Originally I wrote a subflow to incorporate the ideas we discussed but soon realized this could be abstracted even further... So here is a new subflow called "Trigger Convert"..

Trigger Convert

[{"id":"9540b0343c71fb98","type":"group","z":"b38ec3180db9ae33","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["68b640cb8870b75b","14c1f4cf77e27fa7","fcf0f568c4d33c32","8e3a37a649d03bbc","74dc2a23244c2645","c9e77e2065a59f70","14fd3f460f313439"],"x":74,"y":2519,"w":752,"h":262},{"id":"3b59914daaad2161","type":"subflow","name":"Trigger Convert","info":"","category":"","in":[{"x":40,"y":40,"wires":[{"id":"f4a31b548b18b7f0"}]}],"out":[{"x":740,"y":100,"wires":[{"id":"14dc4f13e03c8ecf","port":0}]}],"env":[{"name":"CHECK_TABLE","type":"json","value":"[{\"name\":\"Test 1\",\"inputVals\":[{\"topic\":\"Device1 Button\",\"input\":\"1\"},{\"topic\":\"Device2 Button\",\"input\":\"1\"},{\"topic\":\"Device2 Button\",\"input\":\"1\"}],\"output\":12},{\"name\":\"Test 2\",\"inputVals\":[{\"topic\":\"Device2 Button\",\"input\":\"3\"},{\"topic\":\"Device2 Button\",\"input\":\"4\"}],\"output\":11}]"},{"name":"DELAYMS","type":"num","value":"250"},{"name":"REQUIRE_SEQ","type":"bool","value":"false"},{"name":"TRIGGER_ONLY","type":"bool","value":"false"},{"name":"INPUT_PROP","type":"str","value":"payload.value"},{"name":"OUTPUT_PROP","type":"str","value":"payload"}],"meta":{},"color":"#FFCC66","icon":"node-red/swap.svg","status":{"x":740,"y":200,"wires":[{"id":"14dc4f13e03c8ecf","port":1}]}},{"id":"f4a31b548b18b7f0","type":"function","z":"3b59914daaad2161","name":"accumulate data","func":"var inputProp = env.get(\"INPUT_PROP\");\nvar input = getVal(msg,inputProp);\n\nvar presses = flow.get(\"presses\");\n\nif (!presses) {\n  presses = [];\n}\n\npresses.push({ \"topic\": msg.topic, \"input\": input, \"payload\": msg.payload});\n\nflow.set(\"presses\",presses)\n\nreturn msg;\n\nfunction getVal(obj,propName) {\n\n    var retVal;\n    var props = propName.split(\".\");\n    var name = props[0];\n\n    if (props.length > 1) {\n\n        props.shift();\n        var newProps = props.join(\".\");\n\n        if (!obj.hasOwnProperty(name)) {\n            obj[name] = {};\n        }\n        retVal = getVal(obj[name],newProps);\n\n\n    } else {\n        retVal = obj[name];\n    }\n\n    return retVal;\n\n}\n","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\ncontext.set(\"presses\",undefined)","finalize":"","libs":[],"x":220,"y":60,"wires":[["207f439b5db01e9a"]]},{"id":"207f439b5db01e9a","type":"timed-counter","z":"3b59914daaad2161","name":"","timelimit":"${DELAYMS}","timeunit":1,"withhold":true,"fixedtimeout":false,"pertopic":false,"x":220,"y":140,"wires":[["14dc4f13e03c8ecf"]]},{"id":"14dc4f13e03c8ecf","type":"function","z":"3b59914daaad2161","name":"convert incoming data","func":"const outputProp = env.get(\"OUTPUT_PROP\");\nconst checker = env.get(\"CHECK_TABLE\");\nconst reqSeq = env.get(\"REQUIRE_SEQ\");\nconst trigOnly = env.get(\"TRIGGER_ONLY\");\nconst presses = flow.get(\"presses\");\n\nvar matched = false;\nvar retVal;\nvar status = {\"payload\": {\"fill\":\"green\",\"shape\":\"dot\",\"text\": \"\" }};\n\nfor (var i=0;i<checker.length;i++){\n\n    var inputVals = checker[i].inputVals;\n    var origData = \"\";\n\n    if (presses.length == inputVals.length){\n        if (reqSeq) {\n            /*\n            matched = inputVals.every((v, idx) => {\n                v.input.toString() === presses[idx].input.toString() && v.topic === presses[idx].topic\n            });\n            */\n            \n            origData = \"\";\n            matched = true;\n            for (var k=0;k<presses.length;k++){\n                origData += (origData === \"\"? \"\" : \"/\") + presses[k].input.toString();\n\n                if (!(presses[k].input.toString() === inputVals[k].input.toString() && presses[k].topic === inputVals[k].topic)) {\n                    matched = false;\n                    break;\n                }\n            }\n            \n        } else {\n            var cmpArr = inputVals.slice();\n            \n            origData = \"\";\n            for (var j=0;j<presses.length;j++){\n                const topic = presses[j].topic.toString();\n                const input = presses[j].input.toString();\n                origData += (origData === \"\"? \"\" : \"/\") + input;\n                var foundIdx = cmpArr.findIndex( v => v.topic === topic && v.input === input )\n                if (foundIdx > -1) {\n                    cmpArr.splice(foundIdx,1);\n                }\n            }\n            matched = ((cmpArr.length == 0));\n        }\n\n    }\n\n    if ( matched ) {\n        \n        retVal = {\n            \"topic\": checker[i].name\n        }\n        \n        var output = {\n            \"name\": checker[i].name,\n            \"value\":checker[i].output,\n            \"data\": presses\n        }\n\n        setVal(retVal,outputProp, output)\n        status.payload.fill = \"green\";\n        status.payload.text = origData + \" => \" + checker[i].output;\n        break;\n    } \n}\n\n\nif (matched){\n    node.send([retVal,status]);\n} else {\n    \n    if (!trigOnly){\n        retVal = msg;\n        status.payload.fill = \"green\";\n        for(l=0;l<presses.length;l++){\n            retVal.topic = presses[l].topic\n            retVal.payload = presses[l].payload\n            status.payload.text += (status.payload.text !== \"\" ? \"/\" : \"\" ) + presses[l].input;\n            node.send([retVal,status]);\n        }\n    } else {\n        status.payload.fill = \"red\";\n        node.send([undefined,status]);\n    }\n}\n\nflow.set(\"presses\",undefined);\n\nreturn;\n\nfunction setVal(obj,propName,val) {\n    var props = propName.split(\".\");\n    var name = props[0];\n\n    if (props.length > 1) {\n        if (typeof obj[name] !== \"object\"){\n            obj[name] = {};\n        }\n        props.shift();\n        var newProps = props.join(\".\");\n        setVal(obj[name],newProps,val);\n\n    } else {\n        obj[name] = val;\n    }\n\n    return;\n\n}\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":140,"wires":[[],[]]},{"id":"68b640cb8870b75b","type":"debug","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":720,"y":2660,"wires":[]},{"id":"14c1f4cf77e27fa7","type":"subflow:3b59914daaad2161","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"","env":[{"name":"CHECK_TABLE","value":"[{\"name\":\"Office Test 1\",\"inputVals\":[{\"topic\":\"Office Button\",\"input\":\"1\"},{\"topic\":\"Office Pico\",\"input\":\"1\"},{\"topic\":\"Office Pico\",\"input\":\"1\"}],\"output\":12},{\"name\":\"Office Test 2\",\"inputVals\":[{\"topic\":\"Office Pico\",\"input\":\"3\"},{\"topic\":\"Office Pico\",\"input\":\"4\"}],\"output\":11}]","type":"json"},{"name":"DELAYMS","value":"5000","type":"num"},{"name":"REQUIRE_SEQ","value":"true","type":"bool"}],"x":520,"y":2660,"wires":[["68b640cb8870b75b"]]},{"id":"fcf0f568c4d33c32","type":"inject","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"Office Button: Push 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Office Button","payload":"{\"name\":\"pushed\",\"value\":1,\"displayName\":\"Office Button\",\"deviceId\":\"1781\",\"descriptionText\":\"Office Button button 1 was pushed\",\"unit\":null,\"type\":\"digital\",\"data\":null,\"currentValue\":1,\"dataType\":\"NUMBER\"}","payloadType":"json","x":220,"y":2560,"wires":[["14c1f4cf77e27fa7"]]},{"id":"8e3a37a649d03bbc","type":"inject","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"Office Pico: Push 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Office Pico","payload":"{\"name\":\"pushed\",\"value\":1,\"displayName\":\"Office Pico\",\"deviceId\":\"33\",\"descriptionText\":\"Office Pico button 1 was pushed [physical]\",\"unit\":null,\"type\":\"physical\",\"data\":null,\"currentValue\":1,\"dataType\":\"NUMBER\"}","payloadType":"json","x":230,"y":2620,"wires":[["14c1f4cf77e27fa7"]]},{"id":"74dc2a23244c2645","type":"inject","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"Office Pico: Push 2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Office Pico","payload":"{\"name\":\"pushed\",\"value\":2,\"displayName\":\"Office Pico\",\"deviceId\":\"33\",\"descriptionText\":\"Office Pico button 2 was pushed [physical]\",\"unit\":null,\"type\":\"physical\",\"data\":null,\"currentValue\":2,\"dataType\":\"NUMBER\"}","payloadType":"json","x":230,"y":2660,"wires":[["14c1f4cf77e27fa7"]]},{"id":"c9e77e2065a59f70","type":"inject","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"Office Pico: Push 3","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Office Pico","payload":"{\"name\":\"pushed\",\"value\":3,\"displayName\":\"Office Pico\",\"deviceId\":\"33\",\"descriptionText\":\"Office Pico button 3 was pushed [physical]\",\"unit\":null,\"type\":\"physical\",\"data\":null,\"currentValue\":3,\"dataType\":\"NUMBER\"}","payloadType":"json","x":230,"y":2700,"wires":[["14c1f4cf77e27fa7"]]},{"id":"14fd3f460f313439","type":"inject","z":"b38ec3180db9ae33","g":"9540b0343c71fb98","name":"Office Pico: Push 4","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Office Pico","payload":"{\"name\":\"pushed\",\"value\":4,\"displayName\":\"Office Pico\",\"deviceId\":\"33\",\"descriptionText\":\"Office Pico button 4 was pushed [physical]\",\"unit\":null,\"type\":\"physical\",\"data\":null,\"currentValue\":4,\"dataType\":\"NUMBER\"}","payloadType":"json","x":230,"y":2740,"wires":[["14c1f4cf77e27fa7"]]}]

Why is this fun? Well for example you could have multiple light switches and based on a given sequence of switching you could do something like activate the hidden door to your secret lair... or trip an alarm etc etc.

The subflow accumulates a certain amount of msgs coming in for a predetermined time and then checks against a JSON array to see if any matches - if so then returns an output value.. The delay, devices (delineated by topic), trigger only vs all msgs can be configured via the subflow "properties" aka environment variables.

Here is an example included above- inject nodes standing in for the HE device nodes (in this case button pushed events):

You click on the "Office Button Push 1" once then the "Office Pico" twice (Note: this does not trap double-taps) and a value of 12 is returned.. here is the JSON array for the checking:

[
    {
        "name": "Office Test 1",
        "inputVals": [
            {
                "topic": "Office Button",
                "input": "1"
            },
            {
                "topic": "Office Pico",
                "input": "1"
            },
            {
                "topic": "Office Pico",
                "input": "1"
            }
        ],
        "output": 12
    },
    {
        "name": "Office Test 2",
        "inputVals": [
            {
                "topic": "Office Pico",
                "input": "3"
            },
            {
                "topic": "Office Pico",
                "input": "4"
            }
        ],
        "output": 11
    }
]

edit: You DO need the "node-red-contrib-timed-counter" installed...

3 Likes

Hi everyone. I’m using one of these new Tuya presence sensors and it’s pretty good. However, I’m struggling to get the desired effect in my Node Red motion lighting routine. Maybe someone can help.

Here’s a snippet of the Node Red flow. It’s pretty standard I think.

The problem I have is this sensor (it’s at the top left of the flow, with various other PIR sensors below it that work fine) can be active but doesn’t spit that message out on a regular basis or when retriggered. It will just sit there saying active, but because it doesn’t output anything, my timer will time out and the lights go off even tho it’s still active.

Is there a better way to construct the flow to take account of this? Some way to retrigger the active status output regularly or suchlike?

I think you could add a Device Node for the Tuya Multi Sensor - Living Room sensor with Send Events turned off and a Switch node that passes "inactive" for msg.payload.value at the red arrow in your flow. Then, before turning off Living Room TV (Sony), the flow will make sure Tuya Multi Sensor - Living Room is inactive. Add an output for the Switch Node for "active" that routes back to the 3 Minute Timer so that time starts over.

Cool idea. I will try it and thank you so much.

Does anyone know if Node-Red can read/write Hubitat variables?

Yes, if you create variable connector devices that are exposed to NR.

1 Like

Also can be done using:

2 Likes

I have been using NodeRed for the past few years but I am still a noob with available nodes. Being proficient in JavaScript I often jump to function nodes but trying to utilize prebuilt nodes when possible. One need now is a multi-condition node.

I use the Filter node but really dislike that I have to choose the "property" at the top and all the conditions are based on say msg.payload that I set as the property.

Is there a node that allows me to just specify multiple conditions such as:
Condition 1: msg.name contains Home
Condition 2: msg.type == Switch
etc

Thanks!

in a situation like this I would use an OR node from boolean logic ultimate. Otherwise if multiple motion sensors are tripped at the same time and one times out before the others, the light may go out even though one of the sensors is still active.

that is, I generally want ANY sensor to turn the lights on, but I want ALL sensors inactive to turn the lights off.

1 Like

You could use JSONATA and evaluate to a true/false.

2 Likes

I don't know how to do contains in JSONATA, but you can definitely use JSONATA in a switch node to test if an expression evaluates as true or false. I use this all the time to evaluate multiple global variables in a single switch node.

Something like

$globalContext("light1-switch") = "on" and $globalContext("light2-switch") = "off"

2 Likes

Use the "in" (inclusion) operator

"world" in ["hello", "world"]

Also Predicate Queries

payload[ someItem in ["test1","test2","test3"] ].someItem

And if you REALLY want to go down the rabbit hole... functional programming.

2 Likes

This is great. I moved my Lutron integration over to HA and it was minimal effort to convert this. I've taken so much from you at this point. Thanks again!

1 Like

Ok, for 2024 I figured I'd learn something new so I'm dabbling with Node Red. So far I've come up with triggering a motion detector in HE which runs a flow in HA which inturn lights a sengled color bulb back in HE. Not going to lie, kinda surreal watching this happen. My next step is changing color/intensity of the bulb. This rabbit hole seems to run pretty deep... what would be some of the more simpler ways you guys would pull this off? Thanks.

Jeff

You're using Node Red to to trigger a flow in "HA" (Home assistant)? Or running a flow in Node Red to change the bulb state?