Wednesday, March 26, 2014

BeagleBone Node.js project updated with Socket.io

After I finished my last project, I realized I wasn't entirely satisfied with it. While it basically functioned, relying on it doing a whole page refresh every so often just didn't seem "elegant". As well, when you changed the settings in one browser, they didn't change in other browsers, which, again, didn't seem very slick.

I figured I had to learn to use the Socket.io library anyway to eventually communicated with my Arduino DigiX, so why not update the BeagleBone project to "socketize" it? Naturally, this took a bit of a learning curve, but I like the results.

The project hardware setup is exactly the same as my previous post - using the BeagleBone and the BlueLine Innovations PowerCost Monitor. I have also gotten smart and finally signed up for GitHub and here is a link to the complete source code:


Finally, I have done a YouTube overview of the functionality of the project:



In case it isn't clear in the video, here is what the web page looks like:



The software uses all the libraries from the previous version (Request, Flot, jQuery etc.). The only addition is Socket.io, however, I ended up practically rewriting the application from scratch to accommodate the change!

Server side - App.js

 

Some of the elements of App.js from the last version of this project are the same, so I won't go into detail on them such as using the Request library to do the screen scrape and then popping that into the TingoDB/Mongoose database. However, it seems like Socket.io is only "semi compatible" with Express and you need to make several changes from the default Express setup.

Again, there are many, many good tutorials out there by folks who understand this at a deeper level. Below is just my description of how I got this to work. Here are a couple of good Socket.io tutorials I found along the way that may be helpful:



Here is the section of App.js setting up Socket.io, which seems to have to come later in the App.js file than the usual "require" statements at the beginning:

var io = require('socket.io').listen(server);

app.set('port', process.env.PORT || 3008);

io.set('log level', 1); 


In my last version of this project the "app.get" and "app.post" functions did most of the heavy lifting of rendering information over to web page. Now with Socket.io, the "app.post" section is completely gone since the button pushing is now handled by Socket.io rather than the traditional POST method.

The main action is now all within this one big Socket.io loop:

io.sockets.on('connection', function(socket) {
    console.log('A new user connected!');
    Readings.find({}, {}, {
        sort: {
            'time': -1
        },
        limit: process.env.SAMPLES
    }, function(err, readings) {
        socket.broadcast.emit('readingsData', readings);
        socket.broadcast.emit('sampleSetting', process.env.SAMPLES);
        socket.broadcast.emit('refreshSetting', process.env.REFRESHINT);
        console.log('Initial data over to browser.');
    });
    socket.on('sampleInput', function(sampleInputSetting) {
        console.log('setting data = ' + sampleInputSetting);
        process.env.SAMPLES = sampleInputSetting;
        socket.broadcast.emit('sampleSetting', sampleInputSetting);
        console.log('Sending sample rate back out');
    });
    socket.on('refreshInput', function(refreshInputSetting) {
        console.log('setting data = ' + refreshInputSetting);
        process.env.REFRESHINT = refreshInputSetting;
        socket.broadcast.emit('refreshSetting', refreshInputSetting);
        console.log('Sending refresh rate back out');
        Readings.find({}, {}, {
            sort: {
                'time': -1
            },
            limit: process.env.SAMPLES
        }, function(err, readings) {
            socket.broadcast.emit('readingsData', readings);
            socket.emit('readingsData', readings);
        });
    });
    setInterval(function() {
        Readings.find({}, {}, {
            sort: {
                'time': -1
            },
            limit: process.env.SAMPLES
        }, function(err, readings) {
            socket.broadcast.emit('readingsData', readings);
            console.log(process.env.SAMPLES + ' readings sent over');
        });
    }, (process.env.REFRESHINT * 1000));
});


At it's heart, when a browser connects and establishes a socket, this sends over an initial chunk of data and then kicks off the setInterval loop. This broadcasts out a chunk of database data to all connected browsers according to the refresh interval environment variable (process.env.REFRESHINT).

When the "submit" button is hit on the website, this opens the the refreshInput and sampleInput sockets and they in turn send over the revised values to the other browsers connected to the server.

Overall, this works fine, but really looks ugly to me and I am sure I'm doing something wrong! I have repeated the database query three times. I think this should be put into a separate function, but then this isn't the way Node.js works. I looked at implementing the Promises framework (more here), but I got lost in it and I decided to just close off where I am. Promises will need to wait until later.

Client side - Main.js


Since the client-side JavaScript had gotten rather complicated, I decided to put it off into a  separate file which I called "main.js" which lives in the public/javascripts folder of the project. This is referenced in the "Layout.jade" file (more about Jade in a minute).

At the top is this to load the Socket.io:

var socket = io.connect();

 Then we have the jQuery function started up and the logic for the Settings button:

$(document).ready(function() {
    console.log('Doc loaded');
    $('#submitBtn').click(function() {
        var sampleInput = document.getElementById("dataSampleInput").value;
        console.log(sampleInput);
        var refreshInput = document.getElementById("refreshTimeInput").value;
        console.log(refreshInput);
        socket.emit('sampleInput', sampleInput);
        socket.emit('refreshInput', refreshInput);
    });


This is some jQuery that activates when the button is pushed, and picks up the values entered into the two fields with Javascript and then the pushes the values back to the server with socket.emit. You can see the "sampleInput" and "refreshInput" values here that end up over at the server side.

Then there are two further sockets that take the settings value back from the server when they are rebroadcast out:

    socket.on('sampleSetting', function(serverSampleSetting) {
        console.log('Received new sample rate ' + serverSampleSetting);
        $('#dataSampleInput').val(serverSampleSetting);
    });
    socket.on('refreshSetting', function(serverRefreshSetting) {
        console.log('Received new refresh rate ' + serverRefreshSetting);
        $('#refreshTimeInput').val(serverRefreshSetting);
    });


This uses jQuery to write these values back to the fields so all browsers show the same settings.

The socket.on 'readingsData' is what receives the data object over from the server when it is sent over according to the refresh interval. There is then basically the same logic as I used in the last project to break down the data object into two separate arrays and then feed that into Flot to generate the graph. Just check out my previous post on how that works.

The "Server connected" and "Server disconnected" status line is an idea I got from James Doyle and this great tutorial on YouTube (GitHub here). It is very simple and works like this. When the main readingsData socket starts up a couple of lines of jQuery write to a DIV called "logger":

        $('#logger').text('Server connected.');
        $('#logger').css('color', 'green');


When the socket disconnects, it changes the message:

        socket.on('disconnect', function() {
            // visually disconnect
            $('#logger').text('Server disconnected.');
            $('#logger').css('color', 'red');

Mr. Doyle used plain JavaScript for his, but I found for whatever reason this didn't work across all browsers, but jQuery does. The only limitation with this is that it will go to "disconnected" if the server is offline and then back to "connected" if it comes back on fairly quickly (let's say 15 min or so), but if the server is down for too long it won't auto-reconnect and you need to reload the web page.  It also won't go to a "disconnect" status if the Ethernet cable is pulled out of the BeagleBone, for instance.

Jade files - Layout.jade and Index.jade

If you check out these files on GitHub, you will see they are much simpler than they were the first time around. I moved the bulk of the scripting into "Main.js" and the original form I used for submission of the settings has been much simplified.

Hopefully this is interesting to all and sundry! Next up I promise to get on with working with the DigiX and maybe I will even manage a flashing LED using Socket.io!!

No comments:

Post a Comment