UNPKG

johnny-five

Version:

The JavaScript Arduino Programming Framework.

868 lines (726 loc) 18.4 kB
// var lodash = require("lodash"); // var machina = require("machina")(lodash); var five = require("../lib/johnny-five.js"); var temporal = require("temporal"); var keypress = require("keypress"); var dualShock = require("dualshock-controller"); var argv = require("optimist").default({ noscan: false, calibrate: false }).argv; var board = new five.Board(); var controller = dualShock({ config: "dualShock3", analogStickSmoothing: true }); controller.isConnected = false; // argv flags: // // --calibrate // - enables calibrating the servos, defaults to false // // --noscan // - disables the scanner, defaults to false // // n-DOF move function move(positions, speed) { joints.forEach(function(part, i) { legs[part].to(positions[i], speed); }); } function scale(x, fromLow, fromHigh, toLow, toHigh) { return (x - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow; } function degrees(center, val) { if (val >= 128) { return scale(val, 128, 255, center - 1, 0); } return scale(val, 127, 0, center, 180); } function still() { Object.keys(tasks).forEach(function(name) { if (tasks[name]) { tasks[name].stop(); } }); } var all, hips, knees, feet; var tasks = { controls: null, dance: null, turn: null, scanner: null, walk: null }; var isAutonomous = false; var isAvoiding = false; var isMoving = false; var isScanning = false; var isTurning = false; var currently = null; var cinvert = { rev: "fwd", fwd: "rev", turn: "turn" }; var legs = {}; var inits = [ { pin: 10, id: "rf", startAt: 91 }, { pin: 11, id: "rk", startAt: 92 }, { pin: 12, id: "rh", startAt: 98 }, { pin: 7, id: "lf", startAt: 83, isInverted: true }, { pin: 8, id: "lk", startAt: 89, isInverted: true }, { pin: 9, id: "lh", startAt: 89, isInverted: true } ]; var joints = inits.map(function(init) { return init.id; }); var starts = inits.map(function(init) { return init.startAt; }); // TODO: These can/should be loaded in from a JSON file var gaits = [ { id: "rev", sets: [ { id: "short", isLoop: true, isInterruption: false, lapse: 500, sequence: [ // [ rf, rk, rh, lf, lk, lh ] { moves: [ -7,-20,-20, 7, 20, 20 ] }, { moves: [ 7,-20,-20, -7, 20, 20 ] }, { moves: [ 7, 20, 20, -7,-20,-20 ] }, { moves: [ -7, 20, 20, 7,-20,-20 ] } ] }, { id: "long", isLoop: true, isInterruption: false, lapse: 600, sequence: [ { moves: [-12,-35,-35, 12, 35, 35 ] }, { moves: [ 12,-35,-35,-12, 35, 35 ] }, { moves: [ 12, 35, 35,-12,-35,-35 ] }, { moves: [-12, 35, 35, 12,-35,-35 ] } ] } ] }, { id: "fwd", sets: [ { id: "short", isLoop: true, isInterruption: false, lapse: 500, sequence: [ // [ rf, rk, rh, lf, lk, lh ] { moves: [ 7,-20,-20, -7, 20, 20 ] }, { moves: [ -7,-20,-20, 7, 20, 20 ] }, { moves: [ -7, 20, 20, 7,-20,-20 ] }, { moves: [ 7, 20, 20, -7,-20,-20 ] } ] }, { id: "long", isLoop: true, isInterruption: false, lapse: 600, sequence: [ // [ rf, rk, rh, lf, lk, lh ] { moves: [ 12,-35,-35,-12, 35, 35 ] }, { moves: [-12,-35,-35, 12, 35, 35 ] }, { moves: [-12, 35, 35, 12,-35,-35 ] }, { moves: [ 12, 35, 35,-12,-35,-35 ] } ] } ] }, { id: "action", sets: [ { id: "rkick", isLoop: false, isInterruption: true, lapse: 500, sequence: [ // [ rf, rk, rh, lf, lk, lh ] { moves: [ 0, 0, 0, 0, 0, 0 ] }, { moves: [ 42, 0, 0,-14, 0, 0 ] }, { moves: [ 0,-32, 41,-23, 0, 0 ] }, { moves: [ 0, 24,-20,-23, 0, 0 ], lapse: 250 }, { moves: [ 0, 0, 0,-18, 0, 0 ] }, { moves: [ 0, 0, 0, 0, 0, 0 ] } ] }, { id: "lkick", isLoop: false, isInterruption: true, lapse: 500, sequence: [ // [ rf, rk, rh, lf, lk, lh ] { moves: [ 0, 0, 0, 0, 0, 0 ] }, { moves: [-14, 0, 0, 42, 0, 0 ] }, { moves: [-23, 0, 0, 0,-32, 41 ] }, { moves: [-23, 0, 0, 0, 24,-20 ], lapse: 250 }, { moves: [-18, 0, 0, 0, 0, 0 ] }, { moves: [ 0, 0, 0, 0, 0, 0 ] } ] } ] }, { id: "turn", sets: [ { id: "left", isLoop: false, isInterruption: true, lapse: 300, sequence: [ { moves: [ 0,-35,-40, 0, 35, 37 ] }, { moves: [ 0, 35, 37, 0,-35,-40 ] }, { moves: [-14, 35, 37, 20,-35,-40 ] }, { moves: [-14, 35, 37, 20, 0, 0 ] }, { moves: [ 20, 0, 0,-14, 0, 0 ] } ] }, { id: "right", isLoop: false, isInterruption: true, lapse: 300, sequence: [ { moves: [ 0, 35, 37, 0,-35,-40 ] }, { moves: [ 0,-35,-40, 0, 35, 37 ] }, { moves: [ 20,-35,-40,-14, 35, 37 ] }, { moves: [ 20, 0, 0,-14, 35, 37 ] }, { moves: [-14, 0, 0, 20, 0, 0 ] }, ] } ] }, { id: "correct", sets: [ { id: "left", isLoop: false, isInterruption: true, lapse: 250, sequence: [ // { moves: [ 0, 0, 0, 0, 0, 0 ] }, { moves: [ 35, 0, 0,-18, 0, 0 ] }, { moves: [], lapse: 100 }, { moves: [ 2,-20,-20,-18, 20, 20 ] }, { moves: [ 0,-20,-20, 0, 20, 20 ] }, { moves: [ 0, 0, 0, 0, 0, 0 ] }, ] }, { id: "right", isLoop: false, isInterruption: true, lapse: 250, sequence: [ // { moves: [ 0, 0, 0, 0, 0, 0 ] }, { moves: [-18, 0, 0, 35, 0, 0 ] }, { moves: [], lapse: 100 }, { moves: [-18, 20, 20, 2,-20,-20 ] }, { moves: [ 0, 20, 20, 0,-20,-20 ] }, { moves: [ 0, 0, 0, 0, 0, 0 ] }, ] } ] }, { id: "home", sets: [ { id: "stand", isLoop: false, isInterruption: true, lapse: 500, sequence: [ { moves: [ 0, 0, 0, 0, 0, 0 ] } ] }, { id: "rest", isLoop: false, isInterruption: true, lapse: 500, sequence: [ { moves: [ 0, 35, 35, 0, 35, 35 ] } ] } ] } ]; var paths = [ "left", "right" ]; var looks = { left: 50, center: 90, right: 130, which: "center" }; var redirects = { left: "right", center: null, right: "left" }; var Scanner = { lookingAt: "center", isSafe: true, isObserving: false, isThinking: false, get isScanning() { return this.data.isScanning; }, set isScanning(value) { if (value) { this.reset(); } this.data.isScanning = value; }, data: { isScanning: false, distance: 10, samples: [] }, reset: function() { this.data.samples.length = 0; }, process: function(value) { if (value < this.data.distance) { this.data.samples.push(value); if (this.data.samples.length >= 5) { this.data.samples.length = 0; this.isSafe = false; return; } } else { this.data.samples.length = 0; } this.isSafe = true; } }; var isRightfirst = false; var isWalking = false; function walk(direction, speed) { var sequence; var set = gaits[direction].sets[speed < 600 ? 1 : 0]; var lapse = set.lapse; if (isWalking) { return; } isWalking = true; sequence = isRightfirst ? set.sequence.slice(0, 2) : set.sequence.slice(2); // Flip the side bit for the next run. isRightfirst = !isRightfirst; still(); var queue = sequence.map(function(relative) { // var lapse = relative.lapse | 0; var absolute; // Convert the relative movement values // to absolute servo positions. absolute = relative.moves.map(function(value, i) { return value + starts[i]; }); return { wait: lapse, task: function() { if (absolute.length) { move(absolute, lapse); } } }; }); queue.push({ wait: 10, task: function() { isWalking = false; } }); tasks.walk = temporal.queue(queue); } function turn(direction) { var sequence; var set = gaits[3].sets[direction]; var lapse = set.lapse; if (isTurning) { return; } isTurning = true; sequence = set.sequence; Scanner.lookingAt = direction ? "right" : "left"; still(); // if (tasks.walk) { // tasks.walk.stop(); // } // if (tasks.turn) { // tasks.turn.stop(); // } var queue = sequence.map(function(relative) { // var lapse = relative.lapse | 0; var absolute; // Convert the relative movement values // to absolute servo positions. absolute = relative.moves.map(function(value, i) { return value + starts[i]; }); return { wait: lapse, task: function() { if (absolute.length) { move(absolute, lapse); } } }; }); queue.push({ wait: 10, task: function() { isTurning = false; } }); tasks.turn = temporal.queue(queue); } board.on("ready", function() { var neck = new five.Servo({ pin: 5, range: [20, 160], startAt: 90 }); var eyes = new five.IR.Distance({ device: "2Y0A21", pin: "A0", freq: 10 }); // Leg initialization legs = inits.reduce(function(accum, init) { accum[init.id] = new five.Servo(init); return accum; }, {}); function retreat() { Scanner.isScanning = true; Scanner.isPanning = true; controls.rev.long(function() { if (eyes.inches < 10) { retreat(); } else { Scanner.isPanning = false; Scanner.isScanning = true; Scanner.reset(); controls.fwd.long(); } }); } function safeCourse(callback) { function look() { neck.to(range[dir], 250); } var dir = 0; var readings = [ /* left, right */ ]; var data = {}; var range = [ 20, 160 ]; var onComplete = function(data) { if (data !== null) { Scanner.isThinking = false; } if (typeof callback === "function") { callback(data); } }; if (Scanner.isThinking || Scanner.isPanning) { onComplete(null); return; } Scanner.isThinking = true; neck.on("move:complete", function moveComplete() { var sorted, safest; readings.push(eyes.inches); if (readings.length < 2) { dir ^= 1; process.nextTick(look); } else { neck.removeListener("move:complete", moveComplete); data[readings[0]] = "left"; data[readings[1]] = "right"; sorted = readings.sort(function(a, b) { return a > b; }); safest = data[sorted[1]]; onComplete(data, safest); } }); process.nextTick(look); } eyes.on("data", function() { if (Scanner.isThinking || Scanner.isObserving) { return; } Scanner.process(this.inches); if (!Scanner.isSafe) { console.log( "COLLISION IMMINENT" ); still(); Scanner.isObserving = true; controls.home.rest(function() { safeCourse(function(data, safest) { // var readings = Object.keys(data).map(Number).sort(function(a, b) { // return a > b; // }); // var safest = data[readings[1]]; Scanner.lookingAt = safest; controls.correct[safest](function() { neck.center(); Scanner.isObserving = false; Scanner.isScanning = true; }); }); }); } else { if (Scanner.isScanning) { // console.log( "TURN TOWARDS: ", Scanner.lookingAt ); Scanner.isObserving = true; Scanner.isScanning = false; var correction = Scanner.lookingAt; controls.correct[correction](function() { console.log( "CORRECTED: ", correction ); neck.to(90, 50).once("move:complete", function() { if (eyes.inches > 10) { console.log( "COLLISION: AVOIDED" ); Scanner.lookingAt = "center"; Scanner.isObserving = false; Scanner.isScanning = false; Scanner.reset(); controls.fwd.long(); } else { retreat(); } }); }); } } }); var controls = gaits.reduce(function(controls, gait) { controls[gait.id] = gait.sets.reduce(function(seq, set) { // Create an API from gait.set // seq[set.id] = function(opts, callback) { var noLoop; if (typeof opts === "function") { callback = opts; opts = {}; } opts = opts || {}; noLoop = opts.noLoop; var hash = [gait.id, set.id].join(":"); // console.log( "set.isInterruption", set.isInterruption ); if (set.isInterruption) { isRightfirst = false; isWalking = false; isMoving = false; currently = null; } // Allow interruptions if (currently !== hash) { currently = hash; isMoving = false; } // If `isMoving` is currently `true`, bail out // to avoid pile up. if (isMoving) { return; } // If `isMoving` is currently `false`, // set it to `true` to avoid pile up isMoving = true; still(); var queue = set.sequence.map(function(relative) { var lapse = set.lapse; var absolute; if (relative.lapse) { lapse = relative.lapse; } // Convert the relative movement values // to absolute servo positions. absolute = relative.moves.map(function(value, i) { return value + starts[i]; }); return { wait: lapse, task: function() { if (absolute.length) { move(absolute, lapse); } } } }); if (set.isLoop || opts.isLoop) { queue.push({ wait: 10, task: function() { isMoving = false; if (callback) { callback(set.id); } else { controls[gait.id][set.id](); } } }); } else { queue.push({ wait: 10, task: function() { isMoving = false; if (callback) { callback(set.id); } } }); } tasks.controls = temporal.queue(queue); }; return seq; }, {}); return controls; }, {}); var deadzone = { low: 512 - 50, high: 512 + 50 }; var left = { isActivated: false, x: 0, y: 0 }; var right = { isActivated: false, x: 0, y: 0 }; temporal.loop(100, function() { if (right.isActivated) { if (right.y > deadzone.high || right.y < deadzone.low) { walk( right.y > 512 ? 1 : 0, 750 - (Math.abs(right.y - 512) / 512 * 250) ); } else { if (tasks.walk) { tasks.walk.stop(); isWalking = false; } } } if (left.isActivated) { if (left.x > deadzone.high || left.x < deadzone.low) { turn(left.x > 512 ? 1 : 0); } else { if (tasks.turn) { tasks.turn.stop(); isTurning = false; } } } }); controller.on("connected", function() { controller.isConnected = true; }); controller.on("leftAnalogBump:press", function() { left.isActivated = !left.isActivated; }); controller.on("rightAnalogBump:press", function() { right.isActivated = !right.isActivated; }); controller.on("left:move", function(position) { left.x = scale(position.x, 0, 255, 0, 1024) | 0; left.y = scale(position.y, 255, 0, 0, 1024) | 0; if (tasks.scanner) { tasks.scanner.stop(); } var degrees = scale(position.x, 0, 255, 20, 160) | 0; Scanner.lookingAt = degrees > 90 ? "right" : "left"; Scanner.isObserving = false; neck.to(degrees); }); controller.on("right:move", function(position) { right.x = scale(position.x, 0, 255, 0, 1024) | 0; right.y = scale(position.y, 255, 0, 0, 1024) | 0; }); controller.on("circle:press", function() { still(); controls.home.rest(); }); controller.on("triangle:press", function() { still(); controls.home.stand(); }); controller.on("l1:press", function() { controls.correct.left(); }); controller.on("r1:press", function() { controls.correct.right(); }); controller.on("l2:press", function() { controls.action.lkick(); }); controller.on("r2:press", function() { controls.action.rkick(); }); controller.on("dpadLeft:press", function() { controls.correct.left(); }); controller.on("dpadRight:press", function() { controls.correct.right(); }); controller.on("dpadUp:press", function() { controls.fwd.long(); }); controller.on("dpadDown:press", function() { controls.rev.long(); }); controller.connect(); this.repl.inject({ Scanner: Scanner, safeCourse: safeCourse, get tasks() { return tasks; }, neck: neck, all: all, hips: hips, knees: knees, feet: feet, legs: legs, controls: controls, // fwd: fwd, // fwdlong: fwdlong, // scan: scan, eyes: eyes, peanuts: function() { var direction = 0; tasks.dance = temporal.loop(500, function() { if (direction) { feet.to(150, 500); } else { feet.to(90, 500); } direction = direction ? 0 : 1; }); } }); controls.home.rest(); // setTimeout(function() { // safeCourse(function(data) { // console.log( data ); // }); // }, 500); // controls.fwd.long(); });