Perfect, the switch node configuration is the piece I was missing.
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...
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.
Also can be done using:
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.
You could use JSONATA and evaluate to a true/false.
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"
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.
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!
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?
A motion detector or contact switch trigger from Hubitat will run a flow in Node Red in Home Assistant. The flow will tell Hubitiat to turn on a color bulb. Results are near instantly.
The trigger from Hubitat to Home Assistant uses Maker API Home Assistant.
From Home Assistant back to Hubitat I'm using the Home Assistant Device Bridge.
Home Assistant has the Hubitat and Node Red intergrations.
I thought this how everyone else is using Node Red with Hubitat?
Pretty much the only reason I have Home Assistant is database storage and grahping. 100% of my devices and rules are in Hubitat. This was just an exercise to see if I can pull it off getting the hubs talking to one another and experimenting with Node Red. I've checked that box off so now lets see what else. I'd like to change the color and intensity and seems it's much more involved than what's required for a simple on/off. Just wanting to hear some suggestions from those much smarter out there on the interweb.
Thanks.
Not really - I use Node-RED directly with HE. There is a HE node available for Node-RED.
Well that simplifies things hopefully. Thanks rakeshg. I'll check it out.
ok, little confused... you are using Node Red via a pc talking to HE?