UNPKG

johnny-five

Version:

The JavaScript Robotics and Hardware Programming Framework. Use with: Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, TI Launchpad and more!

2,048 lines (1,704 loc) 49.7 kB
const Board = require("./board"); const Emitter = require("./mixins/emitter"); const sleep = require("./sleep"); const Fn = require("./fn"); const priv = new Map(); const active = new Map(); class Base extends Emitter { constructor() { super(); this.HIGH = 1; this.LOW = 0; this.isReady = false; this.MODES = {}; this.pins = []; this.analogPins = []; } } const Controllers = { DEFAULT: { initialize: { value() { throw new Error("Expander expects a valid controller"); } } }, MCP23017: { ADDRESSES: { value: [0x20] }, REGISTER: { value: { // IO A IODIRA: 0x00, GPPUA: 0x0C, GPIOA: 0x12, OLATA: 0x14, // IO B IODIRB: 0x01, GPPUB: 0x0D, GPIOB: 0x13, OLATB: 0x15, } }, initialize: { value(options) { const state = priv.get(this); state.iodir = [0xff, 0xff]; state.olat = [0xff, 0xff]; state.gpio = [0xff, 0xff]; state.gppu = [0x00, 0x00]; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); 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 (let 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(pin) { return pin; } }, // 1.6.1 I/O DIRECTION REGISTER pinMode: { value(pin, mode) { const state = priv.get(this); const pinIndex = pin; let port = 0; let 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(pin, value) { const state = priv.get(this); const pinIndex = pin; let port = 0; let gpio = 0; // var olataddr = 0; let 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(pin, value) { const state = priv.get(this); let port = 0; let gppu = 0; let 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(pin, callback) { const pinIndex = pin; let 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-${pinIndex}`, callback); this.io.i2cRead(this.address, gpioaddr, 1, data => { const byte = data[0]; const value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit(`digital-read-${pinIndex}`, value); }); } }, }, MCP23008: { ADDRESSES: { value: [0x20] }, REGISTER: { value: { IODIR: 0x00, GPPU: 0x06, GPIO: 0x09, OLAT: 0x0A, } }, initialize: { value(options) { const state = priv.get(this); state.iodir = [0xff]; state.olat = [0xff]; state.gpio = [0xff]; state.gppu = [0x00]; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); this.io.i2cWrite(this.address, [this.REGISTER.IODIR, state.iodir[this.REGISTER.IODIR]]); Object.assign(this.MODES, this.io.MODES); for (let 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(pin) { return pin; } }, // 1.6.1 I/O DIRECTION REGISTER pinMode: { value(pin, mode) { const state = priv.get(this); const pinIndex = pin; const port = this.REGISTER.IODIR; let 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(pin, value) { const state = priv.get(this); const pinIndex = pin; const port = this.REGISTER.IODIR; const gpioaddr = this.REGISTER.GPIO; let 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(pin, value) { const state = priv.get(this); const port = this.REGISTER.IODIR; const gppuaddr = this.REGISTER.GPPU; let 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(pin, callback) { const pinIndex = pin; const gpioaddr = this.REGISTER.GPIO; this.pins[pinIndex].report = 1; this.on(`digital-read-${pin}`, callback); this.io.i2cRead(this.address, gpioaddr, 1, data => { const byte = data[0]; const value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit(`digital-read-${pin}`, value); }); } }, }, PCF8574: { ADDRESSES: { value: [0x20] }, REGISTER: {}, initialize: { value(options) { const state = priv.get(this); state.port = 0x00; state.ddr = 0x00; state.pins = 0x00; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); for (let 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(pin) { return pin; } }, pinMode: { value(pin, mode) { const state = priv.get(this); const pinIndex = pin; let port = state.port; let ddr = state.ddr; const 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(pin, value) { const state = priv.get(this); const pinIndex = pin; let port = state.port; const ddr = state.ddr; const 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(pin, callback) { const state = priv.get(this); const pinIndex = pin; this.pins[pinIndex].report = 1; this.on(`digital-read-${pin}`, callback); this.io.i2cRead(this.address, 1, data => { const byte = data[0]; const value = byte >> pin & 0x01; state.pins = byte; this.pins[pinIndex].value = value; this.emit(`digital-read-${pin}`, value); }); } }, }, PCF8575: { ADDRESSES: { value: [0x20] }, REGISTER: {}, initialize: { value(options) { const state = priv.get(this); state.port = [0x00, 0x01]; state.gpio = [0x00, 0x00]; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); for (let 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(pin) { return pin; } }, pinMode: { value(pin, mode) { const pinIndex = pin; this.pins[pinIndex].mode = mode; } }, digitalWrite: { value(pin, value) { const state = priv.get(this); const pinIndex = pin; let 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(pin, callback) { const pinIndex = pin; let 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, data => { const byte = data[port]; const value = byte >> pin & 0x01; this.pins[pinIndex].value = value; this.emit(`digital-read-${pin}`, value); }); } }, }, PCA9685: { ADDRESSES: { value: [0x40] }, REGISTER: { value: { MODE1: 0x00, PRESCALE: 0xFE, BASE: 0x06 } }, initialize: { value(options) { const state = priv.get(this); // 7.3.5 PWM frequency PRE_SCALE // // These number correspond to: // min PWM frequency: 24 Hz // max PWM frequency: 1526 Hz state.frequency = Fn.constrain(options.frequency || 1526, 24, 1526); this.address = options.address || this.ADDRESSES[0]; this.pwmRange = options.pwmRange || [0, 4095]; Object.defineProperties(this, { prescale: { get() { // PCA9685 has an on-board 25MHz clock source // 7.3.5 PWM frequency PRE_SCALE return Math.round(25000000 / (4096 * state.frequency)) - 1; } }, frequency: { get() { return state.frequency; } } }); options.address = this.address; this.io.i2cConfig(options); // 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 microseconds for restart sleep.micro(5); // Auto-increment this.io.i2cWriteReg(this.address, this.REGISTER.MODE1, 0xa0); Object.assign(this.MODES, this.io.MODES); for (let 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); } this.name = "PCA9685"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { return this.io.name.includes("Tessel 2") ? pin - 1 : pin; } }, pinMode: { value(pin, mode) { if (this.pins[pin] === undefined) { throw new RangeError(`Invalid PCA9685 pin: ${pin}`); } this.pins[pin].mode = mode; } }, digitalWrite: { value(pin, value) { this.pwmWrite(pin, value ? 255 : 0); } }, analogWrite: { value(pin, value) { this.pwmWrite(pin, value); } }, servoWrite: { value(pin, value) { let off; if (value < 544) { value = Fn.constrain(value, 0, 180); off = Fn.map(value, 0, 180, this.pwmRange[0] / 4, this.pwmRange[1] / 4); } else { off = value / 4; } off |= 0; this.io.i2cWrite(this.address, [ this.REGISTER.BASE + 4 * pin, 0, 0, off, off >> 8 ]); } }, pwmWrite: { value(pin, value) { if (this.pins[pin] === undefined) { throw new RangeError(`Invalid PCA9685 pin: ${pin}`); } value = Fn.constrain(value, 0, 255); let on = 0; let off = this.pwmRange[1] * value / 255; if (value === 0) { // Special value for signal fully off. on = 0; off = 4096; } if (value === 255) { // Special value for signal fully on. on = 4096; off = 0; } this.io.i2cWrite(this.address, [ this.REGISTER.BASE + 4 * pin, on, on >> 8, off, off >> 8 ]); this.pins[pin].value = value; } } }, PCF8591: { ADDRESSES: { value: [0x48] }, REGISTER: {}, initialize: { value(options) { const state = priv.get(this); state.control = 0x45; state.reading = false; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); for (let 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(pin) { if (typeof pin === "string" && pin[0] === "A") { return +pin.slice(1); } return pin; } }, pinMode: { value(pin, mode) { this.pins[pin].mode = mode; } }, analogRead: { value(pin, callback) { const state = priv.get(this); const 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, data => { let value; for (let i = 0; i < 4; i++) { value = data[i] << 2; this.pins[i].value = value; if (this.pins[i].report) { this.emit(`analog-read-${i}`, value); } } }); } } }, }, MUXSHIELD2: { initialize: { value() { const state = priv.get(this); // _S[\d] (Digital: 2, 4, 6, 7) state.select = [2, 4, 6, 7]; // _IOS[\d] (Digital: 10, 11, 12) state.ios = [null, 10, 11, 12]; // _IO[\d] (Analog In: "A0", "A1", "A2") state.io = [null, 14, 15, 16]; state.aio = [null, 0, 1, 2]; state.outMode = 8; state.pinMap = {}; state.rowReading = [false, false, false]; state.rowMode = [null, null, null]; // Each rowValue is a single uint16 state.rowValues = [0, 0, 0]; Object.assign(this.MODES, { INPUT: 0, OUTPUT: 1, ANALOG: 2, }); this.io.pinMode(state.select[0], this.MODES.OUTPUT); this.io.pinMode(state.select[1], this.MODES.OUTPUT); this.io.pinMode(state.select[2], this.MODES.OUTPUT); this.io.pinMode(state.select[3], this.MODES.OUTPUT); this.io.pinMode(state.outMode, this.MODES.OUTPUT); this.io.digitalWrite(state.outMode, this.LOW); this.io.pinMode(state.ios[1], this.MODES.OUTPUT); this.io.pinMode(state.ios[2], this.MODES.OUTPUT); this.io.pinMode(state.ios[3], this.MODES.OUTPUT); let row = 1; let mask = 16; let index = 0; for (let i = 0; i < 48; i++) { const band = i & mask; if (band === mask) { row++; mask *= 2; index = 0; } state.pinMap[`IO${row}-${index}`] = i; this.pins.push({ row, index, supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT, this.MODES.ANALOG, ], mode: 1, value: 0, report: 0, analogChannel: i }); this.analogPins.push(i); // TODO: Not sure about this? // this.io.pinMode(i, this.MODES.OUTPUT); // this.io.digitalWrite(i, this.LOW); index++; } this.name = "MUXSHIELD2"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { return pin; } }, pinMode: { value(pin, mode) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; if (pinIndex === undefined) { throw new Error(`MUXSHIELD2: Invalid Pin number or name: ${pin}`); } const row = this.pins[pinIndex].row; const rowModeIndex = row - 1; const rowMode = state.rowMode[rowModeIndex]; if (rowMode === mode) { return this; } if (rowMode !== null && rowMode !== mode) { throw new Error("MUXSHIELD2: Cannot set mixed modes per IO row."); } state.rowMode[rowModeIndex] = mode; // MUXSHIELD2 Disallows mixing modes per row. // Once a mode is set for a given pin in a given row, // set all the pins in that row to the same mode. for (let i = 0; i < 16; i++) { this.pins[rowModeIndex + i].mode = mode; } const IO = state.io[row]; const IOS = state.ios[row]; if (mode === this.MODES.INPUT) { // Read an analog input as digital this.io.pinMode(IO, this.MODES.INPUT); // this.io.digitalWrite(IOS, this.LOW); } if (mode === this.MODES.OUTPUT) { this.io.pinMode(IO, this.MODES.OUTPUT); this.io.digitalWrite(IOS, this.HIGH); } } }, digitalWrite: { value(pin, value) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; if (pinIndex === undefined) { throw new Error(`MUXSHIELD2: Invalid Pin number or name: ${pin}`); } const row = this.pins[pinIndex].row; const rowValueIndex = row - 1; let rowValue = state.rowValues[rowValueIndex]; const ioPin = row - 1; const offset = ioPin * 16; const channel = pinIndex - offset; if (value) { rowValue |= 1 << channel; } else { rowValue &= ~(1 << channel); } this.io.digitalWrite(state.select[3], this.LOW); this.io.digitalWrite(state.outMode, this.HIGH); const S = state.select[row - 1]; const IO = state.io[row]; for (let i = 15; i >= 0; i--) { this.io.digitalWrite(S, this.LOW); this.io.digitalWrite(IO, (rowValue >> i) & 1); this.io.digitalWrite(S, this.HIGH); } this.io.digitalWrite(state.select[3], this.HIGH); this.io.digitalWrite(state.outMode, this.LOW); this.pins[pinIndex].value = value; state.rowValues[rowValueIndex] = rowValue; } }, digitalRead: { value(pin, callback) { this.ioRead("digital", pin, callback); } }, analogRead: { value(pin, callback) { this.ioRead("analog", pin, callback); } }, ioRead: { value(type, pin, callback) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; if (pinIndex === undefined) { throw new Error(`MUXSHIELD2: Invalid Pin number or name: ${pin}`); } this.on(`${type}-read-${pinIndex}`, callback); const isAnalog = type === "analog" ? true : false; const row = this.pins[pinIndex].row; const rowReadingIndex = row - 1; const offset = rowReadingIndex * 16; const channel = pinIndex - offset; this.pins[pinIndex].report = 1; this.pins[pinIndex].channel = channel; this.pins[pinIndex].ioPin = isAnalog ? rowReadingIndex : rowReadingIndex + 14; const nextPinIndex = () => { const startAt = nextPinIndex.lastPinIndex + 1; for (let i = startAt; i < this.pins.length; i++) { if (this.pins[i].report === 1) { nextPinIndex.lastPinIndex = i; return nextPinIndex.lastPinIndex; } } nextPinIndex.lastPinIndex = -1; return nextPinIndex(); }; nextPinIndex.lastPinIndex = -1; const handler = value => { const pinIndex = nextPinIndex.lastPinIndex; const pin = this.pins[pinIndex]; this.emit(`${type}-read-${pinIndex}`, value); this.io.removeListener(`${type}-read-${pin.ioPin}`, handler); setTimeout(read, 10); }; var read = () => { const pinIndex = nextPinIndex(); const pin = this.pins[pinIndex]; this.select(pin.channel); if (isAnalog) { this.io.pinMode(pin.ioPin, this.io.MODES.ANALOG); this.io.analogRead(pin.ioPin, handler); } else { this.io.digitalRead(pin.ioPin, handler); } }; if (!state.rowReading[rowReadingIndex]) { state.rowReading[rowReadingIndex] = true; read(); } } }, select: { value(channel) { const state = priv.get(this); this.io.digitalWrite(state.outMode, this.LOW); this.io.digitalWrite(state.select[0], (channel & 1)); this.io.digitalWrite(state.select[1], (channel & 3) >> 1); this.io.digitalWrite(state.select[2], (channel & 7) >> 2); this.io.digitalWrite(state.select[3], (channel & 15) >> 3); } } }, GROVEPI: { ADDRESSES: { value: [0x04] }, REGISTER: {}, COMMANDS: { value: { DIGITAL_READ: 0x01, DIGITAL_WRITE: 0x02, ANALOG_READ: 0x03, ANALOG_WRITE: 0x04, PIN_MODE: 0x05, PING_READ: 0x07, } }, initialize: { value(options) { const state = priv.get(this); state.isReading = false; state.pinMap = { D2: 2, D3: 3, D4: 4, D5: 5, D6: 6, D7: 7, D8: 8, A0: 14, A1: 15, A2: 16, }; // Override the relevant default "isType" methods this.isPwm = name => { const number = typeof name === "number" ? name : parseInt(name[1]); return number === 3 || number === 5 || number === 6; }; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); let analogChannel; for (let i = 0; i < 17; i++) { analogChannel = 127; if (i <= 1 || (i >= 9 && i < 14)) { // There are no connections for: // O, 1, 9, 10, 11, 12, 13 this.pins.push({ supportedModes: [], mode: 0, value: 0, report: 0, analogChannel }); } else { this.pins.push({ supportedModes: [ this.MODES.INPUT, this.MODES.OUTPUT, ], mode: 0, value: 0, report: 0, analogChannel }); // Digital pins with PWM Support // D3, D5, D6 if (this.isPwm(i)) { this.pins[i].supportedModes.push( this.MODES.PWM ); } if (i >= 14 && i <= 17) { // A0 = 0 = 14 // A1 = 1 = 15 // A2 = 2 = 16 // // 14 is the analog offset this.pins[i].analogChannel = i - 14; // Add ANALOG "read" mode this.pins[i].supportedModes.push( this.MODES.ANALOG ); this.analogPins.push(i); // Default all analog IO pins to // ANALOG "read" this.pinMode(`A${this.pins[i].analogChannel}`, this.MODES.ANALOG); } else { // Default all digital IO pins to // OUTPUT and LOW this.pinMode(`D${i}`, this.MODES.OUTPUT); this.digitalWrite(`D${i}`, this.LOW); } } } this.name = "GROVEPI"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { return pin; } }, pinMode: { value(pin, mode) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; if (mode === this.io.MODES.INPUT || mode === this.io.MODES.ANALOG) { this.pins[pinIndex].mode = 0; } else { this.pins[pinIndex].mode = 1; } this.io.i2cWrite( this.address, [ this.COMMANDS.PIN_MODE, pinIndex, this.pins[pinIndex].mode, 0 ] ); } }, digitalWrite: { value(pin, value) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; // Any truthy value is converted to HIGH (1) value = value ? 1 : 0; this.io.i2cWrite( this.address, [ this.COMMANDS.DIGITAL_WRITE, pinIndex, value, 0 ] ); this.pins[pinIndex].value = value; } }, ioRead: { value(pin, type, callback) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; const isAnalog = type === "analog"; const length = isAnalog ? 3 : 1; const command = isAnalog ? this.COMMANDS.ANALOG_READ : this.COMMANDS.DIGITAL_READ; this.on(`${type}-read-${pinIndex}`, callback); this.pins[pinIndex].report = 1; this.pins[pinIndex].command = command; this.pins[pinIndex].type = type; this.pins[pinIndex].length = length; const nextPinIndex = () => { const startAt = nextPinIndex.lastPinIndex + 1; for (let i = startAt; i < this.pins.length; i++) { if (this.pins[i].report === 1) { nextPinIndex.lastPinIndex = i; return nextPinIndex.lastPinIndex; } } nextPinIndex.lastPinIndex = -1; return nextPinIndex(); }; nextPinIndex.lastPinIndex = -1; const handler = (pinIndex, value) => { const pin = this.pins[pinIndex]; let canEmit = true; if (pin.type === "digital" && this.pins[pinIndex].value === value) { canEmit = false; } this.pins[pinIndex].value = value; if (canEmit) { this.emit(`${pin.type}-read-${pinIndex}`, value); } setTimeout(read, 1); }; var read = () => { const pinIndex = nextPinIndex(); const pin = this.pins[pinIndex]; const isAnalog = pin.type === "analog"; this.io.i2cWrite(this.address, [pin.command, pinIndex, 0, 0]); this.io.i2cReadOnce(this.address, pin.length, data => { let value; if (isAnalog) { value = (data[1] << 8) + data[2]; } else { value = data[0]; } handler(pinIndex, value); }); }; if (!state.isReading) { state.isReading = true; read(); } } }, digitalRead: { value(pin, callback) { this.ioRead(pin, "digital", callback); }, }, analogRead: { value(pin, callback) { this.ioRead(pin, "analog", callback); }, }, pingRead: { value({pin}, callback) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; this.io.i2cWrite( this.address, [ this.COMMANDS.PING_READ, pinIndex, 0, 0 ] ); setTimeout(() => { this.once(`ping-read-${pin}`, callback); this.io.i2cReadOnce(this.address, 3, data => { // The GrovePi firmware sends this value in CM // so the value must be converted back to duration. const value = Math.round(((data[1] << 8) + data[2]) * 29 * 2); this.pins[pinIndex].value = value; this.emit(`ping-read-${pin}`, value); }); }, 200); }, }, analogWrite: { value(pin, value) { this.pwmWrite(pin, value); } }, pwmWrite: { writable: true, value(pin, value) { const state = priv.get(this); const pinIndex = state.pinMap[pin]; value = Fn.constrain(value, 0, 255); this.io.i2cWrite( this.address, [ this.COMMANDS.ANALOG_WRITE, pinIndex, value, 0 ] ); this.pins[pinIndex].value = value; } } }, "74HC595": { initialize: { value({pins}) { const state = priv.get(this); if (!pins.data) { throw new Error("Expected pins.data"); } if (!pins.clock) { throw new Error("Expected pins.clock"); } if (!pins.latch) { throw new Error("Expected pins.latch"); } state.data = pins.data; state.clock = pins.clock; state.latch = pins.latch; state.value = 0x00; Object.assign(this.MODES, this.io.MODES); // Reset pins property to empty array. this.pins = []; for (let i = 0; i < 8; i++) { this.pins.push({ supportedModes: [ this.MODES.OUTPUT ], mode: 1, value: 0, report: 0, analogChannel: 127 }); } this.portWrite(0, state.value); this.name = "74HC595"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { return pin; } }, pinMode: { value(pin, mode) { this.pins[pin].mode = mode; } }, digitalWrite: { value(pin, value) { const state = priv.get(this); if (value) { state.value |= 1 << pin; } else { state.value &= ~(1 << pin); } this.pins[pin].value = value; this.portWrite(0, state.value); } }, portWrite: { writable: true, configurable: true, value(port, value) { const state = priv.get(this); state.value = value; this.board.digitalWrite(state.latch, this.io.LOW); this.board.shiftOut(state.data, state.clock, true, state.value); this.board.digitalWrite(state.latch, this.io.HIGH); for (let i = 0; i < 8; i++) { this.pins[i].value = (state.value >> i) & 1; } } }, }, CD74HC4067: { /* | Address 1 (D9) | Address 0 (D8) | Address | | -------------- | -------------- | ------- | | 0 | 0 | 0x0A | | 0 | 1 | 0x0B | | 1 | 0 | 0x0C | | 1 | 1 | 0x0D | */ ADDRESSES: { value: [0x0A, 0x0B, 0x0C, 0x0D] }, REGISTER: {}, initialize: { value(options) { const state = priv.get(this); state.reading = false; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); for (let i = 0; i < 16; i++) { this.pins.push({ supportedModes: [ this.MODES.ANALOG ], mode: 1, value: 0, report: 0, analogChannel: i }); this.analogPins.push(i); } this.name = "CD74HC4067"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { if (typeof pin === "string" && pin[0] === "A") { return +pin.slice(1); } return pin; } }, pinMode: { value(pin, mode) { this.pins[pin].mode = mode; } }, analogRead: { value(pin, callback) { const state = priv.get(this); const pinIndex = pin; this.pins[pinIndex].report = 1; this.on(`analog-read-${pin}`, callback); this.io.i2cWrite(this.address, pinIndex, 1); // 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, 32, data => { let value; for (let i = 0; i < 16; i++) { const index = i * 2; value = (data[index] << 8) + data[index + 1]; this.pins[i].value = value; if (this.pins[i].report) { this.emit(`analog-read-${i}`, value); } } }); } } }, }, LIS3DH: { ADDRESSES: { value: [0x18] }, REGISTER: { value: { // Page 26 // Table 17. Register address map // // NAME: BYTE OUT_ADC1_L: 0x08, OUT_X_L: 0x28, CTRL_REG1: 0x20, CTRL_REG2: 0x21, CTRL_REG3: 0x22, CTRL_REG4: 0x23, CTRL_REG5: 0x24, TEMP_CFG_REG: 0x1F, }, }, initialize: { value(options) { const state = priv.get(this); state.reading = false; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); // Page 29 // 8.8 CTRL_REG1 // Table 24. CTRL_REG1 register // // ODR3 ODR2 ODR1 ODR0 LPen Zen Yen Xen // // // Enable Axis // 0b00000111 // ZYX // let ctrl1 = 0x07; // 0b00000111 // // Date Rate // Table 26. Data rate configuration // // ODR3 ODR2 ODR1 ODR0 Power mode selection // 0 0 0 1 = 1 Hz // 0 0 1 0 = 10 Hz // 0 0 1 1 = 25 Hz // 0 1 0 0 = 50 Hz // 0 1 0 1 = 100 Hz // 0 1 1 0 = 200 Hz // 0 1 1 1 = 400 Hz // // 0b0111 << 4 = 0b01110000 // ctrl1 = (ctrl1 & ~(0xF0)) | (0x07 << 4); // ctrl1 = 0b01110111 // 0b01110000 = 0x70 = 112 this.io.i2cWrite(this.address, this.REGISTER.CTRL_REG1, ctrl1); // Page 31 // 8.11 CTRL_REG4 // // Table 32. CTRL_REG4 register // // BDU BLE FS1 FS0 HR ST1 ST0 SIM // // BDU Block data update. Default value: 0 // 0: Continuous update // 1: Updated when MSB and LSB read // // HR High resolution output mode: Default value: 0 // 0: Disable // 1: Enable // // Setting BDU and HR: // 0b1---1--- // // 0b10001000 = 0x88 = 136 // this.io.i2cWrite(this.address, this.REGISTER.CTRL_REG4, 0x88); // // Page 31 // 8.10 CTRL_REG3 // // I1_DRDY1 -> ON // // 0b00010000 = 0x10 = 16 this.io.i2cWrite(this.address, this.REGISTER.CTRL_REG3, 0x10); Object.assign(this.MODES, this.io.MODES); for (let i = 0; i < 4; i++) { if (i === 0) { this.pins.push({ supportedModes: [], mode: 0, value: 0, report: 0, analogChannel: 0x7F }); } else { this.pins.push({ supportedModes: [ this.MODES.ANALOG ], mode: 1, value: 0, report: 0, analogChannel: i }); this.analogPins.push(i); } } this.name = "LIS3DH"; this.isReady = true; this.emit("connect"); this.emit("ready"); }, }, normalize: { value(pin) { if (typeof pin === "string" && pin[0] === "A") { return +pin.slice(1); } return pin; }, }, pinMode: { value(pin, mode) { this.pins[pin].mode = mode; }, }, analogRead: { value(pin, callback) { const state = priv.get(this); const pinIndex = pin; this.pins[pinIndex].report = 1; this.on(`analog-read-${pin}`, callback); // Since this operation will read all 3 ADC pins, // it only needs to be initiated once. if (!state.reading) { state.reading = true; // Page 29 // 8.7 TEMP_CFG_REG (1Fh) // Table 23. TEMP_CFG_REG description // // ADC_PD TEMP_EN 0 0 0 0 0 0 // // 0b10000000 = 128 = 0x80 // this.io.i2cWrite(this.address, this.REGISTER.TEMP_CFG_REG, 0x80); // Page 23, 24, 25 // bit 1: MS bit. When 0, the address remains unchanged in multiple read/write commands. // When 1, the address is auto incremented in multiple read/write commands. this.io.i2cRead(this.address, this.REGISTER.OUT_ADC1_L | 0x80, 6, data => { // V range is 900 // First, scale the value to range that these ADCs support, which is // // 1.8V - 0.9V // // Then, scale that value to the full 10-bit 0-3.3V range // this.pins[1].value = Fn.scale(Fn.int16(data[1], data[0]), -32512, 32512, 1023, 0); this.pins[2].value = Fn.scale(Fn.int16(data[3], data[2]), -32512, 32512, 1023, 0); this.pins[3].value = Fn.scale(Fn.int16(data[5], data[4]), -32512, 32512, 1023, 0); for (let i = 1; i < 4; i++) { if (this.pins[i].report) { this.emit(`analog-read-${i}`, this.pins[i].value); } } }); } }, }, i2cConfig: { value(...args) { return this.io.i2cConfig.apply(this.io, args); }, }, i2cWrite: { value(...args) { return this.io.i2cWrite.apply(this.io, args); }, }, i2cWriteReg: { value(...args) { return this.io.i2cWriteReg.apply(this.io, args); }, }, i2cRead: { value(...args) { return this.io.i2cRead.apply(this.io, args); }, }, i2cReadOnce: { value(...args) { return this.io.i2cReadOnce.apply(this.io, args); }, }, }, ADS1115: { ADDRESSES: { value: [0x48, 0x49, 0x4A, 0x4B] }, REGISTER: { value: { CONFIG: 0x01, READ: 0x00, PIN: [0xC1, 0xD1, 0xE1, 0xF1], PIN_DATA: 0x83, } }, initialize: { value(options) { const state = priv.get(this); state.reading = false; this.address = options.address || this.ADDRESSES[0]; options.address = this.address; this.io.i2cConfig(options); Object.assign(this.MODES, this.io.MODES); for (let i = 0; i < 4; i++) { this.pins.push({ supportedModes: [ this.MODES.ANALOG ], mode: 1, value: 0, report: 0, analogChannel: i }); this.analogPins.push(i); } this.name = "ADS1115"; this.isReady = true; this.emit("connect"); this.emit("ready"); } }, normalize: { value(pin) { if (typeof pin === "string" && pin[0] === "A") { return +pin.slice(1); } return pin; } }, pinMode: { value(pin, mode) { this.pins[pin].mode = mode; } }, analogRead: { value(pin, callback) { const state = priv.get(this); this.pins[pin].report = 1; let ready = false; 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; // CONVERSION DELAY const delay = () => { setTimeout(() => { ready = true; }, 8); }; this.io.i2cWrite(this.address, this.REGISTER.CONFIG, [this.REGISTER.PIN[pin], this.REGISTER.PIN_DATA]); delay(); this.io.i2cRead(this.address, this.REGISTER.READ, 2, data => { if (ready) { ready = false; const newPin = pin === this.pins.length - 1 ? 0 : pin + 1; this.io.i2cWrite(this.address, this.REGISTER.CONFIG, [this.REGISTER.PIN[newPin], this.REGISTER.PIN_DATA]); const value = (data[0] << 8) + data[1]; this.pins[pin].value = value; if (this.pins[pin].report) { this.emit(`analog-read-${pin}`, value); } pin = newPin; delay(); } }); } } }, } }; Controllers["CD74HCT4067"] = Controllers.CD74HC4067; Controllers["74HC4067"] = Controllers.CD74HC4067; Controllers.PCF8574A = Object.assign({}, Controllers.PCF8574, { ADDRESSES: { value: [0x38] } }); const methods = Object.keys(Board.prototype); Object.keys(Controllers).forEach(name => { methods.forEach(key => { if (Controllers[name][key] === undefined) { Controllers[name][key] = { writable: true, configurable: true, value() { throw new Error(`Expander:${name} does not support ${key}`); } }; } }); }); const nonAddressable = [ "74HC595" ]; class Expander extends Base { constructor(options) { super(); let addressError = "Expander cannot reuse an active address"; let expander = null; let controllerValue; if (typeof options === "string") { controllerValue = options; } Board.Component.call( this, options = Board.Options(options), { normalizePin: false, requestPin: false } ); if (nonAddressable.includes(options.controller) && typeof this.address === "undefined") { this.address = Fn.uid(); } expander = active.get(this.address); if (expander) { if (this.bus && (expander.bus !== undefined && expander.bus === this.bus)) { addressError += " on this bus"; } throw new Error(addressError); } if (typeof options.controller === "undefined" && controllerValue) { options.controller = controllerValue; } Board.Controller.call(this, Controllers, options); priv.set(this, {}); if (typeof this.initialize === "function") { this.initialize(options); } active.set(this.address, this); } } Expander.get = required => { if (!required.address || !required.controller) { throw new Error("Expander.get(...) requires an address and controller"); } if (required.address !== undefined) { required.address = Number(required.address); } if (Number.isNaN(required.address)) { throw new Error("Expander.get(...) expects address to be a number"); } if (typeof required.controller !== "string") { throw new Error("Expander.get(...) expects controller name to be a string"); } // If no address was sent them assume the request wants // to re-use an active Expander, by controller name. // if (!required.address) { // return Expander.byController(required.controller); // } const expander = active.get(required.address); if (expander && (expander.name === required.controller.toUpperCase())) { return expander; } return new Expander(required); }; Expander.byAddress = address => active.get(address); Expander.byController = name => { let controller = null; active.forEach(value => { if (value.name === name.toUpperCase()) { controller = value; } }); return controller; }; Expander.hasController = key => Controllers[key] !== undefined; /* istanbul ignore else */ if (!!process.env.IS_TEST_MODE) { Expander.Controllers = Controllers; Expander.purge = () => { priv.clear(); active.clear(); }; } module.exports = Expander;