node.js and the AR.Drone: building a real-time dashboard using socket.io
- 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);
- 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>
- 25. Node.js on the AR.Drone
https://gist.github.com/maxogden/4152815
autonomous AR.Drone!
interface with Arduino