UNPKG

johnny-five

Version:

Firmata based Arduino Programming Framework.

639 lines (500 loc) 13.4 kB
# Ed Run with: ```bash node eg/ed.js ``` ```javascript var five = require("johnny-five"), compulsive = require("compulsive"); var ED, priv = new WeakMap(); /** * ED * * Enforcement Droid Series * * http://www.lynxmotion.com/images/jpg/bratjr00.jpg * http://www.lynxmotion.com/images/html/build112.htm * Hardware: - 1 x Alum. Channel - 3" Single Pack (ASB-503) - 2 x Multi-Purpose Servo Bracket Two Pack (ASB-04) - 1 x "L" Connector Bracket Two Pack (ASB-06) - 1 x "C" Servo Bracket w/ Ball Bearings Two Pack (ASB-09) - 1 x Robot Feet Pair (ARF-01) - 1 x SES Electronics Carrier (EC-02) - 1 x SSC-32 Servo Controller (SSC-32) - 4 x HS-422 (57oz.in.) Standard Servo (S422) * * @param {Object} opts Optional properties object */ function ED( opts ) { opts = opts || {}; // Standard servos center at 90° this.center = opts.center || 90; // Initiale movement is forward this.direction = "fwd"; // Accessor for reading the current servo position will // be defined and assigned to this.degrees object. this.degrees = {}; // holds a reference to the current repeating/looping sequence this.sequence = null; // Table of times (avg) to complete tasks this.times = { step: 0, attn: 0 }; // Minor normalization of incoming properties opts.right = opts.right || {}; opts.left = opts.left || {}; // Initialize the right and left cooperative servos // TODO: Support pre-initialized servo instances this.servos = { right: { hip: opts.right.hip && new five.Servo( opts.right.hip ), foot: opts.right.foot && new five.Servo( opts.right.foot ) }, left: { hip: opts.left.hip && new five.Servo( opts.left.hip ), foot: opts.left.foot && new five.Servo( opts.left.foot ) } }; // Create shortcut properties this.right = this.servos.right; this.left = this.servos.left; // Create accessor descriptors: // // .left { .foot, .hip } // .right { .foot, .hip } // [ "right", "left" ].forEach(function( key ) { var descriptor = {}; [ "foot", "hip" ].forEach(function( part ) { descriptor[ part ] = { get: function() { var history = this.servos[ key ][ part ].history, last = history[ history.length - 1 ]; return last && last.degrees || 90; }.bind(this) }; }, this); this.degrees[ key ] = {}; // And finally, create properties with the generated descriptor Object.defineProperties( this.degrees[ key ], descriptor ); }, this ); Object.defineProperty( this, "isCentered", { get: function() { var right, left; right = this.degrees.right; left = this.degrees.left; if ( (right.foot === 90 && right.hip === 90) && (left.foot === 90 && left.foot === 90) ) { return true; } return false; } }); // Store a recallable history of movement // TODO: Include in savable history this.history = [{ timestamp: Date.now(), side: "right", right: { hip: 0, foot: 0 }, left: { hip: 0, foot: 0 } }]; // Create an entry in the private data store. priv.set( this, { // `isWalking` is used in: // ED.prototype.(attn|stop) // ED.prototype.(forward|fwd;reverse|rev) isWalking: false, // Allowed to hit the dance floor. canDance: true }); } /** * attn Stop and stand still * @return {Object} this */ //ED.prototype.attn = ED.prototype.stop = function() { ED.prototype.attn = function( options ) { options = options || {}; if ( !options.isWalking ) { if ( this.sequence ) { this.sequence.stop(); this.sequence = null; } priv.set( this, { isWalking: false }); } this.move({ type: "attn", right: { hip: 90, foot: 90 }, left: { hip: 90, foot: 90 } }); }; /** * step Take a step * * @param {String} instruct Give the step function a specific instruction, * one of: (fwd, rev, left, right) * */ ED.prototype.step = function( direct ) { var isLeft, isFwd, opposing, direction, state; state = priv.get(this); if ( /fwd|rev/.test(direct) ) { direction = direct; direct = undefined; } else { direction = "fwd"; } // Derive which side to step on; based on last step or explicit step this.side = direct || ( this.side !== "right" ? "right" : "left" ); // Update the value of the current direction this.direction = direction; // Determine if the bot is moving fwd // Used in phase 3 to conditionally control the servo degrees isFwd = this.direction === "fwd"; // Determine if this is the left foot // Used in phase 3 to conditionally control the servo degrees isLeft = this.side === "left"; // Opposing leg side, used in prestep and phase 2; // opposing = isLeft ? "right" : "left"; // Begin stepping movements. // this.queue([ // Phase 1 { wait: 500, task: function() { var stepping, opposing, instruct; stepping = isLeft ? "left" : "right"; opposing = isLeft ? "right" : "left"; instruct = {}; // Lift the currently stepping foot, while // leaning on the currently opposing foot. instruct[ stepping ] = { foot: isLeft ? 40 : 140 }; instruct[ opposing ] = { foot: isLeft ? 70 : 110 }; // Swing currently stepping hips this.move( instruct ); }.bind(this) }, // Phase 2 { wait: 500, task: function() { var degrees = isLeft ? ( isFwd ? 120 : 60 ) : ( isFwd ? 60 : 120 ); // Swing currently stepping hips this.move({ type: "swing", right: { hip: degrees }, left: { hip: degrees } }); }.bind(this) }, // Phase 3 { wait: 500, task: function() { // Flatten feet to surface this.servos.right.foot.center(); this.servos.left.foot.center(); }.bind(this) } ]); }; [ /** * forward, fwd * * Move the bot forward */ { name: "forward", abbr: "fwd" }, /** * reverse, rev * * Move the bot in reverse */ { name: "reverse", abbr: "rev" } ].forEach(function( dir ) { ED.prototype[ dir.name ] = ED.prototype[ dir.abbr ] = function() { var startAt, stepper, state; startAt = 10; state = priv.get(this); // If ED is already walking in this direction, return immediately; // This prevents multiple movement loops from being scheduled. if ( this.direction === dir.abbr && state.isWalking ) { return; } // If a sequence reference exists, kill it. This will // clear all pending queue repeaters. if ( this.sequence ) { this.sequence.stop(); this.sequence = null; } this.direction = dir.abbr; // Update the private state to indicate // that the bot is currently walking. // // This is used by the behaviour loop to // conditionally continue walking or to terminate. // // Walk termination occurs in the ED.prototype.attn method // priv.set(this, { isWalking: true }); stepper = function( loop ) { // Capture of sequence queue reference if ( this.sequence === null ) { this.sequence = loop; } this.step( dir.abbr ); if ( !priv.get(this).isWalking ) { loop.stop(); } }.bind(this); // If the bot is not centered, ie. all servos at 90degrees, // bring the bot to attention before proceeding. if ( !this.isCentered ) { this.attn({ isWalking: true }); // Offset the amount ms required for attn() to complete startAt = 750; } this.queue([ { wait: startAt, task: function() { this.step( dir.abbr ); }.bind(this) }, { loop: 1500, task: stepper } ]); }; }); ED.prototype.dance = function() { var isLeft, restore, state; // Derive which side to step on; based on last step or explicit step this.side = this.side !== "right" ? "right" : "left"; // Determine if this is the left foot // Used in phase 3 to conditionally control the servo degrees isLeft = this.side === "left"; this.attn(); if ( typeof this.moves === "undefined" ) { this.moves = 0; } this.queue([ // Phase 1 { wait: 500, task: function() { var degrees = isLeft ? 120 : 60; if ( this.moves % 2 === 0 ) { this.move({ type: "attn", right: { hip: 90, foot: 60 }, left: { hip: 90, foot: 120 } }); } else { this.move({ type: "attn", right: { hip: 90, foot: 120 }, left: { hip: 90, foot: 60 } }); } // Swing currently stepping hips this.move({ type: "swing", right: { hip: degrees }, left: { hip: degrees } }); // restore = this.servos[ this.side ].foot.last.degrees; // this.servos[ this.side ].foot.move( restore === 140 ? 120 : 60 ); }.bind(this) }, // Phase 2 { wait: 500, task: function() { var degrees = isLeft ? 60 : 120; // Swing currently stepping hips this.move({ type: "swing", right: { hip: degrees }, left: { hip: degrees } }); // this.servos[ this.side ].foot.move( restore ); }.bind(this) }, // Phase 3 { wait: 500, task: function() { this.move({ type: "attn", right: { hip: 90, foot: 90 }, left: { hip: 90, foot: 90 } }); this.dance(); }.bind(this) } ]); this.moves++; }; /** * move Move the bot in an arbitrary direction * @param {Object} positions left/right hip/foot positions * */ ED.prototype.move = function( positions ) { var start, type; if ( this.history.length ) { start = this.history[ this.history.length - 1 ]; } type = positions.type || "step"; [ "foot", "hip" ].forEach(function( section ) { [ "right", "left" ].forEach(function( side ) { var interval, endAt, startAt, servo, step, s; if ( typeof positions[ side ] === "undefined" ) { return; } endAt = positions[ side ][ section ]; servo = this.servos[ side ][ section ]; startAt = this.degrees[ side ][ section ]; // Degrees per step step = 2; s = Date.now(); if ( !endAt || endAt === startAt ) { return; } if ( start ) { // Determine degree step direction if ( endAt < startAt ) { step *= -1; } // Repeat each step for required number of steps to move // servo into new position. Each step is ~20ms duration this.repeat( Math.abs( endAt - startAt ) / 2, 10, function() { // console.log( startAt ); servo.move( startAt += step ); if ( startAt === endAt ) { this.times[ type ] = (this.times[ type ] + (Date.now() - s)) / 2; } }.bind(this)); } else { // TODO: Stop doing this servo.move( endAt ); five.Fn.sleep(500); } }, this ); }, this ); // Push a record object into the stepping history this.history.push({ timestamp: Date.now(), side: this.side, right: five.Fn.extend( { hip: 0, foot: 0 }, this.degrees.right, positions.right ), left: five.Fn.extend( { hip: 0, foot: 0 }, this.degrees.left, positions.left ) }); }; // Borrow API from Compulsive [ "wait", "loop", "queue", "repeat" ].forEach(function( api ) { ED.prototype[ api ] = compulsive[ api ]; }); // Begin program when the board, serial and // firmata are connected and ready (new five.Board()).on("ready", function() { var biped; // Create new Enforcement Droid // assign servos biped = new ED({ right: { hip: 9, foot: 11 }, left: { hip: 10, foot: 12 } }); // Inject into REPL for manual controls this.repl.inject({ s: new five.Servo.Array(), b: biped }); biped.attn(); biped.wait(1000, function() { biped.fwd(); }); // Controlled via REPL: // b.fwd(), b.rev(), b.attn() }); // http://www.lynxmotion.com/images/html/build112.htm ``` ## Breadboard/Illustration <img src="https://raw.github.com/rwldrn/johnny-five/master/docs/breadboard/ed.png"> ## 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.