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: