Node-Red – Display Real Time Arduino Sensor Readings thru WebSocket
This flow will show you how to display real-time Arduino sensor readings on our Node-Red application using WebSocket.
It displays the sensor readings in a custom HTML/CSS/Javascript user interface that I have written.
Companion Writeup
Node-Red – Display Arduino Sensor Readings thru WebSocket
[{"id":"3de7f87250c169e2","type":"websocket in","z":"b93e9b260d53649d","name":"","server":"8dbef215b4efa8a7","client":"","x":400,"y":600,"wires":[["ce592e4dc2db096b"]]},{"id":"2a88ff5bce73144b","type":"debug","z":"b93e9b260d53649d","name":"debug 5","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":920,"y":680,"wires":[]},{"id":"2d0866385f48fa75","type":"http in","z":"b93e9b260d53649d","name":"","url":"/sensorReadings","method":"get","upload":false,"swaggerDoc":"","x":380,"y":240,"wires":[["f14f34c54fcfaed0"]]},{"id":"3f31af6081e79e4b","type":"inject","z":"b93e9b260d53649d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":500,"y":500,"wires":[["ad3e1c6852897a8a"]]},{"id":"ad3e1c6852897a8a","type":"websocket out","z":"b93e9b260d53649d","name":"","server":"8dbef215b4efa8a7","client":"","x":930,"y":500,"wires":[]},{"id":"ce592e4dc2db096b","type":"function","z":"b93e9b260d53649d","name":"Clear Session","func":"msg._session = \"\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":600,"wires":[["2a88ff5bce73144b","ad3e1c6852897a8a"]]},{"id":"f14f34c54fcfaed0","type":"template","z":"b93e9b260d53649d","name":"Javascript","field":"payload.script","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"const sensorValues = document.querySelector(\"#sensor-values\");\n\nconst sensorData = [];\n\n/*\n Plotly.js graph and chart setup code\n*/\nvar sensorChartDiv = document.getElementById(\"sensor-chart\");\n\n// History Data\nvar sensorTrace = {\n x: [],\n y: [],\n name: \"LDR/Photoresistor\",\n mode: \"lines+markers\",\n type: \"line\",\n};\n\nvar sensorLayout = {\n autosize: false,\n width: 800,\n height: 500,\n colorway: [\"#05AD86\"],\n margin: { t: 40, b: 40, l: 80, r: 80, pad: 0 },\n xaxis: {\n gridwidth: \"2\",\n autorange: true,\n },\n yaxis: {\n gridwidth: \"2\",\n autorange: true,\n },\n};\nvar config = { responsive: true };\n\nPlotly.newPlot(sensorChartDiv, [sensorTrace], sensorLayout, config);\n\n// Will hold the sensor reads\nlet newSensorXArray = [];\nlet newSensorYArray = [];\n\n// The maximum number of data points displayed on our scatter/line graph\nlet MAX_GRAPH_POINTS = 50;\nlet ctr = 0;\n\nfunction updateChart(sensorRead) {\n if (newSensorXArray.length >= MAX_GRAPH_POINTS) {\n newSensorXArray.shift();\n }\n if (newSensorYArray.length >= MAX_GRAPH_POINTS) {\n newSensorYArray.shift();\n }\n newSensorXArray.push(ctr++);\n newSensorYArray.push(sensorRead);\n\n var data_update = {\n x: [newSensorXArray],\n y: [newSensorYArray],\n };\n\n Plotly.update(sensorChartDiv, data_update);\n}\n\n// WebSocket support\nvar targetUrl = `ws://${location.host}/ws/readings`;\nvar websocket;\nwindow.addEventListener(\"load\", onLoad);\n\nfunction onLoad() {\n initializeSocket();\n}\n\nfunction initializeSocket() {\n console.log(\"Opening WebSocket connection Node-Red Server...\");\n websocket = new WebSocket(targetUrl);\n websocket.onopen = onOpen;\n websocket.onclose = onClose;\n websocket.onmessage = onMessage;\n}\nfunction onOpen(event) {\n console.log(\"Starting connection to WebSocket server..\");\n}\nfunction onClose(event) {\n console.log(\"Closing connection to server..\");\n setTimeout(initializeSocket, 2000);\n}\nfunction onMessage(event) {\n console.log(\"WebSocket message received:\", event);\n updateValues(event.data);\n updateChart(event.data);\n}\n\nfunction sendMessage(message) {\n websocket.send(message);\n}\n\nfunction updateValues(data) {\n sensorData.unshift(data);\n if (sensorData.length > 20) sensorData.pop();\n sensorValues.value = sensorData.join(\"\\r\\n\");\n}\n","output":"str","x":580,"y":340,"wires":[["ce0f6f510b40daa3"]]},{"id":"ce0f6f510b40daa3","type":"template","z":"b93e9b260d53649d","name":"CSS","field":"payload.style","fieldType":"msg","format":"handlebars","syntax":"mustache","template":":root {\n --color-white: #fff;\n --color-dark-variant: #f3f5f8;\n --border-radius-1: 0.4rem;\n}\n\n* {\n margin: 0;\n padding: 0;\n outline: 0;\n}\n\nbody {\n width: 100vw;\n height: 100vh;\n overflow-x: hidden;\n background: var(--color-dark-variant);\n}\nh1 {\n margin-top: 0.4rem;\n margin-left: 1.6rem;\n}\n\n.container {\n display: grid;\n width: 96%;\n margin: 0 auto;\n gap: 1.8rem;\n grid-template-columns: 14rem auto;\n}\naside {\n margin-top: 1.4rem;\n height: 100vh;\n background: var(--color-white);\n border-radius: var(--border-radius-1);\n padding: 0.4rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n.values {\n text-align: center;\n resize: none;\n height: 100%;\n font-size: 1.5rem;\n font-weight: bold;\n}\n\nmain {\n margin-top: 1.4rem;\n background: var(--color-white);\n padding: 0.4rem;\n /* border: 1px solid red; */\n text-align: center;\n}\n\n.wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n","output":"str","x":730,"y":240,"wires":[["0f0def2d6e7cb078"]]},{"id":"0f0def2d6e7cb078","type":"template","z":"b93e9b260d53649d","name":"HTML","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n <title>Node-Red WebSocket</title>\n <style>{{{payload.style}}}</style>\n <link rel=\"icon\" href=\"./favicon.ico\" type=\"image/x-icon\" />\n <script src=\"https://cdn.plot.ly/plotly-2.16.1.min.js\"></script>\n </head>\n <body>\n <h1>Node-Red WebSocket</h1>\n <div class=\"container\">\n <aside>\n <h2>Real time values</h2>\n <textarea\n id=\"sensor-values\"\n name=\"\"\n class=\"values\"\n cols=\"10\"\n rows=\"20\"\n ></textarea>\n </aside>\n <main class=\"chart\">\n <h2>LDR/Photoresistor</h2>\n <div class=\"wrapper\">\n <div id=\"sensor-chart\" class=\"sensor-chart\"></div>\n </div>\n </main>\n </div>\n <script>{{{payload.script}}}</script>\n </body>\n</html>\n","output":"str","x":870,"y":340,"wires":[["b48f6f14310d044c"]]},{"id":"b48f6f14310d044c","type":"http response","z":"b93e9b260d53649d","name":"","statusCode":"","headers":{},"x":990,"y":240,"wires":[]},{"id":"d24e9af23649e742","type":"comment","z":"b93e9b260d53649d","name":"Web Application","info":"","x":380,"y":180,"wires":[]},{"id":"cc3002e58ce69bc3","type":"comment","z":"b93e9b260d53649d","name":"WebSocket Server","info":"","x":390,"y":440,"wires":[]},{"id":"8dbef215b4efa8a7","type":"websocket-listener","path":"/ws/readings","wholemsg":"false"}]