UNPKG

johnny-five-electron

Version:

Temporary fork to support Electron (to be deprecated)

902 lines (749 loc) 20.8 kB
var IS_TEST_MODE = !!process.env.IS_TEST_MODE; var Board = require("../lib/board"); var Emitter = require("events").EventEmitter; var util = require("util"); var nanosleep = require("../lib/sleep").nano; var __ = require("../lib/fn"); var priv = new Map(); var used = new Map(); function Base() { Emitter.call(this); this.HIGH = 1; this.LOW = 0; this.isReady = false; this.MODES = {}; this.pins = []; this.analogPins = []; } util.inherits(Base, Emitter); var Controllers = { // http://www.adafruit.com/datasheets/mcp23017.pdf MCP23017: { REGISTER: { value: { ADDRESS: 0x20, // IO A IODIRA: 0x00, GPPUA: 0x0C, GPIOA: 0x12, OLATA: 0x14, // IO B IODIRB: 0x01, GPPUB: 0x0D, GPIOB: 0x13, OLATB: 0x15, } }, initialize: { value: function(opts) { var state = priv.get(this); state.iodir = [ 0xff, 0xff ]; state.olat = [ 0xff, 0xff ]; state.gpio = [ 0xff, 0xff ]; state.gppu = [ 0x00, 0x00 ]; this.address = opts.address || this.REGISTER.ADDRESS; this.io.i2cConfig(opts); this.io.i2cWrite(this.address, [ this.REGISTER.IODIRA, state.iodir[this.REGISTER.IODIRA] ]); this.io.i2cWrite(this.address, [ this.REGISTER.IODIRB, state.iodir[this.REGISTER.IODIRB] ]); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 16; i++) { this.pins.push({ supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT ], mode: 0, value: 0, report: 0, analogChannel: 127 }); this.pinMode(i, this.MODES.OUTPUT); this.digitalWrite(i, this.LOW); } this.name = "MCP23017"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { return pin; } }, // 1.6.1 I/O DIRECTION REGISTER pinMode: { value: function(pin, mode) { var state = priv.get(this); var pinIndex = pin; var port = 0; var iodir = null; if (pin < 8) { port = this.REGISTER.IODIRA; } else { port = this.REGISTER.IODIRB; pin -= 8; } iodir = state.iodir[port]; if (mode === this.io.MODES.INPUT) { iodir |= 1 << pin; } else { iodir &= ~(1 << pin); } this.pins[pinIndex].mode = mode; this.io.i2cWrite(this.address, [ port, iodir ]); state.iodir[port] = iodir; } }, // 1.6.10 PORT REGISTER digitalWrite: { value: function(pin, value) { var state = priv.get(this); var pinIndex = pin; var port = 0; var gpio = 0; // var olataddr = 0; var gpioaddr = 0; if (pin < 8) { port = this.REGISTER.IODIRA; // olataddr = this.REGISTER.OLATA; gpioaddr = this.REGISTER.GPIOA; } else { port = this.REGISTER.IODIRB; // olataddr = this.REGISTER.OLATB; gpioaddr = this.REGISTER.GPIOB; pin -= 8; } gpio = state.olat[port]; if (value === this.io.HIGH) { gpio |= 1 << pin; } else { gpio &= ~(1 << pin); } this.pins[pinIndex].report = 0; this.pins[pinIndex].value = value; this.io.i2cWrite(this.address, [ gpioaddr, gpio ]); state.olat[port] = gpio; state.gpio[port] = gpio; } }, // 1.6.7 PULL-UP RESISTOR // CONFIGURATION REGISTER pullUp: { value: function(pin, value) { var state = priv.get(this); var port = 0; var gppu = 0; var gppuaddr = 0; if (pin < 8) { port = this.REGISTER.IODIRA; gppuaddr = this.REGISTER.GPPUA; } else { port = this.REGISTER.IODIRB; gppuaddr = this.REGISTER.GPPUB; pin -= 8; } gppu = state.gppu[port]; if (value === this.io.HIGH) { gppu |= 1 << pin; } else { gppu &= ~(1 << pin); } this.io.i2cWrite(this.address, [ gppuaddr, gppu ]); state.gppu[port] = gppu; } }, digitalRead: { value: function(pin, callback) { var pinIndex = pin; var gpioaddr = 0; if (pin < 8) { gpioaddr = this.REGISTER.GPIOA; } else { gpioaddr = this.REGISTER.GPIOB; pin -= 8; } this.pins[pinIndex].report = 1; this.on("digital-read-" + pin, callback); this.io.i2cRead(this.address, gpioaddr, 1, function(data) { var byte = data[0]; var value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit("digital-read-" + pin, value); }.bind(this)); } }, }, MCP23008: { REGISTER: { value: { ADDRESS: 0x20, IODIR: 0x00, GPPU: 0x06, GPIO: 0x09, OLAT: 0x0A, } }, initialize: { value: function(opts) { var state = priv.get(this); state.iodir = [ 0xff ]; state.olat = [ 0xff ]; state.gpio = [ 0xff ]; state.gppu = [ 0x00 ]; this.address = opts.address || this.REGISTER.ADDRESS; this.io.i2cConfig(opts); this.io.i2cWrite(this.address, [ this.REGISTER.IODIR, state.iodir[this.REGISTER.IODIR] ]); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 8; i++) { this.pins.push({ supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT ], mode: 0, value: 0, report: 0, analogChannel: 127 }); this.pinMode(i, this.MODES.OUTPUT); this.digitalWrite(i, this.LOW); } this.name = "MCP23008"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { return pin; } }, // 1.6.1 I/O DIRECTION REGISTER pinMode: { value: function(pin, mode) { var state = priv.get(this); var pinIndex = pin; var port = this.REGISTER.IODIR; var iodir = state.iodir[port]; if (mode === this.io.MODES.INPUT) { iodir |= 1 << pin; } else { iodir &= ~(1 << pin); } this.pins[pinIndex].mode = mode; this.io.i2cWrite(this.address, [ port, iodir ]); state.iodir[port] = iodir; } }, // 1.6.10 PORT REGISTER digitalWrite: { value: function(pin, value) { var state = priv.get(this); var pinIndex = pin; var port = this.REGISTER.IODIR; var gpioaddr = this.REGISTER.GPIO; var gpio = state.olat[port]; if (value === this.io.HIGH) { gpio |= 1 << pin; } else { gpio &= ~(1 << pin); } this.pins[pinIndex].report = 0; this.pins[pinIndex].value = value; this.io.i2cWrite(this.address, [ gpioaddr, gpio ]); state.olat[port] = gpio; state.gpio[port] = gpio; } }, // 1.6.7 PULL-UP RESISTOR // CONFIGURATION REGISTER pullUp: { value: function(pin, value) { var state = priv.get(this); var port = this.REGISTER.IODIR; var gppuaddr = this.REGISTER.GPPU; var gppu = state.gppu[port]; if (value === this.io.HIGH) { gppu |= 1 << pin; } else { gppu &= ~(1 << pin); } this.io.i2cWrite(this.address, [ gppuaddr, gppu ]); state.gppu[port] = gppu; } }, digitalRead: { value: function(pin, callback) { var pinIndex = pin; var gpioaddr = this.REGISTER.GPIO; this.pins[pinIndex].report = 1; this.on("digital-read-" + pin, callback); this.io.i2cRead(this.address, gpioaddr, 1, function(data) { var byte = data[0]; var value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit("digital-read-" + pin, value); }.bind(this)); } }, }, PCF8574: { REGISTER: { value: { ADDRESS: 0x20, } }, initialize: { value: function(opts) { var state = priv.get(this); state.port = 0x00; state.ddr = 0x00; state.pins = 0x00; this.address = opts.address || this.REGISTER.ADDRESS; this.io.i2cConfig(opts); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 8; i++) { this.pins.push({ supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT ], mode: 1, value: 0, report: 0, analogChannel: 127 }); this.pinMode(i, this.MODES.OUTPUT); this.digitalWrite(i, this.LOW); } this.name = "PCF8574"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { return pin; } }, pinMode: { value: function(pin, mode) { var state = priv.get(this); var pinIndex = pin; var port = state.port; var ddr = state.ddr; var pins = state.pins; if (mode === this.MODES.INPUT) { ddr &= ~(1 << pin); port &= ~(1 << pin); } else { ddr |= (1 << pin); port &= ~(1 << pin); } this.pins[pinIndex].mode = mode; state.port = port; state.ddr = ddr; this.io.i2cWrite(this.address, (pins & ~ddr) | port); } }, digitalWrite: { value: function(pin, value) { var state = priv.get(this); var pinIndex = pin; var port = state.port; var ddr = state.ddr; var pins = state.pins; if (value) { port |= 1 << pin; } else { port &= ~(1 << pin); } this.pins[pinIndex].report = 0; this.pins[pinIndex].value = value; state.port = port; this.io.i2cWrite(this.address, (pins & ~ddr) | port); } }, digitalRead: { value: function(pin, callback) { var state = priv.get(this); var pinIndex = pin; this.pins[pinIndex].report = 1; this.on("digital-read-" + pin, callback); this.io.i2cRead(this.address, 1, function(data) { var byte = data[0]; var value = byte >> pin & 0x01; state.pins = byte; this.pins[pinIndex].value = value; this.emit("digital-read-" + pin, value); }.bind(this)); } }, }, PCF8575: { REGISTER: { value: { ADDRESS: 0x20, } }, initialize: { value: function(opts) { var state = priv.get(this); state.port = [0x00, 0x01]; state.gpio = [0x00, 0x00]; this.address = opts.address || this.REGISTER.ADDRESS; this.io.i2cConfig(opts); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 16; i++) { this.pins.push({ supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT ], mode: 1, value: 0, report: 0, analogChannel: 127 }); this.pinMode(i, this.MODES.OUTPUT); this.digitalWrite(i, this.LOW); } // Set all pins low on initialization this.io.i2cWrite(this.address, state.gpio); this.name = "PCF8575"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { return pin; } }, pinMode: { value: function(pin, mode) { var pinIndex = pin; this.pins[pinIndex].mode = mode; } }, digitalWrite: { value: function(pin, value) { var state = priv.get(this); var pinIndex = pin; var port; if (pin < 8) { port = 0; } else { port = 1; pin -= 8; } if (value === this.io.HIGH) { state.gpio[port] |= 1 << pin; } else { state.gpio[port] &= ~(1 << pin); } this.pins[pinIndex].report = 0; this.pins[pinIndex].value = value; this.io.i2cWrite(this.address, state.gpio); } }, digitalRead: { value: function(pin, callback) { var pinIndex = pin; var port; if (pin < 8) { port = 0; } else { port = 1; pin -= 8; } this.pins[pinIndex].report = 1; this.on("digital-read-" + pin, callback); this.io.i2cRead(this.address, 2, function(data) { var byte = data[port]; var value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit("digital-read-" + pin, value); }.bind(this)); } }, }, PCA9685: { REGISTER: { value: { ADDRESS: 0x40, MODE1: 0x00, PRESCALE: 0xFE, BASE: 0x06 } }, initialize: { value: function(opts) { var state = priv.get(this); state.frequency = opts.frequency || 50; this.address = opts.address || this.REGISTER.ADDRESS; this.range = opts.range || [0, 4095]; this.io.i2cConfig(opts); // Reset this.io.i2cWriteReg(this.address, this.REGISTER.MODE1, 0x00); // Sleep this.io.i2cWriteReg(this.address, this.REGISTER.MODE1, 0x10); // Set prescalar this.io.i2cWriteReg(this.address, this.REGISTER.PRESCALE, this.prescale); // Wake up this.io.i2cWriteReg(this.address, this.REGISTER.MODE1, 0x00); // Wait 5 nanoseconds for restart nanosleep(5); // Auto-increment this.io.i2cWriteReg(this.address, this.REGISTER.MODE1, 0xa1); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 16; i++) { this.pins.push({ supportedModes: [ this.MODES.OUTPUT, this.MODES.PWM, this.MODES.SERVO, ], mode: 0, value: 0, report: 0, analogChannel: 127 }); this.pinMode(i, this.MODES.OUTPUT); this.digitalWrite(i, this.LOW); } Object.defineProperties(this, { prescale: { get: function() { // PCA9685 has an on-board 25MHz clock source return (25000000 / (4096 * (state.frequency || 50))) | 0; } }, frequency: { get: function() { return state.frequency; } } }); this.name = "PCA9685"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { return pin; } }, pinMode: { value: function(pin, mode) { if (this.pins[pin] === undefined) { throw new RangeError("Invalid PCA9685 pin: " + pin); } this.pins[pin].mode = mode; } }, digitalWrite: { value: function(pin, value) { this.pwmWrite(pin, value ? 255 : 0); } }, analogWrite: { value: function(pin, value) { this.pwmWrite(pin, value); } }, servoWrite: { value: function(pin, degrees) { this.pwmWrite(pin, __.map(degrees, 0, 180, 0, 255)); } }, pwmWrite: { value: function(pin, value) { if (this.pins[pin] === undefined) { throw new RangeError("Invalid PCA9685 pin: " + pin); } value = Board.constrain(value, 0, 255); var off = this.range[1] * value / 255; this.io.i2cWrite(this.address, [ this.REGISTER.BASE + 4 * pin, 0, 0, off, off >> 8 ]); this.pins[pin].value = value; } } }, // http://www.nxp.com/documents/data_sheet/PCF8591.pdf PCF8591: { REGISTER: { value: { ADDRESS: 0x48, } }, initialize: { value: function(opts) { var state = priv.get(this); state.control = 0x45; state.reading = false; this.address = opts.address || this.REGISTER.ADDRESS; this.io.i2cConfig(opts); Object.assign(this.MODES, this.io.MODES); for (var i = 0; i < 4; i++) { this.pins.push({ supportedModes: [ this.MODES.ANALOG ], mode: 1, value: 0, report: 0, analogChannel: i }); } this.analogPins.push(0, 1, 2, 3); this.io.i2cWrite(this.address, state.control); this.name = "PCF8591"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value: function(pin) { if (typeof pin === "string" && pin[0] === "A") { return +pin.slice(1); } return pin; } }, pinMode: { value: function(pin, mode) { this.pins[pin].mode = mode; } }, analogRead: { value: function(pin, callback) { var state = priv.get(this); var pinIndex = pin; this.pins[pinIndex].report = 1; this.on("analog-read-" + pin, callback); // Since this operation will read all 4 pins, // it only needs to be initiated once. if (!state.reading) { state.reading = true; this.io.i2cRead(this.address, 4, function(data) { var value; for (var i = 0; i < 4; i++) { value = data[i] << 2; this.pins[i].value = value; if (this.pins[i].report) { this.emit("analog-read-" + pin, value); } } }.bind(this)); } } }, }, }; Controllers.PCF8574A = Object.assign({}, Controllers.PCF8574, { REGISTER: { value: { ADDRESS: 0x38, } }, }); var methods = Object.keys(Board.prototype); Object.keys(Controllers).forEach(function(name) { methods.forEach(function(key) { if (Controllers[name][key] === undefined) { Controllers[name][key] = { writable: true, configurable: true, value: function() { throw new Error("Expander:" + name + " does not support " + key); } }; } }); }); function Expander(opts) { if (!(this instanceof Expander)) { return new Expander(opts); } Base.call(this); var controller = null; var state = {}; var controllerValue; if (typeof opts === "string") { controllerValue = opts; } Board.Component.call( this, opts = Board.Options(opts), { normalizePin: false } ); if (typeof opts.controller === "undefined" && controllerValue) { opts.controller = controllerValue; } if (opts.controller && typeof opts.controller === "string") { controller = Controllers[opts.controller.toUpperCase()]; } else { controller = opts.controller; } if (controller == null) { throw new Error("Expander expects a valid controller"); } Object.defineProperties(this, controller); priv.set(this, state); if (typeof this.initialize === "function") { this.initialize(opts); } used.set(this.address, this); } util.inherits(Expander, Base); Expander.Active = { has: function(filter) { var byAddress = filter.address !== undefined; var byController = filter.controller !== undefined; if (byAddress && byController) { // If the address is in use, then the controller doesn't matter. if (this.byAddress(filter.address)) { return true; } if (this.byController(filter.controller)) { return true; } } else { if (byAddress) { return Boolean(this.byAddress(filter.address)); } if (byController) { return Boolean(this.byController(filter.controller)); } } return false; }, byAddress: function(address) { return used.get(address); }, byController: function(name) { var controller; used.forEach(function(value) { if (value.name === name.toUpperCase()) { controller = value; } }); return controller; } }; if (IS_TEST_MODE) { Expander.purge = function() { priv.clear(); used.clear(); }; } module.exports = Expander;