UNPKG

29 kBJavaScriptView Raw
1var Board = require("./board");
2var Expander = require("./expander");
3var Emitter = require("events").EventEmitter;
4var util = require("util");
5var Fn = require("./fn");
6
7var constrain = Fn.constrain;
8var fma = Fn.fma;
9var int16 = Fn.int16;
10var sum = Fn.sum;
11var toFixed = Fn.toFixed;
12
13var priv = new Map();
14var calibrationSize = 10;
15
16var aX = "x";
17var aY = "y";
18var aZ = "z";
19var axes = [aX, aY, aZ];
20
21function analogInitialize(opts, dataHandler) {
22 var state = priv.get(this);
23 var dataPoints = {};
24
25 state.zeroV = opts.zeroV || this.DEFAULTS.zeroV;
26 state.sensitivity = opts.sensitivity || this.DEFAULTS.sensitivity;
27
28 this.pins.forEach(function(pin, index) {
29 this.io.pinMode(pin, this.io.MODES.ANALOG);
30 this.io.analogRead(pin, function(data) {
31 var axis = axes[index];
32 dataPoints[axis] = data;
33 dataHandler(dataPoints);
34 }.bind(this));
35 }, this);
36}
37
38function analogToGravity(raw, axis) {
39 var state = priv.get(this);
40 var zeroV = state.zeroV;
41
42 if (Array.isArray(zeroV) && zeroV.length > 0) {
43 var axisIndex = axes.indexOf(axis);
44 zeroV = zeroV[axisIndex || 0];
45 }
46
47 return (raw - zeroV) / state.sensitivity;
48}
49
50var Controllers = {
51 ANALOG: {
52 DEFAULTS: {
53 value: {
54 zeroV: 478,
55 sensitivity: 96
56 }
57 },
58 initialize: {
59 value: analogInitialize
60 },
61 toGravity: {
62 value: analogToGravity
63 }
64 },
65 MPU6050: {
66 initialize: {
67 value: function(opts, dataHandler) {
68 var IMU = require("./imu");
69 var driver = IMU.Drivers.get(this.board, "MPU6050", opts);
70 var state = priv.get(this);
71
72 state.sensitivity = opts.sensitivity || 16384;
73
74 driver.on("data", function(data) {
75 dataHandler(data.accelerometer);
76 });
77 }
78 },
79 toGravity: {
80 value: function(raw) {
81 // Table 6.2 (Accelerometer specifications)
82 // Sensitivity for AFS_SEL=0
83 // Full scale range +- 2g
84 // ADC word length 16 bit 2's complement
85 // 16384 LSB/g = 0.000061035 g/LSB = 0.061035156 mg/LSB
86 var state = priv.get(this);
87 // Returing a decimal part fixed at 3 digits, not sure if this assumption is correct
88 // (approximating to 0.061 mg/LSB)
89 return toFixed(raw / state.sensitivity, 3);
90 }
91 }
92 },
93 BNO055: {
94 initialize: {
95 value: function(opts, dataHandler) {
96 var IMU = require("./imu");
97 var driver = IMU.Drivers.get(this.board, "BNO055", opts);
98 var state = priv.get(this);
99
100 // Page 31, Table 3-17
101 state.sensitivity = 100;
102
103 driver.on("data", function(data) {
104 dataHandler(data.accelerometer);
105 });
106 }
107 },
108 toGravity: {
109 value: function(raw) {
110 // Page 31, Table 3-17
111 // Assuming that the the `m/s^2` representation is used given that `state.sensitvity = 100`
112 // 1m/s^2 = 100LSB -> 1LSB = 0.01m/s^2
113 var state = priv.get(this);
114 return toFixed(raw / state.sensitivity, 2);
115 }
116 }
117 },
118
119 ADXL335: {
120 DEFAULTS: {
121 value: {
122 zeroV: 330,
123 sensitivity: 66.5
124 }
125 },
126 initialize: {
127 value: analogInitialize
128 },
129 toGravity: {
130 // Page 3, Table 1
131 // Typical range +- 3.6g
132 // Sensitivity: 300mV/g
133 // MaxSensitvity: 330mv/g
134 value: function(value, axis) {
135 var read = analogToGravity.call(this, value, axis);
136 return toFixed(read, 3);
137 }
138 }
139 },
140
141 ADXL345: {
142 ADDRESSES: {
143 value: [0x53]
144 },
145 REGISTER: {
146 value: {
147 // Page 23
148 // REGISTER MAP
149 //
150 POWER: 0x2D,
151 // 0x31 49 DATA_FORMAT R/W 00000000 Data format control
152 DATA_FORMAT: 0x31,
153 // 0x32 50 DATAX0 R 00000000 X-Axis Data 0
154 DATAX0: 0x32
155 }
156 },
157 initialize: {
158 value: function(opts, dataHandler) {
159 var READLENGTH = 6;
160 var address = opts.address || this.ADDRESSES[0];
161
162 opts.address = address;
163
164 this.io.i2cConfig(opts);
165
166 // Standby mode
167 this.io.i2cWrite(address, this.REGISTER.POWER, 0);
168
169 // Enable measurements
170 this.io.i2cWrite(address, this.REGISTER.POWER, 8);
171
172 /*
173
174 Page 26
175
176 Register 0x31—DATA_FORMAT (Read/Write)
177
178 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
179 | - | - | - | - | - | - | - | - |
180 | T | S | I | - | F | J | R |
181
182 T: SELF_TEST
183 S: SPI
184 I: INT_INVERT
185 -:-
186 F: FULL_RES
187 J: JUSTIFY
188 R: RANGE
189
190 Range notes: https://github.com/rwaldron/johnny-five/issues/1135#issuecomment-219541346
191
192 +/- 16g 0b11
193 +/- 8g 0b10
194 +/- 4g 0b01
195 +/- 2g 0b00
196
197
198 Start with FULL_RES bit on
199
200 0b00001000 = 0x08 = 8
201 */
202 var format = 0x08;
203
204 /*
205 Determine range
206
207 0b00000000 = 0 = ±2g
208 0b00000001 = 1 = ±4g
209 0b00000010 = 2 = ±8g
210 0b00000011 = 3 = ±16g
211 */
212 var range = ({
213 2: 0,
214 4: 1,
215 8: 2,
216 16: 3
217 })[opts.range || 2];
218
219 // Merge the format and range bits to set the DATA_FORMAT
220 this.io.i2cWrite(address, this.REGISTER.DATA_FORMAT, format | range);
221
222 this.io.i2cRead(address, this.REGISTER.DATAX0, READLENGTH, function(data) {
223 dataHandler({
224 x: int16(data[1], data[0]),
225 y: int16(data[3], data[2]),
226 z: int16(data[5], data[4])
227 });
228 });
229 },
230 },
231 toGravity: {
232 value: function(raw) {
233 // Page 4, Table 1
234 //
235 // Sensitivity
236 // All g-ranges, full resolution, 256LSB/g, 0.00390625g/LSB
237 return toFixed(raw * 0.00390625, 8);
238 }
239 }
240 },
241 MMA7361: {
242 DEFAULTS: {
243 value: {
244 zeroV: [372, 372, 287],
245 sensitivity: 170
246 }
247 },
248 initialize: {
249 value: function(opts, dataHandler) {
250 var state = priv.get(this);
251
252 /* istanbul ignore else */
253 if (opts.sleepPin !== undefined) {
254 state.sleepPin = opts.sleepPin;
255 this.io.pinMode(state.sleepPin, 1);
256 this.io.digitalWrite(state.sleepPin, 1);
257 }
258
259 analogInitialize.call(this, opts, dataHandler);
260 }
261 },
262 toGravity: {
263 // Page 3, Table 2
264 //
265 // Sensitivity
266 // 1.5g, 800mV/g
267 // 6g, 221.5mV/g
268 value: function(value, axis) {
269 var read = analogToGravity.call(this, value, axis);
270 return toFixed(read, 3);
271 }
272 },
273 enabledChanged: {
274 value: function(value) {
275 var state = priv.get(this);
276
277 /* istanbul ignore else */
278 if (state.sleepPin !== undefined) {
279 this.io.digitalWrite(state.sleepPin, value ? 1 : 0);
280 }
281 }
282 }
283 },
284 MMA8452: {
285 ADDRESSES: {
286 value: [0x1D]
287 },
288 REGISTER: {
289 value: {
290 // Page 18
291 // 6. Register Descriptions
292 STATUS: 0x00,
293 OUT_X_MSB: 0x01,
294 XYZ_DATA_CFG: 0x0E,
295 PULSE_CFG: 0x21,
296 PULSE_SRC: 0x22,
297 PULSE_THSX: 0x23,
298 PULSE_THSY: 0x24,
299 PULSE_THSZ: 0x25,
300 PULSE_TMLT: 0x26,
301 PULSE_LTCY: 0x27,
302 PULSE_WIND: 0x28,
303 CTRL_REG1: 0x2A,
304 CTRL_REG4: 0x2E,
305 CTRL_REG5: 0x2F,
306 }
307 },
308 initialize: {
309 value: function(opts, dataHandler) {
310 var state = priv.get(this);
311 var address = opts.address || this.ADDRESSES[0];
312
313 opts.address = address;
314
315 // TODO: make user definable.
316 // 0b000 800Hz
317 // 0b001 400Hz
318 // 0b010 200Hz
319 // 0b011 100Hz
320 // 0b100 50Hz
321 // 0b101 12Hz
322 // 0b110 6Hz
323
324 var rates = [800, 400, 200, 100, 50, 12, 6, ];
325 var odr = rates.indexOf(opts.odr || 800);
326 var scale = opts.range || 2;
327 var fsr = ({
328 2: 0,
329 4: 1,
330 8: 2
331 })[scale];
332
333 opts.taps = opts.taps || {
334 x: false,
335 y: false,
336 z: true,
337 };
338
339 var taps = {
340 x: opts.taps.x ? 0x08 : 0x80,
341 y: opts.taps.y ? 0x08 : 0x80,
342 z: opts.taps.z ? 0x08 : 0x80,
343 };
344
345 state.scale = scale;
346
347 var computed = {
348 x: null,
349 y: null,
350 z: null,
351 };
352
353 this.io.i2cConfig(
354 Object.assign(opts, {
355 settings: {
356 stopTX: false
357 }
358 })
359 );
360
361 if (odr === -1) {
362 throw new RangeError("Invalid odr. Expected one of: 800, 400, 200, 100, 50, 12, 6");
363 }
364
365 /*
366 Initial CTRL_REG1 State
367
368 11000010 = 194 = 0xC2 -> ?
369 00000010 = 8 = 0x08
370 ^--------- ASLP_RATE1
371 ^-------- ASLP_RATE0
372 ^------- DR2
373 ^------ DR1
374 ^----- DR0
375 ^---- Noise
376 ^--- Fast Read
377 ^-- Standby Mode
378 */
379
380 var config = 0x08;
381
382 /*
383 Page 5 (AN4076)
384 4.0 Setting the Data Rate
385
386 Set ODR
387
388 Shift the odr bits into place.
389
390 Default: 800Hz
391
392 11000010 = 194 = 0xC2 -> ?
393 00000010 = 8 = 0x08
394 ^^^----- DR2, DR1, DR0: 000
395 */
396 config |= odr << 3;
397
398 /*
399 Enter Standby Mode
400
401 11000010 = 194 = 0xC2 -> ?
402 ^--- Fast Read
403 ^-- Standby Mode
404
405 00000010 = 8 = 0x08
406 ^--- Fast Read
407 ^-- Standby Mode
408
409 */
410
411 this.io.i2cWriteReg(address, this.REGISTER.CTRL_REG1, config);
412
413 /*
414 Set FSR
415
416 Default: ±2g
417
418 00000000 = 0 = 0x00 ()
419 ^^----- FS1, FS2
420 */
421 this.io.i2cWriteReg(address, this.REGISTER.XYZ_DATA_CFG, fsr);
422
423 var temp = 0;
424
425 /*
426 Page 10 (AN4072)
427 4.2 Registers 0x23 - 0x25 PULSE_THSX, Y, Z
428 Pulse Threshold for X, Y and Z Registers
429
430 0x80 = B7 is HIGH
431 10000000
432 If B7 is HIGH, do not enable
433 */
434 if (!(taps.x & 0x80)) {
435 // 00000011
436 temp |= 0x03;
437 this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSX, taps.x);
438 }
439
440 if (!(taps.y & 0x80)) {
441 // 00001100
442 temp |= 0x0C;
443 this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSY, taps.y);
444 }
445
446 if (!(taps.z & 0x80)) {
447 // 00110000
448 temp |= 0x30;
449 this.io.i2cWriteReg(address, this.REGISTER.PULSE_THSZ, taps.z);
450 }
451
452 /*
453 Page 11, 12, 13 (AN4072)
454
455 Configure Tap Axis'
456
457 Table 1. Register 0x21 PULSE_CFG Register (Read/Write) and Description
458
459 | Tap Enable | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
460 | ---------- | --- | --- | --- | --- | --- | --- | --- | --- |
461 | | DPA | ELE | ZD | ZS | YD | YS | XD | XS |
462 | Single | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
463 | Double | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 |
464 | Both | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
465
466
467 In the default case, `temp | 0x40` will be:
468
469 01110000 = 112 = 0x70
470
471 Latch On
472 ZD On
473 ZS On
474
475 */
476
477 this.io.i2cWriteReg(address, this.REGISTER.PULSE_CFG, temp | 0x40);
478
479 /*
480 Set TIME LIMIT for tap detection
481
482 60ms / 800Hz = 60ms / 1.25ms = 48 (counts) = 0x30
483 80ms / 800Hz = 80ms / 1.25ms = 64 (counts) = 0x40
484 */
485 this.io.i2cWriteReg(address, this.REGISTER.PULSE_TMLT, 60 / (1000 / rates[odr]));
486 /*
487 Set the PULSE LATENCY.
488
489 This is the time that must pass after the first
490 tap, but within the PULSE WINDOW for a double tap to register.
491
492 200ms / 800Hz = 200ms / 1.25ms = 160 (counts) = 0xA0
493 */
494 this.io.i2cWriteReg(address, this.REGISTER.PULSE_LTCY, 200 / (1000 / rates[odr]));
495
496 /*
497 Set the PULSE WINDOW.
498
499 This is the maximum interval of time to elapse after the latency
500 interval, for which the second pulse must occur for double taps.
501
502 The maximum allowed time:
503
504 800Hz * 255 = 1.25ms * 255 = 318ms
505 */
506 this.io.i2cWriteReg(address, this.REGISTER.PULSE_WIND, 0xFF);
507
508 /*
509 Leave Standby Mode
510
511 11000011 = 195 = 0xC3
512 00000011 = 3 = 0x03
513 ^--- Fast Read
514 ^-- Standby Mode
515 */
516
517 config |= 0x01;
518
519 this.io.i2cWriteReg(address, this.REGISTER.CTRL_REG1, config);
520
521
522 this.io.i2cRead(address, this.REGISTER.STATUS, 7, function(data) {
523 var status = (data.shift() & 0x08) >>> 3;
524
525 /* istanbul ignore else */
526 if (status) {
527 // Page 9 (AN4076)
528 //
529 // 7.0 14-bit, 12-bit or 10-bit Data Streaming and Data Conversions
530 computed.x = int16(data[0], data[1]) >> 4;
531 computed.y = int16(data[2], data[3]) >> 4;
532 computed.z = int16(data[4], data[5]) >> 4;
533
534 dataHandler(computed);
535 }
536 }.bind(this));
537
538 this.io.i2cRead(address, this.REGISTER.PULSE_SRC, 1, function(data) {
539 var status = data[0];
540 var tap = status & 0x7F;
541
542 /* istanbul ignore else */
543 if (status & 0x80) {
544 this.emit("tap");
545
546 // Single Tap
547 /* istanbul ignore else */
548 if ((tap >> 2) & 0x01) {
549 this.emit("tap:single");
550
551 // Double Tap (must be both S and D bits)
552 /* istanbul ignore else */
553 if ((tap >> 3) & 0x01) {
554 this.emit("tap:double");
555 }
556 }
557 }
558 }.bind(this));
559 },
560 },
561 toGravity: {
562 value: function(raw) {
563 //
564 // Paragraph 3.1, page 9
565 // Sensitivity
566 // 2g, 1024 counts/g, 0.000976562g/count
567 // 4g, 512 counts/g, 0.001953125g/count
568 // 8g, 256 counts/g, 0.00390625g/count
569 var state = priv.get(this);
570 // return raw / ((1 << 11) * state.scale);
571 return toFixed(raw / ((1 << 11) * state.scale), 4);
572 }
573 }
574 },
575 MMA7660: {
576 ADDRESSES: {
577 value: [0x4C]
578 },
579 REGISTER: {
580 value: {
581 XOUT: 0x00,
582 MODE: 0x07,
583 SR: 0x08,
584 }
585 },
586 initialize: {
587 value: function(opts, dataHandler) {
588 var READLENGTH = 3;
589 var address = opts.address || this.ADDRESSES[0];
590 var state = priv.get(this);
591
592 state.sensitivity = 21.33;
593
594 opts.address = address;
595
596 this.io.i2cConfig(opts);
597
598 //
599 // Standby mode
600 this.io.i2cWrite(address, this.REGISTER.MODE, 0x00);
601
602 // Sample Rate ()
603 this.io.i2cWrite(address, this.REGISTER.SR, 0x07);
604
605 // Active Mode
606 this.io.i2cWrite(address, this.REGISTER.MODE, 0x01);
607
608 this.io.i2cRead(address, this.REGISTER.XOUT, READLENGTH, function(data) {
609 dataHandler({
610 // Page. 13
611 // D7 D6 D5 D4 D3 D2 D1 D0
612 // -- -A XOUT[5] XOUT[4] XOUT[3] XOUT[2] XOUT[1] XOUT[0]
613 x: data[0] & 0b00111111,
614 y: data[1] & 0b00111111,
615 z: data[2] & 0b00111111,
616 });
617 });
618 },
619 },
620 toGravity: {
621 value: function(raw) {
622 var state = priv.get(this);
623 // Page 28
624 return toFixed(raw / state.sensitivity, 3);
625 }
626 }
627 },
628
629 ESPLORA: {
630 DEFAULTS: {
631 value: {
632 zeroV: [320, 330, 310],
633 sensitivity: 170
634 }
635 },
636 initialize: {
637 value: function(opts, dataHandler) {
638 this.pins = [5, 11, 6];
639 analogInitialize.call(this, opts, dataHandler);
640 }
641 },
642 toGravity: {
643 value: function(value, axis) {
644 var read = analogToGravity.call(this, value, axis);
645 return toFixed(read, 2);
646 }
647 }
648 },
649
650 LIS3DH: {
651 ADDRESSES: {
652 value: [0x18]
653 },
654 REGISTER: {
655 value: {
656 OUT_X_L: 0x28,
657 CTRL_REG1: 0x20,
658 CTRL_REG2: 0x21,
659 CTRL_REG3: 0x22,
660 CTRL_REG4: 0x23,
661 CTRL_REG5: 0x24,
662
663 TEMP_CFG_REG: 0x1F,
664
665 CLICK_CFG: 0x38,
666 CLICK_SRC: 0x39,
667 CLICK_THS: 0x3A,
668 TIME_LIMIT: 0x3B,
669 TIME_LATENCY: 0x3C,
670 TIME_WINDOW: 0x3D,
671 }
672 },
673 initialize: {
674 value: function(opts, dataHandler) {
675 var state = priv.get(this);
676 var address = opts.address || 0x18;
677
678 // 2G = 0b00
679 // 4G = 0b01
680 // 8G = 0b10
681 // 16G = 0b11
682 var range = ({
683 2: 0,
684 4: 1,
685 8: 2,
686 16: 3
687 })[opts.range || 4];
688
689 /* istanbul ignore if */
690 if (range === undefined) {
691 range = 1;
692 }
693
694 var divider = [
695 16380,
696 8190,
697 4096,
698 1365,
699 ][range];
700
701 /* istanbul ignore if */
702 if (divider === undefined) {
703 divider = 1;
704 }
705
706 var threshold = [
707 80,
708 40,
709 20,
710 10,
711 ][range];
712
713 /* istanbul ignore if */
714 if (threshold === undefined) {
715 threshold = 10;
716 }
717
718
719 state.divider = divider;
720 state.expander = Expander.get({
721 address: address,
722 controller: this.controller,
723 bus: this.bus,
724 });
725
726 // TODO: this should come from the expander
727 var ctrl4 = 0x88 | (range << 4);
728
729 state.expander.i2cWrite(address, this.REGISTER.CTRL_REG4, ctrl4);
730
731 // Acceleration
732 state.expander.i2cReadOnce(address, this.REGISTER.CTRL_REG1, 1, function(data) {
733 var ctrl1 = data[0];
734
735 // Set to 200Hz
736 ctrl1 &= ~0xF0;
737 ctrl1 |= 6 << 4;
738
739 state.expander.i2cWrite(address, this.REGISTER.CTRL_REG1, ctrl1);
740
741 // Page 21
742 // 6.1.1 I2C operation
743 // Autoincrement bit set on register (0x80)
744 state.expander.i2cRead(address, this.REGISTER.OUT_X_L | 0x80, 6, function(data) {
745 dataHandler({
746 x: Fn.int16(data[1], data[0]),
747 y: Fn.int16(data[3], data[2]),
748 z: Fn.int16(data[5], data[4]),
749 });
750 });
751
752
753 // Tap
754 // TODO: make this optional (use "newListener"?)
755 //
756 // See MMA8452 driver for strategy
757 //
758 // state.expander.i2cReadOnce(address, this.REGISTER.CTRL_REG3, 1, function(data) {
759 // var ctrl3 = data[0];
760
761 // // Shut off Int 1 Click
762 // ctrl3 &= ~0x80;
763 // ctrl3 |= 6 << 4;
764
765 // state.expander.i2cWrite(address, this.REGISTER.CTRL_REG1, ctrl3);
766
767 // // Page 21
768 // // 6.1.1 I2C operation
769 // // Autoincrement bit set on register (0x80)
770 // state.expander.i2cRead(address, this.REGISTER.OUT_X_L | 0x80, 6, function(data) {
771 // dataHandler({
772 // x: Fn.int16(data[1], data[0]),
773 // y: Fn.int16(data[3], data[2]),
774 // z: Fn.int16(data[5], data[4]),
775 // });
776 // });
777 // }.bind(this));
778
779
780
781 // Page 35
782 // 8.3.7 CTRL_REG3 [Interrupt CTRL register] (22h)
783 state.expander.i2cWrite(address, this.REGISTER.CTRL_REG3, 0x80);
784
785 // Page 40
786 // 9.2.1 Control register 5 (0x24)
787 state.expander.i2cWrite(address, this.REGISTER.CTRL_REG5, 0x08);
788
789 // Page 32
790 // 8.3.1 TAP_CFG
791 //
792 // This register is called both CLICK_CFG and TAP_CFG
793 //
794 // 0b00101010 = 0x2A = 42
795 state.expander.i2cWrite(address, this.REGISTER.CLICK_CFG, 0x2A);
796
797 // Page 36
798 // 8.4.1 Playing with TAP_TimeLimit
799 //
800 // ...Offers some guidance. Ultimately I opted to take inspiration
801 // from Adafruit's driver and example:
802 var timelimit = 10;
803 var timelatency = 20;
804 var timewindow = 255;
805
806 state.expander.i2cWrite(address, this.REGISTER.CLICK_THS, threshold);
807 state.expander.i2cWrite(address, this.REGISTER.TIME_LIMIT, timelimit);
808 state.expander.i2cWrite(address, this.REGISTER.TIME_LATENCY, timelatency);
809 state.expander.i2cWrite(address, this.REGISTER.TIME_WINDOW, timewindow);
810
811 // Page 33
812 // 8.3.2 TAP_SRC (39h)
813 var lastEmitTime = null;
814
815 state.expander.i2cRead(address, this.REGISTER.CLICK_SRC, 1, function(data) {
816 var status = data[0];
817 var thisEmitTime = Date.now();
818 // var tap = status & 0x7F;
819
820 if (lastEmitTime === null) {
821 lastEmitTime = thisEmitTime - 101;
822 }
823
824 /* istanbul ignore if */
825 if (thisEmitTime < (lastEmitTime + 100)) {
826 return;
827 }
828
829 if (status === 0x00) {
830 return;
831 }
832
833 /* istanbul ignore if */
834 if (!(status & 0x30)) {
835 return;
836 }
837
838 lastEmitTime = thisEmitTime;
839
840 this.emit("tap");
841
842 if (status & 0x10) {
843 this.emit("tap:single");
844 }
845
846 if (status & 0x20) {
847 // TODO: Figure out if we can determine a
848 // combined single + double tap
849 this.emit("tap:single");
850 this.emit("tap:double");
851 }
852 }.bind(this));
853 }.bind(this));
854 },
855 },
856 toGravity: {
857 value: function(raw) {
858 // Table 4, page 10
859 var state = priv.get(this);
860 return toFixed(raw / state.divider, 3);
861 },
862 },
863 },
864 LSM303C: {
865 initialize: {
866 value: function(opts, dataHandler) {
867 var IMU = require("./imu");
868 var driver = IMU.Drivers.get(this.board, "LSM303C", opts);
869 driver.on("data", function(data) {
870 dataHandler(data.accelerometer);
871 });
872 }
873 },
874 toGravity: {
875 value: function(raw) {
876 return toFixed(raw, 2);
877 }
878 }
879 },
880};
881
882// Otherwise known as...
883Controllers.TINKERKIT = Controllers.ANALOG;
884Controllers.MMA8452Q = Controllers.MMA8452;
885
886function magnitude(x, y, z) {
887 var a;
888
889 a = x * x;
890 a = fma(y, y, a);
891 a = fma(z, z, a);
892
893 return Math.sqrt(a);
894}
895
896/**
897 * Accelerometer
898 * @constructor
899 *
900 * five.Accelerometer([ x, y[, z] ]);
901 *
902 * five.Accelerometer({
903 * pins: [ x, y[, z] ]
904 * zeroV: ...
905 * sensitivity: ...
906 * });
907 *
908 *
909 * @param {Object} opts [description]
910 *
911 */
912
913function Accelerometer(opts) {
914 if (!(this instanceof Accelerometer)) {
915 return new Accelerometer(opts);
916 }
917
918 var controller = null;
919
920 var state = {
921 enabled: true,
922 x: {
923 value: 0,
924 previous: 0,
925 stash: [],
926 orientation: null,
927 inclination: null,
928 acceleration: null,
929 calibration: []
930 },
931 y: {
932 value: 0,
933 previous: 0,
934 stash: [],
935 orientation: null,
936 inclination: null,
937 acceleration: null,
938 calibration: []
939 },
940 z: {
941 value: 0,
942 previous: 0,
943 stash: [],
944 orientation: null,
945 inclination: null,
946 acceleration: null,
947 calibration: []
948 }
949 };
950
951 Board.Component.call(
952 this, opts = Board.Options(opts)
953 );
954
955 if (opts.controller && typeof opts.controller === "string") {
956 controller = Controllers[opts.controller.toUpperCase()];
957 } else {
958 controller = opts.controller;
959 }
960
961 if (controller == null) {
962 controller = Controllers.ANALOG;
963 }
964
965 Board.Controller.call(this, controller, opts);
966
967 if (!this.toGravity) {
968 this.toGravity = opts.toGravity || function(raw) {
969 return raw;
970 };
971 }
972
973 if (!this.enabledChanged) {
974 this.enabledChanged = function() {};
975 }
976
977 priv.set(this, state);
978
979 /* istanbul ignore else */
980 if (typeof this.initialize === "function") {
981 this.initialize(opts, function(data) {
982 var isChange = false;
983
984 if (!state.enabled) {
985 return;
986 }
987
988 Object.keys(data).forEach(function(axis) {
989 var value = data[axis];
990 var sensor = state[axis];
991
992 if (opts.autoCalibrate && sensor.calibration.length < calibrationSize) {
993 var axisIndex = axes.indexOf(axis);
994 sensor.calibration.push(value);
995
996 if (!Array.isArray(state.zeroV)) {
997 state.zeroV = [];
998 }
999
1000 state.zeroV[axisIndex] = sum(sensor.calibration) / sensor.calibration.length;
1001 if (axis === aZ) {
1002 state.zeroV[axisIndex] -= state.sensitivity;
1003 }
1004 }
1005
1006 // The first run needs to prime the "stash"
1007 // of data values.
1008 if (sensor.stash.length === 0) {
1009 for (var i = 0; i < 5; i++) {
1010 sensor.stash[i] = value;
1011 }
1012 }
1013
1014 sensor.previous = sensor.value;
1015 sensor.stash.shift();
1016 sensor.stash.push(value);
1017
1018 sensor.value = (sum(sensor.stash) / 5) | 0;
1019
1020 if (this.acceleration !== sensor.acceleration) {
1021 sensor.acceleration = this.acceleration;
1022 isChange = true;
1023 this.emit("acceleration", sensor.acceleration);
1024 }
1025
1026 if (this.orientation !== sensor.orientation) {
1027 sensor.orientation = this.orientation;
1028 isChange = true;
1029 this.emit("orientation", sensor.orientation);
1030 }
1031
1032 if (this.inclination !== sensor.inclination) {
1033 sensor.inclination = this.inclination;
1034 isChange = true;
1035 this.emit("inclination", sensor.inclination);
1036 }
1037 }, this);
1038
1039 this.emit("data", {
1040 x: state.x.value,
1041 y: state.y.value,
1042 z: state.z.value
1043 });
1044
1045 if (isChange) {
1046 this.emit("change", {
1047 x: this.x,
1048 y: this.y,
1049 z: this.z
1050 });
1051 }
1052 }.bind(this));
1053 }
1054
1055 Object.defineProperties(this, {
1056 hasAxis: {
1057 writable: true,
1058 value: function(axis) {
1059 /* istanbul ignore next */
1060 return state[axis] ? state[axis].stash.length > 0 : false;
1061 }
1062 },
1063 enable: {
1064 value: function() {
1065 state.enabled = true;
1066 this.enabledChanged(true);
1067 return this;
1068 }
1069 },
1070 disable: {
1071 value: function() {
1072 state.enabled = false;
1073 this.enabledChanged(false);
1074 return this;
1075 }
1076 },
1077 zeroV: {
1078 get: function() {
1079 return state.zeroV;
1080 }
1081 },
1082 /**
1083 * [read-only] Calculated pitch value
1084 * @property pitch
1085 * @type Number
1086 */
1087 pitch: {
1088 get: function() {
1089 var x = this.x;
1090 var y = this.y;
1091 var z = this.z;
1092 var rads = this.hasAxis(aZ) ?
1093 Math.atan2(x, Math.hypot(y, z)) :
1094 Math.asin(constrain(x, -1, 1));
1095
1096 return toFixed(rads * Fn.RAD_TO_DEG, 2);
1097 }
1098 },
1099 /**
1100 * [read-only] Calculated roll value
1101 * @property roll
1102 * @type Number
1103 */
1104 roll: {
1105 get: function() {
1106 var x = this.x;
1107 var y = this.y;
1108 var z = this.z;
1109 var rads = this.hasAxis(aZ) ?
1110 Math.atan2(y, Math.hypot(x, z)) :
1111 Math.asin(constrain(y, -1, 1));
1112
1113 return toFixed(rads * Fn.RAD_TO_DEG, 2);
1114 }
1115 },
1116 x: {
1117 get: function() {
1118 return this.toGravity(state.x.value, aX);
1119 }
1120 },
1121 y: {
1122 get: function() {
1123 return this.toGravity(state.y.value, aY);
1124 }
1125 },
1126 z: {
1127 get: function() {
1128 return this.hasAxis(aZ) ?
1129 this.toGravity(state.z.value, aZ) : 0;
1130 }
1131 },
1132 acceleration: {
1133 get: function() {
1134 return magnitude(
1135 this.x,
1136 this.y,
1137 this.z
1138 );
1139 }
1140 },
1141 inclination: {
1142 get: function() {
1143 return Math.atan2(this.y, this.x) * Fn.RAD_TO_DEG;
1144 }
1145 },
1146 orientation: {
1147 get: function() {
1148 var abs = Math.abs;
1149 var x = this.x;
1150 var y = this.y;
1151 var z = this.hasAxis(aZ) ? this.z : 1;
1152 var absX = abs(x);
1153 var absY = abs(y);
1154 var absZ = abs(z);
1155
1156 if (absX < absY && absX < absZ) {
1157 if (x > 0) {
1158 return 1;
1159 }
1160 return -1;
1161 }
1162 if (absY < absX && absY < absZ) {
1163 if (y > 0) {
1164 return 2;
1165 }
1166 return -2;
1167 }
1168 if (absZ < absX && absZ < absY) {
1169 // TODO: figure out how to test this
1170 /* istanbul ignore else */
1171 if (z > 0) {
1172 return 3;
1173 }
1174 /* istanbul ignore next */
1175 return -3;
1176 }
1177 return 0;
1178 }
1179 }
1180 });
1181}
1182
1183util.inherits(Accelerometer, Emitter);
1184
1185/* istanbul ignore else */
1186if (!!process.env.IS_TEST_MODE) {
1187 Accelerometer.Controllers = Controllers;
1188 Accelerometer.purge = function() {
1189 priv.clear();
1190 };
1191}
1192
1193
1194module.exports = Accelerometer;