Node.js how to work with Serial ports and display data on a beautiful frontend using Highcharts API Part 2
Now you have some basic idea of the technologies we are using here. So let's get our hands dirty.
First, we have to set up the Node js libraries correctly to handle the serial data and open a WebSocket.
So simple what we here going to do is getting data from USB and put them into a GUI to visualize our data nicely.
So for this tutorial, I'm going to use Arduino as my data source. Piratically this will be your sensor or devices which you are using.
Here http://www.highcharts.com/
Highcharts makes it easy for developers to set up interactive charts in their web pages. This has good community support and this is best for our works.
1. First step
clone this repository
https://github.com/akilawickey/nodejs-serial-frontend/tree/master
Here is the Arduino sketch sample.ino which I'm using to emulate data which you can find inside the repository
1: /*
2: * This code will emulate USB data
3: *
4: *
5: */
6: // the setup function runs once when you press reset or power the board
7: void setup() {
8: // initialize digital pin 13 as an output.
9: Serial.begin(9600);
10: }
11: // the loop function runs over and over again forever
12: void loop() {
13: Serial.println("#M,5,!");
14: delay(1000);
15: Serial.println("#T,1,19,29,30,29,26,26,26,29,26,26,30,29,27,26,22,0,26,0,!");
16: delay(1000);
17: Serial.println("#M,12,!");
18: delay(1000);
19: Serial.println("#T,1,29,29,22,29,22,23,26,29,26,26,30,29,26,26,29,0,26,0,!");
20: delay(1000);
21: Serial.println("#M,15,!");
22: delay(1000);
23: Serial.println("#T,1,22,29,30,29,26,26,26,29,26,26,30,22,26,26,29,0,26,0,!");
24: delay(1000);
25: Serial.println("#M,17,!");
26: delay(1000);
27: Serial.println("#T,1,25,29,30,29,25,26,26,29,22,26,30,29,26,26,22,0,26,0,!");
28: delay(1000);
29: Serial.println("#M,20,!");
30: delay(1000);
31: }
here I created two types of data. From M I'm specifying a gauge in the front end think this is a speed of a car.
From T as an example, I'm going to display a Graph of a set of values think this is some temperature values of the battery cell.
2. Next step
Upload this code to the Arduino and you can have a virtual stream of serial data.
Now you have to run the node js backend to get this serial data and put them to a WebSocket which can push data to the GUI.
3. Next step
Now let's move to the Node js backend part
So you have cloned the repository where you have to run the test.js file.
To run it you have to install node modules for serialport,socket.io, express
you can grab it by searching nodejs tutorials on how to install node modules to run an app.
After installing required packages in the terminal type
node test.js
then the node server will start.
and serial data will print on the terminal.
3. final step
And here is the cool step go to the Google Chrome and http://localhost:3000/
and you can open up the GUI here you can see graph is changing according to data.
Understanding the source codes
here is the test.js codehere this will create a simple HTTP server and send USB serial data to the index.html using sockets.
Here express is a framework which is used to rendering HTML pages. HTTP will create a basic HTTP server. Here router directing the path to the index.html file.
serial port is Node.js package to access serial ports for reading and writing
socket.io is will enables real-time, bi-directional communication between web clients and servers. It has two parts: a client-side library that runs in the browser, and a server-side library for Node.js. Here we used it to push serial port data to the web socket
1: /*
2: NODE js library handler to get data from serial ports and push them to the websockets and display in GUI
3: */
4: var express = require("express");
5: var app = express();
6: var http = require('http').Server(app);
7: var path = __dirname + '/';
8: var io = require('socket.io')(http);
9: var router = express.Router();
10: var SerialPort = require('serialport'); /*Serial Port Intitiate*/
11: var port = new SerialPort("/dev/ttyUSB0", {
12: baudrate: 9600,
13: bufferSize: 1 ,
14: rtscts: true ,
15: });
16: var str = "";
17: var count=0;
18: var no_pkt = 0;
19: var flag_V = 0; /*Validation Flag*/
20: /*Socket IO*/
21: router.use("/",function(req,res){
22: res.sendFile(path + "index.html");
23: });
24: app.use("/",router);
25: app.use(express.static(__dirname + '/public'));
26: port.on('data', function (data) {
27: if(flag_V == 0) validateData(data) ;
28: else{
29: str += data;
30: if(data == "!"){
31: myPrint(str);
32: count = 0;
33: io.emit('chat message', str); //send msg to web interface.
34: str=""
35: flag_V = 0;
36: no_pkt++;
37: console.log("data number :" + no_pkt);
38: }
39: count++;
40: }
41: });
42: io.on('connection', function(socket){
43: console.log('User connected'); // this will print when users are connected
44: socket.on('chat message', function(msg){
45: });
46: socket.on('disconnect', function(data) {
47: console.log('-----------------disconnected the socket!-------------');
48: });
49: });
50: /*Create http server*/
51: app.get('/', function(req, res){
52: res.sendFile(__dirname + '/index.html');
53: });
54: http.listen(3000, function(){
55: console.log('listening on :3000');
56: console.log('--------------------Server Started---------------------------');
57: });
58: // these functions are for data validation
59: function myPrint(data) {
60: var i;
61: console.log('Data: ' + data);
62: }
63: // this function will validate data
64: function validateData(x){
65: if(x != "#"){
66: port.flush();
67: }else if( x == "#"){
68: flag_V=1;
69: console.log("Validated");
70: }
71: }
And here is the index.html where we are GUI stuff is working. I think you can get an idea after looking at this code. You can debug inside the browser using console.log commands to check whether data is coming or not. Here we have to import some libraries from high charts and
1: <!--
2: Backend GUI
3: -->
4: <!doctype html>
5: <html>
6: <head>
7: <title>Serial Data Plot </title>
8: <!-- Websocket IO -->
9: <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
10: <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
11: <!-- ANUGULAR SPEEDOMETER AND HOME COMPONENTS-->
12: <!-- Battry_Chart Imports -->
13: <script src="https://code.highcharts.com/highcharts.js"></script>
14: <script src="https://code.highcharts.com/modules/exporting.js"></script>
15: <script src="https://code.highcharts.com/modules/solid-gauge.js"></script>
16: <script src="http://code.highcharts.com/stock/highstock.src.js"></script>
17: <script src="https://code.highcharts.com/modules/solid-gauge.js"></script>
18: <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
19: <script src="https://code.highcharts.com/highcharts-more.js"></script>
20: <script src="https://code.highcharts.com/modules/solid-gauge.js"></script>
21: <script type="text/javascript" src="battryChart.js"></script>
22: <!-- JavaScript -->
23: <!-- <script src="//cdn.jsdelivr.net/alertifyjs/1.8.0/alertify.min.js"></script> -->
24: <!-- Tabs -->
25: <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
26: <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
27: <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
28: <style>
29: body{
30: padding-top:1px;
31: background-image:url("../public/back.jpg");
32: }
33: .navbar{
34: min-height:20px;
35: /*or whatever height you require*/
36: }
37: .six-sec-ease-in-out {
38: -webkit-transition: width 6s ease-in-out;
39: -moz-transition: width 6s ease-in-out;
40: -ms-transition: width 6s ease-in-out;
41: -o-transition: width 6s ease-in-out;
42: transition: width 6s ease-in-out;
43: }
44: .container { margin-top: 50px; }
45: .progress-bar-vertical {
46: width: 50px;
47: min-height: 500px;
48: display: flex;
49: align-items: flex-end;
50: margin-right: 20px;
51: float: left;
52: }
53: .progress-bar-vertical .progress-bar {
54: width: 100%;
55: height: 0;
56: -webkit-transition: height 0.6s ease;
57: -o-transition: height 0.6s ease;
58: transition: height 0.6s ease;
59: }
60: .label {
61: font-size: 23px;
62: font-style: italic;
63: }
64: .row {margin-top: 35px;}
65: .nopadding {
66: padding: 0;
67: margin: 0;
68: }
69: .tablecontainer { width: 1200px; overflow; hidden;}
70: tr {display: block; }
71: th, td { width: 1200px; }
72: tbody { display: block; height: 300px; overflow: auto;}
73: </style>
74: <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
75: <style type="text/css">
76: html { height: 100%; }
77: body { height: 100%;}
78: #map_canvas {
79: position: absolute;
80: height: 60%;
81: width: 40%;
82: float:right;
83: top: 190px;
84: left:700px;
85: }
86: /*border: 3px solid #73AD21;*/
87: #temp{
88: position: absolute;
89: z-index: 999;
90: }
91: </style>
92: </head>
93: <body onload="javascript:Init()" ng-controller="demoController as dm" style="background-color:#262626;color:white;">
94: <!-- Tabs -->
95: <nav class="navbar navbar-default" style="font-family: Lucida Sans Unicode, Lucida Grande, sans-serif;">
96: <div class="container-fluid">
97: <div class="navbar-header">
98: <a class="navbar-brand" href="index.html">Serial Data Plot</a>
99: </div>
100: <ul class="nav navbar-nav">
101: <li class="active"><a href="javascript:void(0)" class="tablinks" onclick="openCity(event, 'home')">Home</a></li>
102: <li><a href="javascript:void(0)" class="tablinks" onclick="openCity(event, 'battery')">data1</a></li>
103: <li><a href="javascript:void(0)" class="tablinks" onclick="openCity(event, 'data')">data2</a></li>
104: <li><a href="javascript:void(0)" class="tablinks" onclick="openCity(event, 'Debug')">data3</a></li>
105: </ul>
106: </div>
107: </nav>
108: </div>
109: <!-- Tab Paris -->
110: <div id="battery" class="tabcontent">
111: <!-- add data here -->
112: </div>
113: <div id="data" class="tabcontent">
114: <!-- add data here -->
115: </div>
116: </div>
117: <div id="home" class="tabcontent">
118: <div class="alert alert-success" style="width: 30%;">
119: <span>
120: <a href="#" id = "msg" class="close" data-dismiss="alert" aria-label="close">×</a>
121: <strong>Success!</strong> Data Recieved.
122: </span>
123: </div>
124: <div class= "col-md-5 nopadding">
125: <div id="temp" style="width: 97%;"></div>
126: </div>
127: <div>
128: <highchart id="container-torque" config="chartConfig" style="width:250px;height:220px;float:left">
129: </div>
130: </div>
131: <div id="Debug" class="tabcontent">
132: <div id="sTitle" style="margin: 20px;
133: font-size: 20px;
134: font-family: Lucida Sans Unicode, Lucida Grande, sans-serif;" > Data Received
135: </div>
136: <div id="test" style="margin-left: 80px;
137: border: 2px solid black;
138: border-radius: 10px;
139: width:1150px;
140: height: 200px;
141: overflow: scroll;
142: font-size: 18px;
143: padding-left: 10px;
144: border-width: 1px;">
145: </div>
146: </div>
147: <script type="text/javascript">
148: function getDateTime() {
149: var now = new Date();
150: var year = now.getFullYear();
151: var month = now.getMonth()+1;
152: var day = now.getDate();
153: var hour = now.getHours();
154: var minute = now.getMinutes();
155: var second = now.getSeconds();
156: if(month.toString().length == 1) {
157: var month = '0'+month;
158: }
159: if(day.toString().length == 1) {
160: var day = '0'+day;
161: }
162: if(hour.toString().length == 1) {
163: var hour = '0'+hour;
164: }
165: if(minute.toString().length == 1) {
166: var minute = '0'+minute;
167: }
168: if(second.toString().length == 1) {
169: var second = '0'+second;
170: }
171: var dateTime = hour+':'+minute+':'+second;
172: return dateTime;
173: }
174: </script>
175: <script>
176: var chart;
177: var batt_temp;
178: var y = [];
179: var speed;
180: var socket = io();
181: socket.on('chat message', function(msg){
182: var res = msg.split(",");
183: time = getDateTime();
184: console.log(res);
185: if(res[0] == 'T'){
186: batt_temp = [];
187: y[0] = [];
188: for(var i=1; i<19; i++) {
189: res[i] = parseInt(res[i], 10);
190: if(res[i]>100){
191: res[i] = 0;
192: }
193: batt_temp.push(res[i]);
194: }
195: y[0] = batt_temp;
196: //console.log(batt_temp)
197: }else if(res[0] == 'M'){
198: speed = res[1];
199: }
200: $("#msg").empty();
201: $("#msg").text(res[0]);
202: });
203: //This is not a highcharts object. It just looks a little like one!
204: var chartConfig = {
205: options: {
206: //This is the Main Highcharts chart config. Any Highchart options are valid here.
207: //will be overriden by values specified below.
208: chart: {
209: type: 'bar'
210: },
211: tooltip: {
212: style: {
213: padding: 10,
214: fontWeight: 'bold'
215: }
216: }
217: },
218: //The below properties are watched separately for changes.
219: //Series object (optional) - a list of series using normal Highcharts series options.
220: series: [{
221: data: [10, 15, 12, 8, 7]
222: }],
223: //Title configuration (optional)
224: title: {
225: text: 'Hello'
226: },
227: //Boolean to control showing loading status on chart (optional)
228: //Could be a string if you want to show specific loading text.
229: loading: false,
230: //Configuration for the xAxis (optional). Currently only one x axis can be dynamically controlled.
231: //properties currentMin and currentMax provided 2-way binding to the chart's maximum and minimum
232: xAxis: {
233: currentMin: 0,
234: currentMax: 20,
235: title: {text: 'values'}
236: },
237: //Whether to use Highstocks instead of Highcharts (optional). Defaults to false.
238: useHighStocks: false,
239: //size (optional) if left out the chart will default to size of the div or something sensible.
240: size: {
241: width: 400,
242: height: 300
243: },
244: //function (optional)
245: func: function (chart) {
246: //setup some logic for the chart
247: }
248: };
249: //-------------------------------Battery---------------------------------------------------
250: chat_battery = Highcharts.chart('temp', {
251: chart: {
252: type: 'line'
253: },
254: title: {
255: text: 'Cell Temperatures'
256: },
257: subtitle: {
258: text: 'Source: VEGA'
259: },
260: yAxis: {
261: title: {
262: text: 'Temperature (°C)'
263: }
264: },
265: plotOptions: {
266: line: {
267: dataLabels: {
268: enabled: true
269: },
270: enableMouseTracking: true
271: }
272: },
273: series: [{
274: name: 'Battery cell Temperatures',
275: data: Math.abs(batt_temp) % 100
276: }]
277: });
278: //------------------Guage-----------------------------------------------------------
279: $(function () {
280: var gaugeOptions = {
281: chart: {
282: type: 'solidgauge',
283: backgroundColor: '#F6F6F6'
284: },
285: title: null,
286: pane: {
287: center: ['50%', '60%'],
288: borderWidth: 0,
289: size: '80%',
290: startAngle: -90,
291: endAngle: 90,
292: background: {
293: backgroundColor: (Highcharts.theme && Highcharts.theme.background2) || '#FFF',
294: innerRadius: '60%',
295: outerRadius: '100%',
296: shape: 'arc'
297: }
298: },
299: tooltip: {
300: enabled: false
301: },
302: // the value axis
303: yAxis: {
304: stops: [
305: [0.1, '#55BF3B'], // green
306: [0.5, '#DDDF0D'], // yellow
307: [0.9, '#DF5353'] // red
308: ],
309: lineWidth: 0,
310: minorTickInterval: null,
311: tickAmount: 2,
312: title: {
313: y: -70
314: },
315: labels: {
316: y: 16
317: }
318: },
319: plotOptions: {
320: solidgauge: {
321: animation: {
322: duration :1,
323: easing: 'easeOutBounce'
324: },
325: dataLabels: {
326: y: 5,
327: borderWidth: 0,
328: useHTML: true
329: }
330: }
331: }
332: };
333: var chartlTorque = Highcharts.chart('container-torque', Highcharts.merge(gaugeOptions, {
334: yAxis: {
335: min: 0,
336: max: 20,
337: title: {
338: text: 'speed'
339: }
340: },
341: series: [{
342: name: 'Speed',
343: data: [1],
344: dataLabels: {
345: format: '<div style="text-align:center"><span style="font-size:25px;color:' +
346: ((Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black') + '">{y:.1f}</span><br/>' +
347: '<span style="font-size:12px;color:silver">* 1000 / min</span></div>'
348: },
349: tooltip: {
350: valueSuffix: ' revolutions/min'
351: }
352: }]
353: }));
354: //---------------------------------------------------------------------------------------------------
355: });
356: function reload(){
357: chat_battery.series[0].setData(batt_temp);
358: }
359: setInterval(reload, 1000);
360: function openCity(evt, cityName) {
361: var i, tabcontent, tablinks;
362: tabcontent = document.getElementsByClassName("tabcontent");
363: for (i = 0; i < tabcontent.length; i++) {
364: tabcontent[i].style.display = "none";
365: }
366: tablinks = document.getElementsByClassName("tablinks");
367: for (i = 0; i < tablinks.length; i++) {
368: tablinks[i].className = tablinks[i].className.replace(" active", "");
369: }
370: document.getElementById(cityName).style.display = "block";
371: evt.currentTarget.className += " active";
372: }
373: function Init(){
374: openCity(event, 'home');
375: // initialize();
376: }
377: </script>
378: </body>
379: </html>
If you have any problems regarding this you can contact me.
Thank you
I really appreciate information shared above. It’s of great help.
ReplyDeleteFull Stack Online Training
Great site and a great topic as well I really get amazed to read this.It was very interesting and meaningful.I gained many unknown information, the way you have clearly explained is really fantastic.
ReplyDeleteFull Stack Training in Chennai | Certification | Online Training Course
Full Stack Training in Bangalore | Certification | Online Training Course
Full Stack Training in Hyderabad | Certification | Online Training Course
Full Stack Developer Training in Chennai | Mean Stack Developer Training in Chennai
Full Stack Training
Full Stack Online Training