Node and Html5 Logo

How to use Node.js WebSockets on Raspberry Pi

· by Jared · Read in about 12 min · (2353 words)

In order to get interactive with your now serial enabled Raspberry Pi we need a framework that will provide the ability to communicate in real time over the web. For example, if you press a button in a web browser on your Mac it enables a GPIO on your Raspberry Pi/Arduino which subsequently enables a relay connected to a light in your house.

Before we get started: since creating this post back in 2014, much has changed in the landscape of Node. My Git example has been updated due to some changes in socket.io. There are also tons of options besides socket.io. Here is a crowdsourced list.

In my previous post, I described how to release the use of the the serial port on the Raspberry Pi from the kernel so it could freely communicate with other devices like the Arduino. This opens up a world of opportunities for people who are more comfortable in prototyping with the Arduino than they are with Raspberry Pi.

Now, there’s only one problem. If you’re like me, you don’t necessarily want to log in to a ssh shell every time you want to run a «turn on the fan» command or «turn off the light» command. There are several options when it comes to remotely connecting to your Raspberry Pi. Unfortunately, most of these alternatives are not very robust or easy to work with. Luckily, recent web standards have pushed for the development of WebSockets which fit the bill quite nicely.

Diagram

WebSockets: akin to the idea of a TCP socket, WebSockets provide a programmer to create a server and client. The client connects to the server and emits and receives messages to and from the server. Much like a TCP socket, you can send some simple data like a string or integer to the server. There are no real restrictions on the data you can send and therefore no special format. Thus, you are on your own to create a protocol that fits your needs for communicating bidirectionally.

What are the differences you ask? WebSockets can be run simultaneously with a normal HTTP server. A WebSocket client can be created in a javascript file included on your home page. That client then can open a realtime bidirectional websocket connection with the very same machine that is is serving the static web content.

There are several frameworks that support WebSockets but, after a long and thorough search, I decided on the WebSocket implementation on Node.js. My main reason was that I felt like Node.js has the best Web Socket support end to end than any other framework.

Another plus is that the library is written in the language of the web thus it makes integrating into your browser trivial. The folks over at socket.io provide lots of examples for different situations which was a plus when I was knee deep trying to figure this all out.

Lets get going.

Before we get started (for the impatient), a full working example is on Github. Check it out.

Ok, enough lallygagging, lets get to work!

Install Node

For those of you who missed my last post on installing Node.js you should start there and come back. Don’t worry, I’ll be right here!

Install socket.io

First, before we use socket.io we need to install it. Make sure you run this command within the directory you plan on using for this example: npm install socket.io

Here is an example output below:

$ npm install socket.io --save-dev
npm http GET https://registry.npmjs.org/socket.io
npm http 200 https://registry.npmjs.org/socket.io
npm http GET https://registry.npmjs.org/policyfile/0.0.4
npm http GET https://registry.npmjs.org/base64id/0.1.0
npm http GET https://registry.npmjs.org/redis/0.7.3
npm http GET https://registry.npmjs.org/socket.io-client/0.9.16
npm http 304 https://registry.npmjs.org/policyfile/0.0.4
npm http 304 https://registry.npmjs.org/base64id/0.1.0
npm http 200 https://registry.npmjs.org/redis/0.7.3
npm http 304 https://registry.npmjs.org/socket.io-client/0.9.16
npm http GET https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http 200 https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http GET https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http GET https://registry.npmjs.org/uglify-js/1.2.5
npm http GET https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/ws
npm http 304 https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http 304 https://registry.npmjs.org/uglify-js/1.2.5
npm http 304 https://registry.npmjs.org/ws
npm http 304 https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/zeparser/0.0.5
npm http 304 https://registry.npmjs.org/zeparser/0.0.5
npm http GET https://registry.npmjs.org/commander
npm http GET https://registry.npmjs.org/options
npm http GET https://registry.npmjs.org/tinycolor
npm http GET https://registry.npmjs.org/nan
npm http 304 https://registry.npmjs.org/tinycolor
npm http 304 https://registry.npmjs.org/nan
npm http 304 https://registry.npmjs.org/options
npm http 200 https://registry.npmjs.org/commander

> ws@0.4.31 install /Users/jaredwolff/Desktop/Rasp-Pi/Documentation/Websockets/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws
> (node-gyp rebuild 2> builderror.log) || (exit 0)

  CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
  SOLINK_MODULE(target) Release/bufferutil.node
  SOLINK_MODULE(target) Release/bufferutil.node: Finished
  CXX(target) Release/obj.target/validation/src/validation.o
  SOLINK_MODULE(target) Release/validation.node
  SOLINK_MODULE(target) Release/validation.node: Finished
socket.io@0.9.16 node_modules/socket.io
├── base64id@0.1.0
├── policyfile@0.0.4
├── redis@0.7.3
└── socket.io-client@0.9.16 (xmlhttprequest@1.4.2, uglify-js@1.2.5, active-x-obfuscator@0.0.1, ws@0.4.31)

Alternatively you can also install using the included package.json file by running:

npm install

Setup for HTTP Server

I’m going to walk you through a brief example of writing an http server for Node.js and then including WebSockets.

Setup the filesystem

First thing we need to do is create a simple file structure for organization. I ended up using the following:

.
└── public
    └── js

Where public has all my static html files and javascript.

Index.html

Let’s throw together some HTML to be used with javascript later. Let’s make it super simple:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Test</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
  <button id="hello" type="button">
      Click me!
  </button>
</body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.js"></script>
  <script src="/js/client.js"></script>
</html>

The most important thing here is to ensure that the button has an id. I set mine to hello.

You can also notice that there are three javascript files.

  1. socket.io is the client side WebSocket «library» needed to connect to a WebSocket server.

  2. client contains the WebSocket initialization. More on this file in a bit.

Once you create this file, you can place it in your public folder.

Javascript

Note: I have simplified this to the point where you only need two .js files. The client .js and the server.js. The client.js will live in public/js/ while app.js will live in your server root directory.

The socket.io.js client will be referenced to the latest and greatest on socket.io’s CDN while the server will reference and use socket.io.js from your npm install

Node HTTP Server

Now, let’s throw together an example of a Node.js html server. We will enabled it so when requested it will serve index.html file located in the public folder and any javascript that is located in the /public/js/ folder.

Open a text editor of your choice and create a new file in your project’s directory called app.js.

First of all, we’ll need to require a few components that we’ll be using shortly:

var app = require('http').createServer(handler)
var io = require('socket.io')(app)
var url = require('url')
var fs = require('fs')
  • http does the handling requests and serving content
  • url is a helper used to parse requested urls
  • fs is a filesystem helper used to retrieve and read your files

As you can see when the createServer method has been called I gave it an external callback function instead of defining the function inline. There is no special reason for this. (So if you like to declare callback functions inline be my guest!)

Next line down, we want to start the server and listen on an open port. I used port 5000.

app.listen(5000);

Now, let’s write our handler function. The handler function has two parameters request and response.

function handler (req, res) {

Now we want to parse the request to figure out what we need to serve:

var path = url.parse(req.url).pathname;

We can now use the path variable to determine, with some logic, what to do next. For instance, if you were to visit this server’s root directory then the path would equal «/».

Lets make some conditionals that will process the path:

   if (path == '/') {

So let’s think about what we want to serve here. We want the index.html file located in the /public/ directory. So let’s use the filesystem instance to get that file!

        index = fs.readFile(__dirname+'/public/index.html', 
            function(error,data) {

                if (error) {
                    res.writeHead(500);
                    return res.end("Error: unable to load index.html");
                }

                res.writeHead(200,{'Content-Type': 'text/html'});
                res.end(data);
            });

Because this function is asynchronous, it uses a callback to hand you the data once retrieved from the filesystem. In this case I provided a basic callback function for you to use. The callback function has two arguments, error and data; The error argument is only populated with an error message if the file cannot be found (otherwise it’s null). The data argument is the binary data of the file retrieved.

If we do have valid data, we need to indicate what type of content it is. Using the writeHead method of the response argument, we indicate what exactly we’re pushing back to the client. In this case, it’s ‘text/html’.

Finally, we need to end our response to the clients request by sending the data back to the client using the response.end() method. (their web browser.)

**Note: ** for more routes and file types we can follow the same process as above. See full fledged example below.

At this point, we can run the server and see if it serves up index.html Below is a fully functional http server with additional javascript handling:

var app = require('http').createServer(handler)
var io = require('socket.io')(app)
var url = require('url')
var fs = require('fs')


//This will open a server at localhost:5000. Navigate to this in your browser.
app.listen(5000);

// Http handler function
function handler (req, res) {

    // Using URL to parse the requested URL
    var path = url.parse(req.url).pathname;

    // Managing the root route
    if (path == '/') {
        index = fs.readFile(__dirname+'/public/index.html', 
            function(error,data) {

                if (error) {
                    res.writeHead(500);
                    return res.end("Error: unable to load index.html");
                }

                res.writeHead(200,{'Content-Type': 'text/html'});
                res.end(data);
            });
    // Managing the route for the javascript files
    } else if( /\.(js)$/.test(path) ) {
        index = fs.readFile(__dirname+'/public'+path, 
            function(error,data) {

                if (error) {
                    res.writeHead(500);
                    return res.end("Error: unable to load " + path);
                }

                res.writeHead(200,{'Content-Type': 'text/plain'});
                res.end(data);
            });
    } else {
        res.writeHead(404);
        res.end("Error: 404 - File not found.");
    }

}

Note: full example code can be found on Github under the MIT license here: Node.js Websocket Example

Run the server by running the following command:

node app.js

You can now visit your web browser at http://raspberrypi.local:5000 Note: replace raspberrypi with your Raspberry Pi’s network address. With any success your screen should look like this:

Node.js Http server running.

Note: once you’re done make sure you kill the server instance. ctrl + c on the command line.

Note: if you have any problems with testing your code, check out a working example here.

Write a WebSocket Client

Ok! Now that we have a preliminary Node.js web server up and running, let’s get to the good stuff: WebSockets.

Let’s make a file called client.js and place it in the /public/js/ folder. Below includes the 11 lines of code required to get WebSockets up and running on the client side.

var socket = io('http://localhost:5000');

socket.on('example-pong', function (data) {
    console.log("pong");
});

window.addEventListener("load", function(){

  var button = document.getElementById('hello');

  button.addEventListener('click', function() {
      console.log("ping");
      socket.emit('example-ping', { duration: 2 });
  });

});

Most of this for you javascript veterans out there is pretty simple. For those who need an explanation here’s a brief line-by-line:

var socket = io('http://localhost:5000');

Line 1: creates the socket object used to send and receive data (via WebSockets) from the server.

socket.on('example-pong', function (data) {
    console.log("pong");
});

Line 3: creates a callback for whenever the client receives a «pong» event. In this case I just want it to print out «pong» so I know that it received the message.

window.addEventListener("load", function(){

  var button = document.getElementById('hello');

  button.addEventListener('click', function() {
      console.log("ping");
      socket.emit('example-ping', { duration: 2 });
  });

});

Line 7: attaches a click listener to the html button you made back in index.html. Once triggered, it will emit a «ping» event with some data. The duration variable will be used in a bit.

Write the WebSocket Server

Almost there…..

Open back up app.js. Let’s add support for socket.io by creating an IO object. This can be just below the other require statements.

io = require('socket.io')(app)

Next, we need to create a handler for WebSocket events. See below:

// Web Socket Connection
io.sockets.on('connection', function (socket) {

  // If we recieved a command from a client to start watering lets do so
  socket.on('example-ping', function(data) {
      console.log("ping");

      delay = data["duration"];

      // Set a timer for when we should stop watering
      setTimeout(function(){
          socket.emit("example-pong");
      }, delay*1000);

  });

});

Here are some brief descriptions for each line:

io.sockets.on('connection', function (socket) {

Line 2: creates a handler when a connection is opened with a client. The associated callback function performs all the logic when a client connects.

socket.on('example-ping', function(data) {

Line 5: Once we know we’re connected we can now bind to a certain incoming event. In this case the event is called «ping» (look familiar?)

Line 6-14 takes the data argument from the callback argument and uses it to create a delay before the script returns a response.

Note: the setTimeout delay parameter is in milliseconds. You must multiply by 1000 to get seconds.

Note: full example code can be found on Github under the MIT license here: Node.js Websocket Example

Test it!

Just like earlier, let’s run the following command to get your HTTP server up and running:

DEBUG=* node app.js

Fire up a web browser and go to the same address as earlier. If everything is working you should see some interesting things in the console output for the server:

$ node app.js 
info  - socket.io started
debug - client authorized
info  - handshake authorized S8kKoGW6RbeLG0KYs7kB
debug - setting request GET /socket.io/1/websocket/S8kKoGW6RbeLG0KYs7kB
debug - set heartbeat interval for client S8kKoGW6RbeLG0KYs7kB

You should be seeing some keep alive information for your WebSocket client. It’s working!

Open up a javascript console in your web browser and click the button. You should immediately see a «ping» show up in your server console.

  debug - client authorized for 
   debug - websocket writing 1::
   ping

Now, look at your web console. In two seconds you should see a response: pong

Success!

Note: if you have any problems with testing your code, check out a working example here.

Conclusion

Congrats! You have been introduced to the wild world of WebSockets. Now get out there and start hacking! For those of you who are interested I threw together a very simple example http/WebSocket server on Github. It can be located here.

More Raspberry Pi posts

Want more Raspberry Pi Posts? Click here for more!

Are you looking for the best way to get your hardware to production?

I'm Jared Wolff. I write about startups and the nuances at every nook and cranny. If you liked this article and you want to hear about the next one, sign up for my newsletter below. You get one email a week. Plus, I respect your time, no spamming.