UNPKG

johnny-five

Version:

Firmata based Arduino Programming Framework.

577 lines (424 loc) 12.2 kB
# Navigator Run with: ```bash node eg/navigator.js ``` ```javascript var five = require("johnny-five"), __ = require("../lib/fn.js"), board, Navigator, navigator, servos, pivotExpansion, directionMap, scale; directionMap = { reverse: { right: "left", left: "right", fwd: "rev", rev: "fwd" }, translations: [ { f: "forward", r: "reverse", fwd: "forward", rev: "reverse" }, { r: "right", l: "left" } ] }; scale = function( speed, low, high ) { return Math.floor( five.Fn.map( speed, 0, 5, low, high ) ); }; /** * Navigator * @param {Object} opts Optional properties object */ function Navigator( opts ) { // Boe Navigator continuous are calibrated to stop at 90° this.center = opts.center || 90; // Initialize the right and left cooperative servos this.servos = { right: new five.Servo({ pin: opts.right, type: "continuous" }), left: new five.Servo({ pin: opts.left, type: "continuous" }) }; // Set the initial servo cooperative direction this.direction = opts.direction || { right: this.center, left: this.center }; this.compass = opts.compass || null; this.gripper = opts.gripper || null; // Store the cooperative speed this.speed = opts.speed === undefined ? 0 : opts.speed; // Store a recallable history of movement // TODO: Include in savable history this.history = []; // Initial direction this.which = "forward"; // Track directional state this.isTurning = false; // Wait 10ms, send fwd pulse on, then off to // "wake up" the servos setTimeout(function() { this.fwd(1).fwd(0); }.bind(this), 10); } Navigator.DIR_MAP = directionMap; /** * 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 */ Navigator.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", args: 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", args: function( center, val ) { return [ val, center - (val - center) ]; } } ].forEach(function( dir ) { var method = function( speed ) { // Set default direction method speed = speed === undefined ? 1 : speed; this.speed = speed; this.which = dir.name; return this.move.apply( this, dir.args( this.center, scale( speed, this.center, 110 ) ) ); }; Navigator.prototype[ dir.name ] = Navigator.prototype[ dir.abbr ] = method; }); /** * stop Stops the bot, regardless of current direction * @return {Object} this */ Navigator.prototype.stop = function() { this.speed = this.center; this.which = "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 ) { Navigator.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[ directionMap.reverse[ dir ] ]; time = time || 500; if ( !this.isTurning ) { // Set turning lock this.isTurning = true; // Send turning command this.move( actual, actual ); // Cap turning time setTimeout(function() { // Restore direction after turn this[ this.which ]( this.speed || 2 ); // Release turning lock this.isTurning = false; }.bind(this), time ); } return this; }; }); pivotExpansion = function( which ) { var parts; if ( which.length === 2 ) { parts = [ which[0], which[1] ]; } if ( /\-/.test(which) ) { parts = which.split("-"); } return parts.map(function( val, i ) { console.log( val ); return directionMap.translations[ i ][ val ]; }).join("-"); }; /** * 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 */ Navigator.prototype.pivot = function( which, time ) { var actual, directions, scaled; scaled = scale( this.speed, this.center, 110 ); 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) ); } }; which = directions[ which ] || directions[ pivotExpansion( which ) ]; which.call( this, this.speed ); setTimeout(function() { this[ this.which ]( this.speed ); }.bind(this), time || 1000 ); return this; }; // Begin program when the board, serial and // firmata are connected and ready (board = new five.Board()).on("ready", function() { // TODO: Refactor into modular program code var center, collideAt, degrees, step, facing, range, laser, look, isScanning, scanner, gripper, isGripping, sonar, gripAt, ping, mag, bearing; // Collision distance (inches) collideAt = 6; gripAt = 2; // Servo scanning steps (degrees) step = 2; // Current facing direction facing = ""; // Scanning range (degrees) range = [ 10, 170 ]; // Servo center point (degrees) center = ( (range[1] - range[0]) / 2 ) + range[0]; // Starting scanner scanning position (degrees) degrees = center; // Direction to look after releasing scanner lock (degrees) // look = { // forward: center, // left: 130, // right: 40 // }; // Scanning state isScanning = true; // Gripping state isGripping = false; // compass/magnetometer mag = new five.Magnetometer(); // Servo gripper gripper = new five.Gripper({ servo: { pin: 13, range: [ 20, 160 ] }, scale: [ 0, 10 ] }); // New base navigator // right servo = pin 10, left servo = pin 11 navigator = new Navigator({ right: 10, left: 11, compass: mag, gripper: gripper }); // The laser is just a special case Led laser = new five.Led(9); // Digital PWM (range) ping = new five.Ping(7); // Analog Voltage (range) // sonar = new five.Sonar("A0"); // Servo scanner instance (panning) scanner = new five.Servo({ pin: 12, range: range }); // Inject navigator object into REPL this.repl.inject({ b: navigator, g: gripper }); // Initialize the scanner at it's center point // Will be exactly half way between the range's // lower and upper bound scanner.center(); // Wait 1000ms, then initialize forward movement this.wait( 1000, function() { // navigator.fwd(3); }); // Scanner/Panning loop this.loop( 50, function() { var bounds; bounds = { left: center + 15, //center + 10, right: center - 15 //center - 10 }; // During course change, scanning is paused to avoid // overeager redirect instructions[1] if ( isScanning ) { // Calculate the next step position if ( degrees >= scanner.range[1] || degrees <= scanner.range[0] ) { step *= -1; } // Update the position in degrees degrees += step; // The following three conditions will help determine // which way the navigator should turn if a potential collideAt // may occur in the ping "change" event handler[2] if ( degrees > bounds.left ) { facing = "left"; } if ( degrees < bounds.right ) { facing = "right"; } // if ( degrees > bounds.right && degrees < bounds.left ) { if ( __.range( bounds.right, bounds.left ).indexOf( degrees ) > -1 ) { facing = "fwd"; } scanner.move( degrees ); } }); // sonar.on("change", function() { // ping.on("change", function() { // var distance = Math.abs(this.inches); // // TODO: Wrap this behaviour in an abstraction // if ( distance <= collideAt && !isGripping ) { // gripper.max(); // // simulate drop instruction // setTimeout(function() { // isGripping = false; // gripper.min(); // }, 5000); // } // }); // Compass heading monitor // mag.on("headingchange", function() { // if ( !/[\-by]/.test(this.bearing.name) && this.bearing.name !== bearing ) { // bearing = this.bearing.name; // console.log( this.bearing ); // } // }); // [2] ping "change" events are emitted when the value of a // distance reading has changed since the previous reading // // TODO: Avoid false positives? ping.on("read", function( err ) { var release = 750, distance = Math.abs(this.inches), isReverse = false, turnTo; if ( navigator.isTurning ) { return; } // If distance value is null or NaN if ( distance === null || isNaN(distance) ) { return; } // Detect collideAt // && isScanning if ( distance <= collideAt && isScanning ) { laser.strobe(); // Scanning lock will prevent multiple collideAt // detections piling up for the same obstacle isScanning = false; // Determine direction to turn turnTo = Navigator.DIR_MAP.reverse[ facing ]; // Set reversal flag. isReverse = turnTo === "rev"; // Log collideAt detection to REPL console.log( [ Date.now(), "\tCollision detected " + this.inches + " inches away.", "\tTurning " + turnTo.toUpperCase() + " to avoid" ].join("\n") ); // Turn the navigator navigator[ turnTo ]( navigator.speed ); if ( isReverse ) { release = 1500; } // [1] Allow Nms to pass and release the scanning lock // by setting isScanning state to true. board.wait( release, function() { console.log( "Release Scanner Lock" ); degrees = 89; scanner.center(); if ( isReverse ) { // navigator.fwd( navigator.speed ); navigator.pivot("reverse-right"); navigator.which = "fwd"; } laser.brightness(0); isScanning = true; }); } }); }); // References // // http://www.maxbotix.com/documents/MB1010_Datasheet.pdf ``` ## Breadboard/Illustration ## Devices ## Documentation _(Nothing yet)_ ## Contributing All contributions must adhere to the [Idiomatic.js Style Guide](https://github.com/rwldrn/idiomatic.js), by maintaining the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). ## Release History _(Nothing yet)_ ## License Copyright (c) 2012 Rick Waldron <waldron.rick@gmail.com> Licensed under the MIT license.