johnny-five-electron
Version:
Temporary fork to support Electron (to be deprecated)
617 lines (529 loc) • 16.2 kB
JavaScript
var Board = require("../lib/board.js");
var Emitter = require("events").EventEmitter;
var util = require("util");
var __ = require("../lib/fn.js");
var Accelerometer = require("../lib/accelerometer.js");
var Barometer = require("../lib/barometer.js");
var Temperature = require("../lib/temperature.js");
var Gyro = require("../lib/gyro.js");
var int16 = __.int16;
var uint16 = __.uint16;
var uint24 = __.uint24;
var priv = new Map();
var activeDrivers = new Map();
var Drivers = {
// Based on the example code from
// http://playground.arduino.cc/Main/MPU-6050
// http://www.invensense.com/mems/gyro/mpu6050.html
MPU6050: {
ADDRESSES: {
value: [0x68, 0x69]
},
REGISTER: {
value: {
SETUP: [0x6B, 0x00], // += 250
READ: 0x3B
}
},
initialize: {
value: function(board, opts) {
var READLENGTH = 14;
var io = board.io;
var address = opts.address || this.ADDRESSES[0];
var computed = {
accelerometer: {},
temperature: {},
gyro: {}
};
io.i2cConfig(opts);
io.i2cWrite(address, this.REGISTER.SETUP);
io.i2cRead(address, this.REGISTER.READ, READLENGTH, function(data) {
computed.accelerometer = {
x: int16(data[0], data[1]),
y: int16(data[2], data[3]),
z: int16(data[4], data[5])
};
computed.temperature = int16(data[6], data[7]);
computed.gyro = {
x: int16(data[8], data[9]),
y: int16(data[10], data[11]),
z: int16(data[12], data[13])
};
this.emit("data", computed);
}.bind(this));
},
},
identifier: {
value: function(opts) {
var address = opts.address || Drivers["MPU6050"].ADDRESSES.value[0];
return "mpu-6050-" + address;
}
}
},
MPL115A2: {
ADDRESSES: {
value: [0x60]
},
REGISTER: {
value: {
COEFFICIENTS: 0x04,
READ: 0x00,
STARTCONVERSION: 0x12,
}
},
initialize: {
value: function(board, opts) {
var READLENGTH = 4;
var io = board.io;
var address = opts.address || this.ADDRESSES[0];
var cof = {
a0: null,
b1: null,
b2: null,
c12: null
};
io.i2cConfig(opts);
var pCoefficients = new Promise(function(resolve) {
io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 8, function(data) {
var A0 = int16(data[0], data[1]);
var B1 = int16(data[2], data[3]);
var B2 = int16(data[4], data[5]);
var C12 = int16(data[6], data[7]) >> 2;
// Source:
// https://github.com/adafruit/Adafruit_MPL115A2
// a0 is the pressure offset coefficient
// b1 is the pressure sensitivity coefficient
// b2 is the temperature coefficient of offset (TCO)
// c12 is the temperature coefficient of sensitivity (TCS)
cof.a0 = A0 / 8;
cof.b1 = B1 / 8192;
cof.b2 = B2 / 16384;
cof.c12 = C12 / 4194304;
resolve();
}.bind(this));
}.bind(this));
pCoefficients.then(function() {
io.i2cWrite(address, [this.REGISTER.STARTCONVERSION, 0x00]);
io.i2cRead(address, this.REGISTER.READ, READLENGTH, function(data) {
var padc = uint16(data[0], data[1]) >> 6;
var tadc = uint16(data[2], data[3]) >> 6;
var pressure = cof.a0 + (cof.b1 + cof.c12 * tadc) * padc + cof.b2 * tadc;
var temperature = tadc;
this.emit("data", {
pressure: pressure,
temperature: temperature,
});
}.bind(this));
}.bind(this));
}
},
identifier: {
value: function(opts) {
var address = opts.address || Drivers["MPL115A2"].ADDRESSES.value[0];
return "mpl115a2-" + address;
}
}
},
BMP180: {
ADDRESSES: {
value: [0x77]
},
REGISTER: {
value: {
COEFFICIENTS: 0xAA,
READ: 0x00,
READ_START: 0xF4,
READ_RESULT: 0xF6,
}
},
initialize: {
value: function(board, opts) {
var io = board.io;
var address = opts.address || this.ADDRESSES[0];
/**
* http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
* Table 1: Operating conditions, output signal and mechanical characteristics
*
* Pressure Conversion Delay (ms)
*
* [
* 5, LOW
* 8, STANDARD
* 14, HIGH
* 26, ULTRA
* ]
*/
var mode = opts.mode || 3;
var kpDelay = [ 5, 8, 14, 26 ][ mode ];
var oss = __.constrain(mode, 0, 3);
var cof = {
a1: null,
a2: null,
a3: null,
a4: null,
a5: null,
a6: null,
b1: null,
b2: null,
b5: null,
mb: null,
mc: null,
md: null,
};
io.i2cConfig(opts);
var pCoefficients = new Promise(function(resolve) {
io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 22, function(data) {
// http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
// Pages 11, 15
// 3.3 Measurement of pressure and temperature
// 3.5 Calculating pressure and temperature
cof.a1 = int16(data[0], data[1]);
cof.a2 = int16(data[2], data[3]);
cof.a3 = int16(data[4], data[5]);
cof.a4 = uint16(data[6], data[7]);
cof.a5 = uint16(data[8], data[9]);
cof.a6 = uint16(data[10], data[11]);
cof.b1 = int16(data[12], data[13]);
cof.b2 = int16(data[14], data[15]);
cof.mb = int16(data[16], data[17]);
cof.mc = int16(data[18], data[19]);
cof.md = int16(data[20], data[21]);
resolve();
});
}.bind(this));
pCoefficients.then(function() {
var computed = {
pressure: null,
temperature: null,
};
var cycle = 0;
// http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
// Pages 11, 15
// 3.3 Measurement of pressure and temperature
// 3.5 Calculating pressure and temperature
var readCycle = function() {
// cycle 0: temperature
// cycle 1: pressure
var isTemperatureCycle = cycle === 0;
var component = isTemperatureCycle ? 0x2E : 0x34 + (oss << 6);
var numBytes = isTemperatureCycle ? 2 : 3;
var delay = isTemperatureCycle ? 5 : kpDelay;
io.i2cWriteReg(address, this.REGISTER.READ_START, component);
// Once the READ_START register is set,
// delay the READ_RESULT request based on the
// mode value provided by the user, or default.
setTimeout(function() {
io.i2cReadOnce(address, this.REGISTER.READ_RESULT, numBytes, function(data) {
var compensated, uncompensated;
var x1, x2, x3, b3, b4, b6, b7, b6s, bx;
if (isTemperatureCycle) {
// TEMPERATURE
uncompensated = int16(data[0], data[1]);
// Compute the true temperature
x1 = ((uncompensated - cof.a6) * cof.a5) >> 15;
x2 = ((cof.mc << 11) / (x1 + cof.md)) >> 0;
// Compute b5, which is used by the pressure cycle
cof.b5 = (x1 + x2) | 0;
// Steps of 0.1°C
computed.temperature = ((cof.b5 + 8) >> 4) / 10;
} else {
// PRESSURE
uncompensated = uint24(data[0], data[1], data[2]) >> (8 - oss);
b6 = cof.b5 - 4000;
b6s = b6 * b6;
bx = b6s >> 12;
// Intermediary x1 & x2 to calculate x3 for b3
x1 = (cof.b2 * bx) >> 11;
x2 = (cof.a2 * b6) >> 11;
x3 = x1 + x2;
b3 = ((((cof.a1 * 4 + x3) << oss) + 2) / 4) >> 0;
// Intermediary x1 & x2 to calculate x3 for b4
x1 = (cof.a3 * b6) >> 13;
x2 = (cof.b1 * bx) >> 16;
x3 = ((x1 + x2) + 2) >> 2;
b4 = (cof.a4 * (x3 + 32768)) >> 15;
b7 = (uncompensated - b3) * (50000 >> oss);
if (b7 < 0x80000000) {
compensated = (b7 * 2) / b4;
} else {
compensated = (b7 / b4) * 2;
}
compensated >>= 0;
x1 = (compensated >> 8) * (compensated >> 8);
x1 = (x1 * 3038) >> 16;
x2 = (-7357 * compensated) >> 16;
compensated += (x1 + x2 + 3791) >> 4;
// Steps of 1Pa (= 0.01hPa = 0.01mbar) (=> 0.001kPa)
computed.pressure = compensated;
}
if (++cycle === 2) {
cycle = 0;
this.emit("data", computed);
}
readCycle();
}.bind(this));
}.bind(this), delay);
}.bind(this);
// Kick off "read loop"
//
readCycle();
}.bind(this));
}
},
identifier: {
value: function(opts) {
var address = opts.address || Drivers["BMP180"].ADDRESSES.value[0];
return "bmp180-" + address;
}
}
},
SI7020: {
ADDRESSES: {
value: [0x40]
},
REGISTER: {
value: {
HUMIDITY: 0xF5,
TEMPERATURE: 0xE3,
}
},
initialize: {
value: function(board, opts) {
var io = board.io;
var address = opts.address || this.ADDRESSES[0];
// The "no hold" measurement requires waiting
// _at least_ 22ms between register write and
// register read. Delay is measured in μs:
// 22ms = 22000μs; recommend 50ms = 50000μs
opts.delay = 50000;
io.i2cConfig(opts);
// Reference
// https://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020-A20.pdf
// P. 19
var computed = {
temperature: null
};
io.i2cRead(address, this.REGISTER.TEMPERATURE, 2, function(data) {
var raw = int16(data[0], data[1]);
// https://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020-A20.pdf
// P. 23
computed.temperature = (175.25 * raw / 65536) - 46.85;
this.emit("data", computed);
}.bind(this));
}
},
identifier: {
value: function(opts) {
var address = opts.address || Drivers["SI7020"].ADDRESSES.value[0];
return "si7020-" + address;
}
},
}
};
// Otherwise known as...
Drivers["MPU-6050"] = Drivers.MPU6050;
Drivers.get = function(board, driverName, opts) {
var drivers, driverKey, driver;
if (!activeDrivers.has(board)) {
activeDrivers.set(board, {});
}
drivers = activeDrivers.get(board);
driverKey = Drivers[driverName].identifier.value(opts);
if (!drivers[driverKey]) {
driver = new Emitter();
Object.defineProperties(driver, Drivers[driverName]);
driver.initialize(board, opts);
drivers[driverKey] = driver;
}
return drivers[driverKey];
};
Drivers.clear = function() {
activeDrivers.clear();
};
var Controllers = {
/**
* MPU-6050 3-axis Gyro/Accelerometer and Temperature
*
* http://playground.arduino.cc/Main/MPU-6050
*/
MPU6050: {
initialize: {
value: function(opts) {
var state = priv.get(this);
state.accelerometer = new Accelerometer(
Object.assign({
controller: "MPU6050",
freq: opts.freq,
board: this.board,
}, opts)
);
state.temperature = new Temperature(
Object.assign({
controller: "MPU6050",
freq: opts.freq,
board: this.board,
}, opts)
);
state.gyro = new Gyro(
Object.assign({
controller: "MPU6050",
freq: opts.freq,
board: this.board,
}, opts)
);
}
},
components: {
value: ["accelerometer", "temperature", "gyro"]
},
accelerometer: {
get: function() {
return priv.get(this).accelerometer;
}
},
temperature: {
get: function() {
return priv.get(this).temperature;
}
},
gyro: {
get: function() {
return priv.get(this).gyro;
}
}
},
MPL115A2: {
initialize: {
value: function(opts) {
var state = priv.get(this);
state.barometer = new Barometer(
Object.assign({
controller: "MPL115A2",
freq: opts.freq,
board: this.board,
}, opts)
);
state.temperature = new Temperature(
Object.assign({
controller: "MPL115A2",
freq: opts.freq,
board: this.board,
}, opts)
);
}
},
components: {
value: ["barometer", "temperature"]
},
barometer: {
get: function() {
return priv.get(this).barometer;
}
},
temperature: {
get: function() {
return priv.get(this).temperature;
}
}
},
BMP180: {
initialize: {
value: function(opts) {
var state = priv.get(this);
state.barometer = new Barometer(
Object.assign({
controller: "BMP180",
freq: opts.freq,
board: this.board,
}, opts)
);
state.temperature = new Temperature(
Object.assign({
controller: "BMP180",
freq: opts.freq,
board: this.board,
}, opts)
);
}
},
components: {
value: ["barometer", "temperature", /* "altitude" */]
},
barometer: {
get: function() {
return priv.get(this).barometer;
}
},
temperature: {
get: function() {
return priv.get(this).temperature;
}
}
},
SI7020: {
initialize: {
value: function(opts) {
var state = priv.get(this);
// Eventually humidity will also be exposed.
state.temperature = new Temperature(
Object.assign({
controller: "SI7020",
freq: opts.freq,
board: this.board,
}, opts)
);
}
},
components: {
value: ["temperature"]
},
temperature: {
get: function() {
return priv.get(this).temperature;
}
}
}
};
// Otherwise known as...
Controllers["MPU-6050"] = Controllers.MPU6050;
Controllers["GY521"] = Controllers["GY-521"] = Controllers.MPU6050;
function IMU(opts) {
if (!(this instanceof IMU)) {
return new IMU(opts);
}
var controller, state;
Board.Component.call(
this, opts = Board.Options(opts)
);
if (opts.controller && typeof opts.controller === "string") {
controller = Controllers[opts.controller.toUpperCase()];
} else {
controller = opts.controller;
}
if (controller == null) {
controller = Controllers["MPU6050"];
}
this.freq = opts.freq || 500;
state = {};
priv.set(this, state);
Object.defineProperties(this, controller);
if (typeof this.initialize === "function") {
this.initialize(opts);
}
setInterval(function() {
this.emit("data", this);
}.bind(this), this.freq);
if (this.components && this.components.length > 0) {
this.components.forEach(function(component) {
if (!(this[component] instanceof Emitter)) {
return;
}
this[component].on("change", function() {
this.emit("change", this, component);
}.bind(this));
}, this);
}
}
util.inherits(IMU, Emitter);
IMU.Drivers = Drivers;
module.exports = IMU;