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!
1,938 lines (1,613 loc) • 48 kB
JavaScript
var IS_TEST_MODE = !!process.env.IS_TEST_MODE;
var Board = require("./board");
var Emitter = require("events").EventEmitter;
var util = require("util");
var nanosleep = require("./sleep").nano;
var Fn = require("./fn");
var priv = new Map();
var active = 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: {
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: 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.ADDRESSES[0];
opts.address = this.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: {
ADDRESSES: {
value: [0x20]
},
REGISTER: {
value: {
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.ADDRESSES[0];
opts.address = this.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: {
ADDRESSES: {
value: [0x20]
},
REGISTER: {},
initialize: {
value: function(opts) {
var state = priv.get(this);
state.port = 0x00;
state.ddr = 0x00;
state.pins = 0x00;
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.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: {
ADDRESSES: {
value: [0x20]
},
REGISTER: {},
initialize: {
value: function(opts) {
var state = priv.get(this);
state.port = [0x00, 0x01];
state.gpio = [0x00, 0x00];
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.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: {
ADDRESSES: {
value: [0x40]
},
REGISTER: {
value: {
MODE1: 0x00,
PRESCALE: 0xFE,
BASE: 0x06
}
},
initialize: {
value: function(opts) {
var state = priv.get(this);
// 7.3.5 PWM frequency PRE_SCALE
//
state.frequency = Board.constrain(opts.frequency || 1526, 24, 1526) * 0.9;
this.address = opts.address || this.ADDRESSES[0];
this.pwmRange = opts.pwmRange || [0, 4095];
Object.defineProperties(this, {
prescale: {
get: function() {
// 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: function() {
return state.frequency;
}
}
});
opts.address = this.address;
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);
}
this.name = "PCA9685";
this.isReady = true;
this.emit("connect");
this.emit("ready");
}
},
normalize: {
value: function(pin) {
return this.io.name === "Tessel 2" ? (pin - 1) : 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, value) {
value = Board.constrain(value, 0, 180);
var off = Fn.map(value, 0, 180, this.pwmRange[0] / 4, this.pwmRange[1] / 4);
this.io.i2cWrite(this.address, [
this.REGISTER.BASE + 4 * pin,
0, 0,
off, off >> 8
]);
}
},
pwmWrite: {
value: function(pin, value) {
if (this.pins[pin] === undefined) {
throw new RangeError("Invalid PCA9685 pin: " + pin);
}
value = Board.constrain(value, 0, 255);
var on = 0;
var 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;
}
}
},
// http://www.nxp.com/documents/data_sheet/PCF8591.pdf
PCF8591: {
ADDRESSES: {
value: [0x48]
},
REGISTER: {},
initialize: {
value: function(opts) {
var state = priv.get(this);
state.control = 0x45;
state.reading = false;
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.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-" + i, value);
}
}
}.bind(this));
}
}
},
},
MUXSHIELD2: {
initialize: {
value: function() {
var 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);
var row = 1;
var mask = 16;
var index = 0;
for (var i = 0; i < 48; i++) {
var band = i & mask;
if (band === mask) {
row++;
mask *= 2;
index = 0;
}
state.pinMap["IO" + row + "-" + index] = i;
this.pins.push({
row: row,
index: 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: function(pin) {
return pin;
}
},
pinMode: {
value: function(pin, mode) {
var state = priv.get(this);
var pinIndex = state.pinMap[pin];
if (pinIndex === undefined) {
throw new Error("MUXSHIELD2: Invalid Pin number or name: " + pin);
}
var row = this.pins[pinIndex].row;
var rowModeIndex = row - 1;
var 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 (var i = 0; i < 16; i++) {
this.pins[rowModeIndex + i].mode = mode;
}
var IO = state.io[row];
var 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: function(pin, value) {
var state = priv.get(this);
var pinIndex = state.pinMap[pin];
if (pinIndex === undefined) {
throw new Error("MUXSHIELD2: Invalid Pin number or name: " + pin);
}
var row = this.pins[pinIndex].row;
var rowValueIndex = row - 1;
var rowValue = state.rowValues[rowValueIndex];
var ioPin = row - 1;
var offset = ioPin * 16;
var 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);
var S = state.select[row - 1];
var IO = state.io[row];
for (var 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: function(pin, callback) {
this.ioRead("digital", pin, callback);
}
},
analogRead: {
value: function(pin, callback) {
this.ioRead("analog", pin, callback);
}
},
ioRead: {
value: function(type, pin, callback) {
var state = priv.get(this);
var pinIndex = state.pinMap[pin];
if (pinIndex === undefined) {
throw new Error("MUXSHIELD2: Invalid Pin number or name: " + pin);
}
this.on(type + "-read-" + pinIndex, callback);
var isAnalog = type === "analog" ? true : false;
var row = this.pins[pinIndex].row;
var rowReadingIndex = row - 1;
var offset = rowReadingIndex * 16;
var channel = pinIndex - offset;
this.pins[pinIndex].report = 1;
this.pins[pinIndex].channel = channel;
this.pins[pinIndex].ioPin = isAnalog ? rowReadingIndex : rowReadingIndex + 14;
var nextPinIndex = function() {
var startAt = nextPinIndex.lastPinIndex + 1;
for (var i = startAt; i < this.pins.length; i++) {
if (this.pins[i].report === 1) {
nextPinIndex.lastPinIndex = i;
return nextPinIndex.lastPinIndex;
}
}
nextPinIndex.lastPinIndex = -1;
return nextPinIndex();
}.bind(this);
nextPinIndex.lastPinIndex = -1;
var handler = function(value) {
var pinIndex = nextPinIndex.lastPinIndex;
var pin = this.pins[pinIndex];
this.emit(type + "-read-" + pinIndex, value);
this.io.removeListener(type + "-read-" + pin.ioPin, handler);
setTimeout(read, 10);
}.bind(this);
var read = function() {
var pinIndex = nextPinIndex();
var 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);
}
}.bind(this);
if (!state.rowReading[rowReadingIndex]) {
state.rowReading[rowReadingIndex] = true;
read();
}
}
},
select: {
value: function(channel) {
var 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: function(opts) {
var 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 = function(name) {
var number = typeof name === "number" ? name : parseInt(name[1]);
return number === 3 || number === 5 || number === 6;
};
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.address;
this.io.i2cConfig(opts);
Object.assign(this.MODES, this.io.MODES);
var analogChannel;
for (var 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: analogChannel
});
} else {
this.pins.push({
supportedModes: [
this.MODES.INPUT,
this.MODES.OUTPUT,
],
mode: 0,
value: 0,
report: 0,
analogChannel: 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: function(pin) {
return pin;
}
},
pinMode: {
value: function(pin, mode) {
var state = priv.get(this);
var 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: function(pin, value) {
var state = priv.get(this);
var 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: function(pin, type, callback) {
var state = priv.get(this);
var pinIndex = state.pinMap[pin];
var isAnalog = type === "analog";
var length = isAnalog ? 3 : 1;
var 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;
var nextPinIndex = function() {
var startAt = nextPinIndex.lastPinIndex + 1;
for (var i = startAt; i < this.pins.length; i++) {
if (this.pins[i].report === 1) {
nextPinIndex.lastPinIndex = i;
return nextPinIndex.lastPinIndex;
}
}
nextPinIndex.lastPinIndex = -1;
return nextPinIndex();
}.bind(this);
nextPinIndex.lastPinIndex = -1;
var handler = function(pinIndex, value) {
var pin = this.pins[pinIndex];
var 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);
}.bind(this);
var read = function() {
var pinIndex = nextPinIndex();
var pin = this.pins[pinIndex];
var isAnalog = pin.type === "analog";
this.io.i2cWrite(this.address, [pin.command, pinIndex, 0, 0]);
this.io.i2cReadOnce(this.address, pin.length, function(data) {
var value;
if (isAnalog) {
value = (data[1] << 8) + data[2];
} else {
value = data[0];
}
handler(pinIndex, value);
}.bind(this));
}.bind(this);
if (!state.isReading) {
state.isReading = true;
read();
}
}
},
digitalRead: {
value: function(pin, callback) {
this.ioRead(pin, "digital", callback);
},
},
analogRead: {
value: function(pin, callback) {
this.ioRead(pin, "analog", callback);
},
},
pingRead: {
value: function(settings, callback) {
var state = priv.get(this);
var pinIndex = state.pinMap[settings.pin];
this.io.i2cWrite(
this.address, [
this.COMMANDS.PING_READ,
pinIndex,
0, 0
]
);
setTimeout(function() {
this.once("ping-read-" + settings.pin, callback);
this.io.i2cReadOnce(this.address, 3, function(data) {
// The GrovePi firmware sends this value in CM
// so the value must be converted back to duration.
var value = Math.round(((data[1] << 8) + data[2]) * 29 * 2);
this.pins[pinIndex].value = value;
this.emit("ping-read-" + settings.pin, value);
}.bind(this));
}.bind(this), 200);
},
},
analogWrite: {
value: function(pin, value) {
this.pwmWrite(pin, value);
}
},
pwmWrite: {
writable: true,
value: function(pin, value) {
var state = priv.get(this);
var pinIndex = state.pinMap[pin];
value = Board.constrain(value, 0, 255);
this.io.i2cWrite(
this.address, [
this.COMMANDS.ANALOG_WRITE,
pinIndex,
value,
0
]
);
this.pins[pinIndex].value = value;
}
}
},
"74HC595": {
initialize: {
value: function(opts) {
var state = priv.get(this);
if (!opts.pins.data) {
throw new Error("Expected pins.data");
}
if (!opts.pins.clock) {
throw new Error("Expected pins.clock");
}
if (!opts.pins.latch) {
throw new Error("Expected pins.latch");
}
state.data = opts.pins.data;
state.clock = opts.pins.clock;
state.latch = opts.pins.latch;
state.value = 0x00;
Object.assign(this.MODES, this.io.MODES);
// Reset pins property to empty array.
this.pins = [];
for (var 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: function(pin) {
return pin;
}
},
pinMode: {
value: function(pin, mode) {
this.pins[pin].mode = mode;
}
},
digitalWrite: {
value: function(pin, value) {
var 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: function(port, value) {
var 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 (var 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: function(opts) {
var state = priv.get(this);
state.reading = false;
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.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.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: 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);
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, function(data) {
var value;
for (var i = 0; i < 16; i++) {
var 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);
}
}
}.bind(this));
}
}
},
},
// https://cdn-shop.adafruit.com/datasheets/LIS3DH.pdf
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: function(opts) {
var state = priv.get(this);
state.reading = false;
this.address = opts.address || this.ADDRESSES[0];
opts.address = this.address;
this.io.i2cConfig(opts);
// Page 29
// 8.8 CTRL_REG1
// Table 24. CTRL_REG1 register
//
// ODR3 ODR2 ODR1 ODR0 LPen Zen Yen Xen
//
//
// Enable Axis
// 0b00000111
// ZYX
//
var 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 (var 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: 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 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, function(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 (var i = 1; i < 4; i++) {
if (this.pins[i].report) {
this.emit("analog-read-" + i, this.pins[i].value);
}
}
}.bind(this));
}
},
},
i2cConfig: {
value: function() {
return this.io.i2cConfig.apply(this.io, arguments);
},
},
i2cWrite: {
value: function() {
return this.io.i2cWrite.apply(this.io, arguments);
},
},
i2cWriteReg: {
value: function() {
return this.io.i2cWriteReg.apply(this.io, arguments);
},
},
i2cRead: {
value: function() {
return this.io.i2cRead.apply(this.io, arguments);
},
},
i2cReadOnce: {
value: function() {
return this.io.i2cReadOnce.apply(this.io, arguments);
},
},
},
};
Controllers["CD74HCT4067"] = Controllers.CD74HC4067;
Controllers["74HC4067"] = Controllers.CD74HC4067;
Controllers.PCF8574A = Object.assign({}, Controllers.PCF8574, {
ADDRESSES: {
value: [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 expander = null;
var addressError = "Expander cannot reuse an active address";
var controller = null;
var state = {};
var controllerValue;
if (typeof opts === "string") {
controllerValue = opts;
}
Board.Component.call(
this, opts = Board.Options(opts), {
normalizePin: false,
requestPin: false
}
);
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 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");
}
Board.Controller.call(this, controller, opts);
priv.set(this, state);
if (typeof this.initialize === "function") {
this.initialize(opts);
}
active.set(this.address, this);
}
util.inherits(Expander, Base);
Expander.get = function(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);
// }
var expander = active.get(required.address);
if (expander && (expander.name === required.controller.toUpperCase())) {
return expander;
}
return new Expander(required);
};
Expander.byAddress = function(address) {
return active.get(address);
};
Expander.byController = function(name) {
var controller = null;
active.forEach(function(value) {
if (value.name === name.toUpperCase()) {
controller = value;
}
});
return controller;
};
Expander.hasController = function(key) {
return Controllers[key] !== undefined;
};
/* istanbul ignore else */
if (IS_TEST_MODE) {
Expander.purge = function() {
priv.clear();
active.clear();
};
}
module.exports = Expander;