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,150 lines (982 loc) • 28.5 kB
JavaScript
const Board = require("./board");
const Expander = require("./expander");
const Emitter = require("./mixins/emitter");
const { constrain, fma, int16, sum, toFixed, RAD_TO_DEG } = require("./fn");
const priv = new Map();
const calibrationSize = 10;
const aX = "x";
const aY = "y";
const aZ = "z";
const axes = [aX, aY, aZ];
function analogInitialize({zeroV, sensitivity}, callback) {
const state = priv.get(this);
const dataPoints = {};
state.zeroV = zeroV || this.DEFAULTS.zeroV;
state.sensitivity = sensitivity || this.DEFAULTS.sensitivity;
this.pins.forEach(function(pin, index) {
this.io.pinMode(pin, this.io.MODES.ANALOG);
this.io.analogRead(pin, data => {
const axis = axes[index];
dataPoints[axis] = data;
callback(dataPoints);
});
}, this);
}
function analogToGravity(value, axis) {
const state = priv.get(this);
let zeroV = state.zeroV;
if (Array.isArray(zeroV) && zeroV.length > 0) {
const axisIndex = axes.indexOf(axis);
zeroV = zeroV[axisIndex || 0];
}
return (value - zeroV) / state.sensitivity;
}
const Controllers = {
ANALOG: {
DEFAULTS: {
value: {
zeroV: 478,
sensitivity: 96
}
},
initialize: {
value: analogInitialize
},
toGravity: {
value: analogToGravity
}
},
MPU6050: {
initialize: {
value(options, callback) {
const state = priv.get(this);
state.sensitivity = options.sensitivity || 16384;
const { Drivers } = require("./sip");
Drivers.get(this.board, "MPU6050", options)
.on("data", ({accelerometer}) => callback(accelerometer));
}
},
toGravity: {
value(value) {
// Table 6.2 (Accelerometer specifications)
// Sensitivity for AFS_SEL=0
// Full scale range +- 2g
// ADC word length 16 bit 2's complement
// 16384 LSB/g = 0.000061035 g/LSB = 0.061035156 mg/LSB
// Returing a decimal part fixed at 3 digits,
// not sure if this assumption is correct
// (approximating to 0.061 mg/LSB)
return toFixed(value / priv.get(this).sensitivity, 3);
}
}
},
BNO055: {
initialize: {
value(options, callback) {
const state = priv.get(this);
// Page 31, Table 3-17
state.sensitivity = 100;
const { Drivers } = require("./sip");
Drivers.get(this.board, "BNO055", options)
.on("data", ({accelerometer}) => callback(accelerometer));
}
},
toGravity: {
value(value) {
// Page 31, Table 3-17
// Assuming that the the `m/s^2` representation is used given that `state.sensitvity = 100`
// 1m/s^2 = 100LSB -> 1LSB = 0.01m/s^2
return toFixed(value / priv.get(this).sensitivity, 2);
}
}
},
ADXL335: {
DEFAULTS: {
value: {
zeroV: 330,
sensitivity: 66.5
}
},
initialize: {
value: analogInitialize
},
toGravity: {
value(value, axis) {
// Page 3, Table 1
// Typical range +- 3.6g
// Sensitivity: 300mV/g
// MaxSensitvity: 330mv/g
return toFixed(analogToGravity.call(this, value, axis), 3);
}
}
},
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(options, callback) {
const { Drivers } = require("./sip");
const address = Drivers.addressResolver(this, options);
const READLENGTH = 6;
this.io.i2cConfig(options);
// 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
*/
const format = 0x08;
/*
Determine range
0b00000000 = 0 = ±2g
0b00000001 = 1 = ±4g
0b00000010 = 2 = ±8g
0b00000011 = 3 = ±16g
*/
const range = ({
2: 0,
4: 1,
8: 2,
16: 3
})[options.range || 2];
// 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, data => {
callback({
x: int16(data[1], data[0]),
y: int16(data[3], data[2]),
z: int16(data[5], data[4])
});
});
},
},
toGravity: {
value(value) {
// Page 4, Table 1
//
// Sensitivity
// All g-ranges, full resolution, 256LSB/g, 0.00390625g/LSB
return toFixed(value * 0.00390625, 8);
}
}
},
MMA7361: {
DEFAULTS: {
value: {
zeroV: [372, 372, 287],
sensitivity: 170
}
},
initialize: {
value(options, callback) {
const state = priv.get(this);
/* istanbul ignore else */
if (options.sleepPin !== undefined) {
state.sleepPin = options.sleepPin;
this.io.pinMode(state.sleepPin, 1);
this.io.digitalWrite(state.sleepPin, 1);
}
analogInitialize.call(this, options, callback);
}
},
toGravity: {
value(value, axis) {
// Page 3, Table 2
//
// Sensitivity
// 1.5g, 800mV/g
// 6g, 221.5mV/g
return toFixed(analogToGravity.call(this, value, axis), 3);
}
},
enabledChanged: {
value(value) {
const state = priv.get(this);
/* istanbul ignore else */
if (state.sleepPin !== undefined) {
this.io.digitalWrite(state.sleepPin, value ? 1 : 0);
}
}
}
},
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(options, callback) {
const { Drivers } = require("./sip");
const address = Drivers.addressResolver(this, options);
const state = priv.get(this);
// TODO: make user definable.
// 0b000 800Hz
// 0b001 400Hz
// 0b010 200Hz
// 0b011 100Hz
// 0b100 50Hz
// 0b101 12Hz
// 0b110 6Hz
const rates = [800, 400, 200, 100, 50, 12, 6, ];
const odr = rates.indexOf(options.odr || 800);
const scale = options.range || 2;
const fsr = ({
2: 0,
4: 1,
8: 2
})[scale];
options.taps = options.taps || {
x: false,
y: false,
z: true,
};
const taps = {
x: options.taps.x ? 0x08 : 0x80,
y: options.taps.y ? 0x08 : 0x80,
z: options.taps.z ? 0x08 : 0x80,
};
state.scale = scale;
const computed = {
x: null,
y: null,
z: null,
};
this.io.i2cConfig(
Object.assign(options, {
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
*/
let 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);
let 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, data => {
const 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;
callback(computed);
}
});
this.io.i2cRead(address, this.REGISTER.PULSE_SRC, 1, data => {
const status = data[0];
const 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");
}
}
}
});
},
},
toGravity: {
value(value) {
//
// Paragraph 3.1, page 9
// Sensitivity
// 2g, 1024 counts/g, 0.000976562g/count
// 4g, 512 counts/g, 0.001953125g/count
// 8g, 256 counts/g, 0.00390625g/count
return toFixed(value / ((1 << 11) * priv.get(this).scale), 4);
}
}
},
MMA7660: {
ADDRESSES: {
value: [0x4C]
},
REGISTER: {
value: {
XOUT: 0x00,
MODE: 0x07,
SR: 0x08,
}
},
initialize: {
value(options, callback) {
const { Drivers } = require("./sip");
const address = Drivers.addressResolver(this, options);
const READLENGTH = 3;
const state = priv.get(this);
state.sensitivity = 21.33;
this.io.i2cConfig(options);
//
// Standby mode
this.io.i2cWrite(address, this.REGISTER.MODE, 0x00);
// Sample Rate ()
this.io.i2cWrite(address, this.REGISTER.SR, 0x07);
// Active Mode
this.io.i2cWrite(address, this.REGISTER.MODE, 0x01);
this.io.i2cRead(address, this.REGISTER.XOUT, READLENGTH, data => {
callback({
// Page. 13
// D7 D6 D5 D4 D3 D2 D1 D0
// -- -A XOUT[5] XOUT[4] XOUT[3] XOUT[2] XOUT[1] XOUT[0]
x: data[0] & 0b00111111,
y: data[1] & 0b00111111,
z: data[2] & 0b00111111,
});
});
},
},
toGravity: {
value(value) {
// Page 28
return toFixed(value / priv.get(this).sensitivity, 3);
}
}
},
ESPLORA: {
DEFAULTS: {
value: {
zeroV: [320, 330, 310],
sensitivity: 170
}
},
initialize: {
value(options, callback) {
this.pins = [5, 11, 6];
analogInitialize.call(this, options, callback);
}
},
toGravity: {
value(value, axis) {
return toFixed(analogToGravity.call(this, value, axis), 2);
}
}
},
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(options, callback) {
const state = priv.get(this);
const address = options.address || 0x18;
// 2G = 0b00
// 4G = 0b01
// 8G = 0b10
// 16G = 0b11
let range = ({
2: 0,
4: 1,
8: 2,
16: 3
})[options.range || 4];
/* istanbul ignore if */
if (range === undefined) {
range = 1;
}
let divider = [
16380,
8190,
4096,
1365,
][range];
/* istanbul ignore if */
if (divider === undefined) {
divider = 1;
}
let threshold = [
80,
40,
20,
10,
][range];
/* istanbul ignore if */
if (threshold === undefined) {
threshold = 10;
}
state.divider = divider;
state.expander = Expander.get({
address,
controller: this.controller,
bus: this.bus,
});
// TODO: this should come from the expander
const ctrl4 = 0x88 | (range << 4);
state.expander.i2cWrite(address, this.REGISTER.CTRL_REG4, ctrl4);
// Acceleration
state.expander.i2cReadOnce(address, this.REGISTER.CTRL_REG1, 1, data => {
let 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, data => {
callback({
x: int16(data[1], data[0]),
y: int16(data[3], data[2]),
z: 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) {
// callback({
// x: int16(data[1], data[0]),
// y: int16(data[3], data[2]),
// z: 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:
const timelimit = 10;
const timelatency = 20;
const 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)
let lastEmitTime = null;
state.expander.i2cRead(address, this.REGISTER.CLICK_SRC, 1, data => {
const status = data[0];
const 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");
}
});
});
},
},
toGravity: {
value(raw) {
// Table 4, page 10
return toFixed(raw / priv.get(this).divider, 3);
},
},
},
LSM303C: {
initialize: {
value(options, callback) {
const { Drivers } = require("./sip");
Drivers.get(this.board, "LSM303C", options)
.on("data", ({accelerometer}) => callback(accelerometer));
}
},
toGravity: {
value(raw) {
return toFixed(raw, 2);
}
}
},
};
// Otherwise known as...
Controllers.TINKERKIT = Controllers.ANALOG;
Controllers.MMA8452Q = Controllers.MMA8452;
Controllers.DEFAULT = Controllers.ANALOG;
function magnitude(x, y, z) {
let 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} options [description]
*
*/
class Accelerometer extends Emitter {
constructor(options) {
super();
const 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, options = Board.Options(options)
);
Board.Controller.call(this, Controllers, options);
if (!this.toGravity) {
this.toGravity = options.toGravity || (x => x);
}
if (!this.enabledChanged) {
this.enabledChanged = () => {};
}
priv.set(this, state);
/* istanbul ignore else */
if (typeof this.initialize === "function") {
this.initialize(options, data => {
let isChange = false;
if (!state.enabled) {
return;
}
Object.keys(data).forEach(axis => {
const value = data[axis];
const sensor = state[axis];
if (options.autoCalibrate && sensor.calibration.length < calibrationSize) {
const 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 (let 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.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
});
}
});
}
Object.defineProperties(this, {
hasAxis: {
writable: true,
value(axis) {
/* istanbul ignore next */
return state[axis] ? state[axis].stash.length > 0 : false;
}
},
enable: {
value() {
state.enabled = true;
this.enabledChanged(true);
return this;
}
},
disable: {
value() {
state.enabled = false;
this.enabledChanged(false);
return this;
}
},
zeroV: {
get() {
return state.zeroV;
}
},
/**
* [read-only] Calculated pitch value
* @property pitch
* @type Number
*/
pitch: {
get() {
const x = this.x;
const y = this.y;
const z = this.z;
const rads = this.hasAxis(aZ) ?
Math.atan2(x, Math.hypot(y, z)) :
Math.asin(constrain(x, -1, 1));
return toFixed(rads * RAD_TO_DEG, 2);
}
},
/**
* [read-only] Calculated roll value
* @property roll
* @type Number
*/
roll: {
get() {
const x = this.x;
const y = this.y;
const z = this.z;
const rads = this.hasAxis(aZ) ?
Math.atan2(y, Math.hypot(x, z)) :
Math.asin(constrain(y, -1, 1));
return toFixed(rads * RAD_TO_DEG, 2);
}
},
x: {
get() {
return this.toGravity(state.x.value, aX);
}
},
y: {
get() {
return this.toGravity(state.y.value, aY);
}
},
z: {
get() {
return this.hasAxis(aZ) ?
this.toGravity(state.z.value, aZ) : 0;
}
},
acceleration: {
get() {
return magnitude(
this.x,
this.y,
this.z
);
}
},
inclination: {
get() {
return Math.atan2(this.y, this.x) * RAD_TO_DEG;
}
},
orientation: {
get() {
const abs = Math.abs;
const x = this.x;
const y = this.y;
const z = this.hasAxis(aZ) ? this.z : 1;
const absX = abs(x);
const absY = abs(y);
const 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;
}
}
});
}
}
/* istanbul ignore else */
if (!!process.env.IS_TEST_MODE) {
Accelerometer.Controllers = Controllers;
Accelerometer.purge = function() {
priv.clear();
};
}
module.exports = Accelerometer;