Another subflow I made for use with `button` nodes

This is another subflow to use with button nodes. Though you could use other ones, I am sticking to the basic idea of a button and accidentally pressing it.

The button has two states: ON and OFF.
For me that is used to control a gate node to stop / allow messages to pass.

Rather than having TWO buttons, I have one.

It has 3 states.
1 - idle / disabled.
2 - on / disabled.
3 - off / disabled.

Though it has a couple of extra intermediate states too.

Starting at Square 1:
It is either ON or OFF.
You want to change it.

You press the button.
It changes colour (background).
Within 3 seconds you press it again and the text and background colour change, indicating the new state.
After 3 seconds it goes back to the original background colour but the text has changed.

Meanwhile a message was sent to control (in my usage case) the state of the gate node.

It needs to be configured / set up at boot.
This is done by sending a special message with the topic set to SETUP.

The structure is described in the documentation of the node.

[{"id":"ce274e69.c6f778","type":"subflow","name":"Button toggle with enable","info":"/**\n*  Used to help toggle `button` node state to give a nicer look to the `button`.\n*  \n*  @version 0.1.0\n*  @auther Andrew\n*/\n\n/**\n*   Before use the node needs a `setup` message sent to it to configure things.\n*   The structure is shown below.\n*/\n\n/**\n*   At the end there is a bit on how to use the node.\n*/\n\nStructure (eg):\n```\nmsg = {\n    \"topic\":\"SETUP\",\n    \"disabledColour\":\"brown\",\n    \"colourA\": \"lime\",\n    \"txtA\": \"Log\",\n    \"txtclrA\": \"black\",\n    \"payloadA\": \"GO\",\n    \"colourB\": \"green\",\n    \"txtB\": \"Stop\",\n    \"txtclrB\": \"black\",\n    \"payloadB\": \"STOP\",\n    \"topicSET\": \"CONTROL\"\n}\nreturn msg;\n```\n\nBreakdown of lines:\n\n```\ndisabledColour - the colour the button is when disabled.\n```\nThis is the background colour of the button which will be seen the most.\nThis is when you have selected the state you want and the button is *disabled*\n\n```\ncolourA - the background colour if condition `A` is selected.  (Temporary)\n```\nThis colour is shown at the background colour when option `A` is the one selected.\n\n```\ncolourB - the background colour if condition `B` is selected.  (Temporary)\n```\nThis colour is shown at the background colour when option `B` is the one selected.\n\n```\ntxtA - The text to display if condition `A` is selected.\n```\nThis is the text shown when option `A` is selected.\n\n```\ntxtB - The text to display if condition `B` is selected.\n```\nThis is the text shown when option `B` is selected.\n\n```\ntxtclrA - the colour of the text if condition `A` is selected.\n```\nThis is the text colour used when option `A` is selected.\n\n```\ntxtclrB - the colour of the text if condtion `B` is selected.\n```\nThis is the text colour used when option `B` is selected.\n\n```\npayloadA - the text sent to the `gate` if condtion `A` is selected.\n```\nThis is the message output to the flow when option `A` is selected.\n\n```\npayloadB - the text sent to the `gate` if condition `B` is selected.\n```\nThis is the message output to the flow when option `B` is selected.\n\n```\ntopicSET - the message topic which is sent to the `gate`.\n```\nThis is needed if you are sending the message to a `gate` node as they need a\nspecific `topic` to be set to indicate the message is for them.\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 to press/toggle the button once initially pressed.\n\n/**\n*   **USING THE NODE:**\n*   \n*   **INPUT**\n*   The input is connected to the `button`'s output.  (Note:  The button must send `X` when pressed, and not send the input to the ouput.)\n*   The `button` node must be configured as so:\n*   `Label` set to `{{msg.txt}}`\n*   `Colour` set to `{{msg.fontclr}}`\n*   `Background` set to `{{msg.colour}}`\n*   `Payload` set to `X`\n*   `if msg arrives on input, emulate a button click` NOT selected.\n*   \n*   **OUTPUTS**\n*   The first one is used to control what is happening in the flow.\n*   I use it to control `gate` nodes. (`node-red-contrib-simple-gate`)\n*   \n*   The second one goes back to the `button` input.\n*/\n","category":"","in":[{"x":190,"y":120,"wires":[{"id":"761936f2.605b3"},{"id":"475a3d05.9bbc34"}]}],"out":[{"x":560,"y":80,"wires":[{"id":"475a3d05.9bbc34","port":0}]},{"x":560,"y":160,"wires":[{"id":"475a3d05.9bbc34","port":1}]}],"env":[],"color":"#3FADB5","outputLabels":["To the `GATE` node.  This is what gets things done.","To the `BUTTON` node"],"icon":"node-red-dashboard/ui_switch.png","status":{"x":560,"y":240,"wires":[{"id":"59a2a371.c4ee5c","port":0}]}},{"id":"475a3d05.9bbc34","type":"function","z":"ce274e69.c6f778","name":"Push Button","func":"//  2021 06 08  tidied up a lot of mess.   Working now.\n//  Rewriting this for subflow usability.\nmsg1 = {};\nvar state = context.get(\"STATE\")||0;\nvar enabled = context.get(\"ENABLED\")||0;\n\n//  Use message with `topic` set to `SETUP` to set up values.\nif (msg.topic == \"SETUP\")\n{\n    //\n    //  Background colours first.\n    //\n    context.set(\"ABGC\", msg.colourA);\n    context.set(\"BBGC\", msg.colourB);\n    //\n    //  Disabled button background colour.\n    //\n    context.set(\"DISABLEDCLR\",msg.disabledColour);\n    //\n    //  Text to be displayed / used.\n    //\n    context.set(\"Atxt\", msg.txtA);\n    context.set(\"Btxt\", msg.txtB);\n    //\n    //  Font colours.\n    //\n    context.set(\"AFC\",msg.txtclrA);\n    context.set(\"BFC\",msg.txtclrB);\n    //\n    //  Payloads.  (Used to get things done outside the subflow)\n    //\n    context.set(\"PayloadA\", msg.payloadA);\n    context.set(\"PayloadB\", msg.payloadB);\n    //\n    //  Topic.\n    //\n    if (msg.topicSET !== null)\n    {\n        context.set(\"Topic\",msg.topicSET);\n    } else\n    {\n        context.set(\"Topic\",\"~\");\n    }\n    //  To control the state of the `gate` node.\n    msg.payload = context.get(\"PayloadA\");\n    msg.topic = context.get(\"Topic\");\n    //  To set the `button` colour/text/etc.\n    msg1.txt = context.get(\"Atxt\");\n    msg1.colour = context.get(\"DISABLEDCLR\");\n    msg1.fontclr = context.get(\"AFC\");\n\n    return [msg,msg1];\n}\n\n//  Normal operation of the node.\nif (msg.payload == \"X\")\n{\n    //  Abort if context values are not set.\n    if (context.get(\"PayloadA\") == undefined)\n    {\n        node.status({text:\"Button_Toggle_Enable_Context_Not_Set\"});\n        return [null,null];\n    }\n    //node.warn(\"X detected\");\n    //  Insert here code to enable other stuff.\n    if (enabled === 0)\n    {\n        if (state === 0)\n        {\n            //\n            //  Set things for state 0\n            //\n            msg.payload = context.get(\"PayloadA\");\n            msg.topic = context.get(\"Topic\");\n            msg1.colour = context.get(\"ABGC\");\n            msg1.txt = context.get(\"Atxt\");\n            msg1.fontclr = context.get(\"AFC\");\n        }\n        else if (state === 1)\n        {\n            //\n            //  Set things for state 1\n            //\n            msg.payload = context.get(\"PayloadB\");\n            msg.topic = context.get(\"Topic\");\n            msg1.colour = context.get(\"BBGC\");\n            msg1.txt = context.get(\"Btxt\");\n            msg1.fontclr = context.get(\"BFC\");\n        }\n        context.set(\"ENABLED\",1);\n        return [null,msg1];\n    }\n\n    state = (state + 1)% 2;\n    context.set(\"STATE\",state);\n    if (enabled === 1)\n    {\n        if (state === 0)\n        {\n            //  Condition A\n            msg.payload = context.get(\"PayloadA\");\n            msg1.colour = context.get(\"ABGC\");\n            msg1.txt = context.get(\"Atxt\");\n            msg1.fontclr = context.get(\"AFC\");\n        } else\n        if (state === 1)\n        {\n            //  Condition B\n            msg.payload = context.get(\"PayloadB\");\n            msg1.colour = context.get(\"BBGC\");\n            msg1.txt = context.get(\"Btxt\");\n            msg1.fontclr = context.get(\"BFC\");\n        }\n    }\n    \n    if (context.get(\"Topic\") == \"~\")\n    {\n        msg.topic = \"\";\n    } else\n    {\n        msg.topic = context.get(\"Topic\");\n    }\n    return [msg,msg1];\n}\nif (msg.payload == \"Z\")\n{\n    //  Abort if context values are not set.\n    if (context.get(\"PayloadA\") == undefined)\n    {\n        return [null,null];\n    }\n    context.set(\"ENABLED\",0);\n    if (state === 0)\n    {\n        //\n        //  Set things for state 1\n        //\n        msg.payload = context.get(\"PayloadA\");\n        msg.topic = context.get(\"Topic\");\n        msg1.colour = context.get(\"DISABLEDCLR\");\n        msg1.txt = context.get(\"Atxt\");\n        msg1.fontclr = context.get(\"AFC\");\n    }\n    else if (state === 1)\n    {\n        //\n        //  Set things for state 2\n        //\n        msg.payload = context.get(\"PayloadB\");\n        msg.topic = context.get(\"Topic\");\n        msg1.colour = context.get(\"DISABLEDCLR\");\n        msg1.txt = context.get(\"Btxt\");\n        msg1.fontclr = context.get(\"BFC\");\n    }\n    return [null,msg1];\n}\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":380,"y":120,"wires":[[],[]],"outputLabels":["To gate","To button"]},{"id":"4b9bf99.eb5f688","type":"trigger","z":"ce274e69.c6f778","name":"","op1":"","op2":"Z","op1type":"nul","op2type":"str","duration":"3","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":445,"y":170,"wires":[["475a3d05.9bbc34"]],"l":false},{"id":"761936f2.605b3","type":"switch","z":"ce274e69.c6f778","name":"Block SETUP messages","property":"topic","propertyType":"msg","rules":[{"t":"neq","v":"SETUP","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":280,"y":170,"wires":[["4b9bf99.eb5f688"]]},{"id":"59a2a371.c4ee5c","type":"status","z":"ce274e69.c6f778","name":"","scope":["475a3d05.9bbc34"],"x":410,"y":240,"wires":[[]]},{"id":"9f3a3dc4.7e43e8","type":"subflow:ce274e69.c6f778","z":"7879609.4fb06a","name":"","env":[],"x":640,"y":110,"wires":[[],[]]}]
1 Like

Nice idea, that saves a lot of Dashboard space compared to 2 different buttons. :+1:

Regarding the setup message: You could make these obsolete and use subflow instance properties instead. Those are available to your subflow as environment variables and you can configure these settings beforehand on each subflow instance. There's also a nice UI for them since NR 1.0, so subflows can be configured almost like normal nodes. :nerd_face:

The only Problem with that is that the node needs an initial message anyway.

As is, when you send the setup message, it sets up the button node to show the correct state too.

Doing it the way you suggest, there is no initial message and so things aren't set.

Yeah, ok, you could just inject an X at boot anyway. But I can't see much benefit to using env variables to a setup message.

It was just a suggestion. :wink:

Of course, an initial message with the current state is required, most likely that would originate from a retained mqtt message or something similar. But everything else that is more or less static could be done in the properties.

No worries.

I am still at the shallow end of the learning curve and when I was learning programming, that sort of thing didn't exist.

So I wrote it the only way I know.

I am trying to do it, but it isn't quite working.

Well, the original message is/could be what is preferred. So when you deploy the flow, it gets what you want as the default.

Basically the A options are the one to be used as the default.

The other thing which is tripping me is the initial message doesn't get the disabled message/colour coming through.