diff --git a/dev/autorun.js b/dev/autorun.js
index a1541e1..a50e527 100644
--- a/dev/autorun.js
+++ b/dev/autorun.js
@@ -26,11 +26,17 @@ var tasks = [
watch: /^local\//,
exec: function () {
info('Local: Starting...');
- exec('node', ['arduino.js'], { cwd: './local', pipe: true, name: 'local' }, function (err) {
+ var env = { LOCAL_SERVER: 'mock' };
+ var cmd = [
+ 'killall --quiet -SIGHUP --user ' + process.env.USER + ' petbot_local',
+ 'sleep 0.5', // wait for the petbot process to shut down gracefully
+ 'node localserver'
+ ].join(';');
+ exec('sh', ['-c', cmd], { cwd: 'local/', pipe: true, name: 'local', env: env }, function (err) {
info('Local: Exited.');
});
},
- execOnStart: true
+ execOnStart: 500
},
{
name: 'reset remote',
@@ -82,7 +88,9 @@ _.each(tasks, function(opts) {
return false;
};
if (opts.execOnStart) {
- opts.exec();
+ setTimeout(function () {
+ opts.exec();
+ }, typeof opts.execOnStart === 'number' ? opts.execOnStart : 0);
}
});
diff --git a/local/localserver.js b/local/localserver.js
index e2e7ccf..4b4203c 100644
--- a/local/localserver.js
+++ b/local/localserver.js
@@ -1,61 +1,82 @@
+process.title = 'petbot_local';
+
var config = require('../common/config');
var client = require('socket.io-client');
var _ = require('underscore');
-var LocalServer = function () {
- this.socket = client.connect(config.HOST);
- this.BOARD_LOW = 0;
- this.BOARD_HIGH = 1;
- this.pinMap = {};
-};
-
-LocalServer.prototype.handleSignal = function (data) {
- var self = this;
- if (data.name == 'killswitch') {
- self.kill();
- } else {
- var level = (data.active) ? self.BOARD_HIGH : self.BOARD_LOW;
- self.write(data.name, level);
+var T = require('tbone').tbone;
+var tbone = T;
+
+var servers = tbone.collections.base.make({
+ lookupById: true
+});
+
+var localServer = tbone.make();
+T('servers', servers);
+
+localServer('drive', function () {
+ var driving = _.filter(_.pluck(T('servers') || {}, 'drive'), function (drive) {
+ return drive && (!!drive.right || !!drive.forward);
+ });
+ return driving.length === 0 ? {} : driving[0];
+});
+localServer('awake', function () {
+ return _.keys(T('servers')).length > 0;
+});
+
+var nextId = 1;
+var socket = client.connect(config.HOST, { transports: ['xhr-polling'] });
+socket.on('connect', function() {
+ var me = tbone.make();
+ me('id', nextId++);
+ servers.add(me);
+
+ console.log('connected to remote server');
+ socket.emit('clientId', {id: config.DEVICE_ID});
+
+ // Expect the server to say "keepDriving" every ~500 ms.
+ var driveTimeout;
+ function stopDrivingAfterTimeout() {
+ if (driveTimeout) {
+ clearTimeout(driveTimeout);
+ }
+ driveTimeout = setTimeout(function () {
+ me('drive', {});
+ }, 1000);
}
-}
-
-LocalServer.prototype.sleep = function () {
- console.log('local server is idle');
-}
-
-LocalServer.prototype.wake = function () {
- console.log('local server is awake');
-}
-
-LocalServer.prototype.write = function (direction, level) {
- console.log('direction ' + direction + ' at level ' + level);
-}
-
-LocalServer.prototype.kill = function () {
- var self = this;
- console.log('KILLSWITCH ENGAGE');
- _.each(pinMap, function (k, v) {
- self.write(v.pin, self.BOARD_LOW)
- });
-}
-
-LocalServer.prototype.run = function () {
- var self = this;
- self.socket.on('connect', function() {
- console.log('connected to remote server');
- self.socket.emit('clientId', {id: config.DEVICE_ID});
-
- self.wake();
-
- self.socket.on('direction', function(data) {
- self.handleSignal(data);
- });
+ socket.on('drive', function(data) {
+ me('drive', data);
+ stopDrivingAfterTimeout();
+ });
+ socket.on('keepDriving', function () {
+ stopDrivingAfterTimeout();
+ });
+ T(function () {
+ if (!socket.disconnected) {
+ socket.emit('pins', localServer('pins'));
+ }
+ });
+ T(function () {
+ if (!socket.disconnected) {
+ socket.emit('drive', localServer('drive'));
+ }
+ });
- self.socket.on('disconnect', function() {
- console.log('disconnected from remote server');
- self.sleep();
- });
+ socket.on('disconnect', function() {
+ console.log('disconnected from remote server');
+ servers.remove(me);
});
-};
+});
+
+require('./' + process.env.LOCAL_SERVER + '.js')(localServer);
-module.exports = LocalServer;
+// Gracefully shutdown on SIGHUP
+process.on('SIGHUP', function () {
+ try {
+ socket.disconnect(function() {
+ process.exit(0);
+ });
+ } catch(e) {
+ process.exit(0);
+ }
+});
diff --git a/local/mock.js b/local/mock.js
new file mode 100644
index 0000000..c6aec2a
--- /dev/null
+++ b/local/mock.js
@@ -0,0 +1,15 @@
+var T = require('tbone').tbone;
+var tbone = T;
+
+module.exports = function (me) {
+ T(function () {
+ var drive = me('drive');
+ me('pins.right', drive.right === 1 ? 5 : 0);
+ me('pins.left', drive.right === -1 ? 5 : 0);
+ me('pins.forward', drive.forward === 1 ? 5 : 0);
+ me('pins.backward', drive.forward === -1 ? 5 : 0);
+ });
+ T(function () {
+ me('pins.ready', !!me('awake'));
+ });
+};
diff --git a/public/index.html b/public/index.html
index 4b03c61..b7a4e5f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,7 +6,7 @@
-
+
PETBOT v0.0.1
@@ -25,15 +25,20 @@ PETBOT v0.0.1
-
diff --git a/public/static/bindings.js b/public/static/bindings.js
index 20deee9..7b533da 100644
--- a/public/static/bindings.js
+++ b/public/static/bindings.js
@@ -1,38 +1,15 @@
-var socket = io.connect();
+var socket = io.connect('/', {
+ transports: ['xhr-polling'],
+ 'reconnection delay': 100,
+ 'max reconnection attempts': 10000
+});
socket.emit('clientId', {id: 'browser'});
-// var keys = {
-// 37: {
-// name: 'left',
-// active: false,
-// mode: 'steer'
-// },
-// 38: {
-// name: 'forward',
-// active: false,
-// mode: 'move'
-// },
-// 39: {
-// name: 'right',
-// active: false,
-// mode: 'steer'
-// },
-// 40: {
-// name: 'back',
-// active: false,
-// mode: 'move'
-// }
-// };
var keys = {
37: 'left',
38: 'forward',
39: 'right',
40: 'backward'
};
-T('localStatus', {
- online: false,
- status: 'connecting',
- message: 'Attempting to connect to PETBOT...'
-});
socket.on('yourId', function (data) {
T('browserId', data);
});
@@ -42,9 +19,52 @@ socket.on('localStatus', function (data) {
socket.on('browsers', function (data) {
T('browsers', JSON.stringify(data));
});
+socket.on('bots', function (data) {
+ T('bots', JSON.stringify(data));
+});
socket.on('drive', function (data) {
T('actualDrive', data);
});
+socket.on('botOnline', function (data) {
+ T('botOnline', data);
+});
+socket.on('connect', function () {
+ T('online', true);
+});
+socket.on('reconnect', function () {
+ T('online', true);
+});
+socket.on('disconnect', function () {
+ T('online', false);
+});
+
+T('onlineStr', function () {
+ return T('online') + '';
+});
+T('botOnlineStr', function () {
+ return T('botOnline') + '';
+});
+T('status', function () {
+ if (T('online')) {
+ if (T('botOnline')) {
+ return {
+ name: 'online',
+ message: 'Connected.'
+ };
+ } else {
+ return {
+ name: 'connecting',
+ message: 'Connecting to petbot...'
+ };
+ }
+ } else {
+ return {
+ name: 'offline',
+ message: 'Connecting to server...'
+ };
+ }
+});
+
socket.on('reloadui', function () {
console.log('Reloading...')
location.reload();
@@ -70,10 +90,14 @@ T(function () {
setTimeout(function () {
T.toggle('isDrivingTimer');
}, 500);
- } else {
-
}
});
+tbone.createView('topBanner', function () {
+ this.$el.removeClass('online');
+ this.$el.removeClass('connecting');
+ this.$el.removeClass('offline');
+ this.$el.addClass(T('status.name'));
+});
tbone.createView('direction', function () {
var $el = this.$el;
var isOtherDriver = !!T('actualDrive.browserId') && T('browserId') !== T('actualDrive.browserId');
diff --git a/public/static/style.less b/public/static/style.less
index 0585f7a..dbde274 100644
--- a/public/static/style.less
+++ b/public/static/style.less
@@ -70,7 +70,7 @@ a, a:visited, a:active {
.backward.left:before {
content: '\2199';
}
-.top-banner {
+.topBanner {
width: 100%;
height: 40px;
display: block;
@@ -81,6 +81,11 @@ a, a:visited, a:active {
&.online {
background: #bada55;
+ overflow: hidden;
+ height: 0px;
+ padding: 0px;
+ transition: height 1s, padding 1s;
+ transition-delay: 1s;
}
&.offline {
diff --git a/remote/petbot.js b/remote/petbot.js
index bb06953..3204734 100644
--- a/remote/petbot.js
+++ b/remote/petbot.js
@@ -21,6 +21,17 @@ T('drive', function () {
});
return driving.length === 0 ? {} : driving[0];
});
+T('isDriving', function () {
+ return !!T('drive.forward') || !!T('drive.right');
+});
+T('botOnline', function () {
+ return _.keys(T('bots')).length > 0;
+});
+
+var bots = tbone.collections.base.make({
+ lookupById: true
+});
+T('bots', bots);
app.configure(function () {
var username = process.env.AUTH_USERNAME;
@@ -72,21 +83,45 @@ io.sockets.on('connection', function (socket) {
}
});
+var nextId = 1;
var handleLocalServer = function (socket) {
console.log('local server is connected');
- localServerUp = true;
- broadcastLocalStatus(localServerUp, socket);
+ var me = tbone.make();
+ var myId = nextId++;
+ socket.emit('yourId', myId);
+ me.query('id', myId);
+ bots.add(me);
+ T(function () {
+ if (!socket.disconnected) {
+ socket.emit('drive', T('drive'));
+ }
+ });
+ T(function () {
+ if (!socket.disconnected) {
+ if (T('isDriving')) {
+ socket.emit('keepDriving');
+ T('isDrivingTimer');
+ setTimeout(function () {
+ T.toggle('isDrivingTimer');
+ }, 500);
+ }
+ }
+ });
+ socket.on('pins', function (data) {
+ me('pins', data);
+ });
+ socket.on('drive', function (data) {
+ me('drive', data);
+ });
socket.on('disconnect', function () {
+ bots.remove(me);
console.log('local server disconnected');
- localServerUp = false;
- broadcastLocalStatus(localServerUp, socket);
});
};
-var nextBrowserId = 1;
function handleBrowser (socket) {
- var me = tbone.models.base.make();
- var myId = nextBrowserId++;
+ var me = tbone.make();
+ var myId = nextId++;
socket.emit('yourId', myId);
me.query('id', myId);
browsers.add(me);
@@ -95,11 +130,21 @@ function handleBrowser (socket) {
socket.emit('browsers', T('browsers'));
}
});
+ T(function () {
+ if (!socket.disconnected) {
+ socket.emit('bots', T('bots'));
+ }
+ });
T(function () {
if (!socket.disconnected) {
socket.emit('drive', T('drive'));
}
});
+ T(function () {
+ if (!socket.disconnected) {
+ socket.emit('botOnline', !!T('botOnline'));
+ }
+ });
// Expect the client to say "keepDriving" every ~500 ms.
var driveTimeout;
function stopDrivingAfterTimeout() {
@@ -121,60 +166,3 @@ function handleBrowser (socket) {
browsers.remove(me);
});
}
-
-var broadcastLocalStatus = function (isUp, socket) {
- var statusObj = {};
- if (isUp === true) {
- statusObj = {
- online: true,
- status: 'online',
- message: 'PETBOT is online and ready to go!'
- };
- } else if (isUp === false) {
- statusObj = {
- online: false,
- status: 'offline',
- message: 'PETBOT is offline'
- };
- } else {
- statusObj = {
- online: false,
- status: 'connecting',
- message: 'Attempting to connect to PETBOT...'
- };
- }
- socket.broadcast.emit('localStatus', statusObj);
-};
-
-// fire when a new connection moves into the #0 spot
-var openCurrentSocket = function (member) {
- console.log('now listening to connection '+member.id);
- broadcastLocalStatus(localServerUp, member.socket);
- member.socket.on('direction', function (data) {
- console.log('broadcasting: '+data);
- member.socket.broadcast.emit('direction', data);
- });
- member.socket.on('disconnect', function () {
- console.log('connection ' + member.id + ' is disconnecting');
- // remove this connection from the queue
- updateQueue(member, false);
- });
-};
-
-// handle changes in the queue. add or remove, then start listening to a new connection
-// if necessary.
-var updateQueue = function (member, shouldAdd) {
- var firstItemId = (queue.length > 0) ? queue[0].id : null;
- if (shouldAdd) {
- queue.push(member);
- } else {
- queue = _.reject(queue, function(item) {
- return item.id === member.id;
- });
- }
- // XXX possible race condition?
- // if guid of first item has changed, there's a new connection in first place
- if (queue.length > 0 && firstItemId !== queue[0].id) {
- openCurrentSocket(queue[0]);
- }
-};