Yeah, I know subflows area a bit Hit and miss you either love them or ate them.
I was writing function nodes that does SOME of this but not all.
They were HUGE blocks of text and though it/they were robust, accidental pressing was a problem.
So I Added the option where you have to double click the button.
Then there is the click and the toggle type.
I've tried to include all the docs, but..... I am not good at that.
Here's a walk through of how it works.
Input:
X is expected as the usual/normal input. Why? More on that later.
Outputs: 2.
1 Is the one to the GATE. This is because they were mostly used to control GATE nodes.
2 Loops back to the button.
It has 3 parts to the message:
msg.txt
msg.fontclr
msg.colour
txt is the text to display
fontclr is what colour the text is.
colour is the background of the button.
Two MODES
press and toggle
Press is basically a button that only sends ONE message. But you have to double click/press it in a given time for the message to be sent.
(Extras)
It shows THREE messages on the button's text field.
(Yes, they can be icons.)
When disabled - at idle.
After the first press
After the second press.
Toggle:
An A and B switch.
Idle - you see what state it is in. (A/B - for simplicity)
First press - the colour changes to the colour set for that state.
Second press - the text and colour changes to the other state.
The payload then changes.
Why X?
You can send RELOAD or RESET.
These are handy if ever you disable the button node.
But sometimes it may be handy to get the state (text) refreshed on the button.
This is where
ReSendPayloadOnRELOAD
comes in.
If 0 RELOAD only refreshes the button's text.
If 1, the payload is also sent.
Anyway, I hope I made the documents readable.
It would be nice if you can have a look and see if you can see any problems.
It is a bit of a dog's breakfast in the function node.
But I have tried to structure it into blocks.
And there may be repeats in there toooo. ![]()
And it would be nice if people found it useful.
No foreign nodes.
Code:
[{"id":"e2af3214236cccda","type":"subflow","name":"Button toggle with enable","info":"Update 2026 05 30 17:10\n======\nWasn't getting font colour sent at setup\nDISABLED: explination.\n\n--\nIt is the COLOUR which signals the state\nof the `button`.\nIf `mode 0`:\n disabledTXT is the DISABLED text\n txtA is shown when the button is initially pressed. (Enabled)\n and txtB is the text shown with the second (quick) pressed\n AND `payloadB` is used/sent. NOT `payloadA`!\nIf `mode 1`:\n txtA and txtB toggle depending on the\n output message to be sent.\n disabledTXT is NOT used.\n\nUpdate 2024 09 27\n======\nFixed up mistakes in previous documentation.\n\n\nUpdate 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 - single mode or toggle 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. Time determined by `delay` value (ms)\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","category":"","in":[{"x":190,"y":260,"wires":[{"id":"0e1b336f6628ae72"},{"id":"751ad8b9c47029b1"}]}],"out":[{"x":930,"y":220,"wires":[{"id":"0e1b336f6628ae72","port":0}]},{"x":950,"y":300,"wires":[{"id":"0e1b336f6628ae72","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":"","ui":{"type":"input","opts":{"types":["num"]}}},{"name":"disabledColour","type":"str","value":""},{"name":"colourA","type":"str","value":""},{"name":"colourB","type":"str","value":""},{"name":"disabledTXT","type":"str","value":""},{"name":"txtA","type":"str","value":""},{"name":"txtB","type":"str","value":""},{"name":"disabledTXTColour","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":""},{"name":"ResendPayloadOnRELOAD","type":"num","value":"","ui":{"label":{"en-US":"Resend payload on RELOADE"},"type":"input","opts":{"types":["num"]}}}],"meta":{"version":"1.0.0","desc":"Protected button press node"},"color":"#3FADB5","outputLabels":["To the `GATE` node","To the `BUTTON` node"],"icon":"node-red-dashboard/ui_switch.png","status":{"x":860,"y":440,"wires":[{"id":"fa7150113df2bfa1","port":0}]}},{"id":"fa7150113df2bfa1","type":"status","z":"e2af3214236cccda","name":"","scope":["0e1b336f6628ae72"],"x":590,"y":440,"wires":[[]]},{"id":"0e1b336f6628ae72","type":"function","z":"e2af3214236cccda","name":"Push Button","func":"// 2026 05 30 18:44 (working on disabledTXT and how to use/show it.)\n\n// ---- 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 mode = parseInt(env.get(\"mode\")) // Should be `const`?\nconst disabledTXT = env.get(\"disabledTXT\")\nconst disabledColour = env.get(\"disabledColour\")\nconst disabledTXTColour = env.get(\"disabledTXTColour\")\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 ReSend = env.get(\"ResendPayloadOnRELOAD\")\nif (ReSend === undefined)\n ReSend = 0\nlet StartOn = env.get(\"StartOn\")\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// STARTUP\nif (msg.payload == \"START\")\n{\n // This is the first message to set things up.\n //msg.payload = StartOn\n msg.payload = \"Z\"\n}\n\n// Reload desired state\nif (msg.payload == \"RESET\")\n{\n // load startOn value.\n msg.payload = StartOn\n}\n\nif (msg.payload == \"RELOAD\")\n{\n // force redisplay for `button` node.\n if (mode == 1)\n {\n if (state == 0)\n {\n //\n msg.payload = payloadA\n msg.topic = topic\n msg1.txt = txtA\n msg1.colour = disabledColour\n msg1.fontclr = txtclrA\n node.status({ fill: \"yellow\", text: \"mode \" + mode + \"A\" })\n } else\n if (state == 1)\n {\n //\n msg.payload = payloadB\n msg.topic = topic\n msg1.txt = txtB\n msg1.colour = disabledColour\n msg1.fontclr = txtclrB\n node.status({ fill: \"red\", text: \"mode \" + mode + \"B\" })\n }\n }\n if (mode == 0)\n {\n //\n node.status({ text: \"mode \" + mode + \"\" })\n context.set(\"STATE\", 0)\n msg1.colour = disabledColour\n msg1.txt = disabledTXT\n msg1.fontclr = disabledTXTColour\n //msg = {} // Don't send message on first set up\n msg.payload = payloadB\n msg.topic = topic\n }\n if (ReSend == 0)\n {\n //\n msg.payload = null\n msg.topic = null\n }\n return [msg,msg1]\n}\n\n// =========================================\n// Set condition if the message is either A or B\n\nif (msg.payload === \"A\")\n{\n msg1.payload = null\n if (mode == 1)\n {\n //\n msg1.payload = payloadA\n }\n if (mode == 0)\n {\n //\n msg1.payload = disabledTXT\n msg.payload = payloadB // NOTE THIS IS ONLY FOR `mode == 0`!!\n }\n msg.topic = topic\n \n\n msg1.txt = txtA\n msg1.colour = disabledColour\n msg1.fontclr = txtclrA\n node.status({ fill: \"yellow\", text: \"mode \" + mode + \"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: \"mode \" + mode + \"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 === 0) // First time button pressed in this sequence\n {\n //\n context.set(\"ENABLED\", 1)\n if (state === 0) // Depending on `state` depends what now happens.\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 node.status({ fill: \"blue\", text: \"enabled\" })\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 if (state == 0)\n {\n //\n // Condition A\n //\n node.status({ fill: \"yellow\", text: \"mode \" + mode + \"A\" })\n msg.payload = payloadA\n msg1.colour = colourA\n msg1.txt = txtA\n msg1.fontclr = txtclrA\n }\n else\n if (state == 1)\n {\n //\n // Condition B\n //\n node.status({ fill: \"yellow\", text: \"mode \" + mode + \"B\" })\n msg.payload = payloadB\n msg1.colour = colourB\n msg1.txt = txtB\n msg1.fontclr = txtclrB\n }\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: \"mode \" + mode + \"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: \"mode \" + mode + \"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 //\n // Condition A\n //\n node.status({ text: \"mode \" + mode + \"\" })\n context.set(\"STATE\", 0)\n msg1.colour = disabledColour\n msg1.txt = disabledTXT\n msg1.fontclr = disabledTXTColour\n msg = {} // Don't send message on first set up\n }\n //-------------------------------------\n\n //-------------------------------------\n if (mode === 1)\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: \"mode \" + mode + \"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: \"mode \" + mode + \"B\" })\n }\n }\n //-------------------------------------\n return [msg,msg1]\n}\n//-------------------------------------\n","outputs":3,"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":["Action","To button","Status"]},{"id":"52a1c859d82355af","type":"trigger","z":"e2af3214236cccda","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":[["0e1b336f6628ae72"]],"l":false},{"id":"751ad8b9c47029b1","type":"switch","z":"e2af3214236cccda","name":"payload == `X`","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"X","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":305,"y":370,"wires":[["15caae5aec953df5"]],"l":false},{"id":"15caae5aec953df5","type":"function","z":"e2af3214236cccda","name":"set msg.delay","func":"msg.delay = env.get(\"delay\") || 1000\nreturn msg","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":365,"y":370,"wires":[["52a1c859d82355af"]],"l":false},{"id":"35e044f43ad8f242","type":"inject","z":"e2af3214236cccda","name":"START","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"START","payloadType":"str","x":350,"y":290,"wires":[["0e1b336f6628ae72"]]},{"id":"1daff2c79dbc60c8","type":"subflow:e2af3214236cccda","z":"6cf51e98651df17b","name":"Mode 1 (toggle)","env":[{"name":"delay","value":"500","type":"num"},{"name":"StartOn","value":"A","type":"str"},{"name":"mode","value":"1","type":"num"},{"name":"disabledColour","value":"maroon","type":"str"},{"name":"colourA","value":"yellow","type":"str"},{"name":"colourB","value":"blue","type":"str"},{"name":"disabledTXT","value":"disabled - mode 1","type":"str"},{"name":"txtA","value":"first (1)","type":"str"},{"name":"txtB","value":"second (1)","type":"str"},{"name":"txtclrA","value":"black","type":"str"},{"name":"txtclrB","value":"black","type":"str"},{"name":"payloadA","value":"PayloadA","type":"str"},{"name":"payloadB","value":"PayloadB","type":"str"}],"x":2250,"y":4660,"wires":[["4ca560db24c63940"],["ea0c73f8890f632b","c3e7ff83d992d020"]]}]