My effort at remote shutdown for ras-pi's

(No offence @dynamicdave but seeing his flow, I thought I would submit mine.)

As in his post/thread I have a few RPIs running and need to be able to remote control them.
And by that I mean:
Shut them down,
Reboot them,
Back up the node-red settings,
Test them for error detection (two ways)
and update/upgrade them. This part is work-in-progress.

This is the basic flow layout:

The structure is pretty much as the flow posted by @dynamicdave but also slightly different.

Starting top left:
MQTT node receives the command. It is then converted from JSON format and the switch node filters out other commands for other machines.

It is then sent to logging with a time stamp. I'll get back to that soon.
Then the command is delayed 1 second and sent to another switch node.

This one routes the command depending on what it is: Shutdown, reboot, etc.
There are a couple more delay nodes which allow replies to be sent before the actual command happens. Useful for acknowledgement.

Next you see the group of exec nodes which do their commands.
Another delay node and all these messages are sent along to a function node and then sent out of an MQTT node, acknowledging the command's receipt.

I did have an option where it echoed the command back, but that was a bit problematic so I stopped doing that. Though the node is still there to allow that.

You will also see there are two command received nodes. That is because the flow acknowledges the receipt of the command and if it is a shutdown or reboot command, it sends back a message to the sender so things can be done there. (That's me being me about messages and sequences.)

I have debug nodes along the way - which shouldn't see much use - but are handy all the same.

The test1 and test2 parts are where a local script/command is run and one works and the other generates an error. This checks that the machine is sending back correct status messages when commands are done. (Not really useful but there for central monitoring of all machines and all states so if a machine has an error, it is correctly reported back.)

I have skipped one node. A link node above the two delay nodes.
That sends a signal to other parts of the flow stopping them as the machine is about to be shut down or rebooted.

Now the logging part:


This is way overkill for most people but again: I like to log things.

There are a few buttons to allow logging and displaying of the data.
You can edit the respective function nodes (setup) to set the default state of the button.

Then you see a list of command received - with date - and a log file is also created.
I included an extra queue only to save me having to read the log files. Mostly during design/set up of the flow. That is in the bottom right of the flow.

extra nodes needed:
node-red-contrib-ui-led
node-red-contrib-simple-gate
node-red-contrib-simple-message-queue

Message/command format:
{"DEVICE":"<device name>", "COMMAND":"<command>","state":1}

device_name: Text determining the computer's name. Used in the first switch node.
command: Text either: Shutdown Reboot Backup Test1 Test2 Update or Upgrade

state, used to check sequence of messages. Probably not needed for most people.

Now the flow:

[{"id":"52bb799a.59cbb","type":"mqtt in","z":"bf66eea1.cd99d","name":"COMMAND","topic":"DO_THIS/#","qos":"2","datatype":"auto","broker":"2d7fe08c.3d8c6","x":90,"y":270,"wires":[["6acc5b2b.3ab6c4"]]},{"id":"6acc5b2b.3ab6c4","type":"json","z":"bf66eea1.cd99d","name":"","property":"payload","action":"","pretty":false,"x":230,"y":270,"wires":[["e820bc50.e988b"]]},{"id":"e820bc50.e988b","type":"switch","z":"bf66eea1.cd99d","name":"","property":"payload.DEVICE","propertyType":"msg","rules":[{"t":"eq","v":"MUSICPI","vt":"str"},{"t":"eq","v":"BEDPI","vt":"str"},{"t":"eq","v":"PIFACE","vt":"str"},{"t":"eq","v":"CAMERAPI","vt":"str"},{"t":"eq","v":"PORTAPI","vt":"str"},{"t":"eq","v":"TelePi","vt":"str"},{"t":"eq","v":"BEEFPI","vt":"str"}],"checkall":"true","repair":false,"outputs":7,"x":350,"y":270,"wires":[[],[],[],[],[],["e3145860.227448","358d5b09.719924","5017c95a.e350c"],[]]},{"id":"15eb299c.11bdc6","type":"debug","z":"bf66eea1.cd99d","name":"Final command","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":485,"y":210,"wires":[]},{"id":"60147ec.1e3d7","type":"switch","z":"bf66eea1.cd99d","name":"","property":"payload.COMMAND","propertyType":"msg","rules":[{"t":"eq","v":"Shutdown","vt":"str"},{"t":"eq","v":"Reboot","vt":"str"},{"t":"eq","v":"Backup","vt":"str"},{"t":"eq","v":"Update","vt":"str"},{"t":"eq","v":"Upgrade","vt":"str"},{"t":"eq","v":"Test1","vt":"str"},{"t":"eq","v":"Test2","vt":"str"}],"checkall":"true","repair":false,"outputs":7,"x":630,"y":260,"wires":[["c661b884.39269","65ee48e3.88f68","94cd03b0.b0e15"],["f17604f2.dc80f","65ee48e3.88f68","94cd03b0.b0e15"],["dd1ee7ca.460238"],["862b9d38.55fb08"],["b29a395e.305608"],["9ad3b375.e16318"],["6b5bfc46.abc8ac"]]},{"id":"2c83c765.67e608","type":"comment","z":"bf66eea1.cd99d","name":"Allow remote shutdown","info":"","x":280,"y":180,"wires":[]},{"id":"dd1ee7ca.460238","type":"exec","z":"bf66eea1.cd99d","command":"bash /home/pi/Mine/NR_Backup.sh","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Backup","x":940,"y":320,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"436ab820.4a052","type":"comment","z":"bf66eea1.cd99d","name":"Command response reply","info":"","x":155,"y":380,"wires":[]},{"id":"ee286.1a157d7b","type":"debug","z":"bf66eea1.cd99d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":345,"y":540,"wires":[]},{"id":"b01bc6d1.08ba88","type":"function","z":"bf66eea1.cd99d","name":"","func":"var prefix = 'COMMAND_REPLY/';\nmsg.topic = global.get('myDeviceName');\nmsg.topic = prefix + msg.topic;\nreturn msg;","outputs":1,"noerr":0,"x":175,"y":440,"wires":[["9430eebb.e5fbf","ee286.1a157d7b","ee242c91.d29e"]]},{"id":"ee242c91.d29e","type":"debug","z":"bf66eea1.cd99d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"topic","x":335,"y":500,"wires":[]},{"id":"9430eebb.e5fbf","type":"mqtt out","z":"bf66eea1.cd99d","name":"Results from commands issued.","topic":"","qos":"2","retain":"","broker":"2d7fe08c.3d8c6","x":405,"y":440,"wires":[]},{"id":"ef02182c.8dfaa8","type":"exec","z":"bf66eea1.cd99d","command":"sudo shutdown -h now","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Shut down","x":950,"y":220,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"fc6a851a.ba85b","type":"exec","z":"bf66eea1.cd99d","command":"sudo reboot","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Reboot","x":940,"y":270,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"e5c6398a.c09c8","type":"delay","z":"bf66eea1.cd99d","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1160,"y":340,"wires":[["b01bc6d1.08ba88"]]},{"id":"65ee48e3.88f68","type":"function","z":"bf66eea1.cd99d","name":"CMD received","func":"if (msg.payload.length !== 0 ) \n{\n    msg = {payload: \"Shutdown/Restart Command received\"};\n}\nreturn msg;","outputs":1,"noerr":0,"x":960,"y":170,"wires":[["b01bc6d1.08ba88"]]},{"id":"c661b884.39269","type":"delay","z":"bf66eea1.cd99d","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":760,"y":230,"wires":[["ef02182c.8dfaa8"]]},{"id":"f17604f2.dc80f","type":"delay","z":"bf66eea1.cd99d","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":760,"y":260,"wires":[["fc6a851a.ba85b"]]},{"id":"6b5bfc46.abc8ac","type":"exec","z":"bf66eea1.cd99d","command":"dirfdgdfg","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Test2","x":930,"y":517,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"e3145860.227448","type":"function","z":"bf66eea1.cd99d","name":"Time Stamp","func":"var a = msg.payload;\nvar b = new Date().toString();\nmsg = {payload: b + \" >> \" + a + \" << \"};\nreturn msg;\n","outputs":1,"noerr":0,"x":530,"y":500,"wires":[["af8cf4da.b922"]]},{"id":"9ad3b375.e16318","type":"exec","z":"bf66eea1.cd99d","command":"dir","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Test1","x":930,"y":467,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"862b9d38.55fb08","type":"exec","z":"bf66eea1.cd99d","command":"sudo apt update","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Update","x":940,"y":370,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"21ce64a9.ab1394","type":"function","z":"bf66eea1.cd99d","name":"Echo command","func":"var c = msg.payload.COMMAND;\nif (msg.payload.length !== 0 ) \n{\n    msg = {payload: c};\n}\nreturn msg;","outputs":1,"noerr":0,"x":661,"y":359,"wires":[["b01bc6d1.08ba88"]]},{"id":"358d5b09.719924","type":"delay","z":"bf66eea1.cd99d","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":485,"y":260,"wires":[["15eb299c.11bdc6","60147ec.1e3d7"]]},{"id":"5017c95a.e350c","type":"function","z":"bf66eea1.cd99d","name":"CMD received","func":"var c = msg.payload;\nif (msg.payload.length !== 0 ) \n{\n    msg = {payload: \"Command received\"};\n}\nreturn msg;","outputs":1,"noerr":0,"x":661,"y":329,"wires":[["b01bc6d1.08ba88"]]},{"id":"94cd03b0.b0e15","type":"link out","z":"bf66eea1.cd99d","name":"STOP","links":["79b7a172.22a23"],"x":715,"y":200,"wires":[]},{"id":"b29a395e.305608","type":"exec","z":"bf66eea1.cd99d","command":"sudo apt update","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Upgrade","x":940,"y":419,"wires":[[],[],["e5c6398a.c09c8"]]},{"id":"a6b22c78.fbf3b8","type":"link in","z":"bf66eea1.cd99d","name":"Received commands log","links":["af8cf4da.b922"],"x":170,"y":970,"wires":[["eb7b85c3.166df8","10d4b561.200853"]],"l":true},{"id":"eb7b85c3.166df8","type":"gate","z":"bf66eea1.cd99d","name":"","controlTopic":"control","defaultState":"open","openCmd":"GO","closeCmd":"STOP","toggleCmd":"toggle","defaultCmd":"default","persist":false,"x":460,"y":970,"wires":[["66379bb.ffaa264"]]},{"id":"10d4b561.200853","type":"gate","z":"bf66eea1.cd99d","name":"","controlTopic":"control","defaultState":"open","openCmd":"GO","closeCmd":"STOP","toggleCmd":"toggle","defaultCmd":"default","persist":false,"x":460,"y":1140,"wires":[["eada4d73.4b00c"]]},{"id":"66379bb.ffaa264","type":"function","z":"bf66eea1.cd99d","name":"name","func":"var path = global.get(\"event_paths\");\nmsg.filename = path + \"commands_received.txt\";\n\nvar time = new Date().toLocaleString();\nmsg.date = time;\nmsg.payload = time + \" \" + msg.payload;\nreturn msg;\n","outputs":1,"noerr":0,"x":650,"y":970,"wires":[["a3327beb.30191","fbc14ad5.b6458"]]},{"id":"443c9c7d.fdcbc4","type":"fan","z":"bf66eea1.cd99d","x":1210,"y":920,"wires":[["eb7b85c3.166df8"]]},{"id":"ec3563eb.269a08","type":"fan","z":"bf66eea1.cd99d","x":650,"y":920,"wires":[["7fbe1dff.688fa4","eb7b85c3.166df8"]]},{"id":"62991004.a73a38","type":"link in","z":"bf66eea1.cd99d","name":"Mark log","links":["7183b790.8f0af8"],"x":265,"y":920,"wires":[["eb7b85c3.166df8"]]},{"id":"4b85f3f9.c1142c","type":"fan","z":"bf66eea1.cd99d","x":760,"y":1100,"wires":[["10d4b561.200853","97ae3de8.dc9b78"]]},{"id":"eada4d73.4b00c","type":"ring-buffer","z":"bf66eea1.cd99d","name":"CMDs","capacity":16,"order":"new-to-old","sendOnlyIfFull":false,"pushAfterClear":false,"extra":false,"perTopic":false,"x":610,"y":1140,"wires":[["4128f2ad.f02f6c"]]},{"id":"a3327beb.30191","type":"file","z":"bf66eea1.cd99d","name":"Commands received","filename":"","appendNewline":true,"createDir":true,"overwriteFile":"false","x":890,"y":970,"wires":[[]]},{"id":"fbc14ad5.b6458","type":"function","z":"bf66eea1.cd99d","name":"Time stamp","func":"msg.time = new Date().toLocaleString();\nreturn msg;","outputs":1,"noerr":0,"x":1285,"y":1020,"wires":[["20247c00.c0e05c"]]},{"id":"522c1d42.8cd2ec","type":"inject","z":"bf66eea1.cd99d","name":"STOP","topic":"CONTROL","payload":"STOP","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":460,"y":880,"wires":[["ec3563eb.269a08"]],"icon":"node-red/alert.png"},{"id":"135ee29c.488bad","type":"inject","z":"bf66eea1.cd99d","name":"GO","topic":"CONTROL","payload":"GO","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"2","x":460,"y":920,"wires":[["ec3563eb.269a08"]]},{"id":"1eace28d.94aa4d","type":"switch","z":"bf66eea1.cd99d","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"CONTROL","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":650,"y":880,"wires":[["271dbaca.9c6566","ec3563eb.269a08"]]},{"id":"7fbe1dff.688fa4","type":"ui_led","z":"bf66eea1.cd99d","group":"43459bd7.7150a4","order":4,"width":"1","height":"1","label":"","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"red","value":"STOP","valueType":"str"},{"color":"green","value":"GO","valueType":"str"}],"name":"Commands Logging LED","x":900,"y":920,"wires":[]},{"id":"1c3a939a.2d7064","type":"switch","z":"bf66eea1.cd99d","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"CONTROL","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":760,"y":1060,"wires":[["5a138d7c.e9c7f4","4b85f3f9.c1142c"]]},{"id":"97ae3de8.dc9b78","type":"ui_led","z":"bf66eea1.cd99d","group":"43459bd7.7150a4","order":2,"width":"1","height":"1","label":"","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"red","value":"STOP","valueType":"str"},{"color":"green","value":"GO","valueType":"str"}],"name":"Commands List LED","x":950,"y":1100,"wires":[]},{"id":"ce53ed99.6a5028","type":"inject","z":"bf66eea1.cd99d","name":"GO","topic":"CONTROL","payload":"GO","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"5","x":620,"y":1100,"wires":[["4b85f3f9.c1142c"]]},{"id":"4128f2ad.f02f6c","type":"ui_template","z":"bf66eea1.cd99d","group":"43459bd7.7150a4","name":"Commands RX log","order":5,"width":"6","height":"6","format":"<table id=\"table\" border=\"1\">\n <tr>\n <th>Commands RX log</th> \n </tr>\n <tbody>\n <tr ng-repeat=\"row in msg.payload track by $index\">\n <td class=\"text\" >{{row}}</td>\n </tr>\n </tbody>\n</table>\n","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":800,"y":1140,"wires":[[]]},{"id":"20247c00.c0e05c","type":"simple-queue","z":"bf66eea1.cd99d","name":"queue1","firstMessageBypass":false,"bypassInterval":"0","x":1480,"y":1021,"wires":[["bca0c12.5c4b1c"]]},{"id":"4761869b.81dbd8","type":"function","z":"bf66eea1.cd99d","name":"Push Button","func":"var state = context.get(\"STATE\")||0;\nvar enabled = context.get(\"ENABLED\")||0;\n\n//  Look for a message with topic set to SETUP to set values.\nif (msg.topic == \"SETUP\")     //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    //  Disabled button background colour.\n    //\n    context.set(\"DISABLEDCLR\",msg.disabledColour);\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    msg.txt = msg.txtA;\n    msg.colour = msg.disabledColour;\n    msg.fontclr = msg.txtclrA;\n    return msg;\n}\n\n//      Now on to the real stuff.\nif (msg.payload == \"X\")\n{\n    //node.warn(\"X detected\");\n    //  Insert here code to enable other stuff.\n//    if (context.get(\"ENABLED\") === 0)\n    if (enabled === 0)\n    {\n        //\n        //  Old code.\n        //\n        //node.warn(\"Setting enable\");\n//        context.set(\"ENABLED\",1);\n//        msg.txt = context.get(\"Atxt\");\n//        msg.colour = context.get(\"ABGC\");\n//        msg.fontclr = context.get(\"AFC\");\n\n        //  New code.\n        \n        //node.warn(\"State = \" + state);\n\n        if (state === 0)\n        {\n            //\n            //  Set things for state 0\n            //\n            //node.warn(\"State 0 detected\");\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        }\n        else if (state === 1)\n        {\n            //\n            //  Set things for state 1\n            //\n            //node.warn(\"State 1 detected\");\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        }\n        context.set(\"ENABLED\",1);\n        return msg;\n    }\n\n//    if (context.get(\"ENABLED\") === 1)\n    state = (state + 1)% 2;\n    //node.warn(state);\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            msg.colour = context.get(\"ABGC\");\n            msg.txt = context.get(\"Atxt\");\n            msg.fontclr = context.get(\"AFC\");\n        } else\n        if (state === 1)\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        }\n    }\n    \n    if (context.get(\"Topic\") == \"~\")\n    {\n        msg.topic = \"\";\n    } else\n    {\n        msg.topic = context.get(\"Topic\");\n    }\n    return msg;\n}\nif (msg.payload == \"Z\")\n{\n    //\n    ////node.warn(\"Timed out\");\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.colour = context.get(\"DISABLEDCLR\");\n        msg.txt = context.get(\"Atxt\");\n        msg.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.colour = context.get(\"DISABLEDCLR\");\n        msg.txt = context.get(\"Btxt\");\n        msg.fontclr = context.get(\"BFC\");\n    }\n    return msg;\n}\n","outputs":1,"noerr":0,"x":480,"y":800,"wires":[["b941b592.355d7","1eace28d.94aa4d"]]},{"id":"271dbaca.9c6566","type":"debug","z":"bf66eea1.cd99d","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":650,"y":840,"wires":[]},{"id":"ab7514b9.7b3438","type":"function","z":"bf66eea1.cd99d","name":"Push Button","func":"var state = context.get(\"STATE\")||0;\nvar enabled = context.get(\"ENABLED\")||0;\n\n//  Look for a message with topic set to SETUP to set values.\nif (msg.topic == \"SETUP\")     //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    //  Disabled button background colour.\n    //\n    context.set(\"DISABLEDCLR\",msg.disabledColour);\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    msg.txt = msg.txtA;\n    msg.colour = msg.disabledColour;\n    msg.fontclr = msg.txtclrA;\n    return msg;\n}\n\n//      Now on to the real stuff.\nif (msg.payload == \"X\")\n{\n    //node.warn(\"X detected\");\n    //  Insert here code to enable other stuff.\n//    if (context.get(\"ENABLED\") === 0)\n    if (enabled === 0)\n    {\n        //\n        //  Old code.\n        //\n        //node.warn(\"Setting enable\");\n//        context.set(\"ENABLED\",1);\n//        msg.txt = context.get(\"Atxt\");\n//        msg.colour = context.get(\"ABGC\");\n//        msg.fontclr = context.get(\"AFC\");\n\n        //  New code.\n        \n        //node.warn(\"State = \" + state);\n\n        if (state === 0)\n        {\n            //\n            //  Set things for state 0\n            //\n            //node.warn(\"State 0 detected\");\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        }\n        else if (state === 1)\n        {\n            //\n            //  Set things for state 1\n            //\n            //node.warn(\"State 1 detected\");\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        }\n        context.set(\"ENABLED\",1);\n        return msg;\n    }\n\n//    if (context.get(\"ENABLED\") === 1)\n    state = (state + 1)% 2;\n    //node.warn(state);\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            msg.colour = context.get(\"ABGC\");\n            msg.txt = context.get(\"Atxt\");\n            msg.fontclr = context.get(\"AFC\");\n        } else\n        if (state === 1)\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        }\n    }\n    \n    if (context.get(\"Topic\") == \"~\")\n    {\n        msg.topic = \"\";\n    } else\n    {\n        msg.topic = context.get(\"Topic\");\n    }\n    return msg;\n}\nif (msg.payload == \"Z\")\n{\n    //\n    ////node.warn(\"Timed out\");\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.colour = context.get(\"DISABLEDCLR\");\n        msg.txt = context.get(\"Atxt\");\n        msg.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.colour = context.get(\"DISABLEDCLR\");\n        msg.txt = context.get(\"Btxt\");\n        msg.fontclr = context.get(\"BFC\");\n    }\n    return msg;\n}\n","outputs":1,"noerr":0,"x":480,"y":1060,"wires":[["c02127a5.268b7","1c3a939a.2d7064"]]},{"id":"5a138d7c.e9c7f4","type":"debug","z":"bf66eea1.cd99d","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":760,"y":1020,"wires":[]},{"id":"bca0c12.5c4b1c","type":"debug","z":"bf66eea1.cd99d","name":"Commands received","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1510,"y":1061,"wires":[]},{"id":"eb98df69.e6039","type":"change","z":"bf66eea1.cd99d","name":"Read","rules":[{"t":"set","p":"trigger","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1270,"y":1060,"wires":[["20247c00.c0e05c"]]},{"id":"d92379cc.41683","type":"change","z":"bf66eea1.cd99d","name":"Wipe","rules":[{"t":"set","p":"reset","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1270,"y":1100,"wires":[["20247c00.c0e05c"]]},{"id":"780313ac.31733c","type":"function","z":"bf66eea1.cd99d","name":"Setup","func":"msg = {\n    \"topic\":\"SETUP\",\n    \"disabledColour\":\"brown\",\n    \"colourA\": \"lime\",\n    \"colourB\": \"green\",\n    \"txtA\": \"Log\",\n    \"txtB\": \"Log Stop\",\n    \"txtclrA\": \"black\",\n    \"txtclrB\": \"black\",\n    \"payloadA\": \"GO\",\n    \"payloadB\": \"STOP\",\n    \"topicSET\": \"CONTROL\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":800,"wires":[["4761869b.81dbd8"]]},{"id":"b941b592.355d7","type":"ui_button","z":"bf66eea1.cd99d","name":"Commands RX Logging","group":"43459bd7.7150a4","order":3,"width":"2","height":"1","passthru":false,"label":"{{msg.txt}}","tooltip":"","color":"{{msg.fontclr}}","bgcolor":"{{msg.colour}}","icon":"","payload":"X","payloadType":"str","topic":"","x":520,"y":760,"wires":[["4761869b.81dbd8","f67a9584.cefc28"]]},{"id":"f67a9584.cefc28","type":"trigger","z":"bf66eea1.cd99d","op1":"","op2":"Z","op1type":"nul","op2type":"str","duration":"3","extend":false,"units":"s","reset":"","bytopic":"all","name":"","x":470,"y":840,"wires":[["4761869b.81dbd8"]]},{"id":"824bbb91.23f2d","type":"function","z":"bf66eea1.cd99d","name":"Setup","func":"msg = {\n    \"topic\":\"SETUP\",\n    \"disabledColour\":\"brown\",\n    \"colourA\": \"lime\",\n    \"colourB\": \"green\",\n    \"txtA\": \"Display\",\n    \"txtB\": \"Display Stop\",\n    \"txtclrA\": \"black\",\n    \"txtclrB\": \"black\",\n    \"payloadA\": \"GO\",\n    \"payloadB\": \"STOP\",\n    \"topicSET\": \"CONTROL\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":1060,"wires":[["ab7514b9.7b3438"]]},{"id":"c02127a5.268b7","type":"ui_button","z":"bf66eea1.cd99d","name":"Commands RX List","group":"43459bd7.7150a4","order":1,"width":"2","height":"1","passthru":false,"label":"{{msg.txt}}","tooltip":"","color":"{{msg.fontclr}}","bgcolor":"{{msg.colour}}","icon":"","payload":"X","payloadType":"str","topic":"","x":500,"y":1020,"wires":[["ab7514b9.7b3438","f6c03f7f.70e3f"]]},{"id":"f6c03f7f.70e3f","type":"trigger","z":"bf66eea1.cd99d","op1":"","op2":"Z","op1type":"nul","op2type":"str","duration":"3","extend":false,"units":"s","reset":"","bytopic":"all","name":"","x":470,"y":1100,"wires":[["ab7514b9.7b3438"]]},{"id":"843b64d0.d43238","type":"inject","z":"bf66eea1.cd99d","name":"Read","topic":"","payload":" ","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":1140,"y":1060,"wires":[["eb98df69.e6039"]]},{"id":"cd99ac04.6034e","type":"inject","z":"bf66eea1.cd99d","name":"Wipe","topic":"","payload":" ","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":1140,"y":1100,"wires":[["d92379cc.41683"]]},{"id":"c0c3b487.3cd558","type":"inject","z":"bf66eea1.cd99d","name":"Inject","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"3","x":280,"y":760,"wires":[["780313ac.31733c"]]},{"id":"f08a2462.1698b","type":"inject","z":"bf66eea1.cd99d","name":"Inject","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"3","x":280,"y":1020,"wires":[["824bbb91.23f2d"]]},{"id":"af8cf4da.b922","type":"link out","z":"bf66eea1.cd99d","name":"LOGGING","links":["a6b22c78.fbf3b8"],"x":690,"y":500,"wires":[],"l":true},{"id":"887fda2c.675b38","type":"comment","z":"bf66eea1.cd99d","name":"Logging","info":"","x":520,"y":710,"wires":[]},{"id":"2d7fe08c.3d8c6","type":"mqtt-broker","z":"","name":"MQTT host","broker":"192.168.0.99","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"SOM","birthQos":"0","birthPayload":"TelePi comms up","closeTopic":"EOM","closePayload":"TelePi shutting down","willTopic":"EOM","willQos":"0","willPayload":"TelePi Comms Failure"},{"id":"43459bd7.7150a4","type":"ui_group","z":"","name":"Cmds Received","tab":"fb960b7a.961008","order":3,"disp":true,"width":"6","collapse":false},{"id":"fb960b7a.961008","type":"ui_tab","z":"","name":"Logging","icon":"list","order":3,"disabled":false,"hidden":false}]

Hi Andrew @Trying_to_learn - no offence taken.
It's always interesting to see how other people solve a similar problem.
Thanks for sharing.

As you know, programming is such an interesting subject as there is normally more than one way to solve a particular problem, and by looking at other people's solutions one can pick up tips and ideas.

Although this particular exercise [ i.e. shutting down one or more Pi(es) ] had a clear objective and specific need, I wanted the group of students who helped create the flow to learn something from the experience.

In fact two other projects came out of this work.

  1. Interrogating a set of simple weather stations at remote locations.
  2. Chat-RED, a point-to-point or point-to-many text messaging system.