UNPKG

johnny-five-electron

Version:

Temporary fork to support Electron (to be deprecated)

945 lines (819 loc) 20 kB
var IS_TEST_MODE = !!process.env.IS_TEST_MODE; var Board = require("../lib/board"); var EVS = require("../lib/evshield"); var __ = require("../lib/fn"); var events = require("events"); var util = require("util"); var Collection = require("../lib/mixins/collection"); var Sensor = require("../lib/sensor"); var ShiftRegister = require("../lib/shiftregister"); var nanosleep = require("../lib/sleep").nano; var priv = new Map(); var registers = new Map(); function registerKey(registerOpts) { return Object.keys(registerOpts).sort().reduce(function(accum, key) { accum = accum + "." + registerOpts[key]; return accum; }, ""); } function latch(state, bit, on) { return on ? state |= (1 << bit) : state &= ~(1 << bit); } function updateShiftRegister(motor, dir) { var rKey = registerKey(motor.opts.register), register = registers.get(motor.board)[rKey], latchState = register.value, bits = priv.get(motor).bits, forward = dir !== "reverse"; // There are two ShiftRegister bits which we need to change based on the // direction of the motor. These will be the pins that control the HBridge // on the board. They will get flipped high/low based on the current flow // in the HBridge. latchState = latch(latchState, bits.a, forward); latchState = latch(latchState, bits.b, !forward); if (register.value !== latchState) { register.send(latchState); } } var Controllers = { ShiftRegister: { initialize: { value: function (opts) { var rKey = registerKey(opts.register); if (!opts.bits || opts.bits.a === undefined || opts.bits.b === undefined) { throw new Error("ShiftRegister Motors MUST contain HBRIDGE bits {a, b}"); } priv.get(this).bits = opts.bits; if (!registers.has(this.board)) { registers.set(this.board, {}); } if (!registers.get(this.board)[rKey]) { registers.get(this.board)[rKey] = new ShiftRegister({ board: this.board, pins: opts.register }); } this.io.pinMode(this.pins.pwm, this.io.MODES.PWM); } }, dir: { value: function(speed, dir) { this.stop(); updateShiftRegister(this, dir.name); this.direction = dir; process.nextTick(this.emit.bind(this, dir.name)); return this; } } }, PCA9685: { REGISTER: { value: { PCA9685_MODE1: 0x0, PCA9685_PRESCALE: 0xFE, LED0_ON_L: 0x6 } }, address: { get: function() { return this.opts.address; } }, setPWM: { value: function(pin, off, on) { if (typeof on === "undefined") { on = 0; } on *= 16; off *= 16; this.io.i2cWrite(this.opts.address, [this.REGISTER.LED0_ON_L + 4 * (pin), on, on >> 8, off, off >> 8]); } }, setPin: { value: function(pin, value, duty, phaseShift) { var on = 0; if (value !== 0) { value = 255; } if (typeof duty !== "undefined") { value = duty; } if (typeof phaseShift !== "undefined") { on = phaseShift; value = value + on; } this.setPWM(pin, value, on); } }, initialize: { value: function() { if (!this.board.Drivers[this.opts.address]) { this.io.i2cConfig(this.opts); this.board.Drivers[this.opts.address] = { initialized: false }; // Reset this.io.i2cWrite(this.opts.address, [this.REGISTER.PCA9685_MODE1, 0x0]); // Sleep this.io.i2cWrite(this.opts.address, [this.REGISTER.PCA9685_MODE1, 0x10]); // Set prescalar this.io.i2cWrite(this.opts.address, [this.REGISTER.PCA9685_PRESCALE, 0x3]); // Wake up this.io.i2cWrite(this.opts.address, [this.REGISTER.PCA9685_MODE1, 0x0]); // Wait 5 nanoseconds for restart nanosleep(5); // Auto-increment this.io.i2cWrite(this.opts.address, [this.REGISTER.PCA9685_MODE1, 0xa1]); // Reset all PWM values for (var i = 0; i < 16; i++) { this.setPWM(i, 0, 0); } this.board.Drivers[this.opts.address].initialized = true; } } } }, EVS_EV3: { initialize: { value: function(opts) { var state = priv.get(this); state.shield = EVS.shieldPort(opts.pin); state.ev3 = new EVS(Object.assign(opts, { io: this.io })); this.opts.pins = { pwm: opts.pin, dir: opts.pin, }; } }, setPWM: { value: function(pin, value) { var state = priv.get(this); var register = state.shield.motor === EVS.M1 ? EVS.SPEED_M1 : EVS.SPEED_M2; var speed = __.scale(value, 0, 255, 0, 100) | 0; if (value === 0) { state.ev3.write(state.shield, EVS.COMMAND, EVS.Motor_Reset); } else { if (!this.direction.value) { speed = -speed; } var data = [ // 0-100 speed, // Duration (0 is forever) 0, // Command B 0, // Command A EVS.CONTROL_SPEED | EVS.CONTROL_GO ]; state.ev3.write(state.shield, register, data); } } }, setPin: { value: function(pin, value) { this.setPWM(this.pin, value); } }, }, }; // Aliases // // NXT motors have the exact same control commands as EV3 motors Controllers.EVS_NXT = Controllers.EVS_EV3; var Devices = { NONDIRECTIONAL: { pins: { get: function() { return { pwm: this.opts.pin }; } }, dir: { value: function(speed) { speed = speed || this.speed(); return this; } }, resume: { value: function() { var speed = this.speed(); this.speed({ speed: speed }); return this; } } }, DIRECTIONAL: { pins: { get: function() { if (Array.isArray(this.opts.pins)) { return { pwm: this.opts.pins[0], dir: this.opts.pins[1] }; } else { return this.opts.pins; } } }, dir: { configurable: true, value: function(speed, dir) { speed = speed || this.speed(); this.stop(); this.setPin(this.pins.dir, dir.value); this.direction = dir; process.nextTick(this.emit.bind(this, dir.name)); return this; } } }, CDIR: { pins: { get: function() { if (Array.isArray(this.opts.pins)) { return { pwm: this.opts.pins[0], dir: this.opts.pins[1], cdir: this.opts.pins[2] }; } else { return this.opts.pins; } } }, dir: { value: function(speed, dir) { if (typeof speed === "undefined") { speed = this.speed(); } this.stop(); this.direction = dir; this.setPin(this.pins.cdir, 1 ^ dir.value); this.setPin(this.pins.dir, dir.value); process.nextTick(this.emit.bind(this, dir.name)); return this; } }, brake: { value: function(duration) { this.speed({ speed: 0, saveSpeed: false }); this.setPin(this.pins.dir, 1, 127); this.setPin(this.pins.cdir, 1, 128, 127); this.speed({ speed: 255, saveSpeed: false, braking: true }); process.nextTick(this.emit.bind(this, "brake")); if (duration) { var motor = this; this.board.wait(duration, function() { motor.stop(); }); } return this; } } } }; /** * Motor * @constructor * * @param {Object} opts Options: pin|pins{pwm, dir[, cdir]}, device, controller, current * @param {Number} pin A single pin for basic * @param {Array} pins A two or three digit array of pins [pwm, dir]|[pwm, dir, cdir] * * * Initializing "Hobby Motors" * * new five.Motor(9); * * ...is the same as... * * new five.Motor({ * pin: 9 * }); * * * Initializing 2 pin, Bi-Directional DC Motors: * * new five.Motor([ 3, 12 ]); * * ...is the same as... * * new five.Motor({ * pins: [ 3, 12 ] * }); * * ...is the same as... * * new five.Motor({ * pins: { * pwm: 3, * dir: 12 * } * }); * * * Initializing 3 pin, I2C PCA9685 Motor Controllers: * i.e. The Adafruit Motor Shield V2 * * new five.Motor({ * pins: [ 8, 9, 10 ], * controller: "PCA9685", * address: 0x60 * }); * * * Initializing 3 pin, Bi-Directional DC Motors: * * new five.Motor([ 3, 12, 11 ]); * * ...is the same as... * * new five.Motor({ * pins: [ 3, 12, 11 ] * }); * * ...is the same as... * * new five.Motor({ * pins: { * pwm: 3, * dir: 12, * cdir: 11 * } * }); * * * Initializing Bi-Directional DC Motors with brake: * * new five.Motor({ * pins: { * pwm: 3, * dir: 12, * brake: 11 * } * }); * * * Initializing Bi-Directional DC Motors with current sensing pins: * See Sensor.js for details on options * * new five.Motor({ * pins: [3, 12], * current: { * pin: "A0", * freq: 250, * range: [0, 2000] * } * }); * * * Initializing Bi-Directional DC Motors with inverted speed for reverse: * Most likely used for non-commercial H-Bridge controllers * * new five.Motor({ * pins: [3, 12], * invertPWM: true * }); * */ function Motor(opts) { var device, controller, state; if (!(this instanceof Motor)) { return new Motor(opts); } Board.Component.call( this, this.opts = Board.Options(opts) ); controller = opts.controller || null; // Derive device based on pins passed if (typeof this.opts.device === "undefined") { this.opts.device = (typeof this.opts.pins === "undefined" && typeof this.opts.register !== "object") ? "NONDIRECTIONAL" : "DIRECTIONAL"; if (this.opts.pins && (this.opts.pins.cdir || this.opts.pins.length > 2)) { this.opts.device = "CDIR"; } if (typeof controller === "string" && controller.startsWith("EVS")) { this.opts.device = "DIRECTIONAL"; } } // Allow users to pass in custom device types device = typeof this.opts.device === "string" ? Devices[this.opts.device] : this.opts.device; this.threshold = typeof this.opts.threshold !== "undefined" ? this.opts.threshold : 30; this.invertPWM = typeof this.opts.invertPWM !== "undefined" ? this.opts.invertPWM : false; Object.defineProperties(this, device); if (this.opts.register) { this.opts.controller = "ShiftRegister"; } /** * Note: Controller decorates the device. Used for adding * special controllers (i.e. PCA9685) **/ if (this.opts.controller) { controller = typeof this.opts.controller === "string" ? Controllers[this.opts.controller] : this.opts.controller; Object.defineProperties(this, controller); } // current just wraps a Sensor if (this.opts.current) { this.opts.current.board = this.board; this.current = new Sensor(this.opts.current); } // Create a "state" entry for privately // storing the state of the motor state = { isOn: false, currentSpeed: typeof this.opts.speed !== "undefined" ? this.opts.speed : 128, braking: false }; priv.set(this, state); Object.defineProperties(this, { // Calculated, read-only motor on/off state // true|false isOn: { get: function() { return state.isOn; } }, currentSpeed: { get: function() { return state.currentSpeed; } }, braking: { get: function() { return state.braking; } } }); // We need to store and initialize the state of the dir pin(s) this.direction = { value: 1 }; if (this.initialize) { this.initialize(opts); } this.dir(0, this.direction); } util.inherits(Motor, events.EventEmitter); Motor.prototype.initialize = function() { this.io.pinMode(this.pins.pwm, this.io.MODES.PWM); ["dir", "cdir", "brake"].forEach(function(pin) { if (this.pins[pin]) { this.io.pinMode(this.pins[pin], this.io.MODES.OUTPUT); } }, this); }; Motor.prototype.setPin = function(pin, value) { this.io.digitalWrite(pin, value); }; Motor.prototype.setPWM = function(pin, value) { this.io.analogWrite(pin, value); }; Motor.prototype.speed = function(opts) { var state = priv.get(this); if (typeof opts === "undefined") { return this.currentSpeed; } else { if (typeof opts === "number") { opts = { speed: opts }; } opts.speed = Board.constrain(opts.speed, 0, 255); opts.saveSpeed = typeof opts.saveSpeed !== "undefined" ? opts.saveSpeed : true; if (opts.speed < this.threshold) { opts.speed = 0; } state.isOn = opts.speed === 0 ? false : true; if (opts.saveSpeed) { state.currentSpeed = opts.speed; } if (opts.braking) { state.braking = true; } if (this.invertPWM && this.direction.value === 1) { opts.speed ^= 0xff; } this.setPWM(this.pins.pwm, opts.speed); return this; } }; // start a motor - essentially just switch it on like a normal motor Motor.prototype.start = function(speed) { // Send a signal to turn on the motor and run at given speed in whatever // direction is currently set. if (this.pins.brake && this.braking) { this.setPin(this.pins.brake, 0); } // get current speed if nothing provided. speed = typeof speed !== "undefined" ? speed : this.speed(); this.speed({ speed: speed, braking: false }); // "start" event is fired when the motor is started if (speed > 0) { process.nextTick(this.emit.bind(this, "start")); } return this; }; Motor.prototype.stop = function() { this.speed({ speed: 0, saveSpeed: false }); process.nextTick(this.emit.bind(this, "stop")); return this; }; Motor.prototype.brake = function(duration) { if (typeof this.pins.brake === "undefined") { if (this.board.io.name !== "Mock") { console.log("Non-braking motor type"); } this.stop(); } else { this.setPin(this.pins.brake, 1); this.setPin(this.pins.dir, 1); this.speed({ speed: 255, saveSpeed: false, braking: true }); process.nextTick(this.emit.bind(this, "brake")); if (duration) { var motor = this; this.board.wait(duration, function() { motor.resume(); }); } } return this; }; Motor.prototype.release = function() { this.resume(); process.nextTick(this.emit.bind(this, "release")); return this; }; Motor.prototype.resume = function() { var speed = this.speed(); this.dir(speed, this.direction); this.start(speed); return this; }; [ /** * forward Turn the Motor in its forward direction * fwd Turn the Motor in its forward direction * * @param {Number} 0-255, 0 is stopped, 255 is fastest * @return {Object} this */ { name: "forward", abbr: "fwd", value: 1 }, /** * reverse Turn the Motor in its reverse direction * rev Turn the Motor in its reverse direction * * @param {Number} 0-255, 0 is stopped, 255 is fastest * @return {Object} this */ { name: "reverse", abbr: "rev", value: 0 } ].forEach(function(dir) { var method = function(speed) { this.dir(speed, dir); this.start(speed); return this; }; Motor.prototype[dir.name] = Motor.prototype[dir.abbr] = method; }); Motor.SHIELD_CONFIGS = { ADAFRUIT_V1: { M1: { pins: { pwm: 11 }, register: { data: 8, clock: 4, latch: 12 }, bits: { a: 2, b: 3 } }, M2: { pins: { pwm: 3 }, register: { data: 8, clock: 4, latch: 12 }, bits: { a: 1, b: 4 } }, M3: { pins: { pwm: 6 }, register: { data: 8, clock: 4, latch: 12 }, bits: { a: 5, b: 7 } }, M4: { pins: { pwm: 5 }, register: { data: 8, clock: 4, latch: 12 }, bits: { a: 0, b: 6 } } }, ADAFRUIT_V2: { M1: { pins: { pwm: 8, dir: 9, cdir: 10 }, address: 0x60, controller: "PCA9685" }, M2: { pins: { pwm: 13, dir: 12, cdir: 11 }, address: 0x60, controller: "PCA9685" }, M3: { pins: { pwm: 2, dir: 3, cdir: 4 }, address: 0x60, controller: "PCA9685" }, M4: { pins: { pwm: 7, dir: 6, cdir: 5 }, address: 0x60, controller: "PCA9685" } }, SEEED_STUDIO: { A: { pins: { pwm:9, dir:8, cdir: 11 } }, B: { pins: { pwm:10, dir:12, cdir: 13 } } }, FREETRONICS_HBRIDGE: { A: { pins: { pwm: 6, dir: 4, cdir: 7 } }, B: { pins: { pwm: 5, dir: 3, cdir: 2 } } }, ARDUINO_MOTOR_SHIELD_R3_1: { A: { pins: { pwm: 3, dir: 12 } }, B: { pins: { pwm: 11, dir: 13 } } }, ARDUINO_MOTOR_SHIELD_R3_2: { A: { pins: { pwm: 3, dir: 12, brake: 9 } }, B: { pins: { pwm: 11, dir: 13, brake: 8 } } }, ARDUINO_MOTOR_SHIELD_R3_3: { A: { pins: { pwm: 3, dir: 12, brake: 9, current: "A0" } }, B: { pins: { pwm: 11, dir: 13, brake: 8, current: "A1" } } }, DF_ROBOT: { A: { pins: { pwm: 6, dir: 7 } }, B: { pins: { pwm: 5, dir: 4 } } }, NKC_ELECTRONICS_KIT: { A: { pins: { pwm: 9, dir: 12 } }, B: { pins: { pwm: 10, dir: 13 } } }, RUGGED_CIRCUITS: { A: { pins: { pwm: 3, dir: 12 } }, B: { pins: { pwm: 11, dir: 13 } } }, SPARKFUN_ARDUMOTO: { A: { pins: { pwm: 3, dir: 12 } }, B: { pins: { pwm: 11, dir: 13 } } }, POLOLU_DRV8835_SHIELD: { M1: { pins: { pwm: 9, dir: 7 } }, M2: { pins: { pwm: 10, dir: 8 } } }, MICRO_MAGICIAN_V2: { A : { pins: { pwm: 6, dir: 8 }, invertPWM: true }, B: { pins: { pwm: 5, dir: 7 }, invertPWM: true } }, SPARKFUN_LUDUS: { A: { pins: { pwm: 3, dir: 4, cdir: 5 } }, B: { pins: { pwm: 6, dir: 7, cdir: 8 } } }, }; /** * Motors() * new Motors() * * Constructs an Array-like instance of all servos */ function Motors(numsOrObjects) { if (!(this instanceof Motors)) { return new Motors(numsOrObjects); } Object.defineProperty(this, "type", { value: Motor }); Collection.call(this, numsOrObjects); } Motors.prototype = Object.create(Collection.prototype, { constructor: { value: Motors } }); /* * Motors, forward(speed)/fwd(speed) * * eg. array.forward(speed); * Motors, reverse(speed)/rev(speed) * * eg. array.reverse(speed); * Motors, start(speed) * * eg. array.start(speed); * Motors, stop() * * eg. array.stop(); * Motors, brake() * * eg. array.brake(); * Motors, release() * * eg. array.release(); */ Object.keys(Motor.prototype).forEach(function(method) { // Create Motors wrappers for each method listed. // This will allow us control over all Motor instances // simultaneously. Motors.prototype[method] = function() { var length = this.length; for (var i = 0; i < length; i++) { this[i][method].apply(this[i], arguments); } return this; }; }); if (IS_TEST_MODE) { Motor.purge = function() { priv.clear(); registers.clear(); }; } // Assign Motors Collection class as static "method" of Motor. Motor.Array = Motors; module.exports = Motor; // References // http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM