UNPKG

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