diff --git a/lib/accelerometer.js b/lib/accelerometer.js index 17e870e65..9d7181673 100644 --- a/lib/accelerometer.js +++ b/lib/accelerometer.js @@ -12,9 +12,12 @@ var sum = Fn.sum; var toFixed = Fn.toFixed; var priv = new Map(); -var rad2deg = 180 / Math.PI; var calibrationSize = 10; -var axes = ["x", "y", "z"]; + +var aX = "x"; +var aY = "y"; +var aZ = "z"; +var axes = [aX, aY, aZ]; function analogInitialize(opts, dataHandler) { var state = priv.get(this); @@ -252,10 +255,11 @@ var Controllers = { value: function(opts, dataHandler) { var state = priv.get(this); + /* istanbul ignore else */ if (opts.sleepPin !== undefined) { state.sleepPin = opts.sleepPin; - this.board.pinMode(state.sleepPin, 1); - this.board.digitalWrite(state.sleepPin, 1); + this.io.pinMode(state.sleepPin, 1); + this.io.digitalWrite(state.sleepPin, 1); } analogInitialize.call(this, opts, dataHandler); @@ -268,8 +272,9 @@ var Controllers = { value: function(value) { var state = priv.get(this); + /* istanbul ignore else */ if (state.sleepPin !== undefined) { - this.board.digitalWrite(state.sleepPin, value ? 1 : 0); + this.io.digitalWrite(state.sleepPin, value ? 1 : 0); } } } @@ -515,6 +520,7 @@ var Controllers = { this.io.i2cRead(address, this.REGISTER.STATUS, 7, function(data) { var status = (data.shift() & 0x08) >>> 3; + /* istanbul ignore else */ if (status) { // Page 9 (AN4076) // @@ -531,14 +537,17 @@ var Controllers = { 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"); } @@ -667,6 +676,7 @@ var Controllers = { 16: 3 })[opts.range || 4]; + /* istanbul ignore if */ if (range === undefined) { range = 1; } @@ -678,6 +688,7 @@ var Controllers = { 1365, ][range]; + /* istanbul ignore if */ if (divider === undefined) { divider = 1; } @@ -689,6 +700,7 @@ var Controllers = { 10, ][range]; + /* istanbul ignore if */ if (threshold === undefined) { threshold = 10; } @@ -808,6 +820,7 @@ var Controllers = { lastEmitTime = thisEmitTime - 101; } + /* istanbul ignore if */ if (thisEmitTime < (lastEmitTime + 100)) { return; } @@ -816,6 +829,7 @@ var Controllers = { return; } + /* istanbul ignore if */ if (!(status & 0x30)) { return; } @@ -945,6 +959,7 @@ function Accelerometer(opts) { priv.set(this, state); + /* istanbul ignore else */ if (typeof this.initialize === "function") { this.initialize(opts, function(data) { var isChange = false; @@ -966,7 +981,7 @@ function Accelerometer(opts) { } state.zeroV[axisIndex] = sum(sensor.calibration) / sensor.calibration.length; - if (axis === "z") { + if (axis === aZ) { state.zeroV[axisIndex] -= state.sensitivity; } } @@ -1022,7 +1037,9 @@ function Accelerometer(opts) { 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; } }, @@ -1052,18 +1069,14 @@ function Accelerometer(opts) { */ pitch: { get: function() { - var x, y, z, rads; - - x = this.x; - y = this.y; - z = this.z; - - - rads = this.hasAxis("z") ? + 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 * rad2deg, 2); + return toFixed(rads * Fn.RAD_TO_DEG, 2); } }, /** @@ -1073,33 +1086,30 @@ function Accelerometer(opts) { */ roll: { get: function() { - var x, y, z, rads; - - x = this.x; - y = this.y; - z = this.z; - - rads = this.hasAxis("z") ? + 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 * rad2deg, 2); + return toFixed(rads * Fn.RAD_TO_DEG, 2); } }, x: { get: function() { - return toFixed(this.toGravity(state.x.value, "x"), 2); + return toFixed(this.toGravity(state.x.value, aX), 2); } }, y: { get: function() { - return toFixed(this.toGravity(state.y.value, "y"), 2); + return toFixed(this.toGravity(state.y.value, aY), 2); } }, z: { get: function() { - return this.hasAxis("z") ? - toFixed(this.toGravity(state.z.value, "z"), 2) : 0; + return this.hasAxis(aZ) ? + toFixed(this.toGravity(state.z.value, aZ), 2) : 0; } }, acceleration: { @@ -1113,7 +1123,7 @@ function Accelerometer(opts) { }, inclination: { get: function() { - return Math.atan2(this.y, this.x) * rad2deg; + return Math.atan2(this.y, this.x) * Fn.RAD_TO_DEG; } }, orientation: { @@ -1121,27 +1131,30 @@ function Accelerometer(opts) { 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); + var z = this.hasAxis(aZ) ? this.z : 1; + var absX = abs(x); + var absY = abs(y); + var absZ = abs(z); - if (xAbs < yAbs && xAbs < zAbs) { + if (absX < absY && absX < absZ) { if (x > 0) { return 1; } return -1; } - if (yAbs < xAbs && yAbs < zAbs) { + if (absY < absX && absY < absZ) { if (y > 0) { return 2; } return -2; } - if (zAbs < xAbs && zAbs < yAbs) { + 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; diff --git a/test/accelerometer.js b/test/accelerometer.js index abf891de3..b45ea2822 100644 --- a/test/accelerometer.js +++ b/test/accelerometer.js @@ -1,3 +1,36 @@ +exports["Accelerometer"] = { + + setUp: function(done) { + this.sandbox = sinon.sandbox.create(); + this.board = newBoard(); + this.sandbox.spy(Board, "Component"); + done(); + }, + + tearDown: function(done) { + Board.purge(); + Accelerometer.purge(); + this.sandbox.restore(); + done(); + }, + + instanceof: function(test) { + test.expect(1); + test.equal(Accelerometer({ board: this.board, pins: [2, 3, 4]}) instanceof Accelerometer, true); + test.done(); + }, + + component: function(test) { + test.expect(1); + + new Accelerometer({ board: this.board, pins: [2, 3, 4]}); + + test.equal(Board.Component.callCount, 1); + test.done(); + }, + +}; + exports["Accelerometer -- Analog"] = { setUp: function(done) { @@ -182,7 +215,16 @@ exports["Accelerometer -- Analog"] = { test.ok(spy.calledTwice); test.done(); - } + }, + + hasZ: function(test) { + test.expect(4); + test.equal(this.accel.hasAxis("z"), false); + test.equal(this.accel.z, 0); + test.equal(this.accel.orientation, 3); + test.equal(this.accel.roll, -90); + test.done(); + }, }; exports["Accelerometer -- distinctZeroV"] = { @@ -226,7 +268,19 @@ exports["Accelerometer -- distinctZeroV"] = { test.equal(this.accel.z, -1); test.done(); - } + }, + + hasZ: function(test) { + test.expect(4); + + this.sandbox.stub(this.accel, "hasAxis").returns(true); + + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, }; exports["Accelerometer -- autoCalibrate"] = { @@ -324,7 +378,16 @@ exports["Accelerometer -- ADXL335"] = { }]); test.done(); - } + }, + + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, }; exports["Accelerometer -- MPU-6050"] = { @@ -418,7 +481,16 @@ exports["Accelerometer -- MPU-6050"] = { }]); test.done(); - } + }, + + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, }; exports["Accelerometer -- ADXL345"] = { @@ -559,7 +631,16 @@ exports["Accelerometer -- ADXL345"] = { test.deepEqual(changeSpy.args[0], [{ x: 0.1, y: 0.16, z: 7.26 }]); test.done(); - } + }, + + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, }; exports["Accelerometer -- MMA7361"] = { @@ -590,14 +671,40 @@ exports["Accelerometer -- MMA7361"] = { }, sleepPinOn: function(test) { - test.expect(2); + test.expect(4); + + test.equal(this.pinMode.callCount, 4); + test.equal(this.digitalWrite.callCount, 1); - test.deepEqual(this.pinMode.args[0], [13, 1]); + test.deepEqual(this.pinMode.args[0], [13, this.board.MODES.OUTPUT]); test.deepEqual(this.digitalWrite.args[0], [13, 1]); test.done(); }, + noSleepPin: function(test) { + test.expect(5); + + this.pinMode.reset(); + this.digitalWrite.reset(); + + this.accel = new Accelerometer({ + controller: "MMA7361", + pins: ["A0", "A1", "A2"], + freq: 100, + board: this.board, + }); + + + test.equal(this.pinMode.callCount, 3); + test.deepEqual(this.pinMode.getCall(0).args, [0, this.board.MODES.ANALOG]); + test.deepEqual(this.pinMode.getCall(1).args, [1, this.board.MODES.ANALOG]); + test.deepEqual(this.pinMode.getCall(2).args, [2, this.board.MODES.ANALOG]); + test.equal(this.digitalWrite.callCount, 0); + + test.done(); + }, + disableEnable: function(test) { test.expect(2); @@ -632,6 +739,12 @@ exports["Accelerometer -- MMA7361"] = { z: -0.34 }]); + test.done(); + }, + + hasZ: function(test) { + test.expect(1); + test.notEqual(this.accel.z, undefined); test.done(); } }; @@ -722,7 +835,15 @@ exports["Accelerometer -- MMA7660"] = { }]); test.done(); - } + }, + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, }; exports["Accelerometer -- ESPLORA"] = { @@ -769,6 +890,12 @@ exports["Accelerometer -- ESPLORA"] = { z: -0.47 }]); + test.done(); + }, + + hasZ: function(test) { + test.expect(1); + test.notEqual(this.accel.z, undefined); test.done(); } }; @@ -827,6 +954,72 @@ exports["Accelerometer -- MMA8452"] = { test.done(); }, + tapsOptionX: function(test) { + test.expect(1); + + this.i2cWriteReg.reset(); + + new Accelerometer({ + controller: "MMA8452", + board: this.board, + taps: { + x: true, + } + }); + + test.deepEqual(this.i2cWriteReg.getCall(2).args, [0x1D, 0x23, 0x08]); + test.done(); + }, + + tapsOptionY: function(test) { + test.expect(1); + + this.i2cWriteReg.reset(); + + new Accelerometer({ + controller: "MMA8452", + board: this.board, + taps: { + y: true, + } + }); + + test.deepEqual(this.i2cWriteReg.getCall(2).args, [0x1D, 0x24, 0x08]); + test.done(); + }, + + tapsOptionZ: function(test) { + test.expect(1); + + this.i2cWriteReg.reset(); + + new Accelerometer({ + controller: "MMA8452", + board: this.board, + taps: { + z: true, + } + }); + + test.deepEqual(this.i2cWriteReg.getCall(2).args, [0x1D, 0x25, 0x08]); + test.done(); + }, + + invalidOdr: function(test) { + test.expect(1); + + this.i2cWriteReg.reset(); + + test.throws(function() { + new Accelerometer({ + controller: "MMA8452", + board: this.board, + odr: Infinity + }); + }.bind(this)); + test.done(); + }, + data: function(test) { var read; var dataSpy = this.sandbox.spy(); @@ -893,6 +1086,12 @@ exports["Accelerometer -- MMA8452"] = { test.done(); }, + + hasZ: function(test) { + test.expect(1); + test.notEqual(this.accel.z, undefined); + test.done(); + } }; exports["Accelerometer -- LIS3DH"] = { @@ -983,18 +1182,15 @@ exports["Accelerometer -- LIS3DH"] = { test.deepEqual(this.i2cWrite.getCall(4).args, [ 24, 32, 96 ]); - var outXLRead = this.i2cRead.firstCall.args[3]; - outXLRead([ 64, 1, 112, 0, 176, 30 ]); - this.clock.tick(5); - outXLRead([ 32, 1, 112, 0, 224, 30 ]); - this.clock.tick(5); - test.ok(dataSpy.callCount, 1); - test.ok(changeSpy.callCount, 1); + this.clock.tick(10); + + test.equal(dataSpy.callCount, 2); + test.equal(changeSpy.callCount, 2); test.done(); }, @@ -1036,4 +1232,211 @@ exports["Accelerometer -- LIS3DH"] = { test.done(); }, + + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, +}; + +exports["Accelerometer -- BNO055"] = { + + setUp: function(done) { + this.sandbox = sinon.sandbox.create(); + this.board = newBoard(); + this.clock = this.sandbox.useFakeTimers(); + this.i2cConfig = this.sandbox.spy(MockFirmata.prototype, "i2cConfig"); + this.i2cWrite = this.sandbox.spy(MockFirmata.prototype, "i2cWrite"); + this.i2cRead = this.sandbox.spy(MockFirmata.prototype, "i2cRead"); + this.accel = new Accelerometer({ + controller: "BNO055", + board: this.board + }); + + done(); + }, + + tearDown: function(done) { + Board.purge(); + Accelerometer.purge(); + Expander.purge(); + this.sandbox.restore(); + done(); + }, + + fwdOptionsToi2cConfig: function(test) { + test.expect(3); + + this.i2cConfig.reset(); + + new Accelerometer({ + controller: "BNO055", + address: 0xff, + bus: "i2c-1", + board: this.board + }); + + var forwarded = this.i2cConfig.lastCall.args[0]; + + test.equal(this.i2cConfig.callCount, 1); + test.equal(forwarded.address, 0xff); + test.equal(forwarded.bus, "i2c-1"); + + test.done(); + }, + + data: function(test) { + test.expect(2); + + var driver = IMU.Drivers.get(this.board, "BNO055"); + var dataSpy = this.sandbox.spy(); + var changeSpy = this.sandbox.spy(); + + this.accel.on("data", dataSpy); + this.accel.on("change", changeSpy); + + driver.emit("data", { + accelerometer: { + x: 1, y: 2, z: 3 + } + }); + + test.equal(dataSpy.callCount, 1); + test.equal(changeSpy.callCount, 1); + test.done(); + }, + hasZ: function(test) { + test.expect(4); + test.notEqual(this.accel.z, undefined); + test.notEqual(this.accel.pitch, undefined); + test.notEqual(this.accel.orientation, undefined); + test.notEqual(this.accel.roll, undefined); + test.done(); + }, +}; + + +exports["Accelerometer -- User toGravity"] = { + + setUp: function(done) { + this.sandbox = sinon.sandbox.create(); + this.board = newBoard(); + this.clock = this.sandbox.useFakeTimers(); + + this.toGravity = this.sandbox.spy(function(value) { + return value; + }); + + this.accel = new Accelerometer({ + controller: { + initialize: { + value: function(opts, dataHandler) { + setInterval(function() { + dataHandler({ + x: 1, y: 2, z: 3 + }); + }, 5); + }, + }, + toGravity: { + value: this.toGravity, + }, + }, + board: this.board, + freq: 10, + }); + + done(); + }, + + tearDown: function(done) { + Board.purge(); + Accelerometer.purge(); + this.sandbox.restore(); + done(); + }, + + data: function(test) { + test.expect(5); + var dataSpy = this.sandbox.spy(); + var changeSpy = this.sandbox.spy(); + + this.accel.on("data", dataSpy); + this.accel.on("change", changeSpy); + + this.clock.tick(5); + + test.equal(dataSpy.callCount, 1); + test.equal(changeSpy.callCount, 1); + + test.equal(this.accel.x, 1); + test.equal(this.accel.y, 2); + test.equal(this.accel.z, 3); + + test.equal(this.toGravity.callCount, 3); + test.equal(this.toGravity.getCall(0).args[0], 1); + test.equal(this.toGravity.getCall(1).args[0], 2); + test.equal(this.toGravity.getCall(2).args[0], 3); + test.equal(this.toGravity.getCall(0).returnValue, 1); + test.equal(this.toGravity.getCall(1).returnValue, 2); + test.equal(this.toGravity.getCall(2).returnValue, 3); + test.done(); + }, +}; + +exports["Accelerometer -- User toGravity"] = { + + setUp: function(done) { + this.sandbox = sinon.sandbox.create(); + this.board = newBoard(); + this.clock = this.sandbox.useFakeTimers(); + this.accel = new Accelerometer({ + controller: { + initialize: { + value: function(opts, dataHandler) { + setInterval(function() { + dataHandler({ + x: 1, y: 2, z: 3 + }); + }, 5); + } + } + }, + board: this.board, + freq: 10, + }); + + done(); + }, + + tearDown: function(done) { + Board.purge(); + Accelerometer.purge(); + this.sandbox.restore(); + done(); + }, + + data: function(test) { + test.expect(5); + var dataSpy = this.sandbox.spy(); + var changeSpy = this.sandbox.spy(); + + this.accel.on("data", dataSpy); + this.accel.on("change", changeSpy); + + this.clock.tick(5); + + test.equal(dataSpy.callCount, 1); + test.equal(changeSpy.callCount, 1); + + test.equal(this.accel.x, 1); + test.equal(this.accel.y, 2); + test.equal(this.accel.z, 3); + + test.done(); + }, };