How to Get Interactive with Websockets Using Raspberry Pi

Jared Wolff · 2017.5.4 · 11 Minute Read · raspberry pi · websockets

Intro image

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, first you press a button in a web browser. Your Raspberry Pi receives the command and 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.

So how can you connect to your device in real time?

Enter Websockets

Diagram

WebSockets are similar 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.

The main advantage?

All of this can happen in real time. No reloading webpages.

There are no real restrictions on the data you can send. There is no special format. Thus, you are on your own to create a protocol that fits your needs for communicating bidirectionally.

Why Websockets?

WebSockets can be run simultaneously with a normal HTTP server. This allows a javascript WebSocket client to make a real-time connection to your server.

There are several frameworks that support WebSockets. 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.

Another plus is that the library is written in the language of the web. That way 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.

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!

Last Modified: 2020.3.7

Subscribe here!

You may also like

Deploy HomeBridge On Raspberry Pi Using Resin.io

I’ve been nerding out. It’s something I just can’t stop sometimes. Most recently working on making our home more connected and efficient. Part of the effort was experimenting with…

Cross Compiling on Mac OSX for Raspberry Pi

In my previous posts, I came to the realization that the Raspberry Pi is not very fast! This results lots of chair spinning time while waiting for my projects to compile. A After I…

Resistive Water Level Measurements

More tinkering happened today. I also had time to make an organizer for my prototyping setup. More details after the break! There are a bunch of ways that you can do measure liquid…