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!

1,176 lines (990 loc) 28.6 kB
var IS_TEST_MODE = !!process.env.IS_TEST_MODE; var Board = require("./board"); var Expander = require("./expander"); var Emitter = require("events").EventEmitter; var util = require("util"); var Fn = require("./fn"); var constrain = Fn.constrain; var fma = Fn.fma; var int16 = Fn.int16; var sum = Fn.sum; var toFixed = Fn.toFixed; var priv = new Map(); var calibrationSize = 10; var aX = "x"; var aY = "y"; var aZ = "z"; var axes = [aX, aY, aZ]; function analogInitialize(opts, dataHandler) { var state = priv.get(this); var dataPoints = {}; state.zeroV = opts.zeroV || this.DEFAULTS.zeroV; state.sensitivity = opts.sensitivity || this.DEFAULTS.sensitivity; this.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("./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; } } }, BNO055: { initialize: { value: function(opts, dataHandler) { var IMU = require("./imu"); var driver = IMU.Drivers.get(this.board, "BNO055", opts); var state = priv.get(this); // AF p.31, Table 3-17: Accelerometer Unit settings state.sensitivity = 100; 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 } }, // http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf ADXL345: { ADDRESSES: { value: [0x53] }, REGISTER: { value: { // Page 23 // REGISTER MAP // POWER: 0x2D, // 0x31 49 DATA_FORMAT R/W 00000000 Data format control DATA_FORMAT: 0x31, // 0x32 50 DATAX0 R 00000000 X-Axis Data 0 DATAX0: 0x32 } }, initialize: { value: function(opts, dataHandler) { var READLENGTH = 6; var address = opts.address || this.ADDRESSES[0]; var state = priv.get(this); opts.address = address; this.io.i2cConfig(opts); // Standby mode this.io.i2cWrite(address, this.REGISTER.POWER, 0); // Enable measurements this.io.i2cWrite(address, this.REGISTER.POWER, 8); /* Page 26 Register 0x31—DATA_FORMAT (Read/Write) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | - | - | - | - | - | - | - | - | | T | S | I | - | F | J | R | T: SELF_TEST S: SPI I: INT_INVERT -:- F: FULL_RES J: JUSTIFY R: RANGE Range notes: https://github.com/rwaldron/johnny-five/issues/1135#issuecomment-219541346 +/- 16g 0b11 +/- 8g 0b10 +/- 4g 0b01 +/- 2g 0b00 Start with FULL_RES bit on 0b00001000 = 0x08 = 8 */ var format = 0x08; /* Determine range 0b00000000 = 0 = ±2g 0b00000001 = 1 = ±4g 0b00000010 = 2 = ±8g 0b00000011 = 3 = ±16g */ var range = ({ 2: 0, 4: 1, 8: 2, 16: 3 })[opts.range || 2]; /* Page 4 Sensitivity at XOUT YOUT ZOUT ±2 g, 10-bit resolution 230 256 282 ±4 g, 10-bit resolution 115 128 141 ±8 g, 10-bit resolution 57 64 71 ±16 g, 10-bit resolution 29 32 35 */ state.sensitivity = [{ x: 230, y: 256, z: 282 }, { x: 115, y: 128, z: 141 }, { x: 57, y: 64, z: 71 }, { x: 29, y: 32, z: 35 }][range]; // Merge the format and range bits to set the DATA_FORMAT this.io.i2cWrite(address, this.REGISTER.DATA_FORMAT, format | range); this.io.i2cRead(address, this.REGISTER.DATAX0, READLENGTH, function(data) { dataHandler({ x: int16(data[1], data[0]), y: int16(data[3], data[2]), z: int16(data[5], data[4]) }); }); }, }, toGravity: { value: function(raw, axis) { var state = priv.get(this); return raw / state.sensitivity[axis]; } } }, MMA7361: { DEFAULTS: { value: { zeroV: [336, 372, 287], sensitivity: 170 } }, initialize: { value: function(opts, dataHandler) { var state = priv.get(this); /* istanbul ignore else */ if (opts.sleepPin !== undefined) { state.sleepPin = opts.sleepPin; this.io.pinMode(state.sleepPin, 1); this.io.digitalWrite(state.sleepPin, 1); } analogInitialize.call(this, opts, dataHandler); } }, toGravity: { value: analogToGravity }, enabledChanged: { value: function(value) { var state = priv.get(this); /* istanbul ignore else */ if (state.sleepPin !== undefined) { this.io.digitalWrite(state.sleepPin, value ? 1 : 0); } } } }, // https://cdn.sparkfun.com/datasheets/Sensors/Accelerometers/MMA8452Q-rev8.1.pdf // http://cache.freescale.com/files/sensors/doc/app_note/AN4076.pdf?fpsp=1 // http://cache.freescale.com/files/sensors/doc/app_note/AN4072.pdf MMA8452: { ADDRESSES: { value: [0x1D] }, REGISTER: { value: { // Page 18 // 6. Register Descriptions STATUS: 0x00, OUT_X_MSB: 0x01, XYZ_DATA_CFG: 0x0E, PULSE_CFG: 0x21, PULSE_SRC: 0x22, PULSE_THSX: 0x23, PULSE_THSY: 0x24, PULSE_THSZ: 0x25, PULSE_TMLT: 0x26, PULSE_LTCY: 0x27, PULSE_WIND: 0x28, CTRL_REG1: 0x2A, CTRL_REG4: 0x2E, CTRL_REG5: 0x2F, } }, initialize: { value: function(opts, dataHandler) { var state = priv.get(this); var address = opts.address || this.ADDRESSES[0]; opts.address = address; // TODO: make user definable. // 0b000 800Hz // 0b001 400Hz // 0b010 200Hz // 0b011 100Hz // 0b100 50Hz // 0b101 12Hz // 0b110 6Hz var rates = [ 800, 400, 200, 100, 50, 12, 6, ]; var odr = rates.indexOf(opts.odr || 800); var scale = opts.range || 2; var fsr = ({ 2: 0, 4: 1, 8: 2 })[scale]; opts.taps = opts.taps || { x: false, y: false, z: true, }; var taps = { x: opts.taps.x ? 0x08 : 0x80, y: opts.taps.y ? 0x08 : 0x80, z: opts.taps.z ? 0x08 : 0x80, }; state.scale = scale; var computed = { x: null, y: null, z: null, }; this.io.i2cConfig( Object.assign(opts, { settings: { stopTX: false } }) ); if (odr === -1) { throw new RangeError("Invalid odr. Expected one of: 800, 400, 200, 100, 50, 12, 6"); } /* Initial CTRL_REG1 State 11000010 = 194 = 0xC2 -> ? 00000010 = 8 = 0x08 ^--------- ASLP_RATE1 ^-------- ASLP_RATE0 ^------- DR2 ^------ DR1 ^----- DR0 ^---- Noise ^--- Fast Read ^-- Standby Mode */ var config = 0x08; /* Page 5 (AN4076) 4.0 Setting the Data Rate Set ODR Shift the odr bits into place. Default: 800Hz 11000010 = 194 = 0xC2 -> ? 00000010 = 8 = 0x08 ^^^----- DR2, DR1, DR0: 000 */ config |= odr << 3; /* Enter Standby Mode 11000010 = 194 = 0xC2 -> ? ^--- Fast Read ^-- Standby Mode 00000010 = 8 = 0x08 ^--- Fast Read ^-- Standby Mode */ this.io.i2cWriteReg(address, this.REGISTER.CTRL_REG1, config); /* Set FSR Default: ±2g 00000000 = 0 = 0x00 () ^^----- FS1, FS2 */ this.io.i2cWriteReg(address, this.REGISTER.XYZ_DATA_CFG, fsr); var temp = 0; /* Page 10 (AN4072) 4.2 Registers 0x23 - 0x25 PULSE_THSX, Y, Z Pulse Threshold for X, Y and Z Registers 0x80 = B7 is HIGH 10000000 If B7 is HIGH, do not enable */ if (!(taps.x & 0x80)) { // 00000011 temp |= 0x03; this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSX, taps.x); } if (!(taps.y & 0x80)) { // 00001100 temp |= 0x0C; this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSY, taps.y); } if (!(taps.z & 0x80)) { // 00110000 temp |= 0x30; this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSZ, taps.z); } /* Page 11, 12, 13 (AN4072) Configure Tap Axis' Table 1. Register 0x21 PULSE_CFG Register (Read/Write) and Description | Tap Enable | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ---------- | --- | --- | --- | --- | --- | --- | --- | --- | | | DPA | ELE | ZD | ZS | YD | YS | XD | XS | | Single | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | | Double | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | | Both | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | In the default case, `temp | 0x40` will be: 01110000 = 112 = 0x70 Latch On ZD On ZS On */ this.io.i2cWriteReg(address, this.REGISTER.PULSE_CFG, temp | 0x40); /* Set TIME LIMIT for tap detection 60ms / 800Hz = 60ms / 1.25ms = 48 (counts) = 0x30 80ms / 800Hz = 80ms / 1.25ms = 64 (counts) = 0x40 */ this.io.i2cWriteReg(address, this.REGISTER.PULSE_TMLT, 60 / (1000 / rates[odr])); /* Set the PULSE LATENCY. This is the time that must pass after the first tap, but within the PULSE WINDOW for a double tap to register. 200ms / 800Hz = 200ms / 1.25ms = 160 (counts) = 0xA0 */ this.io.i2cWriteReg(address, this.REGISTER.PULSE_LTCY, 200 / (1000 / rates[odr])); /* Set the PULSE WINDOW. This is the maximum interval of time to elapse after the latency interval, for which the second pulse must occur for double taps. The maximum allowed time: 800Hz * 255 = 1.25ms * 255 = 318ms */ this.io.i2cWriteReg(address, this.REGISTER.PULSE_WIND, 0xFF); /* Leave Standby Mode 11000011 = 195 = 0xC3 00000011 = 3 = 0x03 ^--- Fast Read ^-- Standby Mode */ config |= 0x01; this.io.i2cWriteReg(address, this.REGISTER.CTRL_REG1, config); this.io.i2cRead(address, this.REGISTER.STATUS, 7, function(data) { var status = (data.shift() & 0x08) >>> 3; /* istanbul ignore else */ if (status) { // Page 9 (AN4076) // // 7.0 14-bit, 12-bit or 10-bit Data Streaming and Data Conversions computed.x = int16(data[0], data[1]) >> 4; computed.y = int16(data[2], data[3]) >> 4; computed.z = int16(data[4], data[5]) >> 4; dataHandler(computed); } }.bind(this)); this.io.i2cRead(address, this.REGISTER.PULSE_SRC, 1, function(data) { var status = data[0]; var tap = status & 0x7F; /* istanbul ignore else */ if (status & 0x80) { this.emit("tap"); // Single Tap /* istanbul ignore else */ if ((tap >> 2) & 0x01) { this.emit("tap:single"); // Double Tap (must be both S and D bits) /* istanbul ignore else */ if ((tap >> 3) & 0x01) { this.emit("tap:double"); } } } }.bind(this)); }, }, toGravity: { value: function(raw) { var state = priv.get(this); // return raw / ((1 << 11) * state.scale); return raw / ((1 << 11) * state.scale); } } }, 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; opts.address = address; 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({ // 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, }); }); }, }, 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) { this.pins = [5, 11, 6]; analogInitialize.call(this, opts, dataHandler); } }, toGravity: { value: analogToGravity } }, // https://cdn-shop.adafruit.com/datasheets/LIS3DHappnote.pdf LIS3DH: { ADDRESSES: { value: [0x18] }, REGISTER: { value: { OUT_X_L: 0x28, CTRL_REG1: 0x20, CTRL_REG2: 0x21, CTRL_REG3: 0x22, CTRL_REG4: 0x23, CTRL_REG5: 0x24, TEMP_CFG_REG: 0x1F, CLICK_CFG: 0x38, CLICK_SRC: 0x39, CLICK_THS: 0x3A, TIME_LIMIT: 0x3B, TIME_LATENCY: 0x3C, TIME_WINDOW: 0x3D, } }, initialize: { value: function(opts, dataHandler) { var state = priv.get(this); var address = opts.address || 0x18; // 2G = 0b00 // 4G = 0b01 // 8G = 0b10 // 16G = 0b11 var range = ({ 2: 0, 4: 1, 8: 2, 16: 3 })[opts.range || 4]; /* istanbul ignore if */ if (range === undefined) { range = 1; } var divider = [ 16380, 8190, 4096, 1365, ][range]; /* istanbul ignore if */ if (divider === undefined) { divider = 1; } var threshold = [ 80, 40, 20, 10, ][range]; /* istanbul ignore if */ if (threshold === undefined) { threshold = 10; } state.divider = divider; state.expander = Expander.get({ address: address, controller: this.controller, bus: this.bus, }); // TODO: this should come from the expander var ctrl4 = 0x88 | (range << 4); state.expander.i2cWrite(address, this.REGISTER.CTRL_REG4, ctrl4); // Acceleration state.expander.i2cReadOnce(address, this.REGISTER.CTRL_REG1, 1, function(data) { var ctrl1 = data[0]; // Set to 200Hz ctrl1 &= ~0xF0; ctrl1 |= 6 << 4; state.expander.i2cWrite(address, this.REGISTER.CTRL_REG1, ctrl1); // Page 21 // 6.1.1 I2C operation // Autoincrement bit set on register (0x80) state.expander.i2cRead(address, this.REGISTER.OUT_X_L | 0x80, 6, function(data) { dataHandler({ x: Fn.int16(data[1], data[0]), y: Fn.int16(data[3], data[2]), z: Fn.int16(data[5], data[4]), }); }); // Tap // TODO: make this optional (use "newListener"?) // // See MMA8452 driver for strategy // // state.expander.i2cReadOnce(address, this.REGISTER.CTRL_REG3, 1, function(data) { // var ctrl3 = data[0]; // // Shut off Int 1 Click // ctrl3 &= ~0x80; // ctrl3 |= 6 << 4; // state.expander.i2cWrite(address, this.REGISTER.CTRL_REG1, ctrl3); // // Page 21 // // 6.1.1 I2C operation // // Autoincrement bit set on register (0x80) // state.expander.i2cRead(address, this.REGISTER.OUT_X_L | 0x80, 6, function(data) { // dataHandler({ // x: Fn.int16(data[1], data[0]), // y: Fn.int16(data[3], data[2]), // z: Fn.int16(data[5], data[4]), // }); // }); // }.bind(this)); // Page 35 // 8.3.7 CTRL_REG3 [Interrupt CTRL register] (22h) state.expander.i2cWrite(address, this.REGISTER.CTRL_REG3, 0x80); // Page 40 // 9.2.1 Control register 5 (0x24) state.expander.i2cWrite(address, this.REGISTER.CTRL_REG5, 0x08); // Page 32 // 8.3.1 TAP_CFG // // This register is called both CLICK_CFG and TAP_CFG // // 0b00101010 = 0x2A = 42 state.expander.i2cWrite(address, this.REGISTER.CLICK_CFG, 0x2A); // Page 36 // 8.4.1 Playing with TAP_TimeLimit // // ...Offers some guidance. Ultimately I opted to take inspiration // from Adafruit's driver and example: // // Times in ms // var timelimit = 10; // var timelatency = 20; // var timewindow = 255; var timelimit = 10; var timelatency = 20; var timewindow = 255; state.expander.i2cWrite(address, this.REGISTER.CLICK_THS, threshold); state.expander.i2cWrite(address, this.REGISTER.TIME_LIMIT, timelimit); state.expander.i2cWrite(address, this.REGISTER.TIME_LATENCY, timelatency); state.expander.i2cWrite(address, this.REGISTER.TIME_WINDOW, timewindow); // Page 33 // 8.3.2 TAP_SRC (39h) // // This register is called both CLICK_SRC and TAP_SRC // var lastEmitTime = null; state.expander.i2cRead(address, this.REGISTER.CLICK_SRC, 1, function(data) { var status = data[0]; var thisEmitTime = Date.now(); // var tap = status & 0x7F; if (lastEmitTime === null) { lastEmitTime = thisEmitTime - 101; } /* istanbul ignore if */ if (thisEmitTime < (lastEmitTime + 100)) { return; } if (status === 0x00) { return; } /* istanbul ignore if */ if (!(status & 0x30)) { return; } lastEmitTime = thisEmitTime; this.emit("tap"); if (status & 0x10) { this.emit("tap:single"); } if (status & 0x20) { // TODO: Figure out if we can determine a // combined single + double tap this.emit("tap:single"); this.emit("tap:double"); } }.bind(this)); }.bind(this)); }, }, toGravity: { value: function(raw) { var state = priv.get(this); return raw / state.divider; }, }, }, }; // Otherwise known as... Controllers.TINKERKIT = Controllers.ANALOG; Controllers.MMA8452Q = Controllers.MMA8452; 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; } Board.Controller.call(this, controller, opts); if (!this.toGravity) { this.toGravity = opts.toGravity || function(raw) { return raw; }; } if (!this.enabledChanged) { this.enabledChanged = function() {}; } priv.set(this, state); /* istanbul ignore else */ 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 === aZ) { 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: { writable: IS_TEST_MODE ? true : /* istanbul ignore next */ false, value: function(axis) { /* istanbul ignore next */ 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 = this.x; var y = this.y; var z = this.z; var rads = this.hasAxis(aZ) ? Math.atan2(x, Math.hypot(y, z)) : Math.asin(constrain(x, -1, 1)); return toFixed(rads * Fn.RAD_TO_DEG, 2); } }, /** * [read-only] Calculated roll value * @property roll * @type Number */ roll: { get: function() { var x = this.x; var y = this.y; var z = this.z; var rads = this.hasAxis(aZ) ? Math.atan2(y, Math.hypot(x, z)) : Math.asin(constrain(y, -1, 1)); return toFixed(rads * Fn.RAD_TO_DEG, 2); } }, x: { get: function() { return toFixed(this.toGravity(state.x.value, aX), 2); } }, y: { get: function() { return toFixed(this.toGravity(state.y.value, aY), 2); } }, z: { get: function() { return this.hasAxis(aZ) ? toFixed(this.toGravity(state.z.value, aZ), 2) : 0; } }, acceleration: { get: function() { return magnitude( this.x, this.y, this.z ); } }, inclination: { get: function() { return Math.atan2(this.y, this.x) * Fn.RAD_TO_DEG; } }, orientation: { get: function() { var abs = Math.abs; var x = this.x; var y = this.y; var z = this.hasAxis(aZ) ? this.z : 1; var absX = abs(x); var absY = abs(y); var absZ = abs(z); if (absX < absY && absX < absZ) { if (x > 0) { return 1; } return -1; } if (absY < absX && absY < absZ) { if (y > 0) { return 2; } return -2; } if (absZ < absX && absZ < absY) { // TODO: figure out how to test this /* istanbul ignore else */ if (z > 0) { return 3; } /* istanbul ignore next */ return -3; } return 0; } } }); } util.inherits(Accelerometer, Emitter); /* istanbul ignore else */ if (IS_TEST_MODE) { Accelerometer.purge = function() { priv.clear(); }; } module.exports = Accelerometer;