SlideShare a Scribd company logo
node.js & AR.Drone
#njugbe meetup 3 - 13 March 2013
@stevenbeeckman
AR.Drone?




                                        2
                                    ith ’s!
                                   w ra
     wifi controlled quadricopter       e
                                   am
node.js packages
                           bes
ar-drone                        tm
                                   aint
https://npmjs.org/package/ar-drone     aine
                                              d
ardrone
https://npmjs.org/package/ardrone
ardrone-web
https://npmjs.org/package/ardrone-web
hello world
var arDrone = require('ar-drone');
var client = arDrone.createClient();

client.animateLeds('blinkGreenRed', 5, 10);

client.on('navdata', console.log);
Real-time Dashboard?
In the browser?
Real-time Dashboard
Express.js for the server
Socket.io for push to the browser (web sockets!)
Client-side:
  jQuery Knob
  Rickshaw.js
Schematics



  AR.Drone’s wifi network
                                     browser - http://localhost:3000/

                                                      socket.io

                              http
                node fly.js           node rtdashboard.js


                             MacBook
fly.js
 var arDrone = require('ar-drone'); //RTFM for flying commands etc
 var http = require('http');
 var client = arDrone.createClient();
 var options = {host: '127.0.0.1’, port: 3000, path: '/api/raw', method: 'POST'};
 var counter = 0; // naïve sampling counter
 client.config('general:navdata_demo', 'FALSE'); // get all the data from the sensors

 client.takeoff();
 client
  .after(3000, function() {
    this.down(0.1);
  })
  .after(1000, function(){
  	       this.stop();
          this.land();
  });
fly.js - logging to dashboard
 client.on('navdata', function(data){ // on receiving navdata, send data to dashboard
 	       counter = counter + 1;
 	       if(counter > 50){ // only send every 50th data header
 	       	     counter = 0;
 	       	     var raw_data_header = new Object();

 	      	     if(data.rawMeasures && data.demo && data.pwm){ // data not always contains demo & pwm
 	      	     	      raw_data_header = {
 	      	     	      	    header: {
 	      	     	      	    	    time: data.time
 	      	     	      	    	    , sequenceNumber: data.sequenceNumber
 	      	     	      	    	    , flying: data.droneState.flying
 	      	     	      	    	    , batteryMilliVolt: data.rawMeasures.batteryMilliVolt
 	      	     	      	    	    , altitude: data.demo.altitude
 	      	     	      	    	    , velocity: {x: data.demo.xVelocity
 	      	     	      	    	    	       	      	     , y: data.demo.yVelocity
 	      	     	      	    	    	       	      	     , z: data.demo.zVelocity}
 	      	     	      	    	    , throttle: {forward: data.pwm.gazFeedForward
 	      	     	      	    	    	       	      	     , height: data.pwm.gazAltitude}
 	      	     	      	    }
 	      	     	      };
 	      	     }else{
fly.js - logging to dashboard
 	   	   	   raw_data_header = {
 	   	   	   	    header: {
 	   	   	   	    	    time: data.time
 	   	   	   	    	    , sequenceNumber: data.sequenceNumber
 	   	   	   	    	    , flying: data.droneState.flying
 	   	   	   	    	    , batteryMilliVolt: 0
 	   	   	   	    	    , altitude: 0
 	   	   	   	    	    , velocity: {x: 0
 	   	   	   	    	    	       	      	     , y: 0
 	   	   	   	    	    	       	      	     , z: 0}
 	   	   	   	    	    , throttle: {forward: 0
 	   	   	   	    	    	       	      	     , height: 0}
 	   	   	   	    }
 	   	   	   };
 	   	   }
fly.js - logging to dashboard
 	     	   var data_to_be_sent = JSON.stringify(raw_data_header);
 	     	   var headers = {
 	     	   	     'Content-Type': 'application/json'
 	     	   	     , 'Content-Length': data_to_be_sent.length
 	     	   };
 	     	   options.headers = headers;

 	     	   var req = http.request(options, function(res){
 	     	   });

 	     	   req.on('error', function(e){
 	     	   	     // per http://nodejs.org/api/http.html#http_http_request_options_callback
 	     	   	     console.log("Problem with request: " + e.message);
 	     	   })

 	     	   req.write(data_to_be_sent);
 	     	   req.end();
 	     }
 });
rtdashboard.js
 var app = express();
 setupApp();

 function setupApp(){
     ...
     db = mongoose.createConnection(app.set('db-uri'));
     db.on('error', console.error.bind(console, 'Connection error:'));

     db.once('open', function(){
         console.log("Connected to database");
         app.use(app.router);
         setupRoutes();
         startServer();
     });
 }
rtdashboard.js
 function setupRoutes(){
 	       app.get('/', routes.index); // contains the dashboard

 	       app.post('/api/raw', addDb, addAltitude, addSpeed, addHeading,
              addThrottleVertical, addThrottleHorizontal, addBattery, routes.raw);
              // receives some navdata from fly.js

 	       app.get('*', function(req, res){
 	       	    console.log("Page not found: " + req.originalUrl);
 	       	    res.render('404');
 	       });
 }

 function addDb(req, res, next){
 	       req.db = db; // contains the database
 	       next();
 }

 function addAltitude(req, res, next){
 	       req.altitude = altitude; // contains a socket.io namespace object, see startServer()
 	       next();
 }
rtdashboard.js
 function startServer(){
 	       server = http.createServer(app);
 	       io = io.listen(server);

 	      server.listen(app.get('port'), function(){
 	      	     console.log("Express server started.");
 	      });

 	      // each sensor gets its own socket.io namespace
 	      altitude = io.of('/altitude');
 	      speed = io.of('/speed');
 	      heading = io.of('/heading');
 	      throttle_vertical = io.of('/throttle_vertical');
 	      throttle_horizontal = io.of('/throttle_horizontal');
 	      battery = io.of('/battery');
 }
routes/index.js
 exports.index = function(req, res){
     res.render('index', { });
 };

 exports.raw = function(req, res){
 	      if(req.body.header){
 	      	      var header = new Object();
 	      	      header.rawData = req.body.header;

 	       	     // send data to dashboard on socket.io
 	       	     req.altitude.emit('altitude', {value: header.rawData.altitude});
 	       	     req.throttle_vertical.emit('throttle', {value: header.rawData.throttle.height});
 	       	     req.throttle_horizontal.emit('throttle', {value: header.rawData.throttle.forward});
 	       	     req.battery.emit('battery', {value: header.rawData.batteryMilliVolt})

 	       	      res.json({message: "Success"});
 	       }else{
 	       	      res.json({message: "No header received."});
 	       }
 }
views/index.ejs
 <!DOCTYPE HTML>
 <html>
 	       <head>
 	       	     <title>Real-Time Dashboard</title>
 	       	     <link rel='stylesheet' href='/bootstrap/css/bootstrap.min.css' />
 	       	     <link rel="stylesheet" href="/css/rickshaw.min.css"/>
 	       	     <script src="/js/jquery-1.9.1.min.js"></script>
 	       	     <script src="/js/jquery.knob.js"></script>
 	       	     <script src="/js/d3.v2.js"></script>
 	       	     <script src="/js/rickshaw.min.js"></script>
 	       	     <script src="/socket.io/socket.io.js"></script>
               <script>
                     ... 
               </script>
           </head>
           <body>
               ... 
           </body>
  </html>
views/index.ejs
 <body>
    ...
    <div class="span4">
         <h3>Horizontal Throttle</h3>
         <div class="span2">
              <input type="text" id="throttleHorizontal" data-fgColor="#66cc66" data-min="-400"
                  data-max="400" data-cursor=true data-angleOffset=-180 data-width="70" value="0"
                  data-readOnly=true> 
 	      </div>
    	 <div style="float: left;" id="throttleHorizontal_chart"> 
    	 </div>
    </div>
    ...
 </body>
views/index.ejs
 <script>
     $(document).ready(function(){
          $("#throttle").knob();
          $("#throttleHorizontal").knob();
     });

     var throttleHorizontal = io.connect('http://localhost/throttle_horizontal'); // connect to socket.io namespace
     var throttleHorizontal_data = new Array(); // array for the horizontal throttle data
     var throttleHorizontal_graph;

     // see next slide
     ...
 </script>
views/index.ejs
 <script>
     ...
     throttleHorizontal.on('throttle', function(data){
          $("#throttleHorizontal").val(data.value);
          $("#throttleHorizontal").trigger("change"); // trigger the knob to redraw itself
          throttleHorizontal_data.push({x: (new Date()).getTime(), y: parseInt(data.value)});

           if(!throttleHorizontal_graph){
                  //console.log("Altitude graph doesn't yet exist, drawing it for the first and only time.");
                  throttleHorizontal_graph = new Rickshaw.Graph( {
     	   	        	      	     	          element: document.querySelector("#throttleHorizontal_chart"),
     	   	        	      	     	          width: 80,
     	   	        	      	     	          height: 60,
     	   	        	      	     	          renderer: "line",
     	   	        	      	     	          interpolation: "step-after",
     	   	        	      	     	          series: [{
     	   	        	      	     	             color: '#66cc66',
     	   	        	      	     	             data: throttleHorizontal_data
     	   	        	      	     	          }]
     	   	        	      	     	      });
                  throttleHorizontal_graph.render();
 	       }else{
views/index.ejs
 <script>
     ...
     throttleHorizontal.on('throttle', function(data){
          ...
          }else{
               //Throttle graph already exists so just update the data and rerender.
     	 	       throttleHorizontal_graph.series[0].data = throttleHorizontal_data;
     	 	       throttleHorizontal_graph.render();
          }
     });
     ...
 </script>
Fork it
https://github.com/stevenbeeckman/ardrone-controller
https://github.com/stevenbeeckman/ardrone-dashboard
Demo time
Way ahead
Access the videostreams
Front & bottom camera

Use OpenCV for computer vision
Node.js on the AR.Drone
https://gist.github.com/maxogden/4152815


                      autonomous AR.Drone!
                       interface with Arduino
Questions?
My name is @stevenbeeckman.
Thanks for listening.

More Related Content

node.js and the AR.Drone: building a real-time dashboard using socket.io

  • 1. node.js & AR.Drone #njugbe meetup 3 - 13 March 2013 @stevenbeeckman
  • 2. AR.Drone? 2 ith ’s! w ra wifi controlled quadricopter e am
  • 3. node.js packages bes ar-drone tm aint https://npmjs.org/package/ar-drone aine d ardrone https://npmjs.org/package/ardrone ardrone-web https://npmjs.org/package/ardrone-web
  • 4. hello world var arDrone = require('ar-drone'); var client = arDrone.createClient(); client.animateLeds('blinkGreenRed', 5, 10); client.on('navdata', console.log);
  • 6. Real-time Dashboard Express.js for the server Socket.io for push to the browser (web sockets!) Client-side: jQuery Knob Rickshaw.js
  • 7. Schematics AR.Drone’s wifi network browser - http://localhost:3000/ socket.io http node fly.js node rtdashboard.js MacBook
  • 8. fly.js var arDrone = require('ar-drone'); //RTFM for flying commands etc var http = require('http'); var client = arDrone.createClient(); var options = {host: '127.0.0.1’, port: 3000, path: '/api/raw', method: 'POST'}; var counter = 0; // naïve sampling counter client.config('general:navdata_demo', 'FALSE'); // get all the data from the sensors client.takeoff(); client .after(3000, function() { this.down(0.1); }) .after(1000, function(){ this.stop(); this.land(); });
  • 9. fly.js - logging to dashboard client.on('navdata', function(data){ // on receiving navdata, send data to dashboard counter = counter + 1; if(counter > 50){ // only send every 50th data header counter = 0; var raw_data_header = new Object(); if(data.rawMeasures && data.demo && data.pwm){ // data not always contains demo & pwm raw_data_header = { header: { time: data.time , sequenceNumber: data.sequenceNumber , flying: data.droneState.flying , batteryMilliVolt: data.rawMeasures.batteryMilliVolt , altitude: data.demo.altitude , velocity: {x: data.demo.xVelocity , y: data.demo.yVelocity , z: data.demo.zVelocity} , throttle: {forward: data.pwm.gazFeedForward , height: data.pwm.gazAltitude} } }; }else{
  • 10. fly.js - logging to dashboard raw_data_header = { header: { time: data.time , sequenceNumber: data.sequenceNumber , flying: data.droneState.flying , batteryMilliVolt: 0 , altitude: 0 , velocity: {x: 0 , y: 0 , z: 0} , throttle: {forward: 0 , height: 0} } }; }
  • 11. fly.js - logging to dashboard var data_to_be_sent = JSON.stringify(raw_data_header); var headers = { 'Content-Type': 'application/json' , 'Content-Length': data_to_be_sent.length }; options.headers = headers; var req = http.request(options, function(res){ }); req.on('error', function(e){ // per http://nodejs.org/api/http.html#http_http_request_options_callback console.log("Problem with request: " + e.message); }) req.write(data_to_be_sent); req.end(); } });
  • 12. rtdashboard.js var app = express(); setupApp(); function setupApp(){ ... db = mongoose.createConnection(app.set('db-uri')); db.on('error', console.error.bind(console, 'Connection error:')); db.once('open', function(){ console.log("Connected to database"); app.use(app.router); setupRoutes(); startServer(); }); }
  • 13. rtdashboard.js function setupRoutes(){ app.get('/', routes.index); // contains the dashboard app.post('/api/raw', addDb, addAltitude, addSpeed, addHeading, addThrottleVertical, addThrottleHorizontal, addBattery, routes.raw); // receives some navdata from fly.js app.get('*', function(req, res){ console.log("Page not found: " + req.originalUrl); res.render('404'); }); } function addDb(req, res, next){ req.db = db; // contains the database next(); } function addAltitude(req, res, next){ req.altitude = altitude; // contains a socket.io namespace object, see startServer() next(); }
  • 14. rtdashboard.js function startServer(){ server = http.createServer(app); io = io.listen(server); server.listen(app.get('port'), function(){ console.log("Express server started."); }); // each sensor gets its own socket.io namespace altitude = io.of('/altitude'); speed = io.of('/speed'); heading = io.of('/heading'); throttle_vertical = io.of('/throttle_vertical'); throttle_horizontal = io.of('/throttle_horizontal'); battery = io.of('/battery'); }
  • 15. routes/index.js exports.index = function(req, res){ res.render('index', { }); }; exports.raw = function(req, res){ if(req.body.header){ var header = new Object(); header.rawData = req.body.header; // send data to dashboard on socket.io req.altitude.emit('altitude', {value: header.rawData.altitude}); req.throttle_vertical.emit('throttle', {value: header.rawData.throttle.height}); req.throttle_horizontal.emit('throttle', {value: header.rawData.throttle.forward}); req.battery.emit('battery', {value: header.rawData.batteryMilliVolt}) res.json({message: "Success"}); }else{ res.json({message: "No header received."}); } }
  • 16. views/index.ejs <!DOCTYPE HTML> <html> <head> <title>Real-Time Dashboard</title> <link rel='stylesheet' href='/bootstrap/css/bootstrap.min.css' /> <link rel="stylesheet" href="/css/rickshaw.min.css"/> <script src="/js/jquery-1.9.1.min.js"></script> <script src="/js/jquery.knob.js"></script> <script src="/js/d3.v2.js"></script> <script src="/js/rickshaw.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> ... <!-- see second next slides --> </script> </head> <body> ... <!-- see next slide --> </body> </html>
  • 17. views/index.ejs <body> ... <div class="span4"> <h3>Horizontal Throttle</h3> <div class="span2"> <input type="text" id="throttleHorizontal" data-fgColor="#66cc66" data-min="-400" data-max="400" data-cursor=true data-angleOffset=-180 data-width="70" value="0" data-readOnly=true> <!-- jQuery Knob --> </div> <div style="float: left;" id="throttleHorizontal_chart"> <!-- rickshaw graph --> </div> </div> ... </body>
  • 18. views/index.ejs <script> $(document).ready(function(){ $("#throttle").knob(); $("#throttleHorizontal").knob(); }); var throttleHorizontal = io.connect('http://localhost/throttle_horizontal'); // connect to socket.io namespace var throttleHorizontal_data = new Array(); // array for the horizontal throttle data var throttleHorizontal_graph; // see next slide ... </script>
  • 19. views/index.ejs <script> ... throttleHorizontal.on('throttle', function(data){ $("#throttleHorizontal").val(data.value); $("#throttleHorizontal").trigger("change"); // trigger the knob to redraw itself throttleHorizontal_data.push({x: (new Date()).getTime(), y: parseInt(data.value)}); if(!throttleHorizontal_graph){ //console.log("Altitude graph doesn't yet exist, drawing it for the first and only time."); throttleHorizontal_graph = new Rickshaw.Graph( { element: document.querySelector("#throttleHorizontal_chart"), width: 80, height: 60, renderer: "line", interpolation: "step-after", series: [{ color: '#66cc66', data: throttleHorizontal_data }] }); throttleHorizontal_graph.render(); }else{
  • 20. views/index.ejs <script> ... throttleHorizontal.on('throttle', function(data){ ... }else{ //Throttle graph already exists so just update the data and rerender. throttleHorizontal_graph.series[0].data = throttleHorizontal_data; throttleHorizontal_graph.render(); } }); ... </script>
  • 24. Access the videostreams Front & bottom camera Use OpenCV for computer vision
  • 25. Node.js on the AR.Drone https://gist.github.com/maxogden/4152815 autonomous AR.Drone! interface with Arduino
  • 26. Questions? My name is @stevenbeeckman. Thanks for listening.