This is a subflow I wrote for use with button
nodes.
It has undergone a bit or recent work.
Probably not perfect, but I did some testing on it and it NOW seems to work better than it did.
Scenario
You have a button (GUI) and want to prevent it being accidentally bumped.
So it requires a double press to get the output to toggle.
It supports single output too.
I'll get to that shortly.
Example flow / subflow.
NO FOREIGN nodes used.
[{"id":"ce274e69.c6f778","type":"subflow","name":"Button toggle with enable","info":"Update 2024 06 08\n======\n\nEdited so you can set the start mode.\nA/B\nHUGE rewrite to get code working correctly.\n\nsend \"RESET\" as `msg.payload` and the `startOn`\nsetting will be reloaded.\nsend \"RELOAD\" as `msg.payload` and the state of\nthe node will be resent to the `button` node.\n(NO output sent)\n\nUpdate 2023 09 03\n------\n\nNow fixed.\n`trigger` node not set to accept `msg.delay`\n\n\nUpdate 2022 02 21\n------\n\nTo initialise the node send a message of `Z` into the input.\n\nIf you want a specific ouptut:\n`A` sets the mode to what the `a` named messages indicate.\n\n`B` sets the mode to what the `b` named messages indicate.\n\nThe output from the `button` node must be `X` to toggle the state, and the button must NOT pass the input onto the output!\n\nYou MUST set the `environment` variables listed below or it won't work.\n(Example)\n```\nmode: (0/1) (see below)\nStartOn: either A or B (the two conditions)\ndisabledColour: brown\ndisabledTXT: something\ncolourA: lime\ncolourB: green\ntxtA: Log\ntxtB: Stop\ntxtclrA: black\ntxtclrB: black\npayloadA: GO\npayloadB: STOP\ntopicSET: CONTROL\ndelay: 800\n```\n```\nmode - toggle mode or single mode (0/1).\nThis means in toggle mode you get 2 output messages.\nSingle mode you only get one output message.\n```\n```\ndelay - (optional) millisecond delay for time button is active.\n```\n```\ndisabledColour - the colour the button is when disabled.\n```\n```\ndisabledTXT - This is used when the `mode` is set to `1`. The text is displayed by default.\n```\n```\ncolourA - the background colour if condition `A` is selected. (Temporary)\n```\n```\ncolourB - the background colour if condition `B` is selected. (Temporary)\n```\n```\ntxtA - The text to display if condition `A` is selected.\n```\n```\ntxtB - The text to display if condition `B` is selected.\n```\n```\ntxtclrA - the colour of the text if condition `A` is selected.\n```\n```\ntxtclrB - the colour of the text if condtion `B` is selected.\n```\n```\npayloadA - the text sent to the output if condtion `A` is selected.\n```\n```\npayloadB - the text sent to the output if condition `B` is selected.\n```\n```\nThese payloads can then be used to set context values or used to control (external node) `gate`\n```\n```\ntopicSET - the message topic which is sent to the `gate`.\n(this is needed if/when used to control the afore mentioned `gate` nodes.)\n```\n\nThe *temporary* meaning is that the button will show that colour for a few seconds\nthen it will turn to the `disabled` colour.\n\nThe active time is `3` seconds (adjustable via the `delay` value) to press/toggle the button once initially pressed.\nNow: if you keep pressing the button, it will *wait* until you finish pressing it.\n\nThe background colours (and text colours) apply to the `button` node (or other) that is pressed.\nThat node must be configured to allow inputs of `{{msg.txt}}`, `{{msg.colour}}` and `{{mdg.fontclr}}`.\n\n****\nFoot note:\n`msg.payload = \"0/1\"` `msg.toic = \"D\"`\nToggles debug mode.","category":"","in":[{"x":190,"y":260,"wires":[{"id":"c43c865d57f36958"},{"id":"e8e0b354f6fd8938"}]}],"out":[{"x":930,"y":220,"wires":[{"id":"c43c865d57f36958","port":0}]},{"x":950,"y":300,"wires":[{"id":"c43c865d57f36958","port":1}]}],"env":[{"name":"delay","type":"num","value":"","ui":{"type":"input","opts":{"types":["num"]}}},{"name":"StartOn","type":"str","value":"","ui":{"label":{"en-US":"StartCond"},"type":"input","opts":{"types":["str"]}}},{"name":"mode","type":"num","value":""},{"name":"disabledColour","type":"str","value":""},{"name":"disabledTXT","type":"str","value":""},{"name":"colourA","type":"str","value":""},{"name":"colourB","type":"str","value":""},{"name":"txtA","type":"str","value":""},{"name":"txtB","type":"str","value":""},{"name":"txtclrA","type":"str","value":""},{"name":"txtclrB","type":"str","value":""},{"name":"payloadA","type":"str","value":""},{"name":"payloadB","type":"str","value":""},{"name":"topicSET","type":"str","value":""}],"meta":{},"color":"#3FADB5","outputLabels":["To the `GATE` node","To the `BUTTON` node"],"icon":"node-red-dashboard/ui_switch.png","status":{"x":860,"y":380,"wires":[{"id":"4d8b7aa6.6af80c","port":0}]}},{"id":"4d8b7aa6.6af80c","type":"status","z":"ce274e69.c6f778","name":"","scope":["c43c865d57f36958"],"x":590,"y":380,"wires":[[]]},{"id":"c43c865d57f36958","type":"function","z":"ce274e69.c6f778","name":"Push Button","func":"// ---- Assign variable names to ENV variables.\n// all future use of variables is by their names below.\n// These are the userdefined variables from ENV\nconst INPUT = msg.payload;\n//var mode = env.get(\"mode\");\nvar mode = parseInt(env.get(\"mode\"));\nconst Disabledcolour = env.get(\"disabledColour\");\nconst disabledTXT = env.get(\"disabledTXT\");\nconst colourA = env.get(\"colourA\");\nconst colourB = env.get(\"colourB\");\nconst txtA = env.get(\"txtA\");\nconst txtB = env.get(\"txtB\");\nconst txtclrA = env.get(\"txtclrA\");\nconst txtclrB = env.get(\"txtclrB\");\nconst payloadA = env.get(\"payloadA\");\nconst payloadB = env.get(\"payloadB\");\nlet topic = env.get(\"topicSET\");\nif (topic === undefined)\n topic = \"\";\n\n// These are other variables\nlet state = context.get(\"STATE\")||0;\nlet enabled = context.get(\"ENABLED\")||0;\nconst msg1 = {};\n\n\n//--------------------------------------------\n\n\nif (msg.topic === \"D\")\n{\n context.set(\"DEBUG\",msg.payload);\n // Show env \n node.warn(\"Disabled colour \" + Disabledcolour);\n node.warn(\"Disabled text \" + disabledTXT);\n node.warn(\"colourA \" + colourA);\n node.warn(\"colourB \" + colourB); \n node.warn(\"txtA \" + txtA);\n node.warn(\"txtB \" + txtB);\n node.warn(\"txtclrA \" + txtclrA);\n node.warn(\"txtclrB \" + txtclrB);\n node.warn(\"payloadA \" + payloadA);\n node.warn(\"payloadB \" + payloadB);\n node.warn(\"topic \" + topic);\n return;\n}\n\nlet debug = context.get(\"DEBUG\") || 0;\n\nif (debug === 1)\n{\n node.warn(\"INPUT \" + INPUT);\n node.warn(\"Disabled colour \" + Disabledcolour);\n node.warn(\"Disabled text \" + disabledTXT);\n node.warn(\"colourA \" + colourA);\n node.warn(\"colourB \" + colourB); \n node.warn(\"txtA \" + txtA);\n node.warn(\"txtB \" + txtB);\n node.warn(\"txtclrA \" + txtclrA);\n node.warn(\"txtclrB \" + txtclrB);\n node.warn(\"payloadA \" + payloadA);\n node.warn(\"payloadB \" + payloadB);\n node.warn(\"topic \" + topic);\n}\n//--------------------------------------------\n\n// Reload desired state\nif (INPUT == \"RESET\")\n{\n // load startOn value.\n msg.payload = env.get(\"StartOn\")\n}\n\nif (INPUT == \"RELOAD\")\n{\n // force redisplay for `button` node.\n if (state == 0)\n {\n //\n msg1.txt = txtA;\n msg1.colour = Disabledcolour;\n msg1.fontclr = txtclrA;\n node.status({ fill: \"yellow\", text: \"A\" });\n } else\n if (state == 1)\n {\n //\n msg1.txt = txtB;\n msg1.colour = Disabledcolour;\n msg1.fontclr = txtclrB;\n node.status({ fill: \"red\", text: \"B\" });\n\n }\n return [null,msg1];\n}\n\n// =========================================\n// Set condition if the message is either A or B\n\nif (msg.payload === \"A\")\n{\n msg.payload = payloadA;\n msg.topic = topic;\n\n msg1.txt = txtA;\n msg1.colour = Disabledcolour;\n msg1.fontclr = txtclrA;\n node.status({ fill: \"yellow\", text: \"A\" });\n \n context.set(\"STATE\",0);\n\n return [msg,msg1];\n}\n\nif (msg.payload === \"B\")\n{\n msg.payload = payloadB;\n msg.topic = topic;\n\n msg1.txt = txtB;\n msg1.colour = Disabledcolour;\n msg1.fontclr = txtclrB;\n node.status({ fill: \"red\", text: \"B\" });\n \n context.set(\"STATE\",1);\n\n return [msg,msg1];\n}\n///////////////////////////////////////////////////////////////////////////////\n///////////////////////////////////////////////////////////////////////////////\n///////////////////////////////////////////////////////////////////////////////\n// Main routine below.\n//-------------------------------------\n// Now on to the real stuff.\nif (msg.payload === \"X\")\n{\n //\n // Button pressed.\n //\n node.status({fill: \"green\",text: \"Pressed\"});\n if (enabled === undefined)\n {\n node.status({text:\"Button_Toggle_Enable_Context_Not_Set\"});\n node.warn(\"Button toggle enable context not set\");\n return [null,null];\n }\n if (enabled === 0)\n {\n //\n node.status({ fill: \"blue\", text: \"enabled\" });\n context.set(\"ENABLED\", 1);\n if (state === 0)\n {\n //\n // Set things for state 0\n //\n msg1.colour = colourA;\n msg1.txt = txtA;\n msg1.fontclr = txtclrA;\n //node.status({ fill: \"yellow\", text: \"A\" });\n }\n else \n if (state === 1)\n {\n //\n // Set things for state 1\n //\n msg1.colour = colourB;\n msg1.txt = txtB;\n msg1.fontclr = txtclrB;\n //node.status({ fill: \"red\", text: \"B\" });\n }\n return [null,msg1];\n }\n // To here `enabled` === 0. Now set to 1.\n //-------------------------------------\n // ENABLED from here down.\n if (enabled === 1)\n {\n\n state = (state + 1)% 2;\n context.set(\"STATE\",state);\n\n if (mode === 0)\n {\n //\n // Condition A\n //\n node.status({ fill: \"yellow\", text: \"A\" });\n msg.payload = payloadA;\n msg1.colour = colourA;\n msg1.txt = txtA;\n msg1.fontclr = txtclrA;\n } // END FOR MODE === 0\n //-------------------------------------\n // Code here for MODE === 1\n if (mode === 1)\n {\n if (state === 0)\n {\n //\n // Condition A\n //\n node.status({fill: \"yellow\",text: \"A\"});\n msg.payload = payloadA;\n msg1.colour = colourA;\n msg1.txt = txtA;\n msg1.fontclr = txtclrA;\n } else\n if (state === 1)\n {\n //\n // Condition B\n //\n node.status({fill: \"red\",text: \"B\"});\n msg.payload = payloadB;\n msg1.colour = colourB;\n msg1.txt = txtB;\n msg1.fontclr = txtclrB;\n }\n } // END FOR MODE === 1\n }\n //-------------------------------------\n msg.topic = topic;\n return [msg,msg1];\n}\n//-------------------------------------\n\n//-------------------------------------\nif (msg.payload === \"Z\")\n{\n //\n // Timed out\n ////node.warn(\"Timed out\");\n //\n //-------------------------------------\n context.set(\"ENABLED\",0);\n if (mode === 0) {\n //\n // Condition A\n //\n node.status({ text: \"\" });\n context.set(\"STATE\", 0);\n msg1.colour = Disabledcolour;\n msg1.txt = disabledTXT;\n }\n //-------------------------------------\n\n //-------------------------------------\n if (mode === 1)\n {\n //node.status({text: \"\"});\n if (payloadA === undefined)\n {\n // This needs work. Throw error?\n return [null,null];\n }\n if (state === 0)\n {\n //\n // Set things for state 0\n //\n msg.payload = payloadA;\n msg.topic = topic;\n msg1.colour = Disabledcolour;\n msg1.txt = txtA;\n msg1.fontclr = txtclrA;\n node.status({ fill: \"yellow\", text: \"A\" });\n }\n else if (state === 1)\n {\n //\n // Set things for state 1\n //\n msg.payload = payloadB;\n msg.topic = topic;\n msg1.colour = Disabledcolour;\n msg1.txt = txtB;\n msg1.fontclr = txtclrB;\n node.status({ fill: \"red\", text: \"B\" });\n }\n }\n //-------------------------------------\n return [null,msg1];\n}\n//-------------------------------------\n","outputs":2,"timeout":"","noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n\ncontext.set(\"ABGC\", env.get(\"colourA\"));\ncontext.set(\"BBGC\", env.get(\"colourB\"));\n//\n// Disabled button background colour.\n//\ncontext.set(\"DISABLEDCLR\",env.get(\"disabledColour\"));\n//\n// Now do text.\n//\ncontext.set(\"Atxt\", env.get(\"txtA\"));\ncontext.set(\"Btxt\", env.get(\"txtB\"));\n//\n// Font colours.\n//\ncontext.set(\"AFC\",env.get(\"txtclrA\"));\ncontext.set(\"BFC\",env.get(\"txtclrB\"));\n//\n// Payloads.\n//\ncontext.set(\"PayloadA\", env.get(\"payloadA\"));\ncontext.set(\"PayloadB\", env.get(\"payloadB\"));\n\n","finalize":"","libs":[],"x":600,"y":260,"wires":[[],[]],"outputLabels":["To gate","To button"]},{"id":"ce88ca05dfbfc987","type":"trigger","z":"ce274e69.c6f778","name":"","op1":"","op2":"Z","op1type":"nul","op2type":"str","duration":"3","extend":true,"overrideDelay":true,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":425,"y":370,"wires":[["c43c865d57f36958"]],"l":false},{"id":"e8e0b354f6fd8938","type":"switch","z":"ce274e69.c6f778","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"X","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":305,"y":370,"wires":[["f3f9aaf11f719403"]],"l":false},{"id":"f3f9aaf11f719403","type":"function","z":"ce274e69.c6f778","name":"set msg.delay","func":"msg.delay = env.get(\"delay\") || 1000;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":365,"y":370,"wires":[["ce88ca05dfbfc987"]],"l":false},{"id":"c906d5b6e5da1f26","type":"inject","z":"ce274e69.c6f778","name":"Selected","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"StartOn","payloadType":"env","x":350,"y":170,"wires":[["c43c865d57f36958","f4758814ab1ca9b8"]]},{"id":"f4758814ab1ca9b8","type":"switch","z":"ce274e69.c6f778","name":"","property":"mode","propertyType":"env","rules":[{"t":"eq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":305,"y":330,"wires":[["f3f9aaf11f719403"]],"l":false},{"id":"4ace159cbaefa773","type":"subflow:ce274e69.c6f778","z":"60d0e3e966d484ae","name":"","env":[{"name":"delay","value":"800","type":"num"},{"name":"StartOn","value":"B","type":"str"},{"name":"mode","value":"1","type":"num"},{"name":"disabledColour","value":"brown","type":"str"},{"name":"disabledTXT","value":"disabled","type":"str"},{"name":"colourA","value":"lime","type":"str"},{"name":"colourB","value":"green","type":"str"},{"name":"txtA","value":"A","type":"str"},{"name":"txtB","value":"B","type":"str"},{"name":"txtclrA","value":"black","type":"str"},{"name":"txtclrB","value":"black","type":"str"},{"name":"payloadA","value":"outputA","type":"str"},{"name":"payloadB","value":"outputB","type":"str"},{"name":"topicSET","value":"topic","type":"str"}],"x":760,"y":1670,"wires":[["110c99add66b6502","f67d99ab2b255de8"],["9d38f301937d0ece","de365ece51f1b832"]]},{"id":"de365ece51f1b832","type":"ui_button","z":"60d0e3e966d484ae","name":"","group":"f149abdd741ca58e","order":4,"width":"4","height":"1","passthru":false,"label":"{{msg.txt}}","tooltip":"","color":"{{msg.fontclr}}","bgcolor":"{{msg.colour}}","className":"","icon":"","payload":"X","payloadType":"str","topic":"topic","topicType":"msg","x":750,"y":1740,"wires":[["4ace159cbaefa773"]]},{"id":"f67d99ab2b255de8","type":"ui_text","z":"60d0e3e966d484ae","group":"f149abdd741ca58e","order":5,"width":"4","height":"1","name":"","label":"","format":"{{msg.payload}}","layout":"row-spread","className":"","x":1000,"y":1580,"wires":[]},{"id":"f149abdd741ca58e","type":"ui_group","name":"Demo","tab":"1caa8458.b17814","order":1,"disp":true,"width":"12","collapse":false,"className":"test"},{"id":"1caa8458.b17814","type":"ui_tab","name":"Demo","icon":"dashboard","order":10,"disabled":false,"hidden":false}]
All names are structures with A
and B
to identify the two possible messages.
delay
is the maximum time between button presses.
startCond
selects if output A
or B
is the active one.
mode
determines if it is single send
or toggle
output.
disabledColour
is the colour of the button when it is disabled. (Inactive - may be better term)
disabledTXT
is the text displayed on the button when disabled.
colourA
the colour of the button when in state A
.
txtA
is the text displayed when in state A
.
payloadA
is what is sent to the next node in state A
.
(and likewise for the B
modes)
topicSET
is the topic of the message.
(I use gate
nodes and this is handy when controlling those kind of nodes.)
Oh, the mode.
0
= toggle
mode. Message A
and B
are sent on alternate double clicks (invocations)
1
= single
mode. When you double press the button, message A
is sent.
Pressing the button repeatedly either toggles the message sent or repeatedly sends A
message.
Check the node's documentation and let me know if there is anything missing.
There are other things but they are covered in the documentation page.
I included them because I also do a lot of dashboard redesign and sometimes the button
nodes is wiped.
Hope someone can find it useful.