Toggle Status Buttons

Hi All,

As many of you have, I had some issues with simply wanting a button that acted as a switch. On each press, to toggle the status of a Boolean variable. The attached flow is what I have now. While this is probably overly complicated, it works well for what we are doing...EXCEPT for the fact that after deploying, each button has to be clicked twice for it to work.

After that initial double click it works fine until deployed again. I read into the nodes from the OPCUA Read to get the current status and continuously be monitoring it and the buttons are accurate and also send to the OPCUA Write node so that it can change the status on click. 1) Does anyone know how I can get it to work on the first click? or 2) Does anyone have a better and more efficient way of doing this?

I forget where but over the past months i have used various examples and kind of made this into my own so any input would be greatly appreciated.

Thanks,
Paul

[{"id":"11b5c675.c3f1ba","type":"function","z":"df652827.d02c3","name":"Push Button","func":"var state = context.get(\"STATE\")||0;\n\n//  Look for a !X message to get values.\nif (msg.payload != \"X\")     //Do this if the message is NOT \"X\"\n{\n    //\n    //  Background colours first.\n    //\n    context.set(\"ABGC\", msg.colourA);\n    context.set(\"BBGC\", msg.colourB);\n    //\n    //  Now do text.\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.\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    return;\n}\n//      Now on to the real stuff.\nif (msg.payload == \"X\")\n{\n    state = (state + 1)% 2;\n    //node.warn(state);\n    context.set(\"STATE\",state);\n}\nif (state === 0)\n{\n    //  Condition A\n    msg.payload = context.get(\"PayloadA\");\n    msg.colour = context.get(\"ABGC\");\n    msg.txt = context.get(\"Atxt\");\n    msg.fontclr = context.get(\"AFC\");\n} else\n{\n    //  Condition B\n    msg.payload = context.get(\"PayloadB\");\n    msg.colour = context.get(\"BBGC\");\n    msg.txt = context.get(\"Btxt\");\n    msg.fontclr = context.get(\"BFC\");\n}\nif (context.get(\"Topic\") == \"~\")\n{\n    msg.topic = \"\";\n} else\n{\n    msg.topic = context.get(\"Topic\");\n}\nreturn msg;\n","outputs":1,"noerr":0,"x":540,"y":300,"wires":[["3c7dc256.8e3e46"]]},{"id":"3c7dc256.8e3e46","type":"ui_button","z":"df652827.d02c3","name":"RBE Button","group":"396240c9.2d409","order":5,"width":"1","height":"1","passthru":false,"label":"{{msg.txt}}","tooltip":"","color":"{{msg.fontclr}}","bgcolor":"{{msg.colour}}","icon":"","payload":"X","payloadType":"str","topic":"","x":540,"y":250,"wires":[["11b5c675.c3f1ba"]]},{"id":"839b0fab.0972e8","type":"inject","z":"df652827.d02c3","name":"Setup","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"3","x":520,"y":160,"wires":[["c3514fad.809d8"]]},{"id":"80d7b072.b72b88","type":"comment","z":"df652827.d02c3","name":"Push Button Flow","info":"This has two states:\nA and B.\nAll future refrence is to these two state names.\n\nREQUIRED:\nInputs:\nButton background colour for state A.  msg.colurA\nButton background colour for state B.  msg.colourB\nButton text for state A.               msg.txtA\nButton text for state B.               msg.txtB\nFont colour for state A.               msg.txtclrA\nFont colour for state B.               msg.txtclrB\nPayload for state A.                   msg.payloadA\nPayload for state B.                   msg.payloadB\nTopic.                                 msg.topicSET\n\nOutputs:\nmsg.payload - this is used to control what ever you need.\nmsg.topic - this is if it is needed for control of the next node.\nmsg.colour - this sets the colour of the button.\nmsg.txt - this is the text to be displayed in the button.\nmsg.fontclr - this is the colour of the text on the button.\n","x":520,"y":110,"wires":[]},{"id":"c3514fad.809d8","type":"function","z":"df652827.d02c3","name":"Setup","func":"msg = {\n    \"colourA\": \"yellow\",\n    \"colourB\": \"blue\",\n    \"txtA\": \"SOM\",\n    \"txtB\": \"SOM-EOM\",\n    \"txtclrA\": \"black\",\n    \"txtclrB\": \"white\",\n    \"payloadA\": \"GO\",\n    \"payloadB\": \"STOP\",\n    \"topicSET\": \"CONTROL\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":210,"wires":[["11b5c675.c3f1ba"]]},{"id":"396240c9.2d409","type":"ui_group","z":"","name":"WAP","tab":"d54cd82e.537658","order":3,"disp":true,"width":"4","collapse":false},{"id":"d54cd82e.537658","type":"ui_tab","z":"","name":"Logging","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

If I understand correctly, try something like this

[{"id":"3c7dc256.8e3e46","type":"ui_button","z":"5a245aa1.510164","name":"RBE Button","group":"396240c9.2d409","order":5,"width":"1","height":"1","passthru":false,"label":"{{msg.txt}}","tooltip":"","color":"{{msg.fontclr}}","bgcolor":"{{msg.colour}}","icon":"","payload":"X","payloadType":"flow","topic":"","topicType":"str","x":320,"y":3630,"wires":[["bef22d71.9f0ae8"]]},{"id":"bef22d71.9f0ae8","type":"change","z":"5a245aa1.510164","name":"","rules":[{"t":"set","p":"X","pt":"flow","to":"payload = 1 ? 0 : 1","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":3680,"wires":[["d4db2e96.f67258"]]},{"id":"d4db2e96.f67258","type":"debug","z":"5a245aa1.510164","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":3680,"wires":[]},{"id":"839b0fab.0972e8","type":"inject","z":"5a245aa1.510164","name":"Setup","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"str","x":340,"y":3540,"wires":[["557c0081.636f88"]]},{"id":"557c0081.636f88","type":"change","z":"5a245aa1.510164","name":"","rules":[{"t":"set","p":"X","pt":"flow","to":"$exists($flowContext(\"X\")) ? $flowContext(\"X\") : 0\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":3540,"wires":[[]]},{"id":"396240c9.2d409","type":"ui_group","name":"WAP","tab":"d54cd82e.537658","order":3,"disp":true,"width":"4","collapse":false},{"id":"d54cd82e.537658","type":"ui_tab","name":"Logging","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

In that example, where would I specify the msg.txt, msg.fontclr, and msg.colour?

something like this

[{"id":"839b0fab.0972e8","type":"inject","z":"5a245aa1.510164","name":"Setup  ","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"str","x":340,"y":3540,"wires":[["557c0081.636f88"]]},{"id":"557c0081.636f88","type":"change","z":"5a245aa1.510164","name":"","rules":[{"t":"set","p":"X","pt":"flow","to":"$exists($flowContext(\"X\")) ? \t$flowContext(\"X\") : 0","tot":"jsonata"},{"t":"set","p":"feedback","pt":"msg","to":"$flowContext(\"X\") = 0 ?\t{\"colour\": \"blue\",\t\"txt\": \"blue/red\",\t\"fontClr\": \"red\"} \t:\t{\"colour\": \"red\",\t\"txt\": \"black/red\",\t\"fontClr\": \"black\"}\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":3540,"wires":[["3c7dc256.8e3e46"]]},{"id":"3c7dc256.8e3e46","type":"ui_button","z":"5a245aa1.510164","name":"RBE Button","group":"8b5cde76.edd58","order":5,"width":"1","height":"1","passthru":false,"label":"{{feedback.txt}}","tooltip":"","color":"{{feedback.fontClr}}","bgcolor":"{{feedback.colour}}","icon":"","payload":"X","payloadType":"flow","topic":"","topicType":"str","x":320,"y":3630,"wires":[["bef22d71.9f0ae8"]]},{"id":"bef22d71.9f0ae8","type":"change","z":"5a245aa1.510164","name":"","rules":[{"t":"set","p":"X","pt":"flow","to":"payload = 1 ? 0 : 1","tot":"jsonata"},{"t":"set","p":"feedback","pt":"msg","to":"payload = 1 ?\t{\"colour\": \"blue\",\t\"txt\": \"blue/red\",\t\"fontClr\": \"red\"} \t:\t{\"colour\": \"red\",\t\"txt\": \"black/red\",\t\"fontClr\": \"black\"}\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":3680,"wires":[["d4db2e96.f67258","3c7dc256.8e3e46"]]},{"id":"d4db2e96.f67258","type":"debug","z":"5a245aa1.510164","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":3680,"wires":[]},{"id":"80d7b072.b72b88","type":"comment","z":"5a245aa1.510164","name":"Push Button Flow","info":"This has two states:\nA and B.\nAll future refrence is to these two state names.\n\nREQUIRED:\nInputs:\nButton background colour for state A.  msg.colurA\nButton background colour for state B.  msg.colourB\nButton text for state A.               msg.txtA\nButton text for state B.               msg.txtB\nFont colour for state A.               msg.txtclrA\nFont colour for state B.               msg.txtclrB\nPayload for state A.                   msg.payloadA\nPayload for state B.                   msg.payloadB\nTopic.                                 msg.topicSET\n\nOutputs:\nmsg.payload - this is used to control what ever you need.\nmsg.topic - this is if it is needed for control of the next node.\nmsg.colour - this sets the colour of the button.\nmsg.txt - this is the text to be displayed in the button.\nmsg.fontclr - this is the colour of the text on the button.\n","x":300,"y":3490,"wires":[]},{"id":"8b5cde76.edd58","type":"ui_group","name":"","tab":"8f03e639.85956","order":1,"disp":true,"width":"6","collapse":false},{"id":"8f03e639.85956","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

I had a similar issue with controlling a set of relays over Modbus - my solution was to ignore the switch state in Node-Red ("switch icon shows state of the input") and loop back the Modbus write response to the switch input. This way the switch always reflects the true state of the relay and never gets out of sync. The downside is that there is a very slight delay when toggling the switch before the visual state gets updated (due to the Modbus round-trip).

Screenshot_2021-04-10_14-14-47

The MQTT input node above listens to the topic which the relay state polling publishes to, so is the equivalent of a Modbus Read node.

Why do you need the loop back via the State node? Does not the MQTT input drive the switch correctly?

Oh that's just because the Modbus Write response has the wrong format for the switch:

Screenshot_2021-04-10_14-50-26

The same goes for the "Bool" node; from MQTT string to boolean:

Screenshot_2021-04-10_14-53-42

Had payload conversion (or MQTT messaging) not been needed it could have been simplified to this:

Screenshot_2021-04-10_14-58-35

I mean why to you need the direct feedback from the write node to the switch. Does not the MQTT input or the Modbus read set the switch? Or is it too slow to use that?

I don't poll my Modbus units very frequently, and a 10s delay between toggling the switch and the display updating would be unacceptable. I also have an RBE node before it goes to MQTT, so relay status only gets sent when it's changed - adding further possibilities for the UI to get out of sync. That's what makes my "feedback solution" (potentially) interesting to the OP.

OK, understood. Can't you just use Show State of Output though?

Ah, no you can't, because you want it to update if the value changes via another route. Looks like you are doing it the best way anyway then. :slight_smile:

Well, yes, but those updates would still come through from the (much slower) Modbus Read input node. I wanted (near) instant switch status update, but only if the command had been correctly received by the RTU. Otherwise the system can get out of sync if the Modbus RTU is unavailable. The Modbus Write node only outputs the value on ACK - hence the small delay in updating the switch UI. This is what keeps it in sync, with minimal UI lag.