If you’ve been here before then you know I use Node-Red, if not and you don’t know what Node-Red is, you should check it out. To get a frontend UI out of Node-Red there has always been the Dashboard node for us to use and a set of Dashboard nodes to go with it. I have never been a huge fan of the Dashboard, but it was what we had and it did the job. The Dashboard nodes allowed you place UI nodes and connect them to your data and it would do the rest, no HTML code required on your behalf. But that was one of the limitations, was that you did not have control over the code. Enter UIBuilder.
The UIBuilder node is amazing. It is a Node-Red interface builder that gives you complete control, over everything. The downside (if it is one) is that you must know how to code HTML and JavaScript. UIBuilder lets you use any frontend framework you want to build your interface to Node-Red. JQuery, VueJS, MoonJS, Angular, etc.. So far I have dropped in a Bootstrap-Vue admin template and I have been going to work on that. It has taken me a minute to get used to how UIB all works, and I am not really all that great with JavaScript but I seem to be figuring things out. Hey, I have shit working.
The UIBuilder Wiki and the Github for the author have a ton of information and examples of how to get a few basic things done. Just expect most of them to be done with VueJS. It appears (at least to me, I could be wrong) that UIBuilder was originally VueJS heavy/dependent and as it matures it is more open to other frameworks. As of v3.1.3 it no longer installs Bootstrap-Vue and VueJS by default, as it had done it all prior versions. So just be aware that you can use any framework you want, but all of the examples (so far) are all written in VueJS. There is also the Node-Red forums if you get stuck, the author also happens to frequent the group.
The basic default UIBuilder file is pretty decent and is a good place to start off from. While digging around the Node-Red forums for UIB information I stumbled upon a post by Squirrelbd and his flow had a form and some buttons. I took his flow and modified it, so now it has multiple buttons that all send their own payload, separately, and their status is reported back. I am not currently interested in using forms for data collection, I don’t need it at this time so that was removed from the flow. I currently have a basic page working with four buttons that fire off a 4-channel relay connected to the Raspberry Pi, and the data is cached so it persists over browser refreshes and multiple devices.
One of the issue I came across while trying to get my buttons to work was Bootstrap-Vue/VueJS does not do native hidden form fields. WTF guys really? It took an hour of Googling and hitting up StackOverflow but I found the answer eventually in the Github issues for VueJS. You cant use a <b-form> tag with a hidden field, it wont work. So you just use the default HTML form tag for a hidden field, <input type="hidden" :value="someData" >. What I found on the web was to add the semi-colon in front of “value”, that is probably for passing data, which I am not so I can not vouch for if it works or not. It seemed to be hit and miss for some people. I did need the form fields to pass information, but my information is static. I set the data for my fields in the index.js file. I am just using it for my buttons to send clicks. I then set up Node-Red to handle the web button clicks and ran them into a toggle node which is connected to GPIO output nodes and wired to a 4-channel relay.
During programming the flow for the relays and the UIB buttons I had to learn how to use a function node with multiple outputs. Some basic Node-Red stuff I just happen to not use it much. If you are unfamiliar with multiple outputs on a function node you should take a look. Here’s a great example flow for multiple outputs for a function node.
The Flow
The example flow below uses all basic nodes except for two, it requires node-red-contrib-uibuilder and node-red-contrib-toggle to be installed. On my Raspberry Pi install the link nodes in the flow connect to another link node on a separate flow to fire off the relays. In this example they wont be connected to anything. The four buttons on the web page send the same payload when clicked. I have them connected to a toggle node and the tied to the relays. I don’t expect the flow to be useful as something that cant be loaded and used right off the bat, but I hope it is a good stating point to help you out. I wish I would have found this flow in the very beginning!
1 |
[{"id":"e4695ab2.c1ac08","type":"inject","z":"b736e61c.c8cc","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId","payload":"111","payloadType":"num","x":320,"y":80,"wires":[["4977524a.c304bc"]]},{"id":"dfe7b83f.29fe78","type":"inject","z":"b736e61c.c8cc","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId","payload":"","payloadType":"date","x":340,"y":120,"wires":[["4977524a.c304bc"]]},{"id":"240a7199.dca9fe","type":"uibuilder","z":"b736e61c.c8cc","name":"","topic":"","url":"buttons","fwdInMessages":true,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":true,"useSecurity":false,"sessionLength":"","tokenAutoExtend":false,"x":740,"y":220,"wires":[["6119f142.2c665","87b393ec.98fec","c8c8bacc.e18a08"],["ff4ad4e0.d92f78"]]},{"id":"6119f142.2c665","type":"debug","z":"b736e61c.c8cc","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1030,"y":80,"wires":[]},{"id":"4b3a2b7c.19e464","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId2","payload":"111","payloadType":"num","x":330,"y":160,"wires":[["4977524a.c304bc"]]},{"id":"6f231922.2da288","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId2","payload":"","payloadType":"date","x":350,"y":200,"wires":[["4977524a.c304bc"]]},{"id":"e90001ca.631e9","type":"comment","z":"b736e61c.c8cc","name":"index.html","info":"<!doctype html>\n\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes\">\n\n <title>Node-RED UI Builder</title>\n <meta name=\"description\" content=\"Node-RED UI Builder - VueJS + bootstrap-vue version\">\n\n <link rel=\"icon\" href=\"./images/node-blue.ico\">\n\n <!-- See https://goo.gl/OOhYW5 -->\n <link rel=\"manifest\" href=\"./manifest.json\">\n <meta name=\"theme-color\" content=\"#3f51b5\">\n\n <!-- Used if adding to homescreen for Chrome on Android. Fallback for manifest.json -->\n <meta name=\"mobile-web-app-capable\" content=\"yes\">\n <meta name=\"application-name\" content=\"Node-RED UI Builder\">\n\n <!-- Used if adding to homescreen for Safari on iOS -->\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n <meta name=\"apple-mobile-web-app-title\" content=\"Node-RED UI Builder\">\n\n <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css\" />\n <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css\" />\n\n <link rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n</head>\n\n<body>\n <br />\n <div class=\"container\">\n <div class=\"row\" id=\"app\">\n <div class=\"col-sm-6\">\n <div id=\"app\" v-cloak>\n <b-container id=\"app_container\">\n\n <b-form class=\"border p-3 m-2\">\n <p>Test out a button</p>\n <input type=\"hidden\" name=\"testInput1\" :value=\"someData1\">\n <b-button pill variant=\"primary\" v-on:click=\"submit1\">Submit 1</b-button>\n Current Card No1: {{cardId}}\n </b-form>\n\n <b-form class=\"border p-3 m-2\">\n <p>Test out a second button</p>\n <input type=\"hidden\" name=\"testInput2\" :value=\"someData2\">\n <b-button pill variant=\"primary\" v-on:click=\"submit2\">Submit 2</b-button>\n Current Card No2: {{cardId2}}\n </b-form>\n\n <b-form class=\"border p-3 m-2\">\n <p>Test out a third button, why not?</p>\n <input type=\"hidden\" name=\"testInput3\" :value=\"someData3\">\n <b-button pill variant=\"primary\" v-on:click=\"submit3\">Submit 3</b-button>\n Current Card No3: {{cardId3}}\n </b-form>\n\n <b-form class=\"border p-3 m-2\">\n <p>Go for gold with four buttons!</p>\n <input type=\"hidden\" name=\"testInput4\" :value=\"someData4\">\n <b-button pill variant=\"primary\" v-on:click=\"submit4\">Submit 4</b-button>\n Current Card No4: {{cardId4}}\n </b-form>\n \n <b-card class=\"mt-3\" header=\"Log Messages\" border-variant=\"primary\" header-bg-variant=\"primary\"\n header-text-variant=\"white\" align=\"left\">\n <p>\n Messages: Received=<b>{{msgsReceived}}</b>, Sent=<b>{{msgsSent}}</b>\n </p>\n\n <pre v-html=\"hLastRcvd\" class=\"syntax-highlight\"></pre>\n <pre v-html=\"hLastSent\" class=\"syntax-highlight\"></pre>\n </b-card>\n </b-container>\n </div>\n </div>\n\n <!--<div class=\"col-md-6\">-->\n <!--<div id=\"app\" v-cloak>-->\n <!--<b-container id=\"app_container\">-->\n <!--<p> Cart Insertion Section</p>-->\n <!--<div>Current Card No: {{cardId}}</div>-->\n <!--<div>Current Card No2: {{cardId2}}</div>-->\n <!--<div>Current Card No3: {{cardId3}}</div>-->\n <!--<div>Current Card No4: {{cardId4}}</div>-->\n <!--</b-container>-->\n <!--</div>-->\n <!--</div>-->\n\n </div>\n\n </div>\n <script src=\"../uibuilder/vendor/socket.io/socket.io.js\"></script>\n\n <!-- Vendor Libraries - Load in the right order -->\n <script src=\"../uibuilder/vendor/vue/dist/vue.js\"></script> <!-- dev version with component compiler -->\n <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.min.js\"></script> prod version with component compiler -->\n <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.runtime.min.js\"></script> prod version without component compiler -->\n <script src=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js\"></script>\n\n <!-- REQUIRED: Sets up Socket listeners and the msg object -->\n <!-- <script src=\"./uibuilderfe.js\"></script> //dev version -->\n <script src=\"./uibuilderfe.min.js\"></script> <!-- //prod version -->\n <!-- OPTIONAL: You probably want this. Put your custom code here -->\n <script src=\"./index.js\"></script>\n\n</body>\n\n</html>","x":120,"y":80,"wires":[]},{"id":"d5c1afe.77a0b5","type":"comment","z":"b736e61c.c8cc","name":"index.js","info":"/* jshint browser: true, esversion: 5, asi: true */\n/*globals Vue, uibuilder */\n// @ts-nocheck\n/*\n Copyright (c) 2019 Julian Knight (Totally Information)\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n*/\n'use strict'\n\n/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */\n/** Heavily modififed from the Github example */\n\n// eslint-disable-next-line no-unused-vars\nvar app1 = new Vue({\n el: '#app',\n data: {\n startMsg : 'Vue has started, waiting for messages',\n feVersion : '',\n testInput1 : 'testInput1',\n testInput2 : 'testInput2',\n testInput3 : 'testInput3',\n testInput4 : 'testInput4',\n socketConnectedState : false,\n serverTimeOffset : '[unknown]',\n imgProps : { width: 75, height: 75 },\n\n msgRecvd : '[Nothing]',\n msgsReceived: 0,\n msgCtrl : '[Nothing]',\n msgsControl : 0,\n\n msgSent : '[Nothing]',\n msgsSent : 0,\n msgCtrlSent : '[Nothing]',\n msgsCtrlSent: 0,\n }, // --- End of data --- //\n computed: {\n \n hLastRcvd: function() {\n var msgRecvd = this.msgRecvd\n if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd\n else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd)\n },\n hLastSent: function() {\n var msgSent = this.msgSent\n if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent\n else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent)\n },\n hLastCtrlRcvd: function() {\n var msgCtrl = this.msgCtrl\n if (typeof msgCtrl === 'string') return 'Last Control Message Received = ' + msgCtrl\n else return 'Last Control Message Received = ' + this.syntaxHighlight(msgCtrl)\n },\n hLastCtrlSent: function() {\n var msgCtrlSent = this.msgCtrlSent\n if (typeof msgCtrlSent === 'string') return 'Last Control Message Sent = ' + msgCtrlSent\n //else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrlSent])\n else return 'Last Control Message Sent = ' + this.syntaxHighlight(msgCtrlSent)\n },\n }, // --- End of computed --- //\n methods: {\n\n submit1: function() {\n var topic = this.msgRecvd.topic || 'uibuilder/vue'\n uibuilder.send( {\n 'topic': topic,\n 'payload': {\n 'type': 'testInput1',\n 'testInput1': this.testInput1,\n }\n } )\n }, // --- End of submit1 --- //\n\n submit2: function() {\n var topic = this.msgRecvd.topic || 'uibuilder/vue'\n uibuilder.send( {\n 'topic': topic,\n 'payload': {\n 'type': 'testInput2',\n 'testInput2': this.testInput2,\n }\n } )\n }, // --- End of submit2 --- //\n\n submit3: function() {\n var topic = this.msgRecvd.topic || 'uibuilder/vue'\n uibuilder.send( {\n 'topic': topic,\n 'payload': {\n 'type': 'testInput3',\n 'testInput3': this.testInput3,\n }\n } )\n }, // --- End of submit3 --- //\n \n submit4: function() {\n var topic = this.msgRecvd.topic || 'uibuilder/vue'\n uibuilder.send( {\n 'topic': topic,\n 'payload': {\n 'type': 'testInput4',\n 'testInput4': this.testInput4,\n }\n } )\n }, // --- End of submit3 --- //\n \n // return formatted HTML version of JSON object\n syntaxHighlight: function(json) {\n json = JSON.stringify(json, undefined, 4)\n json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n json = json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {\n var cls = 'number'\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) {\n cls = 'key'\n } else {\n cls = 'string'\n }\n } else if (/true|false/.test(match)) {\n cls = 'boolean'\n } else if (/null/.test(match)) {\n cls = 'null'\n }\n return '<span class=\"' + cls + '\">' + match + '</span>'\n })\n return json\n }, // --- End of syntaxHighlight --- //\n }, // --- End of methods --- //\n\n // Available hooks: init,mounted,updated,destroyed\n mounted: function(){\n //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')\n\n /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3\n * Pass the namespace and ioPath variables if hosting page is not in the instance root folder\n * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.\n * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names\n */\n uibuilder.start()\n\n var vueApp = this\n\n // Example of retrieving data from uibuilder\n vueApp.feVersion = uibuilder.get('version')\n\n /** You can use the following to help trace how messages flow back and forth.\n * You can then amend this processing to suite your requirements.\n */\n\n //#region ---- Trace Received Messages ---- //\n // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO\n // newVal relates to the attribute being listened to.\n uibuilder.onChange('msg', function(newVal){\n \n //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', newVal)\n vueApp.msgRecvd = newVal\n if ( newVal.topic == 'cardId' ) {\n vueApp.cardId = newVal.payload\n }\n\n vueApp.msgRecvd = newVal\n if ( newVal.topic == 'cardId2' ) {\n vueApp.cardId2 = newVal.payload\n }\n\n vueApp.msgRecvd = newVal\n if ( newVal.topic == 'cardId3' ) {\n vueApp.cardId3 = newVal.payload\n }\n \n vueApp.msgRecvd = newVal\n if ( newVal.topic == 'cardId4' ) {\n vueApp.cardId4 = newVal.payload\n }\n\n })\n // As we receive new messages, we get an updated count as well\n uibuilder.onChange('msgsReceived', function(newVal){\n //console.info('[indexjs:uibuilder.onChange] Updated count of received msgs:', newVal)\n vueApp.msgsReceived = newVal\n })\n\n // If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable\n uibuilder.onChange('ctrlMsg', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', newVal)\n vueApp.msgCtrl = newVal\n })\n // Updated count of control messages received\n uibuilder.onChange('msgsCtrl', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:msgsCtrl] Updated count of received CONTROL msgs:', newVal)\n vueApp.msgsControl = newVal\n })\n //#endregion ---- End of Trace Received Messages ---- //\n\n //#region ---- Trace Sent Messages ---- //\n // You probably only need these to help you understand the order of processing //\n // If a message is sent back to Node-RED, we can grab a copy here if we want to\n uibuilder.onChange('sentMsg', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', newVal)\n vueApp.msgSent = newVal\n })\n // Updated count of sent messages\n uibuilder.onChange('msgsSent', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:msgsSent] Updated count of msgs sent:', newVal)\n vueApp.msgsSent = newVal\n })\n\n // If we send a control message to Node-RED, we can get a copy of it here\n uibuilder.onChange('sentCtrlMsg', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', newVal)\n vueApp.msgCtrlSent = newVal\n })\n // And we can get an updated count\n uibuilder.onChange('msgsSentCtrl', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:msgsSentCtrl] Updated count of CONTROL msgs sent:', newVal)\n vueApp.msgsCtrlSent = newVal\n })\n //#endregion ---- End of Trace Sent Messages ---- //\n\n // If Socket.IO connects/disconnects, we get true/false here\n uibuilder.onChange('ioConnected', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', newVal)\n vueApp.socketConnectedState = newVal\n })\n // If Server Time Offset changes\n uibuilder.onChange('serverTimeOffset', function(newVal){\n //console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', newVal)\n vueApp.serverTimeOffset = newVal\n })\n\n } // --- End of mounted hook --- //\n\n}) // --- End of app1 --- //\n \n// EOF","x":110,"y":120,"wires":[]},{"id":"3289dcf.e6c1024","type":"comment","z":"b736e61c.c8cc","name":"index.css","info":"/* Cloak elements on initial load to hide the possible display of {{ ... }} \n * Add to the app tag or to specific tags\n * To display \"loading...\", change to the following:\n * [v-cloak] > * { display:none }\n * [v-cloak]::before { content: \"loading…\" }\n */\n[v-cloak] { display: none; }\n\n/* Colours for Syntax Highlighted pre's */\n.syntax-highlight {color:white;background-color:black;padding:5px 10px;}\n.syntax-highlight > .key {color:#ffbf35}\n.syntax-highlight > .string {color:#5dff39;}\n.syntax-highlight > .number {color:#70aeff;}\n.syntax-highlight > .boolean {color:#b993ff;}","x":120,"y":160,"wires":[]},{"id":"a5390c01.d927e","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId3","payload":"","payloadType":"date","x":350,"y":280,"wires":[["4977524a.c304bc"]]},{"id":"1e143001.2c52d","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId3","payload":"111","payloadType":"num","x":330,"y":240,"wires":[["4977524a.c304bc"]]},{"id":"933b8ea0.9a552","type":"link in","z":"b736e61c.c8cc","name":"Link The Cache Data","links":["ff4ad4e0.d92f78"],"x":535,"y":300,"wires":[["4977524a.c304bc"]]},{"id":"ff4ad4e0.d92f78","type":"link out","z":"b736e61c.c8cc","name":"Link The Cache Data","links":["933b8ea0.9a552"],"x":775,"y":300,"wires":[]},{"id":"4977524a.c304bc","type":"function","z":"b736e61c.c8cc","name":"Cache Data","func":"// Expects input msgs with topic set\n\n// saved context\nvar homeMsgs = context.get('homeMsgs') || {}\n\n// Replay cache if requested\nif ( msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'REPLAY' ) {\n for (var topic in homeMsgs) {\n // Only send to a single client if we can\n if ( msg.hasOwnProperty('_socketId') )\n node.send({\n \"topic\": topic, \n \"payload\": homeMsgs[topic],\n \"_socketId\": msg._socketId\n })\n else\n node.send({\n \"topic\": topic, \n \"payload\": homeMsgs[topic]\n })\n }\n return null\n}\n\n// ignore cacheControl and uibuilder control messages\nif ( msg.hasOwnProperty('cacheControl') || msg.hasOwnProperty('uibuilderCtrl') ) return null\n\n// Keep the last msg.payload by topic\nhomeMsgs[msg.topic] = msg.payload\n\n// save context for next time\ncontext.set('homeMsgs', homeMsgs)\n\n// Show number of cached msgs in status\nnode.status({fill:'green',shape:'ring',text:'Cached: ' + Object.keys(homeMsgs).length})\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":570,"y":220,"wires":[["240a7199.dca9fe"]]},{"id":"87b393ec.98fec","type":"debug","z":"b736e61c.c8cc","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.testInput3","targetType":"msg","statusVal":"","statusType":"auto","x":1070,"y":120,"wires":[]},{"id":"33b9cb08.e0c5b4","type":"link out","z":"b736e61c.c8cc","name":"","links":["ad8dbfa9.0d646"],"x":1335,"y":380,"wires":[]},{"id":"eda5ac.30f38a58","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1390,"y":260,"wires":[]},{"id":"d9c64d70.401e88","type":"toggle","z":"b736e61c.c8cc","name":"Relay 1","onOffTopic":"","onValue":"true","onType":"bool","offValue":"false","offType":"bool","toggleTopic":"","toggleValue":"testInput1","toggleType":"str","passOnOff":"","x":1200,"y":260,"wires":[["eda5ac.30f38a58","33b9cb08.e0c5b4","30bb2cb7.b46504"]]},{"id":"c8c8bacc.e18a08","type":"function","z":"b736e61c.c8cc","name":"Route the Payload","func":"\nvar msg1 = { payload: null };\nvar msg2 = { payload: null };\nvar msg3 = { payload: null };\nvar msg4 = { payload: null };\n\nif ( msg.payload.testInput1 == 'testInput1' ) {\n msg1 = { payload: msg.payload };\n return [msg1, null, null, null ];\n} else if (msg.payload.testInput2 == 'testInput2') {\n msg2 = { payload: msg.payload };\n return [null, msg2, null, null ];\n} else if ( msg.payload.testInput3 == 'testInput3') {\n msg3 = { payload: msg.payload };\n return [null, null, msg3, null ];\n} else if ( msg.payload.testInput4 == 'testInput4') {\n msg4 = { payload: msg.payload };\n return [null, null, null, msg4 ];\n}\n\n","outputs":4,"noerr":0,"initialize":"","finalize":"","x":950,"y":220,"wires":[["2260e883.1703b8","a7c1883b.9884b8"],["40ea1fae.07a01","a1bf2712.cc88b8"],["202aa7aa.bc4be8","518de824.756258"],["2fba7695.81332a","ba7da14e.0d4b1"]]},{"id":"2260e883.1703b8","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1210,"y":160,"wires":[]},{"id":"40ea1fae.07a01","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1390,"y":420,"wires":[]},{"id":"202aa7aa.bc4be8","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1390,"y":640,"wires":[]},{"id":"ed47ace5.345a9","type":"toggle","z":"b736e61c.c8cc","name":"Relay 2","onOffTopic":"","onValue":"true","onType":"bool","offValue":"false","offType":"bool","toggleTopic":"","toggleValue":"testInput2","toggleType":"str","passOnOff":"","x":1200,"y":460,"wires":[["7202a48d.81a8fc","369fd5be.173f9a","39a8ac5d.cf2c24"]]},{"id":"7202a48d.81a8fc","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1390,"y":460,"wires":[]},{"id":"605a3a57.68d0a4","type":"toggle","z":"b736e61c.c8cc","name":"Relay 3","onOffTopic":"","onValue":"true","onType":"bool","offValue":"false","offType":"bool","toggleTopic":"","toggleValue":"","toggleType":"any","passOnOff":"","x":1200,"y":680,"wires":[["98328330.21f3c","d3d8c72a.8fca08","f7c8fed6.9972"]]},{"id":"98328330.21f3c","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1390,"y":680,"wires":[]},{"id":"d3d8c72a.8fca08","type":"link out","z":"b736e61c.c8cc","name":"","links":["ca999de2.12d26"],"x":1335,"y":800,"wires":[]},{"id":"369fd5be.173f9a","type":"link out","z":"b736e61c.c8cc","name":"","links":["bc12112.5bb3bf"],"x":1335,"y":580,"wires":[]},{"id":"8a6149b5.945308","type":"inject","z":"b736e61c.c8cc","name":"Toggle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"testInput2","payloadType":"str","x":970,"y":460,"wires":[["ed47ace5.345a9"]]},{"id":"a1bf2712.cc88b8","type":"function","z":"b736e61c.c8cc","name":"Set Payload","func":"msg.payload = msg.payload.testInput2;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1210,"y":420,"wires":[["ed47ace5.345a9"]]},{"id":"518de824.756258","type":"function","z":"b736e61c.c8cc","name":"Set Payload","func":"msg.payload = msg.payload.testInput3;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1210,"y":640,"wires":[["605a3a57.68d0a4"]]},{"id":"a7c1883b.9884b8","type":"function","z":"b736e61c.c8cc","name":"Set Payload","func":"msg.payload = msg.payload.testInput1;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1210,"y":220,"wires":[["d9c64d70.401e88"]]},{"id":"2fba7695.81332a","type":"function","z":"b736e61c.c8cc","name":"Set Payload","func":"msg.payload = msg.payload.testInput4;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1210,"y":840,"wires":[["304a4680.d1f87a"]]},{"id":"ba7da14e.0d4b1","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1390,"y":840,"wires":[]},{"id":"ca36fec0.18905","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1390,"y":880,"wires":[]},{"id":"304a4680.d1f87a","type":"toggle","z":"b736e61c.c8cc","name":"Relay 4","onOffTopic":"","onValue":"true","onType":"bool","offValue":"false","offType":"bool","toggleTopic":"","toggleValue":"","toggleType":"any","passOnOff":"","x":1200,"y":880,"wires":[["ca36fec0.18905","c1c84c77.a74aa","a0d2adcb.cee38"]]},{"id":"c1c84c77.a74aa","type":"link out","z":"b736e61c.c8cc","name":"","links":["31b57fed.a66ec"],"x":1335,"y":1000,"wires":[]},{"id":"a504e997.a15f58","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId4","payload":"111","payloadType":"num","x":330,"y":320,"wires":[["4977524a.c304bc"]]},{"id":"710f6358.98587c","type":"inject","z":"b736e61c.c8cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cardId4","payload":"","payloadType":"date","x":350,"y":360,"wires":[["4977524a.c304bc"]]},{"id":"a0d2adcb.cee38","type":"function","z":"b736e61c.c8cc","name":"Show Relay 4 Status","func":"if ( msg.payload == true ) {\n msg.topic = \"cardId4\";\n msg.payload = \"Off\";\n} else if ( msg.payload == false ) {\n msg.topic = \"cardId4\";\n msg.payload = \"On\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1440,"y":920,"wires":[["aa6b82db.12e13","4977524a.c304bc"]]},{"id":"aa6b82db.12e13","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1390,"y":960,"wires":[]},{"id":"30bb2cb7.b46504","type":"function","z":"b736e61c.c8cc","name":"Show Relay 1 Status","func":"if ( msg.payload == true ) {\n msg.topic = \"cardId\";\n msg.payload = \"Off\";\n} else if ( msg.payload == false ) {\n msg.topic = \"cardId\";\n msg.payload = \"On\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1420,"y":300,"wires":[["f598030e.533ea","4977524a.c304bc"]]},{"id":"f598030e.533ea","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1450,"y":340,"wires":[]},{"id":"39a8ac5d.cf2c24","type":"function","z":"b736e61c.c8cc","name":"Show Relay 2 Status","func":"if ( msg.payload == true ) {\n msg.topic = \"cardId2\";\n msg.payload = \"Off\";\n} else if ( msg.payload == false ) {\n msg.topic = \"cardId2\";\n msg.payload = \"On\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1420,"y":500,"wires":[["99503a98.9a4488","4977524a.c304bc"]]},{"id":"99503a98.9a4488","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1450,"y":540,"wires":[]},{"id":"f7c8fed6.9972","type":"function","z":"b736e61c.c8cc","name":"Show Relay 3 Status","func":"if ( msg.payload == true ) {\n msg.topic = \"cardId3\";\n msg.payload = \"Off\";\n} else if ( msg.payload == false ) {\n msg.topic = \"cardId3\";\n msg.payload = \"On\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1420,"y":720,"wires":[["59624a75.b042c4","4977524a.c304bc"]]},{"id":"59624a75.b042c4","type":"debug","z":"b736e61c.c8cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1450,"y":760,"wires":[]},{"id":"bb7e02cd.63878","type":"inject","z":"b736e61c.c8cc","name":"Toggle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"testInput3","payloadType":"str","x":970,"y":680,"wires":[["605a3a57.68d0a4"]]},{"id":"15140af7.54e2b5","type":"inject","z":"b736e61c.c8cc","name":"Toggle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"testInput4","payloadType":"str","x":970,"y":880,"wires":[["304a4680.d1f87a"]]},{"id":"345c6535.38180a","type":"inject","z":"b736e61c.c8cc","name":"Toggle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"testInput1","payloadType":"str","x":970,"y":280,"wires":[["d9c64d70.401e88"]]}] |
Links
https://github.com/TotallyInformation/node-red-contrib-uibuilder
https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki
https://flows.nodered.org/node/node-red-contrib-toggle
https://discourse.nodered.org/t/uibuilder-multiple-button-button-only-work-once/16955
Discover more from Its_All.Lost
Subscribe to get the latest posts sent to your email.