johnny-five
Version:
Firmata based Arduino Programming Framework.
310 lines (248 loc) • 6.7 kB
JavaScript
var Servo = require("../lib/servo.js"),
__ = require("../lib/fn.js"),
temporal = require("temporal"),
DIR_TRANSLATION;
function scale( speed, low, high ) {
return Math.floor( __.scale( speed, 0, 5, low, high ) );
}
DIR_TRANSLATION = {
reverse: {
right: "left",
left: "right",
fwd: "rev",
rev: "fwd"
},
translations: [
{
f: "forward",
r: "reverse",
fwd: "forward",
rev: "reverse"
},
{
r: "right",
l: "left"
}
]
};
function Movement( move ) {
__.extend(this, move, {
timestamp: Date.now()
});
}
Movement.prototype.toString = function() {
return "<" + [ this.timestamp, "L" + this.left, "R" + this.right ].join(" ") + ">";
};
/**
* Nodebot
* @param {Object} opts Optional properties object
*/
function Nodebot( opts ) {
opts.board = opts.board || null;
// Boe Nodebot continuous are calibrated to stop at 90°
this.center = opts.center || 90;
if ( typeof opts.right === "number" ) {
opts.right = new Servo({
board: opts.board,
pin: opts.right,
type: "continuous"
});
}
if ( typeof opts.left === "number" ) {
opts.left = new Servo({
board: opts.board,
pin: opts.left,
type: "continuous"
});
}
// Initialize the right and left cooperative servos
this.servos = {
right: opts.right,
left: opts.left
};
// Set the initial servo cooperative direction
this.direction = {
right: this.center,
left: this.center
};
// Store the cooperative speed
this.speed = opts.speed === undefined ? 0 : opts.speed;
// Used to record a recallable history of movement.
this.history = [];
// Initial motion state
this.motion = "forward";
// Track directional state
this.isTurning = false;
// Wait 10ms, send fwd pulse on, then off to
// "wake up" the servos
temporal.wait(10, function() {
this.fwd(1).fwd(0);
}.bind(this));
}
Nodebot.DIR_TRANSLATION = DIR_TRANSLATION;
/**
* move Move the bot in an arbitrary direction
* @param {Number} right Speed/Direction of right servo
* @param {Number} left Speed/Direction of left servo
* @return {Object} this
*/
Nodebot.prototype.move = function( right, left ) {
// Quietly ignore duplicate instructions
if ( this.direction.right === right &&
this.direction.left === left ) {
return this;
}
// Cooperative servo motion.
// Servos are mounted opposite of each other,
// the values for left and right will be in
// opposing directions.
this.servos.right.move( right );
this.servos.left.move( left );
// Push a record object into the history
this.history.push({
timestamp: Date.now(),
right: right,
left: left
});
// Update the stored direction state
this.direction.right = right;
this.direction.left = left;
return this;
};
[
/**
* forward Move the bot forward
* fwd Move the bot forward
*
* @param {Number} 0-5, 0 is stopped, 5 is fastest
* @return {Object} this
*/
{
name: "forward",
abbr: "fwd",
toArguments: function( center, val ) {
return [ center - (val - center), val ];
}
},
/**
* reverse Move the bot in reverse
* rev Move the bot in reverse
*
* @param {Number}0-5, 0 is stopped, 5 is fastest
* @return {Object} this
*/
{
name: "reverse",
abbr: "rev",
toArguments: function( center, val ) {
return [ val, center - (val - center) ];
}
}
].forEach(function( dir ) {
var method = function( speed ) {
// Set default direction method
speed = speed === undefined ? this.speed : speed;
this.speed = speed;
this.motion = dir.name;
return this.move.apply( this,
dir.toArguments( this.center, scale( speed, this.center, 110 ) )
);
};
Nodebot.prototype[ dir.name ] = Nodebot.prototype[ dir.abbr ] = method;
});
/**
* stop Stops the bot, regardless of current direction
* @return {Object} this
*/
Nodebot.prototype.stop = function() {
this.speed = this.center;
this.motion = "stop";
return this.move( this.center, this.center );
};
[
/**
* right Turn the bot right
* @return {Object} this
*/
"right",
/**
* left Turn the bot left
* @return {Object} this
*/
"left"
].forEach(function( dir ) {
Nodebot.prototype[ dir ] = function( time ) {
// Use direction value and reverse direction map to
// derive the direction values for moving the
// cooperative servos
var actual = this.direction[ DIR_TRANSLATION.reverse[ dir ] ];
time = time || 500;
if ( !this.isTurning ) {
// Set turning lock
this.isTurning = true;
// Send turning command
this.move( actual, actual );
// Cap turning time, defaults to 500ms
temporal.wait( time, function() {
// Restore direction after turn
this[ this.motion ]( this.speed || 2 );
// Release turning lock
this.isTurning = false;
}.bind(this));
}
return this;
};
});
/**
* pivot Pivot the bot with combo directions:
* rev Move the bot in reverse
*
* @param {String} which Combination directions:
* "forward-right", "forward-left",
* "reverse-right", "reverse-left"
* (aliased as: "f-l", "f-r", "r-r", "r-l")
*
* @return {Object} this
*/
Nodebot.prototype.pivot = function( instruct, time ) {
var actual, directions, scaled, expansion;
scaled = scale( this.speed, this.center, 110 );
// Directions are declared where |this| is in scope
directions = {
"forward-right": function() {
this.move( this.center, scaled );
},
"forward-left": function() {
this.move( this.center - (scaled - this.center), this.center );
},
"reverse-right": function() {
this.move( scaled, this.center );
},
"reverse-left": function() {
this.move( this.center, this.center - (scaled - this.center) );
}
};
//
expansion = this.pivot.translate( instruct );
instruct = directions[ instruct ] || directions[ expansion ];
// Commence pivot...
instruct.call( this, this.speed );
// ...Until... time or 1000ms and then
temporal.wait(time || 1000, function() {
this[ this.motion ]( this.speed );
}.bind(this));
return this;
};
Nodebot.prototype.pivot.translate = function( instruct ) {
var instructions;
if ( instruct.length === 2 ) {
instructions = [ instruct[0], instruct[1] ];
}
if ( /\-/.test(instruct) ) {
instructions = instruct.split("-");
}
return instructions.map(function( val, i ) {
return DIR_TRANSLATION.translations[ i ][ val ];
}).join("-");
};
module.exports = Nodebot;