Lab: Serial Communication with Node.js

Originally written on October 3, 2014 by Tom Igoe
Last modified on September 29, 2015 by Tom Igoe

Introduction

You can write programs that handle serial communication in many different languages. This page introduces how to do it using node.js. Node is a JavaScript environment that allows user to write web server programs. Below, you’ll use it to connect a microcontroller to a web browser using the node.js programming environment, HTML, and JavaScript.

To get the most out of this tutorial, you should know what a microcontroller is and how to program them. You should also understand asynchronous serial communication between microcontrollers and personal computers. You should also understand the basics of command line interfaces.

You will struggle with this tutorial if you aren’t familiar with JavaScript.  For more on JavaScript, see Douglas Crockford’s JavaScript: The Good Parts, Manuel Kiessling’s  the Node Beginner Book, or Shelley Powers’ Learning Node.

This Node video will help in understanding this lab. The code in the video is different than this lab, but the basic concepts are similar. This lab simplifies the process a bit.

Node.js

The JavaScript programming language is mainly used to add interactivity to web pages. All modern browsers include a JavaScript interpreter, which allows the browser to run JavaScript code that’s embedded in a web page. Google’s JavaScript engine is called v8, and it’s available under an open source license. Node.js wraps the v8 engine up in an application programming interface that can run on personal computers and servers. On your personal computer, you run it through the command line interface.

Node was originally designed as a tool for writing server programs, but it can do much more. It has a library management system called node package manager or npm that allows you to extend its functionality in many directions. There is also an online registry of node libraries, npmjs.org. You can download libraries from this registry directly using npm. Below you’ll see npm used to add both serial communication functionality and a simple server programming library to node.

To get started, download the node.js installer and install it on your computer. Then open your command line terminal. On OSX, open the Terminal app, which can be found in the Applications/Utilities directory. On Windows, go to the start menu and type cmd then press enter to open a window to the command line.

Image: OSX and Windows command line interfaces

If you’ve never used a command line interface, check out this tutorial.  From here on out, you’ll see the command prompt indicated like this:

$ 

Any commands you need to type will follow the $ symbol. The actual command prompt will vary depending on your operating system. On Windows, it’s typically this: >. On most Unix and Linux systems, including OSX, it’s $.

When you’ve installed node.js, type this command at the command prompt to get the version of node that you’re running:

$ node -v

If you installed it correctly, you’ll get a version number like 0.12.7. The node installer should also install the node package manager (npm). You can check that in the same way:

$ npm -v

Once node and npm are in place, you’re ready to create a new project. Related video: Install the serialport Node module

Hello from Node.js

There’s a library for node.js that allows you to communicate over your computer’s serial ports called serialport. If your computer’s connected to the internet, you can download install it automatically using npm. Create a new directory for your project:

$ mkdir nodeSerialExample

Then change directories to that directory:

$ cd nodeSerialExample

Now make a new text file in that directory with the name index.js.  This will be your main program file.  Add the following text to the file:


console.log("Hello, and welcome to node.");

Save the file, then run it by typing the following on the command line:

$ node index.js

Your program should print out:

Hello, and welcome to node.

The program will finish and return to the command line when it’s done. Seeing that script running is indication that node.js is fully working on your machine.

You can also pass information into your node program using the process arguments on the command line.  Change your program like so:


var name = process.argv[2];
console.log("Hello, and welcome to node," + name);

Run this as you did before, but add your name after the name of your program when you invoke it, like so:

$ node index.js Tom

You’ll get the following:

Hello, and welcome to node, Tom

 

The command line arguments are passed to your program as an array. The name was the third word you typed, or array element 2 (counting “node” as element 0 and “index.js” as element 1).  This is a useful technique, and you’ll see it later to pass the program your serial port’s name.

The Node Serialport Library

You’ll also need the node serialport library, which you can install from the npm registry like so:

$ npm install serialport

This command will make a new subdirectory called node_modules, and in that directory it will install all the necessary assets for the serialport library.  You’ll see a lot of text go by, and hopefully no errors. If you get errors, consult npmjs.org and the github wiki for serialport. Now you’re ready to use the serialport library.

The first thing you’ll want is a list of the serial ports. Replace the text of your program file with the following:

var serialport = require('serialport');
var SerialPort = serialport.SerialPort;

// list serial ports:
serialport.list(function (err, ports) {
  ports.forEach(function(port) {
    console.log(port.comName);
  });
});

When you run this script, you’ll get a list of your serial ports like so:

$ node ListPorts.js 
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.Bluetooth-Modem
/dev/cu.usbmodem1411

If you’ve got an Arduino plugged in via USB, you should see the name of your board’s port there as well. In the example above, which was run on OSX, the Arduino’s port is /dev/cu.usbmodem1411.

The process of using the serialport library will be the same every time:

    • initialize the serialport library
    • open the serial port
    • set up the callback functions and let them do the rest

Which are all explained below.

Opening the Serial Port

To open a serial port in node, you include the library at the beginning of  your script, and make a local instance of the library in a variable. Make a new script or replace index.js with the following:

var serialport = require('serialport');// include the library
   SerialPort = serialport.SerialPort; // make a local instance of it
   // get port name from the command line:
   portName = process.argv[2];

Then you open the port using new() like so:

var myPort = new SerialPort(portName, {
   baudRate: 9600,
   // look for return and newline at the end of each data packet:
   parser: serialport.parsers.readline("\n")
 });

Note the serial parameters, which are passed to the new() function after the port name, as a list. The first item is the baud rate, and the second is a parser, telling the serial library how to interpret data when it comes in. It should read all the incoming data as a line of text, and generate a new data event when it sees  a newline (“\n”). The parser is doing the same thing as Processing’s Serial.bufferUntil() function does.

The Program Won’t Stop!

When you run this program now, it won’t automatically stop and return to the command line. To stop it, you’ll need to type control-C in the terminal window to stop it.   The new instance of Serialport created a software object that listens for events from the serial port. Any node.js script that creates an event listener like this will run until you explicitly stop it.  Both the serialport library and the http library, which you’ll use below, generate event listeners and will need to be explicitly stopped using control-C.

Serialport Library Events

The serialport library, like most node.js libraries, is event-based. This means that when the program is running, the operating system and the user’s actions will generate events and the program will provide functions to deal with those events called callback functions.

The main events that the serial library will deal with are when a serial port opens, when it closes, when new data arrives, and when there’s an error. The new data event in particular performs the same function as Processing’s serialEvent() function.  Once you’ve made an instance of the serialport library using the new() function as shown above, you define what functions will get called when each event occurs by using serialport.on() like so:

myPort.on('open', showPortOpen);
myPort.on('data', sendSerialData);
myPort.on('close', showPortClose);
myPort.on('error', showError);

The functions that are called by the event are the callback functions. In the example above, when the serial port is opened, the showPortOpen function will get called. When new data arrives, the sendSerialData function will get called, and so forth. If the event generates any parameters (for example a new data event will have the data as a parameter), those parameters will get passed to the callback function.

Write the callback functions for these events like so:

function showPortOpen() {
   console.log('port open. Data rate: ' + myPort.options.baudRate);
}

function sendSerialData(data) {
   console.log(data);
}

function showPortClose() {
   console.log('port closed.');
}

function showError(error) {
   console.log('Serial port error: ' + error);
}

Now you’ve got enough of a program to see some results. Upload a simple serial output sketch to your Arduino such as the AnalogReadSerial example or the sketch shown in the Analog Input Lab, you’ll be able to see its output from this script. Save the script. Then invoke it as follows, replacing portname with the name of your serial port:

$ node index.js portname

For example, on OSX, if you haven an Arduino attached to the serial port called /dev/cu.usbmodem1411, then you’d type:

$ node index.js /dev/cu.usbmodem1411

When the port opens, you’ll see the message from the showPortOpen() function, then the output from the Arduino. Here’s the output from AnalogReadSerial:

port open. Data rate: 9600

266
276
261

To send serial output from node.js to the Arduino, use the serialport write() function like so:

myPort.write("Hello");

That’s all it takes to read and write serial data from node.js to a microcontroller. Node.js is designed for writing web server applications, however, so in the following steps you’ll see how to connect a serial port to your web browser using more of node.js.

Connecting from the Browser to the Node Program

In order to connect your node.js program to a web page, your program needs to listen for messages from the web browser, and the web pages need to connect to the program. To do this, you’ll use a connection called a webSocket. WebSockets are connections between web clients and servers that function a bit like serial ports. Both serial connections and webSocket connections are data streams, in which the first byte sent into the stream on one end is the first byte read out on the other end. Data streams are common programming structures and you’ll see them used in lots of places. They connect programs to files on your computer, or client programs to server programs, or desktop programs to serial or network ports.  The diagram below shows how the browser connects to your node.js program and how the node.js program connects to the serial port.

workflow

There’s a node.js library your script will need to listen for webSocket connections that you should install using  the node package manager like so:

$ npm install ws

Once it’s successfully installed, include it with your previous script by adding the following at the beginning of  the script, right after you included the serialport library:

var WebSocketServer = require('ws').Server;

Following that, you need to use new() to make a new instance of the webSocket servver, and then configure a few parameters, and finally start the server:

var SERVER_PORT = 8081;               // port number for the webSocket server
var wss = new WebSocketServer({port: SERVER_PORT}); // the webSocket server
var connections = new Array;          // list of connections to the server

The server will operate on port 8081, so when you write a client-side JavaScript program, you’ll connect to that port number. Your browser will be a client of the node.js script, and the script will be running as a server. Just as there are serialport event listener functions, there will be webSocket event listener functions too. You’ll need to listen for a webSocket connection event, and once connected, you’ll need to listen for incoming messages, and for the webSocket to close.

There can be multiple webSocket clients at any one time, so the server maintains an array to keep track of all the clients. That array is called connections, and every time a new client connects, the client is added to the array using the .push() function. When a client closes, it’s removed from the array using the .slice() function.

Here are the webSocket event listeners:

wss.on('connection', handleConnection);

function handleConnection(client) {
 console.log("New Connection"); // you have a new client
 connections.push(client); // add this client to the connections array

 client.on('message', sendToSerial); // when a client sends a message,

 client.on('close', function() { // when a client closes its connection
 console.log("connection closed"); // print it out
 var position = connections.indexOf(client); // get the client's position in the array
 connections.splice(position, 1); // and delete it from the array
 });
}

You’ll also need to modify the serialport data event listener to send data to the webSocket, and vice versa. Here’s a function to send webSocket data to the serial port. Put this after the serial event listeners above:

function sendToSerial(data) {
 console.log("sending to serial: " + data);
 myPort.write(data);
}

To parallel that, here’s a function to send serial data to the webSocket clients. Put this after the webSocket event listeners:

// This function broadcasts messages to all webSocket clients
function broadcast(data) {
 for (myConnection in connections) {   // iterate over the array of connections
  connections[myConnection].send(data); // send the data to each connection
 }
}

Finally, you need to add the highlighted lines below to the sendSerialData() function. This will send the latest data to all available webSocket clients:

function saveLatestData(data) {
   console.log(data);
   // if there are webSocket connections, send the serial data
   // to all of them:
   if (connections.length > 0) {
     broadcast(data);
   }
}

Now you’ve got a complete script for connecting serial data to a webSocket server, and serving that to a client through a browser. Next you need a web page with its own embedded JavaScript to connect to his script.

Using the Data in HTML

The real value in connecting the serial port to a server is to generate dynamic HTML from the sensor data. You can do this by making an HTML page that includes some JavaScript to request data from the server, and serving that HTML page from a directory called public in your project directory. First, create the directory and change directory into it:

$ mkdir public
$ cd public

Then create a new document, index.html, in this directory. Don’t enter any elements, just the bare skeleton of a page:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8">
 <title></title>
 </head>
 <body>

 </body>
</html>

Including P5.js

If you’re used to Processing, then P5.js is a really good way to get started with JavaScript in the browser. It gives you the same structure as Processing, but in JavaScript.  To use it, you’ll need to include P5.js using script tags in the head of your HTML.

First, you need to include P5.js and its DOM manipulation library in the document head. You can either download P5.js and its libraries directly, or you can include them from P5’s content delivery site. The example below will do it the latter way.

Add an empty script tag as well, because that’s where you’ll write your code:

<!DOCTYPE html>
<html>
 <head>
 <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.8/p5.min.js"></script>
 <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.8/addons/p5.dom.js"></script>
 <script type="text/javascript">
 </script>

 <meta charset="utf-8">
 <title></title>
 </head>
 <body>

 </body>
</html>

P5.js uses setup() and draw() methods, just like Processing, and similar to Arduino. You’re going to create an HTML text div element in the document, and when you receive serial data, you’ll use it to set the text and the position of the text element.  Start with the setup() and draw() and global variables for the text element and a webSocket connection like so (from here on out, the rest of the HTML will be omitted for brevity. Everything you see below will be done in the head of the document between

<script type="text/javascript">

and

</script>:
  var text;		// variable for the text div you'll create
  var socket = new WebSocket("ws://localhost:8081");

 function setup() {
 }

 function draw() {
 }

In the setup function, add listeners for the webSocket, and create the text div and position it. You’ll write the listener functions a bit later:

   // The socket connection needs two event listeners:
    socket.onopen = openSocket;
    socket.onmessage = showData;

    // make a new div and position it at 10, 10:
    text = createDiv("Sensor reading:");
    text.position(10,10);
 }

Next, write the openSocket and showData functions which are the callback functions for the webSocket’s event listeners:

 function openSocket() {
    text.html("Socket open");
    socket.send("Hello server");
  }

function showData(result) {
    // when the server returns, show the result in the div:
    text.html("Sensor reading:" + result.data);
    xPos = int(result.data);        // convert result to an integer
    text.position(xPos, 10);        // position the text
  }

This last function changes the text inside the div. It also moves the div’s horizontal position. Whenever new data arrives from the server, this function will be called automatically, so there’s no need for a draw() function.

Save this file in the public directory, then restart the server and open the file in your browser.  You should see the text moving across the screen, depending on the sensor, as in the video below:

browser-animation from ITP on Vimeo.

Once you’ve got a server program serving data from a serial device to a browser, you have potential to make all kinds dynamic interfaces that combine HTML and physical interfaces.

Notice that the server program doesn’t actually parse the serial data; it just treats it as a string, and passes that string on to the client. The client does any parsing work; in this case, converting the numeric string to an integer. This means you can keep the server very simple, so you only need to make sure the two endpoints (the microcontroller and the web client) are speaking the same protocol. JavaScript has many functions for manipulating strings, particularly comma-separated values. So consider sending your data from Arduino as comma-separated values.

Adding Handshaking (aka Call-and-Response)

Because the wsServer.js script passes through messages through without parsing, you can add handshaking to this application by modifying only the Arduino program and the P5.js script. When you add handshaking the Arduino will only send new data when it receives a byte of data in the serial port. It allows you to control the flow of data better.

To implement handshaking, make the following change in the Arduino sketch. Wrap the contents of the loop() in the Arduino sketch in an if statement like so:

void loop() {
  if (Serial.available()) {
    char input = Serial.read();
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // print out the value you read:
    Serial.println(sensorValue);
    delay(1); // delay in between reads for stability
 }
}

Then add the following line to the end of the showData() function in your P5.js script:

 function showData(result) {
 // when the server returns, show the result in the div:
 text.html("Sensor reading:" + result.data);
 xPos = int(result.data); // convert result to an integer
 text.position(xPos, 10); // position the text
 socket.send('a');        // send a byte to get the Arduino to send new data
 }

You won’t need to change the wsServer.js script at all. Just restart it after you upload to the Arduino, and reload the page in the browser. When the client connects to the server, it will send the “Hello” message, which will be sent from the server to the Arduino, and that will trigger the Arduino to start sending data.

For more info:

A more complete set of code for this lab can be found on gitHub.