johnny-five-electron
Version:
Temporary fork to support Electron (to be deprecated)
577 lines (508 loc) • 13.2 kB
JavaScript
var Board = require("../lib/board.js"),
events = require("events"),
util = require("util"),
__ = require("../lib/fn.js"),
sum = __.sum,
fma = __.fma,
constrain = __.constrain,
int16 = __.int16;
var priv = new Map();
var rad2deg = 180 / Math.PI;
var calibrationSize = 10;
var axes = ["x", "y", "z"];
function analogInitialize(opts, dataHandler) {
var pins = opts.pins || [],
state = priv.get(this),
dataPoints = {};
state.zeroV = opts.zeroV || this.DEFAULTS.zeroV;
state.sensitivity = opts.sensitivity || this.DEFAULTS.sensitivity;
pins.forEach(function(pin, index) {
this.io.pinMode(pin, this.io.MODES.ANALOG);
this.io.analogRead(pin, function(data) {
var axis = axes[index];
dataPoints[axis] = data;
dataHandler(dataPoints);
}.bind(this));
}, this);
}
function analogToGravity(raw, axis) {
var state = priv.get(this);
var zeroV = state.zeroV;
if (Array.isArray(zeroV) && zeroV.length > 0) {
var axisIndex = axes.indexOf(axis);
zeroV = zeroV[axisIndex || 0];
}
return (raw - zeroV) / state.sensitivity;
}
var Controllers = {
ANALOG: {
DEFAULTS: {
value: {
zeroV: 478,
sensitivity: 96
}
},
initialize: {
value: analogInitialize
},
toGravity: {
value: analogToGravity
}
},
// http://www.invensense.com/mems/gyro/mpu6050.html
MPU6050: {
initialize: {
value: function(opts, dataHandler) {
var IMU = require("../lib/imu");
var driver = IMU.Drivers.get(this.board, "MPU6050", opts);
var state = priv.get(this);
state.sensitivity = opts.sensitivity || 16384;
driver.on("data", function(data) {
dataHandler(data.accelerometer);
});
}
},
toGravity: {
value: function(raw) {
var state = priv.get(this);
return raw / state.sensitivity;
}
}
},
ADXL335: {
DEFAULTS: {
value: {
zeroV: 330,
sensitivity: 66.5
}
},
initialize: {
value: analogInitialize
},
toGravity: {
value: analogToGravity
}
},
ADXL345: {
// http://www.analog.com/en/mems-sensors/mems-inertial-sensors/adxl345/products/product.html
// http://www.i2cdevlib.com/devices/adxl345#source
ADDRESSES: {
value: [0x53]
},
REGISTER: {
value: {
POWER: 0x2D,
RANGE: 0x31,
READREGISTER: 0x32
}
},
initialize: {
value: function(opts, dataHandler) {
var READLENGTH = 6;
var address = opts.address || this.ADDRESSES[0];
var state = priv.get(this);
// Sensitivity:
//
// (ADXL345_MG2G_MULTIPLIER * SENSORS_GRAVITY_STANDARD) = (0.004 * 9.80665) = 0.0390625
//
// Reference:
// https://github.com/adafruit/Adafruit_Sensor/blob/master/Adafruit_Sensor.h#L34-L37
// https://github.com/adafruit/Adafruit_ADXL345/blob/master/Adafruit_ADXL345_U.h#L73
// https://github.com/adafruit/Adafruit_ADXL345/blob/master/Adafruit_ADXL345_U.cpp#L298-L309
//
// Invert for parity with other controllers
//
// (1 / 0.0390625) * (1 / 9.8)
//
// OR
//
// (1 / ADXL345_MG2G_MULTIPLIER) = (1 / 0.004)
//
// OR
//
// 250
//
state.sensitivity = opts.sensitivity || 250;
this.io.i2cConfig(opts);
// Standby mode
this.io.i2cWrite(address, this.REGISTER.POWER, 0);
// Enable measurements
this.io.i2cWrite(address, this.REGISTER.POWER, 8);
// Set range (this is 2G range, should be user defined?)
this.io.i2cWrite(address, this.REGISTER.RANGE, 8);
this.io.i2cRead(address, this.REGISTER.READREGISTER, READLENGTH, function(data) {
dataHandler.call(this, {
x: int16(data[1], data[0]),
y: int16(data[3], data[2]),
z: int16(data[5], data[4])
});
}.bind(this));
},
},
toGravity: {
value: function(raw) {
var state = priv.get(this);
return raw / state.sensitivity;
}
}
},
MMA7361: {
DEFAULTS: {
value: {
zeroV: [336, 372, 287],
sensitivity: 170
}
},
initialize: {
value: function(opts, dataHandler) {
var state = priv.get(this);
if (opts.sleepPin !== undefined) {
state.sleepPin = opts.sleepPin;
this.board.pinMode(state.sleepPin, 1);
this.board.digitalWrite(state.sleepPin, 1);
}
analogInitialize.call(this, opts, dataHandler);
}
},
toGravity: {
value: analogToGravity
},
enabledChanged: {
value: function(value) {
var state = priv.get(this);
if (state.sleepPin !== undefined) {
this.board.digitalWrite(state.sleepPin, value ? 1 : 0);
}
}
}
},
MMA7660: {
ADDRESSES: {
value: [0x4C]
},
REGISTER: {
value: {
READREGISTER: 0x00,
RATE: 0x08,
MODE: 0x07,
}
},
initialize: {
value: function(opts, dataHandler) {
var READLENGTH = 3;
var address = opts.address || this.ADDRESSES[0];
var state = priv.get(this);
state.sensitivity = 21.33;
this.io.i2cConfig(opts);
// http://www.freescale.com.cn/files/sensors/doc/data_sheet/MMA7660FC.pdf?fpsp=1
//
// Standby mode
this.io.i2cWrite(address, this.REGISTER.MODE, 0x00);
// Sample Rate ()
this.io.i2cWrite(address, this.REGISTER.RATE, 0x07);
// Active Mode
this.io.i2cWrite(address, this.REGISTER.MODE, 0x01);
this.io.i2cRead(address, this.REGISTER.READREGISTER, READLENGTH, function(data) {
dataHandler.call(this, {
// Shift off the sign bits
// This solution is used in
// https://github.com/intel-iot-devkit/upm/blob/master/src/mma7660/mma7660.cxx
x: (data[0] << 2) / 4,
y: (data[1] << 2) / 4,
z: (data[2] << 2) / 4,
});
}.bind(this));
},
},
toGravity: {
value: function(raw) {
var state = priv.get(this);
return raw / state.sensitivity;
}
}
},
ESPLORA: {
DEFAULTS: {
value: {
zeroV: [320, 330, 310],
sensitivity: 170
}
},
initialize: {
value: function(opts, dataHandler) {
opts.pins = [5, 11, 6];
analogInitialize.call(this, opts, dataHandler);
}
},
toGravity: {
value: analogToGravity
}
}
};
// Otherwise known as...
Controllers["MPU-6050"] = Controllers.MPU6050;
Controllers["TINKERKIT"] = Controllers.ANALOG;
function ToPrecision(val, precision) {
return +(val).toPrecision(precision);
}
function magnitude(x, y, z) {
var a;
a = x * x;
a = fma(y, y, a);
a = fma(z, z, a);
return Math.sqrt(a);
}
/**
* Accelerometer
* @constructor
*
* five.Accelerometer([ x, y[, z] ]);
*
* five.Accelerometer({
* pins: [ x, y[, z] ]
* zeroV: ...
* sensitivity: ...
* });
*
*
* @param {Object} opts [description]
*
*/
function Accelerometer(opts) {
if (!(this instanceof Accelerometer)) {
return new Accelerometer(opts);
}
var controller = null;
var state = {
enabled: true,
x: {
value: 0,
previous: 0,
stash: [],
orientation: null,
inclination: null,
acceleration: null,
calibration: []
},
y: {
value: 0,
previous: 0,
stash: [],
orientation: null,
inclination: null,
acceleration: null,
calibration: []
},
z: {
value: 0,
previous: 0,
stash: [],
orientation: null,
inclination: null,
acceleration: null,
calibration: []
}
};
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["ANALOG"];
}
Object.defineProperties(this, controller);
if (!this.toGravity) {
this.toGravity = opts.toGravity || function(raw) { return raw; };
}
if (!this.enabledChanged) {
this.enabledChanged = function() {};
}
priv.set(this, state);
if (typeof this.initialize === "function") {
this.initialize(opts, function(data) {
var isChange = false;
if (!state.enabled) {
return;
}
Object.keys(data).forEach(function(axis) {
var value = data[axis];
var sensor = state[axis];
if (opts.autoCalibrate && sensor.calibration.length < calibrationSize) {
var axisIndex = axes.indexOf(axis);
sensor.calibration.push(value);
if (!Array.isArray(state.zeroV)) {
state.zeroV = [];
}
state.zeroV[axisIndex] = __.sum(sensor.calibration) / sensor.calibration.length;
if (axis === "z") {
state.zeroV[axisIndex] -= state.sensitivity;
}
}
// The first run needs to prime the "stash"
// of data values.
if (sensor.stash.length === 0) {
for (var i = 0; i < 5; i++) {
sensor.stash[i] = value;
}
}
sensor.previous = sensor.value;
sensor.stash.shift();
sensor.stash.push(value);
sensor.value = (sum(sensor.stash) / 5) | 0;
if (this.acceleration !== sensor.acceleration) {
sensor.acceleration = this.acceleration;
isChange = true;
this.emit("acceleration", sensor.acceleration);
}
if (this.orientation !== sensor.orientation) {
sensor.orientation = this.orientation;
isChange = true;
this.emit("orientation", sensor.orientation);
}
if (this.inclination !== sensor.inclination) {
sensor.inclination = this.inclination;
isChange = true;
this.emit("inclination", sensor.inclination);
}
}, this);
this.emit("data", {
x: state.x.value,
y: state.y.value,
z: state.z.value
});
if (isChange) {
this.emit("change", {
x: this.x,
y: this.y,
z: this.z
});
}
}.bind(this));
}
Object.defineProperties(this, {
hasAxis: {
value: function(axis) {
return state[axis] ? state[axis].stash.length > 0 : false;
}
},
enable: {
value: function() {
state.enabled = true;
this.enabledChanged(true);
return this;
}
},
disable: {
value: function() {
state.enabled = false;
this.enabledChanged(false);
return this;
}
},
zeroV: {
get: function() {
return state.zeroV;
}
},
/**
* [read-only] Calculated pitch value
* @property pitch
* @type Number
*/
pitch: {
get: function() {
var x, y, z, rads;
x = this.x;
y = this.y;
z = this.z;
rads = this.hasAxis("z") ?
Math.atan2(x, Math.hypot(y, z)) :
Math.asin(constrain(x, -1, 1));
return ToPrecision(rads * rad2deg, 2);
}
},
/**
* [read-only] Calculated roll value
* @property roll
* @type Number
*/
roll: {
get: function() {
var x, y, z, rads;
x = this.x;
y = this.y;
z = this.z;
rads = this.hasAxis("z") ?
Math.atan2(y, Math.hypot(x, z)) :
Math.asin(constrain(y, -1, 1));
return ToPrecision(rads * rad2deg, 2);
}
},
x: {
get: function() {
return ToPrecision(this.toGravity(state.x.value, "x"), 2);
}
},
y: {
get: function() {
return ToPrecision(this.toGravity(state.y.value, "y"), 2);
}
},
z: {
get: function() {
return this.hasAxis("z") ?
ToPrecision(this.toGravity(state.z.value, "z"), 2) : 0;
}
},
acceleration: {
get: function() {
return magnitude(
this.x,
this.y,
this.z
);
}
},
inclination: {
get: function() {
return Math.atan2(this.y, this.x) * rad2deg;
}
},
orientation: {
get: function() {
var abs = Math.abs;
var x = this.x;
var y = this.y;
var z = this.hasAxis(z) ? this.z : 1;
var xAbs = abs(x);
var yAbs = abs(y);
var zAbs = abs(z);
if (xAbs < yAbs && xAbs < zAbs) {
if (x > 0) {
return 1;
}
return -1;
}
if (yAbs < xAbs && yAbs < zAbs) {
if (y > 0) {
return 2;
}
return -2;
}
if (zAbs < xAbs && zAbs < yAbs) {
if (z > 0) {
return 3;
}
return -3;
}
return 0;
}
}
});
}
util.inherits(Accelerometer, events.EventEmitter);
module.exports = Accelerometer;