@tom123
Happy to help if I can.
To be clear, I am just looking at this as proof of technology to see what an integration could look like. I am going to take it to the implantation of core dashboard nodes for my own use. I am getting reasonably close to having the flutter infrastructure bits done, so the extension to additional nodes should be pretty straight forward. Clearly happy for you to take any ideas.
So the architecture is as follows.
Core driver
- I am very keen (for this implementation to keep the zero-config dashboard). That is use drag and drop to define the dashboard using existing UI widgets
Tointerface with the flows.json file I use the node red Admin API
To achieve this. In Node-Red
There is a flow that using the Admin API extracts UI elements and adds them to a structure that can be sent to the FE. This is executed on flow deploy
[{"id":"a1c66b31.1cb3b8","type":"http request","z":"232a5ba8.40f134","name":"flows","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://localhost:1880/flows","tls":"","persist":false,"proxy":"","authType":"","x":270,"y":740,"wires":[["a09ca52c.0afd78"]]},{"id":"45ecf60b.962e1","type":"inject","z":"232a5ba8.40f134","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":740,"wires":[["a1c66b31.1cb3b8"]]},{"id":"c4143161.475b58","type":"change","z":"232a5ba8.40f134","name":"","rules":[{"t":"set","p":"flows","pt":"global","to":"payload","tot":"msg"},{"t":"set","p":"control","pt":"flow","to":"control","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":740,"wires":[[]]},{"id":"a09ca52c.0afd78","type":"function","z":"232a5ba8.40f134","name":"Extract UI ftrom flow","func":"let flows = msg.payload\n\nlet tabs = flows.filter(elm => elm.type == \"ui_tab\" )\nlet uigroups = flows.filter(elm => elm.type.substring(0,8) == \"ui_group\" )\n\nlet uiTabs = []\n\nfor (let i =0; i < tabs.length; i++){\n tab = tabs[i]\n let elms = uigroups.filter(elm => elm.tab == tab.id)\n elms = elms.sort((a, b) => a.order - b.order)\n console.log(`elms ${elms.length}`)\n let groups = []\n \n for (let j = 0; j < elms.length; j++){\n group = elms[j];\n let groupElms = flows.filter(elm => elm.group == group.id)\n let cleansedElms = []\n // cleansedElms.push(groupElms);\n for (let k =0; k < groupElms.length; k++){\n elm = groupElms[k]\n elm.width = parseInt(elm.width)\n elm.height = parseInt(elm.height)\n if (elm.height == 0) elm.height = 1;\n if (elm.width == 0) elm.width = 1;\n // elm.height = elm.height == 0 ? 1 : elm.height\n \n delete elm.wires\n cleansedElms.push(elm)\n }\n cleansedElms = cleansedElms.sort((a, b) => a.order - b.order)\n groups.push({id : group.id, group_details : group , elements : cleansedElms})\n }\n uiTabs.push({id : tab.id, tab_details: tab, groups : groups})\n}\n\n\nlet control = {\n type : \"control\",\n scope : \"tab_build\",\n tabs : uiTabs\n \n} \n\nmsg.payload = flows\nmsg.control = control\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":740,"wires":[["c4143161.475b58"]]}]
This really just a filter reorganisation of the existing flows. It will over time confirm to an as of now unwritten specification that defines how a thirds party dashboard would talk to node red.
The Flutter app connects to NR via WS and on connect it is sent the flows
[{"id":"2d8ca97.efe01d6","type":"websocket in","z":"232a5ba8.40f134","name":"","server":"d39d1f66.9fff8","client":"","x":100,"y":600,"wires":[["fd863f08.2d7448"]]},{"id":"fd863f08.2d7448","type":"function","z":"232a5ba8.40f134","name":"Received","func":"let count = flow.get(\"count\") ? flow.get(\"count\") : 0 ;\ncount +=1;\nnode.status({fill:\"green\",shape:\"ring\",text:count});\n\nflow.set(\"count\",count)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":260,"y":600,"wires":[["213e1f13.15c3c"]]},{"id":"2f53ef40.b1bda8","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":790,"y":600,"wires":[]},{"id":"213e1f13.15c3c","type":"json","z":"232a5ba8.40f134","name":"","property":"payload","action":"","pretty":false,"x":410,"y":600,"wires":[["a281864.3f99978"]]},{"id":"a281864.3f99978","type":"switch","z":"232a5ba8.40f134","name":"Is it a connection","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"connected","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":600,"wires":[["2f53ef40.b1bda8","168c20a.22557df"],["f9d5537e.f31498"]]},{"id":"f9d5537e.f31498","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":660,"wires":[]},{"id":"d39d1f66.9fff8","type":"websocket-listener","z":"232a5ba8.40f134","path":"/ws/simple","wholemsg":"false"}]
[{"id":"c69f9376.aa5c58","type":"function","z":"232a5ba8.40f134","name":"Set up UI for client","func":"let flows = global.get(\"flows\")\nlet tabs = flows.filter(elm => elm.type == \"ui_tab\" )\nlet uigroups = flows.filter(elm => elm.type.substring(0,8) == \"ui_group\" )\n\nlet uiTabs = []\n\nfor (let i =0; i < tabs.length; i++){\n tab = tabs[i]\n let elms = uigroups.filter(elm => elm.tab == tab.id)\n elms = elms.sort((a, b) => a.order - b.order)\n console.log(`elms ${elms.length}`)\n let groups = []\n \n for (let j = 0; j < elms.length; j++){\n group = elms[j];\n let groupElms = flows.filter(elm => elm.group == group.id)\n let cleansedElms = []\n // cleansedElms.push(groupElms);\n for (let k =0; k < groupElms.length; k++){\n elm = groupElms[k]\n elm.width = parseInt(elm.width)\n elm.height = parseInt(elm.height)\n if (elm.height == 0) elm.height = 1;\n if (elm.width == 0) elm.width = 1;\n // elm.height = elm.height == 0 ? 1 : elm.height\n \n delete elm.wires\n cleansedElms.push(elm)\n }\n cleansedElms = cleansedElms.sort((a, b) => a.order - b.order)\n groups.push({id : group.id, group_details : group , elements : cleansedElms})\n }\n uiTabs.push({id : tab.id, tab_details: tab, groups : groups})\n}\n\n\nlet control = {\n type : \"control\",\n scope : \"tab_build\",\n tabs : uiTabs\n \n} \nmsg.payload = control\n\nmsg.control = control\n\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":180,"wires":[["fa062c22.e54ef8"]]},{"id":"c5bc2cd5.ab24f","type":"http response","z":"232a5ba8.40f134","name":"","x":550,"y":140,"wires":[]},{"id":"8dc46d35.335858","type":"http in","z":"232a5ba8.40f134","name":"","url":"/simple","method":"get","upload":false,"swaggerDoc":"","x":170,"y":140,"wires":[["70c4db30.6572f4"]]},{"id":"70c4db30.6572f4","type":"template","z":"232a5ba8.40f134","name":"Simple Web Page","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!DOCTYPE HTML>\n<html>\n <head>\n <title>Simple Live Display</title>\n <script type=\"text/javascript\">\n var ws;\n var wsUri = \"ws:\";\n var loc = window.location;\n console.log(loc);\n if (loc.protocol === \"https:\") { wsUri = \"wss:\"; }\n // This needs to point to the web socket in the Node-RED flow\n // ... in this case it's ws/simple\n wsUri += \"//\" + loc.host + loc.pathname.replace(\"simple\",\"ws/simple\");\n\n function wsConnect() {\n console.log(\"connect\",wsUri);\n ws = new WebSocket(wsUri);\n //var line = \"\"; // either uncomment this for a building list of messages\n ws.onmessage = function(msg) {\n var line = \"\"; // or uncomment this to overwrite the existing message\n // parse the incoming message as a JSON object\n var data = msg.data;\n //console.log(data);\n // build the output from the topic and payload parts of the object\n line += \"<p>\"+data+\"</p>\";\n // replace the messages div with the new \"line\"\n document.getElementById('messages').innerHTML = line;\n //ws.send(JSON.stringify({data:data}));\n }\n ws.onopen = function() {\n // update the status div with the connection status\n document.getElementById('status').innerHTML = \"connected\";\n //ws.send(\"Open for data\");\n console.log(\"connected\");\n }\n ws.onclose = function() {\n // update the status div with the connection status\n document.getElementById('status').innerHTML = \"not connected\";\n // in case of lost connection tries to reconnect every 3 secs\n setTimeout(wsConnect,3000);\n }\n }\n \n function doit(m) {\n if (ws) { ws.send(m); }\n }\n </script>\n </head>\n <body onload=\"wsConnect();\" onunload=\"ws.disconnect();\">\n <font face=\"Arial\">\n <h1>Simple Live Display</h1>\n <div id=\"messages\"></div>\n <button type=\"button\" onclick='doit(\"click\");'>Click to send message</button>\n <hr/>\n <div id=\"status\">unknown</div>\n </font>\n </body>\n</html>\n","output":"str","x":370,"y":140,"wires":[["c5bc2cd5.ab24f"]]},{"id":"fa062c22.e54ef8","type":"json","z":"232a5ba8.40f134","name":"Convert to a string","property":"payload","action":"","pretty":true,"x":590,"y":180,"wires":[["980c8bc3.e5ef3","af6f7581.3c5ee8"]]},{"id":"6706233.268e8dc","type":"change","z":"232a5ba8.40f134","name":"Set flows","rules":[{"t":"set","p":"payload","pt":"msg","to":"flows","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":180,"wires":[["c69f9376.aa5c58"]]},{"id":"eb00ba02.4ff2c","type":"websocket out","z":"232a5ba8.40f134","name":"","server":"d39d1f66.9fff8","client":"","x":720,"y":280,"wires":[]},{"id":"a2091d00.24d178","type":"link in","z":"232a5ba8.40f134","name":"Ws send ","links":["980c8bc3.e5ef3","1a69f297.2dee65"],"x":595,"y":280,"wires":[["eb00ba02.4ff2c"]]},{"id":"980c8bc3.e5ef3","type":"link out","z":"232a5ba8.40f134","name":"","links":["a2091d00.24d178"],"x":715,"y":180,"wires":[]},{"id":"de664f22.dc9c6","type":"link in","z":"232a5ba8.40f134","name":"","links":["168c20a.22557df"],"x":115,"y":180,"wires":[["6706233.268e8dc"]]},{"id":"af6f7581.3c5ee8","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":730,"y":80,"wires":[]},{"id":"d39d1f66.9fff8","type":"websocket-listener","z":"232a5ba8.40f134","path":"/ws/simple","wholemsg":"false"}]
If you deploy. the full flow and have an instance of node-red running on 2880 (not 1880)
and the gods are looking upon you kindly, and you visit from a chrome browser from a machine where you can http://localhost:2880 to get to node-red it may just work. But there are absolutely promises. This is a POT exercise very much in evolution
https://chrisn-au.github.io/#/
[{"id":"9fdc928d.df1278","type":"subflow","name":"UIBuilder","info":"# The name of this subflow must be UNIQUE.\n\ne.g\n\nTEXT_FIELD_1","category":"","in":[{"x":50,"y":30,"wires":[{"id":"a7e2bc7b.9ddc78"}]}],"out":[{"x":440,"y":40,"wires":[{"id":"a7e2bc7b.9ddc78","port":0}]},{"x":660,"y":140,"wires":[{"id":"ba91f5c2.fdfa9","port":0}]},{"x":440,"y":200,"wires":[{"id":"a7e2bc7b.9ddc78","port":1}]}],"env":[{"name":"TYPE","type":"str","value":"TestBuilder"}],"color":"#DDAA99","inputLabels":["Up stream flow and Testbuilder"],"outputLabels":["To UI Element","To Testbuilder","To UI Element out"]},{"id":"a7e2bc7b.9ddc78","type":"switch","z":"9fdc928d.df1278","name":"FINDME","property":"testbuilder","propertyType":"msg","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":130,"y":160,"wires":[["4d6a222b.9ee56c"],[]]},{"id":"ba91f5c2.fdfa9","type":"function","z":"9fdc928d.df1278","name":"","func":"id = node.id\n\nlet name = flow.get(\"NAME\")\nif (!name) name = env.get(\"NAME\");\nif (name) flow.set(\"NAME\",name)\n\nelse (name = \"GENERIC\")\n\nlet flows = global.get(\"flows\")\nlet local = flows.filter(elm => elm.id == id )\nlet targets = flows.filter(elm => elm.type.substring(0,8) == \"subflow:\")\nlet current = null\nfor(let i = 0; i < targets.length; i++){\n env = targets[i].env\n c = flows.filter(elm => elm.value == name)\n if (c) { current = targets[i]; break}\n}\nif (current.wires[0][0])\n msg.destination = current.wires[0][0]\nmsg.payload = targets\nmsg.local = local\nmsg.id = id\nmsg.name = name\n\nmsg.payload = Math.random(5)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":500,"y":140,"wires":[[]]},{"id":"4d6a222b.9ee56c","type":"change","z":"9fdc928d.df1278","name":"","rules":[{"t":"set","p":"NAME","pt":"flow","to":"NAME","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":140,"wires":[["ba91f5c2.fdfa9"]]},{"id":"a1c66b31.1cb3b8","type":"http request","z":"232a5ba8.40f134","name":"flows","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://localhost:1880/flows","tls":"","persist":false,"proxy":"","authType":"","x":270,"y":740,"wires":[["c4143161.475b58"]]},{"id":"c69f9376.aa5c58","type":"function","z":"232a5ba8.40f134","name":"Set up UI for client","func":"let flows = global.get(\"flows\")\nlet tabs = flows.filter(elm => elm.type == \"ui_tab\" )\nlet uigroups = flows.filter(elm => elm.type.substring(0,8) == \"ui_group\" )\n\nlet uiTabs = []\n\nfor (let i =0; i < tabs.length; i++){\n tab = tabs[i]\n let elms = uigroups.filter(elm => elm.tab == tab.id)\n elms = elms.sort((a, b) => a.order - b.order)\n console.log(`elms ${elms.length}`)\n let groups = []\n \n for (let j = 0; j < elms.length; j++){\n group = elms[j];\n let groupElms = flows.filter(elm => elm.group == group.id)\n let cleansedElms = []\n // cleansedElms.push(groupElms);\n for (let k =0; k < groupElms.length; k++){\n elm = groupElms[k]\n elm.width = parseInt(elm.width)\n elm.height = parseInt(elm.height)\n if (elm.height == 0) elm.height = 1;\n if (elm.width == 0) elm.width = 1;\n // elm.height = elm.height == 0 ? 1 : elm.height\n \n delete elm.wires\n cleansedElms.push(elm)\n }\n cleansedElms = cleansedElms.sort((a, b) => a.order - b.order)\n groups.push({id : group.id, group_details : group , elements : cleansedElms})\n }\n uiTabs.push({id : tab.id, tab_details: tab, groups : groups})\n}\n\n\nlet control = {\n type : \"control\",\n scope : \"tab_build\",\n tabs : uiTabs\n \n} \nmsg.payload = control\n\nmsg.control = control\n\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":180,"wires":[["fa062c22.e54ef8"]]},{"id":"c5bc2cd5.ab24f","type":"http response","z":"232a5ba8.40f134","name":"","x":550,"y":140,"wires":[]},{"id":"8dc46d35.335858","type":"http in","z":"232a5ba8.40f134","name":"","url":"/simple","method":"get","upload":false,"swaggerDoc":"","x":170,"y":140,"wires":[["70c4db30.6572f4"]]},{"id":"70c4db30.6572f4","type":"template","z":"232a5ba8.40f134","name":"Simple Web Page","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!DOCTYPE HTML>\n<html>\n <head>\n <title>Simple Live Display</title>\n <script type=\"text/javascript\">\n var ws;\n var wsUri = \"ws:\";\n var loc = window.location;\n console.log(loc);\n if (loc.protocol === \"https:\") { wsUri = \"wss:\"; }\n // This needs to point to the web socket in the Node-RED flow\n // ... in this case it's ws/simple\n wsUri += \"//\" + loc.host + loc.pathname.replace(\"simple\",\"ws/simple\");\n\n function wsConnect() {\n console.log(\"connect\",wsUri);\n ws = new WebSocket(wsUri);\n //var line = \"\"; // either uncomment this for a building list of messages\n ws.onmessage = function(msg) {\n var line = \"\"; // or uncomment this to overwrite the existing message\n // parse the incoming message as a JSON object\n var data = msg.data;\n //console.log(data);\n // build the output from the topic and payload parts of the object\n line += \"<p>\"+data+\"</p>\";\n // replace the messages div with the new \"line\"\n document.getElementById('messages').innerHTML = line;\n //ws.send(JSON.stringify({data:data}));\n }\n ws.onopen = function() {\n // update the status div with the connection status\n document.getElementById('status').innerHTML = \"connected\";\n //ws.send(\"Open for data\");\n console.log(\"connected\");\n }\n ws.onclose = function() {\n // update the status div with the connection status\n document.getElementById('status').innerHTML = \"not connected\";\n // in case of lost connection tries to reconnect every 3 secs\n setTimeout(wsConnect,3000);\n }\n }\n \n function doit(m) {\n if (ws) { ws.send(m); }\n }\n </script>\n </head>\n <body onload=\"wsConnect();\" onunload=\"ws.disconnect();\">\n <font face=\"Arial\">\n <h1>Simple Live Display</h1>\n <div id=\"messages\"></div>\n <button type=\"button\" onclick='doit(\"click\");'>Click to send message</button>\n <hr/>\n <div id=\"status\">unknown</div>\n </font>\n </body>\n</html>\n","output":"str","x":370,"y":140,"wires":[["c5bc2cd5.ab24f"]]},{"id":"fa062c22.e54ef8","type":"json","z":"232a5ba8.40f134","name":"Convert to a string","property":"payload","action":"","pretty":true,"x":590,"y":180,"wires":[["980c8bc3.e5ef3","af6f7581.3c5ee8"]]},{"id":"45ecf60b.962e1","type":"inject","z":"232a5ba8.40f134","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":740,"wires":[["a1c66b31.1cb3b8"]]},{"id":"504938b6.95778","type":"ui_text","z":"232a5ba8.40f134","group":"2b0407f5.237138","order":2,"width":0,"height":0,"name":"A text value","label":"A new value","format":"{{msg.payload}}","layout":"row-spread","x":1130,"y":220,"wires":[]},{"id":"d8701e43.d457a8","type":"ui_button","z":"232a5ba8.40f134","name":"","group":"9e27eb4c.1219e8","order":0,"width":"2","height":"2","passthru":false,"label":"Button third group","tooltip":"","color":"","bgcolor":"blue","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":1310,"y":140,"wires":[[]]},{"id":"74e0a720.618a18","type":"ui_text","z":"232a5ba8.40f134","group":"6973e9a4.e091b8","order":1,"width":0,"height":0,"name":"","label":"Text 2 fourth group","format":"{{msg.payload}}","layout":"row-spread","x":1310,"y":260,"wires":[]},{"id":"74b78832.4a4888","type":"ui_text","z":"232a5ba8.40f134","group":"6973e9a4.e091b8","order":0,"width":0,"height":0,"name":"","label":"Text 1 fourth group","format":"{{msg.payload}}","layout":"row-spread","x":1310,"y":200,"wires":[]},{"id":"ff4ba8eb.7de138","type":"ui_button","z":"232a5ba8.40f134","name":"","group":"98092a36.c0eef","order":1,"width":0,"height":0,"passthru":false,"label":"button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":1120,"y":140,"wires":[[]]},{"id":"7ff6d2c2.abe8c4","type":"ui_button","z":"232a5ba8.40f134","name":"","group":"2b0407f5.237138","order":1,"width":"6","height":"3","passthru":false,"label":"button","tooltip":"","color":"","bgcolor":"blue","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":910,"y":160,"wires":[[]]},{"id":"e8b9afd8.2cef28","type":"ui_button","z":"232a5ba8.40f134","name":"","group":"2b0407f5.237138","order":3,"width":"1","height":"1","passthru":false,"label":"button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":910,"y":220,"wires":[[]]},{"id":"dc45a8c6.8b89c8","type":"ui_switch","z":"232a5ba8.40f134","name":"","label":"test switch","tooltip":"","group":"2b0407f5.237138","order":5,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"x":930,"y":260,"wires":[[]]},{"id":"2ef3111c.a068d6","type":"ui_text","z":"232a5ba8.40f134","group":"98092a36.c0eef","order":3,"width":0,"height":0,"name":"","label":"A ID","format":"{{msg.payload}}","layout":"row-spread","x":470,"y":400,"wires":[]},{"id":"197f6da1.4d0b9a","type":"inject","z":"232a5ba8.40f134","name":"FINDME","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":460,"wires":[["edee399c.7d0c9"]]},{"id":"76daedd0.91978c","type":"function","z":"232a5ba8.40f134","name":"No subflow","func":"id = node.id\n\nlet flows = global.get(\"flows\")\nlet local = flows.filter(elm => elm.id == id )\nlet targets = flows.filter(elm => elm.type.substring(0,8) == \"subflow:\")\n\nmsg.payload = targets\nmsg.local = local\nmsg.id = id\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":290,"y":820,"wires":[["89b53ca.799bdc"]]},{"id":"89b53ca.799bdc","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":450,"y":820,"wires":[]},{"id":"441f6a5f.20d80c","type":"inject","z":"232a5ba8.40f134","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":820,"wires":[["76daedd0.91978c"]]},{"id":"6c9f154b.9b4854","type":"ui_button","z":"232a5ba8.40f134","name":"FINDME","group":"2b0407f5.237138","order":4,"width":"4","height":"2","passthru":false,"label":"First group","tooltip":"","color":"","bgcolor":"green","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":1000,"y":900,"wires":[["be956e6c.50c498"]]},{"id":"293285e1.79d962","type":"inject","z":"232a5ba8.40f134","name":"FINDME","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":620,"y":940,"wires":[["3316959a.c4c74a"]]},{"id":"3316959a.c4c74a","type":"subflow:9fdc928d.df1278","z":"232a5ba8.40f134","name":"FINDME","env":[{"name":"NAME","value":"TEST","type":"str"}],"x":800,"y":940,"wires":[["6c9f154b.9b4854"],["a5ea1d83.5a03d8"],[]]},{"id":"be956e6c.50c498","type":"debug","z":"232a5ba8.40f134","name":"FINDME","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1190,"y":940,"wires":[]},{"id":"a5ea1d83.5a03d8","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":990,"y":960,"wires":[]},{"id":"edee399c.7d0c9","type":"function","z":"232a5ba8.40f134","name":"Next","func":"let id = node.id\n\nlet flows = global.get(\"flows\")\nlet [local] = flows.filter(elm => elm.id == id)\n\nif (!local){\n return [msg,null,null]\n}\n\ndownstream = local.wires[0]\n\nuis = flows.filter(elm => elm.type.substring(0,3) == \"ui_\")\nif (!uis){\n return [msg,null,null]\n}\n\nuisOut = []\nfor (let i =0; i < downstream.length; i++){\n exists = uis.filter(elm => elm.id == downstream[i])\n \n if (!exists || exists.length == 0) continue\n let out = {\n \"type\": \"data\",\n \"id\": exists[0].id,\n \"data\": Math.random(5).toFixed(5)}\n let msgOut = {\n payload : out\n }\n uisOut.push(msgOut)\n}\n\nreturn [msg,uisOut];\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":270,"y":460,"wires":[["2ef3111c.a068d6","dbc90153.284d48","df77fa36.1d142"],["1a69f297.2dee65"]]},{"id":"dbc90153.284d48","type":"ui_text","z":"232a5ba8.40f134","group":"9e27eb4c.1219e8","order":1,"width":0,"height":0,"name":"","label":"A second test on three ","format":"{{msg.payload}}","layout":"row-spread","x":520,"y":440,"wires":[]},{"id":"df77fa36.1d142","type":"ui_gauge","z":"232a5ba8.40f134","name":"","group":"98092a36.c0eef","order":4,"width":0,"height":0,"gtype":"gage","title":"gauge","label":"units","format":"{{value}}","min":0,"max":10,"colors":["#00b500","#e6e600","#ca3838"],"seg1":"6","seg2":"8","x":470,"y":500,"wires":[]},{"id":"a85e4105.470f9","type":"ui_date_picker","z":"232a5ba8.40f134","name":"","label":"date","group":"2b0407f5.237138","order":6,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","x":910,"y":320,"wires":[[]]},{"id":"68a7e660.22a6b8","type":"ui_colour_picker","z":"232a5ba8.40f134","name":"","label":"","group":"98092a36.c0eef","format":"hex","outformat":"string","showSwatch":true,"showPicker":false,"showValue":false,"showHue":false,"showAlpha":false,"showLightness":true,"square":"false","dynOutput":"false","order":2,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","x":1130,"y":280,"wires":[[]]},{"id":"6706233.268e8dc","type":"change","z":"232a5ba8.40f134","name":"Set flows","rules":[{"t":"set","p":"payload","pt":"msg","to":"flows","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":220,"y":180,"wires":[["c69f9376.aa5c58"]]},{"id":"c4143161.475b58","type":"change","z":"232a5ba8.40f134","name":"","rules":[{"t":"set","p":"flows","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":740,"wires":[[]]},{"id":"eb00ba02.4ff2c","type":"websocket out","z":"232a5ba8.40f134","name":"","server":"d39d1f66.9fff8","client":"","x":720,"y":280,"wires":[]},{"id":"a2091d00.24d178","type":"link in","z":"232a5ba8.40f134","name":"Ws send ","links":["980c8bc3.e5ef3","1a69f297.2dee65"],"x":595,"y":280,"wires":[["eb00ba02.4ff2c"]]},{"id":"980c8bc3.e5ef3","type":"link out","z":"232a5ba8.40f134","name":"","links":["a2091d00.24d178"],"x":715,"y":180,"wires":[]},{"id":"2d8ca97.efe01d6","type":"websocket in","z":"232a5ba8.40f134","name":"","server":"d39d1f66.9fff8","client":"","x":100,"y":600,"wires":[["fd863f08.2d7448"]]},{"id":"fd863f08.2d7448","type":"function","z":"232a5ba8.40f134","name":"Received","func":"let count = flow.get(\"count\") ? flow.get(\"count\") : 0 ;\ncount +=1;\nnode.status({fill:\"green\",shape:\"ring\",text:count});\n\nflow.set(\"count\",count)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":260,"y":600,"wires":[["213e1f13.15c3c"]]},{"id":"2f53ef40.b1bda8","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":790,"y":600,"wires":[]},{"id":"1a69f297.2dee65","type":"link out","z":"232a5ba8.40f134","name":"","links":["a2091d00.24d178"],"x":445,"y":560,"wires":[]},{"id":"227cbbbf.a490a4","type":"ui_text","z":"232a5ba8.40f134","group":"98092a36.c0eef","order":4,"width":0,"height":0,"name":"","label":"text","format":"{{msg.payload}}","layout":"row-spread","x":1120,"y":340,"wires":[]},{"id":"168c20a.22557df","type":"link out","z":"232a5ba8.40f134","name":"Connected event","links":["de664f22.dc9c6"],"x":715,"y":540,"wires":[]},{"id":"de664f22.dc9c6","type":"link in","z":"232a5ba8.40f134","name":"","links":["168c20a.22557df"],"x":115,"y":180,"wires":[["6706233.268e8dc"]]},{"id":"213e1f13.15c3c","type":"json","z":"232a5ba8.40f134","name":"","property":"payload","action":"","pretty":false,"x":410,"y":600,"wires":[["a281864.3f99978"]]},{"id":"a281864.3f99978","type":"switch","z":"232a5ba8.40f134","name":"Is it a connection","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"connected","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":600,"wires":[["2f53ef40.b1bda8","168c20a.22557df"],["f9d5537e.f31498"]]},{"id":"f9d5537e.f31498","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":660,"wires":[]},{"id":"af6f7581.3c5ee8","type":"debug","z":"232a5ba8.40f134","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":730,"y":80,"wires":[]},{"id":"2b0407f5.237138","type":"ui_group","name":"First group","tab":"1e24552a.a5441b","order":1,"disp":true,"width":"6","collapse":false},{"id":"9e27eb4c.1219e8","type":"ui_group","name":"Third group","tab":"1e24552a.a5441b","order":3,"disp":true,"width":"6","collapse":false},{"id":"6973e9a4.e091b8","type":"ui_group","name":"Fourth group","tab":"1e24552a.a5441b","order":4,"disp":true,"width":"6","collapse":false},{"id":"98092a36.c0eef","type":"ui_group","name":"Second group","tab":"1e24552a.a5441b","order":2,"disp":true,"width":"6","collapse":false},{"id":"d39d1f66.9fff8","type":"websocket-listener","z":"232a5ba8.40f134","path":"/ws/simple","wholemsg":"false"},{"id":"1e24552a.a5441b","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
I am sorry I can give very little support on this if it does not work. I will be over the next week or so uploading the flutter POT to Github. I will send you the link.
BTW here is a copy of the wip dart file which implements that JSON format sent from node-red
node-red-control.dart.txt (11.5 KB)
Hope this helps