Monday, April 21, 2014

Integrating the Beaglebone and DigiX - Part 2 of 2

This is the second part of my adventures with the Digix! In this blog, I will describe the software and the slightly "hackified" way I got the DigiX and my Beaglebone to talk to one another.

The software is all posted to GitHub here:

https://github.com/torchris/digixbeagle

If you missed the first section of this, check it out here.

Hardware setup

 

Here is a diagram of the breadboard I did up in Fritzing. Note that I have used an Arduino Due instead of the DigiX because there is not yet a DigiX image for Fritzing and the Dues is a similar size and shape.



This is very straightforward and there are many good articles on using the DS18B20 OneWire sensor with the Arduino. Here is an excellent article by Simon Tushev. There are also two LEDs. One shows when requests are being processed and the other shows how a digital output on the DigiX can be controlled by the web page off the BeagleBone.

Server side


The server code for this running on the BeagleBone is a basic Express site modified to use Socket.io and using the Request library to get information from the DigiX. The server then uses Socket.io to push the out to a browser. It also takes input from the browser and sends it back over to the DigiX.

The one thing to note here, which I should have remembered from the last time around is that you can't use the Express template with Socket.io right off the bat. This post from Stackoverflow explains the situation. Just to extract the most salient bit:
Express 3 requires that you instantiate a http.Server to attach socket.io to first:

meaning - (1) you must create a server instance:

var app = express();
var http = require('http').createServer(app);
 
(2) couple it with the socket.io:

var io = require('socket.io');
io.listen(http);
 
and ONLY THEN - (3) make the server listen:

http.listen(8080);
 
make sure you keep this order!
I must tattoo  that on my forehead so I don't waste time with Socket.io not working!!

Anyway, the App.js code is basically all stock except for the change above and this function that gets the info from the DigiX using web page calls and the Request library (more on that later on) and then does some simple text processing to extract the temp.:

function getData() {
    request({
        uri: "http://192.168.1.7:3010/temp"
    }, function(error, response, body) {
        if (error){
            //If there is an error or the DigiX is unreachable, set the value to an error.
            console.log("Error: DigiX not available");
            temp = "DigiX not available";
            }
        else if (!error && response.statusCode == 200) {
            var n = body.search("TEMP:");
            temp = body.substr((n + 6), 5);
            temp = temp.trim();
            console.log("Current temp reading: " + temp);
        }
    });
    setTimeout(getData, 10000);
}
getData();



The other main part of App.js is the Socket.io section:

io.sockets.on('connection', function(socket) {
    console.log('A new user connected!');
    console.log("Sending over initial LED state: " + ledStatOn);
    socket.broadcast.emit('ledStatOn', ledStatOn);
    setInterval(function() {
        socket.broadcast.emit('tempData', temp);
        console.log("Temp sent over sockets to client; " + temp);
    }, 10000);
    socket.on('ledStatOn', function(ledStatOn) {
        if (ledStatOn == "ledOn") {
            console.log('LED data = ' + ledStatOn);
            request({
                uri: "http://192.168.1.7:3010/on"
            }, function(error, response, body) {
                if (!error && response.statusCode == 200) {
                    var x = body.search("ledCmded:");
                    ledOnStat = body.substr((x + 10), 2);
                    ledOnStat = ledOnStat.trim();
                    console.log("LED status is " + ledOnStat);
                    socket.emit('ledStatOn', ledOnStat);
                }
            });
        }
        else if (ledStatOn === "ledOff") {
            console.log('LED data = ' + ledStatOn);
            request({
                uri: "http://192.168.1.7:3010/off"
            }, function(error, response, body) {
                if (!error && response.statusCode == 200) {
                    var x = body.search("ledCmded:");
                    ledOnStat = body.substr((x + 10), 3);
                    ledOnStat = ledOnStat.trim();
                    console.log("LED status is " + ledOnStat);
                    socket.emit('ledStatOn', ledOnStat);
                }
            });
        }
    });

});





When the Socket.io connection is established, this sends over the temerature info from the DS18S20to be displayed on the browser. As well, when commands to turn on or off the LED come over from the browser via Socket.io, the Request library is used to trigger these actions on the DigiX.

Browser side

 

The web interface for this is very, very simple. Just a basic Jade-generated page:


Here is the JavaScript code for the page:

var socket = io.connect();



$(document).ready(function() {

 socket.on('ledStatOn', function(ledStatOn) {
        console.log('Received LED status ' + ledStatOn);
    });   
    
$("input:radio[name=ledStat]").click(function() {
    var val = $('input:radio[name=ledStat]:checked').val();
    console.log(val);
        socket.emit('ledStatOn', val);
});

    
    socket.on('tempData', function(tempData) {
        $('#logger').text('Web server connected.');
        $('#logger').css('color', 'green');
        console.log("Server Connected");
        console.log(tempData);
        $('#tempData').html(tempData);
        socket.on('disconnect', function() {
            // visually disconnect
            $('#logger').text('Web server disconnected.');
            $('#logger').css('color', 'red');
        });
    
    });
});





This reuses the server connected/disconnected status used in the previous project. It also uses "socket.emit" to send over to teh server the user selection for the digital output on the DigiX.

Index.jade is also pretty simple - just the DIVs to write the info into and the radio buttons for the LED:

extends layout

block content

  h1= title
  p Welcome to #{title}
  #logger
  br
  p Curent temperature from DigiX:
  b#tempData
  br
  br
  input(type='radio', name='ledStat', value='ledOff', checked='CHECKED')
  | LED Off
  input(type='radio', name='ledStat', value='ledOn')
  | LED On



DigiX

 

This is where it maybe gets a bit silly, but it does work. As I explained in my last post, I couldn't figure out how to get Socket.io or even good-old Websockets (which I used four years ago with this project in PHP). I am sure someone with more brains or patience could easily come up with an answer, but I got impatient to get this going, so I came up with this solution.

I noticed in the DigiX webserver demo application that the DigiX was parsing the value of the page requested (of course it would have to) and I figured that with different pages being called, the sketch could execute different Arduino commands. In other words, to get the application to do something on the on the DigiX, I just called different web addresses. Here is what this sketch implements. When I call these URLs, I get the following responses:
  • http://192.168.1.7/on = turn ON the LED at digital output 8
  • http://192.168.1.7/off = turn OFF the LED at digital output 8
  • http://192.168.1.7/temp = print a simple webpage with the DS18B20 OneWire temp reading
Nice and simple! Also easy to troubleshoot because I can just use any browser to navigate to those pages and see the results. Is this a basic RESTful API? (I use the LED on pin 9 to show when a web request is being processed.)

Here is the code:

#include 
#include 
#include 
DigiFi wifi;
int ledCmded = 8;
int ledStat = 9;
#define ONE_WIRE_BUS 10

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

void setup()
{
  pinMode(ledCmded, OUTPUT);
  pinMode(ledStat, OUTPUT);
  digitalWrite(ledCmded, LOW);
  digitalWrite(ledStat, LOW);
  Serial.begin(9600);
  wifi.begin(9600);
  sensors.begin();
  //DigiX trick - since we are on serial over USB wait for character to be entered in serial terminal
  while (!Serial.available()) {
    Serial.println("Enter any key to begin");
    delay(1000);
  }

  Serial.println("Starting");

  while (wifi.ready() != 1)
  {
    Serial.println("Error connecting to network");
    delay(15000);
  }

  Serial.println("Connected to wifi!");
  Serial.print("Server running at: ");
  String address = wifi.server(3010);//sets up server and returns IP
  Serial.println(address);

  //  wifi.close();
}

void loop()
{

  if ( wifi.serverRequest()) {
    Serial.print("Request for: ");
    Serial.println(wifi.serverRequestPath());
    if (wifi.serverRequestPath() == "/off") {
      digitalWrite(ledStat, HIGH);
      digitalWrite(ledCmded, LOW);
      Serial.println("ledCmded off");
      wifi.serverResponse("

ledCmded: OFF

");       digitalWrite(ledStat, LOW);     }     else if (wifi.serverRequestPath() == "/on") {       digitalWrite(ledStat, HIGH);       digitalWrite(ledCmded, HIGH);       Serial.println("ledCmded on");       wifi.serverResponse("

ledCmded: ON

");       digitalWrite(ledStat, LOW);     } else if (wifi.serverRequestPath() == "/temp") {       digitalWrite(ledStat, HIGH);       float gotTemp;       gotTemp = getTemp();       Serial.print("Temp =  ");       Serial.println(gotTemp);       wifi.println("HTTP/1.1 200 OK");       wifi.println("Content-Type: text/html");       wifi.println("Connection: close");  // the connection will be closed after completion of the response       wifi.println();       wifi.println("");       wifi.print("

TEMP: ");       wifi.print(gotTemp);       wifi.print("

");       digitalWrite(ledStat, LOW);     }     else {       wifi.serverResponse("

Nothing doing

"); //defaults to 200     }   }   delay(10); } float getTemp() {   float currTemp;   sensors.requestTemperatures(); // Send the command to get temperatures   currTemp = sensors.getTempCByIndex(0);   Serial.println(currTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire   return currTemp; }


This works as an easy way to get the kind of interaction I wanted for this project, which is just to exchange some simple sensor info in a non-realtime way and test a Server -> Arduino digital output. However, this has some obvious limitations. It would be hard, for instance, to exchange analog information from the server to the Arduino (for instance, to dim an LED using PWM). You could perhaps have the BeagleBone server send over a series of URLs like "http://192.168.1.7/analog789" where "789" is the analog value and the Arduino code parses that out of the request and then sets the PWM value, but I doubt you could do this more than a few times per minute.  In other words, this is a nice easy way to get this project working, but I can't see how you could do something like my earlier project where I exchanged realtime movement info over the network (see this project).

That's it for now. Next up will be to include this DigiX sensor data in my previous code so I start to have a real "sensor network". Eventually, I want to use the mesh networking off of the DigiX to get sensor data from several other Arduinos (hopefully the tiny DigiSparks if I can make them work).



2 comments:

  1. Greetings, this page really ruined the code.. could you clean things up on the digix side?

    ReplyDelete
    Replies
    1. I think I fixed up the line breaks in the DigiX code. Is there something else you are seeing?

      Delete