Monday, March 24, 2014

Bi-directional control between Raspberry Pi + Node.js + Arduino

This example show how to pass data between Node.js client, Node.js server running on Raspberry Pi, and Arduino, bi-directionally. A Arduino Esplora is connected to Raspberry Pi, with Node.js running up a simple web app. Client can load the web app with Pi's IP and port 8080.

the tablet in the video for demo only.

From Node.js clients to server to Arduino:
- User can toggle the button to set Arduino LED ON/OFF.
- User can select color from of Arduino LCD color.

From Arduino to Node.js server to clients.
- When the Slider on Esplora changed, the setting will be sent to Node.js server to clients' progress bar on page.


Node.js code, app.js
var app = require('http').createServer(handler), 
    io = require('socket.io').listen(app), 
    fs = require('fs'),
    os = require('os'),
    sp = require("serialport");
    
//All clients have a common status
var commonStatus = 'ON';
var commonColor = 0xffffff;
var commonSldValue = 0;
  
//init for SerialPort connected to Arduino
var SerialPort = sp.SerialPort
var serialPort = new SerialPort('/dev/ttyACM0', 
    {   baudrate: 9600,
        dataBits: 8,
        parity: 'none',
        stopBits: 1,
        flowControl: false
    });
    
var receivedData = "";
    
serialPort.on("open", function () {
    console.log('serialPort open');
    serialPort.write("LEDOFF\n");
    
    //handle data receive from Arduino
    serialPort.on('data', function(data) {
  receivedData += data.toString();
  if (receivedData.indexOf("SLD#") >= 0 
   && receivedData.indexOf("\n") >= 0) {
    
   sldValue = receivedData.substring(
    receivedData.indexOf("SLD#")+4, 
    receivedData.indexOf("\n"));
    
   receivedData = "";
   
   if ((sldValue.length == 1)
    || (sldValue.length == 2)){
    commonSldValue = parseInt("0x"+sldValue); 
    io.sockets.emit('update slider', 
       { value: commonSldValue});
    console.log('update slider: ' + commonSldValue);
   } 
  }
 });
    
});
    
//Display my IP
var networkInterfaces=os.networkInterfaces();

for (var interface in networkInterfaces) {
    
    networkInterfaces[interface].forEach(
        function(details){
            
            if (details.family=='IPv4' 
                && details.internal==false) {
                    console.log(interface, details.address);  
        }
    });
}

app.listen(8080);

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}

io.sockets.on('connection', function (socket) {
    
    //Send client with his socket id
    socket.emit('your id', 
        { id: socket.id});
    
    //Info all clients a new client caaonnected
    io.sockets.emit('on connection', 
        { client: socket.id,
          clientCount: io.sockets.clients().length,
        });
        
    //Set the current common status to the new client
    socket.emit('ack button status', { status: commonStatus });
    socket.emit('ack color', { color: commonColor });
    socket.emit('update slider', { value: commonSldValue});
    
    socket.on('button update event', function (data) {
        console.log(data.status);
        
        //acknowledge with inverted status, 
        //to toggle button text in client
        if(data.status == 'ON'){
            console.log("ON->OFF");
            commonStatus = 'OFF';
            serialPort.write("LEDON\n");
        }else{
            console.log("OFF->ON");
            commonStatus = 'ON';
            serialPort.write("LEDOFF\n");
        }
        io.sockets.emit('ack button status', 
            { status: commonStatus,
              by: socket.id
            });
    });
    
    socket.on('update color', function (data) {
  commonColor = data.color;
  console.log("set color: " + commonColor);
  var stringColorHex = "COL" + commonColor.toString(16);
  console.log("stringColorHex: " + stringColorHex);
  
  serialPort.write(stringColorHex + "\n");
  
  io.sockets.emit('ack color', 
   { color: data.color,
     by: socket.id
   });
 });
    
    //Info all clients if this client disconnect
    socket.on('disconnect', function () {
        io.sockets.emit('on disconnect', 
            { client: socket.id,
              clientCount: io.sockets.clients().length-1,
            });
    });
});

index.html
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, user-scalable=no">
<head></head>
<body>
<hi>Test Node.js with socket.io</hi>
<form action="">
<input type="button" id="buttonToggle" value="ON" style="color:blue"
       onclick="toggle(this);">
</form>
<br/>
Select color: <br/>
<input id="colorpick" type="color" name="favcolor" 
 onchange="JavaScript:colorchanged()"><br>
<br/>
<progress id="progressbar" max="255"><span>0</span>%</progress>


<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect(document.location.href);
var myId;

socket.on('update slider', function (data) {
    console.log("update slider: " + data.value);
    document.getElementById("progressbar").value = data.value;
});

socket.on('on connection', function (data) {
    console.log("on connection: " + data.client);
    console.log("Number of client connected: " + data.clientCount);
});

socket.on('on disconnect',function(data) {
    console.log("on disconnect: " + data.client);
    console.log("Number of client connected: " + data.clientCount);
});

socket.on('your id',function(data) {
    console.log("your id: " + data.id);
    myId = data.id;
});

socket.on('ack button status', function (data) {
    console.log("status: " + data.status);
    
    if(myId==data.by){
        console.log("by YOU");
    }else{
        console.log("by: " + data.by);
    }
    
    if(data.status =='ON'){
        document.getElementById("buttonToggle").value="ON";
    }else{
        document.getElementById("buttonToggle").value="OFF";
    }
});

socket.on('ack color', function (data) {
    console.log("ack color: " + data.color);
    document.body.style.background = data.color;
    if(myId==data.by){
        console.log("by YOU");
    }else{
        console.log("by: " + data.by);
    }
});

function toggle(button)
{
 if(document.getElementById("buttonToggle").value=="OFF"){
  socket.emit('button update event', { status: 'OFF' });
 }
 else if(document.getElementById("buttonToggle").value=="ON"){
  socket.emit('button update event', { status: 'ON' });
 }
}

function colorchanged()
{
 //console.log("colorchanged()");
 var colorval = document.getElementById("colorpick").value;
 console.log("colorchanged(): " + colorval);
 //document.body.style.background = colorval;
 socket.emit('update color', { color: colorval });
}

</script>

</body>
</html>

Arduino code:
#include <Esplora.h>
#include <TFT.h>
#include <SPI.h>

int MAX_CMD_LENGTH = 10;
char cmd[10];
int cmdIndex;
char incomingByte;

int prevSlider = 0;

void setup() {
  
    EsploraTFT.begin();  
    EsploraTFT.background(0,0,0);
    EsploraTFT.stroke(255,255,255);  //preset stroke color
     
    //Setup Serial Port with baud rate of 9600
    Serial.begin(9600);
    
    //indicate start
    Esplora.writeRGB(255, 255, 255);
    delay(250);
    Esplora.writeRGB(0, 0, 0);
    
    cmdIndex = 0;
    
}
 
void loop() {
    
    if (incomingByte=Serial.available()>0) {
      
      char byteIn = Serial.read();
      cmd[cmdIndex] = byteIn;
      
      if(byteIn=='\n'){
        //command finished
        cmd[cmdIndex] = '\0';
        //Serial.println(cmd);
        cmdIndex = 0;
        
        String stringCmd = String(cmd);
        
        if(strcmp(cmd, "LEDON")  == 0){
          //Serial.println("Command received: LEDON");
          Esplora.writeRGB(255, 255, 255);
        }else if (strcmp(cmd, "LEDOFF")  == 0) {
          //Serial.println("Command received: LEDOFF");
          Esplora.writeRGB(0, 0, 0);
        }else if(stringCmd.substring(0,4)="COL#"){
          //Serial.println("Command received: COL#");
          if(stringCmd.length()==10){
            char * pEnd;
            long int rgb = strtol(&cmd[4], &pEnd, 16);
            int r = (rgb & 0xff0000) >> 16;
            int g = (rgb & 0xff00) >> 8;
            int b = rgb & 0xff;
            //Serial.println(r);
            //Serial.println(g);
            //Serial.println(b);
            EsploraTFT.background(b,g,r);
          }
        }else{
          //Serial.println("Command received: unknown!");
        }
        
      }else{
        if(cmdIndex++ >= MAX_CMD_LENGTH){
          cmdIndex = 0;
        }
      }
    }
    
    //Read Slider
    int slider = Esplora.readSlider();
    //convert slider value from [0-1023] to [0x00-0xFF]
    slider = slider>>2 & 0xff;
    
    if(slider!=prevSlider){
      prevSlider = slider;
      
      String stringSlider = String(slider, HEX);
      Serial.println("SLD#" + stringSlider +"\n");
    }
    
}

Cross post with Arduino-er, same code run on PC running Ubuntu Linux.

No comments: