UNPKG

21.8 kBJavaScriptView Raw
1var Board = require("./board");
2var Emitter = require("events").EventEmitter;
3var util = require("util");
4var Fn = require("./fn");
5var int16 = Fn.int16;
6var TAU = Fn.TAU;
7
8var priv = new Map();
9
10var Controllers = {
11
12 HMC5883L: {
13 REGISTER: {
14 value: {
15 // Page 11
16 // Table 2: Register List
17 //
18 // Configuration Register A
19 CRA: 0x00,
20 // Configuration Register B
21 // This may change, depending on gauss
22 CRB: 0x01,
23 // Mode Register
24 MODE: 0x02,
25 // Data Output X MSB Register
26 READ: 0x03,
27 }
28 },
29 initialize: {
30 value: function(opts, dataHandler) {
31 var state = priv.get(this);
32 var address = opts.address || 0x1E;
33 var READLENGTH = 6;
34
35 state.scale = 1;
36
37 Object.assign(state, new Compass.Scale(opts.gauss || 0.88));
38
39 opts.address = address;
40
41 this.io.i2cConfig(opts);
42
43 // Page 18
44 // OPERATIONAL EXAMPLES...
45 //
46 // 1. Write CRA (00) – send 0x3C 0x00 0x70 (8-average, 15 Hz default, normal measurement)
47 //
48 // Set CRA
49 // Page 12
50 this.io.i2cWrite(address, this.REGISTER.CRA, 0x70);
51
52 // Set CRB
53 // Page 13
54 this.io.i2cWrite(address, this.REGISTER.CRB, 0x40);
55
56 // Page 14
57 // Measurement: Continuous
58 this.io.i2cWrite(address, this.REGISTER.MODE, 0x00);
59
60 this.io.i2cRead(address, this.REGISTER.READ, READLENGTH, function(bytes) {
61 dataHandler({
62 x: int16(bytes[0], bytes[1]),
63 y: int16(bytes[4], bytes[5]),
64 z: int16(bytes[2], bytes[3]),
65 });
66 });
67 }
68 },
69 toScaledHeading: {
70 value: function(raw) {
71 var state = priv.get(this);
72
73 return ToHeading(raw.x * state.scale, raw.y * state.scale);
74 }
75 }
76 },
77
78 /**
79 * HMC6352: 2-Axis Compass Module
80 * 0x42
81 *
82 * http://bildr.org/2011/01/hmc6352/
83 */
84 HMC6352: {
85 REGISTER: {
86 value: {
87 READ: 0x41
88 }
89 },
90 initialize: {
91 value: function(opts, dataHandler) {
92 var state = priv.get(this);
93 var address = opts.address || 0x21;
94 var READLENGTH = 2;
95
96 state.scale = 1;
97
98 opts.delay = 10;
99 opts.address = address;
100
101 this.io.i2cConfig(opts);
102
103 this.io.i2cWrite(address, this.REGISTER.READ);
104
105 // Initialize continuous read
106 this.io.i2cRead(address, this.REGISTER.READ, READLENGTH, function(bytes) {
107 dataHandler({
108 x: (((bytes[0] << 8) + bytes[1]) / 10) | 0,
109 y: null,
110 z: null,
111 });
112 });
113 }
114 },
115 toScaledHeading: {
116 value: function(raw) {
117 var state = priv.get(this);
118 return raw.x * state.scale;
119 },
120 },
121 },
122
123 BNO055: {
124 initialize: {
125 value: function(opts, dataHandler) {
126 var IMU = require("./imu");
127 var driver = IMU.Drivers.get(this.board, "BNO055", opts);
128 var state = priv.get(this);
129
130 // AF p.32, Table 3-19: Magnetometer Unit settings
131 state.sensitivity = 16;
132
133 driver.on("data", function(data) {
134 dataHandler(data.magnetometer);
135 });
136 }
137 },
138 toScaledHeading: {
139 value: function(raw) {
140 var state = priv.get(this);
141
142 var x = raw.x / state.sensitivity;
143 var y = raw.y / state.sensitivity;
144
145 return ToHeading(x, y);
146 },
147 },
148 },
149
150 // http://www.nxp.com/files/sensors/doc/data_sheet/MAG3110.pdf
151 MAG3110: {
152 REGISTER: {
153 value: {
154 // Page 15
155 // Table 11 Register Address Map
156 // DR_STATUS
157 STATUS: 0x00,
158 // OUT_X_MSB
159 READ: 0x01,
160 // OFF_X_MSB
161 OFFSETS: 0x09,
162 // CTRL_REG1
163 CTRL_REG1: 0x10,
164 // CTRL_REG2
165 CTRL_REG2: 0x11,
166 }
167 },
168 initialize: {
169 value: function(opts, dataHandler) {
170 var state = priv.get(this);
171
172 // MAG3110 has only one possible address
173 var address = 0x0E;
174 var isDataPending = false;
175 var temp;
176
177 state.isCalibrated = false;
178 state.isPreCalibrated = false;
179 state.hasEmittedCalibration = false;
180 state.measurements = 20;
181
182 state.offsets = {
183 x: 0,
184 y: 0,
185 z: 0,
186 };
187 state.accum = {
188 x: { offset: null, high: 0, low: 0 },
189 y: { offset: null, high: 0, low: 0 },
190 z: { offset: null, high: 0, low: 0 },
191 };
192 opts.delay = 2;
193 opts.address = address;
194
195 if (opts.offsets) {
196 state.isCalibrated = true;
197 state.isPreCalibrated = true;
198
199 if (Array.isArray(opts.offsets)) {
200 temp = opts.offsets.slice();
201 opts.offsets = {
202 x: temp[0],
203 y: temp[1],
204 z: temp[2],
205 };
206 }
207
208 state.accum.x.low = opts.offsets.x[0];
209 state.accum.x.high = opts.offsets.x[1];
210 state.accum.x.offset = (state.accum.x.low + state.accum.x.high) / 2;
211
212 state.accum.y.low = opts.offsets.y[0];
213 state.accum.y.high = opts.offsets.y[1];
214 state.accum.y.offset = (state.accum.y.low + state.accum.y.high) / 2;
215
216 state.accum.z.low = opts.offsets.z[0];
217 state.accum.z.high = opts.offsets.z[1];
218 state.accum.z.offset = (state.accum.z.low + state.accum.z.high) / 2;
219 }
220
221 /*
222 Page 14
223 4.2.7 MAG3110 Setup Examples
224
225 Continuous measurements with ODR = 80 Hz, OSR = 1
226
227 1. Enable automatic magnetic sensor resets by setting bit AUTO_MRST_EN in CTRL_REG2.
228 (CTRL_REG2 = 0x80)
229 2. Put MAG3110 in active mode 80 Hz ODR with OSR = 1 by writing 0x01 to CTRL_REG1
230 (CTRL_REG1 = 0x01)
231 3. At this point it is possible to sync with MAG3110 utilizing INT1 pin or
232 using polling of the DR_STATUS register as explained in section 4.2.5.
233 */
234
235 this.io.i2cConfig(opts);
236 /*
237 Page 21
238 5.5.2 CTRL_REG2 (0x11)
239 Table 33.
240 CTRL_REG2 Register
241
242 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
243 |---|---|---|---|---|---|---|---|
244 | A | | R | M | | | | |
245
246 A: Automatic Magnetic Sensor Reset. Default value: 0.
247 R: Data output correction. Default value: 0.
248 M: Magnetic Sensor Reset (One-Shot). Default value: 0.
249
250 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
251 |---|---|---|---|---|---|---|---|
252 | 1 | | 0 | 0 | | | | |
253
254 0b10000000 = 128 = 0x80
255
256 RAW
257 0b10100000 = 160 = 0xA0
258 */
259 this.io.i2cWrite(address, this.REGISTER.CTRL_REG2, 0x80);
260 // this.io.i2cWrite(address, this.REGISTER.CTRL_REG2, 0xA0);
261
262 /*
263 Page 20
264 5.5.1 CTRL_REG1 (0x10)
265 Table 30. CTRL_REG1 Register
266 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
267 |---|---|---|---|---|---|---|---|
268 |DR2|DR1|DR0|OS1|OS0|FR |TM |AC |
269
270 See Table 31. CTRL_REG1 Description for complete descriptions
271
272 (Active mode, 80Hz)
273
274 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
275 |---|---|---|---|---|---|---|---|
276 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
277
278 0b00000001 = 1 = 0x01
279 */
280 this.io.i2cWrite(address, this.REGISTER.CTRL_REG1, 0x01);
281
282 var measured = {
283 x: 0,
284 y: 0,
285 z: 0,
286 };
287
288 var readCycle = function() {
289 this.io.i2cReadOnce(address, this.REGISTER.STATUS, 1, function(data) {
290 /*
291 Page 16
292 5.1.1 DR_STATUS (0x00)
293
294 Table 12
295 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
296 |---|---|---|---|---|---|---|---|
297 |OVR|ZOW|XOW|YOW|DR |ZDR|YDR|XDR|
298
299 Table 13
300 (Contains Complete descriptions)
301
302 OVR (ZYXOW) (X, Y, Z-axis Data Overwrite. Default value: 0.)
303 0: No Data overwritten
304 1: Previous X, Y, Z has been overwritten
305
306 ZOW, YOW, XOW:
307 0: No Data overwritten
308 1: Previous X, Y, Z has been overwritten
309
310 DR (ZYXDR) (X or Y or Z-axis new Data Ready. Default value: 0.)
311 0: No new data is ready
312 1: New full set of data is ready
313
314 ZDR, YDR, XDR:
315 0: No new data is ready
316 1: New X, Y, Z data is ready
317
318 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
319 |---|---|---|---|---|---|---|---|
320 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
321
322
323 0b00001111 = 15 = 0x0F: A complete set of axis data is available
324
325 0b11111111 = 255 = 0xFF: All data is newly written
326
327 */
328 if (!isDataPending && (data[0] === 0x0F || data[0] === 0xFF)) {
329 isDataPending = true;
330
331 this.io.i2cReadOnce(address, this.REGISTER.READ, 6, function(bytes) {
332 var timeout = 0;
333
334 isDataPending = false;
335
336 measured.x = int16(bytes[0], bytes[1]);
337 measured.y = int16(bytes[2], bytes[3]);
338 measured.z = int16(bytes[4], bytes[5]);
339
340 if (!state.isCalibrated) {
341
342 if (state.accum.x.offset === null) {
343 state.accum.x.offset = measured.x;
344 state.accum.x.low = measured.x;
345 state.accum.x.high = measured.x;
346 }
347
348 if (state.accum.y.offset === null) {
349 state.accum.y.offset = measured.y;
350 state.accum.y.low = measured.y;
351 state.accum.y.high = measured.y;
352 }
353
354 state.accum.x.low = Math.min(state.accum.x.low, measured.x);
355 state.accum.x.high = Math.max(state.accum.x.high, measured.x);
356 state.accum.x.offset = Math.trunc((state.accum.x.low + state.accum.x.high) / 2);
357
358 state.accum.y.low = Math.min(state.accum.y.low, measured.y);
359 state.accum.y.high = Math.max(state.accum.y.high, measured.y);
360 state.accum.y.offset = Math.trunc((state.accum.y.low + state.accum.y.high) / 2);
361
362 state.accum.z.low = Math.min(state.accum.z.low, measured.z);
363 state.accum.z.high = Math.max(state.accum.z.high, measured.z);
364 state.accum.z.offset = Math.trunc((state.accum.z.low + state.accum.z.high) / 2);
365
366 --state.measurements;
367
368 if (!state.measurements) {
369 state.isCalibrated = true;
370 }
371 }
372
373 if (state.isCalibrated) {
374 if (!state.hasEmittedCalibration) {
375 state.hasEmittedCalibration = true;
376
377 state.offsets.x = state.accum.x.offset;
378 state.offsets.y = state.accum.y.offset;
379 state.offsets.z = state.accum.z.offset;
380
381 this.io.i2cWrite(address, this.REGISTER.OFFSETS, [
382 state.offsets.x >> 7, (state.offsets.x << 1) & 0xFF,
383 state.offsets.y >> 7, (state.offsets.y << 1) & 0xFF,
384 state.offsets.z >> 7, (state.offsets.z << 1) & 0xFF,
385 ]);
386
387 this.emit("calibrated", {
388 x: [state.accum.x.low, state.accum.x.high],
389 y: [state.accum.y.low, state.accum.y.high],
390 z: [state.accum.z.low, state.accum.z.high],
391 });
392 }
393
394 timeout = Math.floor(1000 / 80);
395
396 dataHandler(measured);
397 }
398
399 // MAG3110 is set to read at 80Hz (do this after calibration)
400 setTimeout(readCycle, timeout);
401 }.bind(this));
402 } else {
403 readCycle();
404 }
405 }.bind(this));
406 }.bind(this);
407
408 readCycle();
409 }
410 },
411 calibrate: {
412 value: function(measurements) {
413 var state = priv.get(this);
414
415 state.isCalibrated = false;
416 state.measurements = measurements;
417 }
418 },
419 toScaledHeading: {
420 value: function(raw) {
421 var state = priv.get(this);
422 var scale = {
423 x: 1 / (state.accum.x.high - state.accum.x.low),
424 y: 1 / (state.accum.y.high - state.accum.y.low),
425 };
426
427 var heading = Math.atan2(-raw.y * scale.y, raw.x * scale.x);
428
429 if (heading < 0) {
430 heading += TAU;
431 }
432
433 return Math.trunc(heading * Fn.RAD_TO_DEG);
434 },
435 },
436 },
437
438 /**
439 * LSM303C: 6Dof 3-Axis Magnetometer & Accelerometer
440 *
441 * https://learn.sparkfun.com/tutorials/lsm303c-6dof-hookup-guide
442 * https://github.com/sparkfun/LSM303C_6_DOF_IMU_Breakout
443 */
444 LSM303C: {
445 initialize: {
446 value: function(opts, dataHandler) {
447 var IMU = require("./imu");
448 var driver = IMU.Drivers.get(this.board, "LSM303C", opts);
449
450 driver.on("data", function(data) {
451 dataHandler(data.magnetometer);
452 });
453 }
454 },
455 toScaledHeading: {
456 value: function(raw) {
457 return ToHeading(raw.x, raw.y);
458 },
459 },
460 },
461};
462
463
464/**
465 * Compass
466 * @constructor
467 *
468 * five.Compass();
469 *
470 * five.Compass({
471 * controller: "HMC5883L",
472 * freq: 50,
473 * });
474 *
475 *
476 * Device Shorthands:
477 *
478 * "HMC5883L": new five.Magnetometer()
479 *
480 *
481 * @param {Object} opts [description]
482 *
483 */
484
485function Compass(opts) {
486
487 if (!(this instanceof Compass)) {
488 return new Compass(opts);
489 }
490
491 Board.Component.call(
492 this, opts = Board.Options(opts)
493 );
494
495 var freq = opts.freq || 25;
496 var controller = null;
497 var raw = {
498 x: null,
499 y: null,
500 z: null,
501 };
502 var state = {
503 x: 0,
504 y: 0,
505 z: 0,
506 scale: 0,
507 register: 0,
508 heading: 0
509 };
510
511 if (opts.controller && typeof opts.controller === "string") {
512 controller = Controllers[opts.controller.toUpperCase()];
513 } else {
514 controller = opts.controller;
515 }
516
517 if (controller == null) {
518 throw new Error("Compass expects a valid controller");
519 }
520
521 Board.Controller.call(this, controller, opts);
522
523 if (!this.toScaledHeading) {
524 this.toScaledHeading = opts.toScaledHeading || function(raw) {
525 return raw;
526 };
527 }
528
529 priv.set(this, state);
530
531 if (typeof this.initialize === "function") {
532 this.initialize(opts, function(data) {
533 raw = data;
534 });
535 }
536
537 setInterval(function() {
538 if (raw.x === null) {
539 return;
540 }
541 var isChange = false;
542
543 state.x = raw.x;
544 state.y = raw.y;
545 state.z = raw.z;
546
547 var heading = this.heading;
548
549 if (heading !== state.heading) {
550 state.heading = heading;
551 isChange = true;
552 }
553
554 this.emit("data", {
555 heading: state.heading
556 });
557
558 if (isChange) {
559 this.emit("change", {
560 heading: state.heading
561 });
562 }
563 }.bind(this), freq);
564
565 Object.defineProperties(this, {
566 /**
567 * [read-only] Bearing information
568 * @name bearing
569 * @property
570 * @type Object
571 *
572 *
573 name
574 abbr
575 low
576 mid
577 high
578 heading
579 *
580 */
581
582 bearing: {
583 get: function() {
584 var length = Compass.Points.length;
585 var heading = this.heading;
586 var point;
587
588 for (var i = 0; i < length; i++) {
589 point = Compass.Points[i];
590
591 if (heading >= point.low && heading <= point.high) {
592 // Specify fields to return to avoid returning the
593 // range array (too much noisy data)
594 return {
595 name: point.name,
596 abbr: point.abbr,
597 low: point.low,
598 high: point.high,
599 heading: heading
600 };
601 }
602 }
603 }
604 },
605
606 /**
607 * [read-only] Raw X/Y/Z
608 * @name raw
609 * @property
610 * @type Object
611 *
612 x
613 y
614 z
615 */
616 raw: {
617 get: function() {
618 return {
619 x: raw.x,
620 y: raw.y,
621 z: raw.z
622 };
623 }
624 },
625
626 /**
627 * [read-only] Heading (azimuth)
628 * @name heading
629 * @property
630 * @type number
631 */
632 heading: {
633 get: function() {
634 return this.toScaledHeading(raw);
635 }
636 }
637 });
638}
639
640
641util.inherits(Compass, Emitter);
642
643function ToHeading(x, y) {
644 /**
645 *
646 * Applications of Magnetoresistive Sensors in Navigation Systems
647 * by Michael J. Caruso of Honeywell Inc.
648 * http://www.ssec.honeywell.com/position-sensors/datasheets/sae.pdf
649 *
650 *
651 * Azimuth (x=0, y<0) = 90.0 (3)
652 * Azimuth (x=0, y>0) = 270.0
653 * Azimuth (x<0) = 180 - [arcTan(y/x)]*180/PI
654 * Azimuth (x>0, y<0) = - [arcTan(y/x)]*180/PI
655 * Azimuth (x>0, y>0) = 360 - [arcTan(y/x)]*180/PI
656 */
657 /**
658 * http://bildr.org/2012/02/hmc5883l_arduino/
659 * @type {[type]}
660 * Copyright (C) 2011 Love Electronics (loveelectronics.co.uk)
661
662 This program is free software: you can redistribute it and/or modify it under the terms of the version 3 GNU General Public License as published by the Free Software Foundation.
663
664 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
665
666 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
667
668 */
669
670 var radians = Math.atan2(y, x);
671
672 if (radians < 0) {
673 radians += TAU;
674 }
675
676 if (radians > TAU) {
677 radians -= TAU;
678 }
679
680 return radians * Fn.RAD_TO_DEG;
681}
682
683
684/**
685 * Compass.scale Set the scale gauss for compass readings
686 * @param {Number} gauss [description]
687 * @return {register} [description]
688 *
689 * Ported from:
690 * http://bildr.org/2012/02/hmc5883l_arduino/
691 */
692
693Compass.Scale = function(gauss) {
694
695 if (gauss === 0.88) {
696 this.register = 0x00;
697 this.scale = 0.73;
698 } else if (gauss === 1.3) {
699 this.register = 0x01;
700 this.scale = 0.92;
701 } else if (gauss === 1.9) {
702 this.register = 0x02;
703 this.scale = 1.22;
704 } else if (gauss === 2.5) {
705 this.register = 0x03;
706 this.scale = 1.52;
707 } else if (gauss === 4.0) {
708 this.register = 0x04;
709 this.scale = 2.27;
710 } else if (gauss === 4.7) {
711 this.register = 0x05;
712 this.scale = 2.56;
713 } else if (gauss === 5.6) {
714 this.register = 0x06;
715 this.scale = 3.03;
716 } else if (gauss === 8.1) {
717 this.register = 0x07;
718 this.scale = 4.35;
719 } else {
720 this.register = 0x00;
721 this.scale = 1;
722 }
723
724 // Setting is in the top 3 bits of the register.
725 this.register = this.register << 5;
726};
727
728
729/**
730 * Compass.Points
731 *
732 * 32 Point Compass
733 * +1 for North
734 *
735 */
736
737Compass.Points = [{
738 name: "North",
739 abbr: "N",
740 low: 354.38,
741 high: 360
742}, {
743 name: "North",
744 abbr: "N",
745 low: 0,
746 high: 5.62
747}, {
748 name: "North by East",
749 abbr: "NbE",
750 low: 5.63,
751 high: 16.87
752}, {
753 name: "North-NorthEast",
754 abbr: "NNE",
755 low: 16.88,
756 high: 28.12
757}, {
758 name: "NorthEast by North",
759 abbr: "NEbN",
760 low: 28.13,
761 high: 39.37
762}, {
763 name: "NorthEast",
764 abbr: "NE",
765 low: 39.38,
766 high: 50.62
767}, {
768 name: "NorthEast by East",
769 abbr: "NEbE",
770 low: 50.63,
771 high: 61.87
772}, {
773 name: "East-NorthEast",
774 abbr: "ENE",
775 low: 61.88,
776 high: 73.12
777}, {
778 name: "East by North",
779 abbr: "EbN",
780 low: 73.13,
781 high: 84.37
782}, {
783 name: "East",
784 abbr: "E",
785 low: 84.38,
786 high: 95.62
787}, {
788 name: "East by South",
789 abbr: "EbS",
790 low: 95.63,
791 high: 106.87
792}, {
793 name: "East-SouthEast",
794 abbr: "ESE",
795 low: 106.88,
796 high: 118.12
797}, {
798 name: "SouthEast by East",
799 abbr: "SEbE",
800 low: 118.13,
801 high: 129.37
802}, {
803 name: "SouthEast",
804 abbr: "SE",
805 low: 129.38,
806 high: 140.62
807}, {
808 name: "SouthEast by South",
809 abbr: "SEbS",
810 low: 140.63,
811 high: 151.87
812}, {
813 name: "South-SouthEast",
814 abbr: "SSE",
815 low: 151.88,
816 high: 163.12
817}, {
818 name: "South by East",
819 abbr: "SbE",
820 low: 163.13,
821 high: 174.37
822}, {
823 name: "South",
824 abbr: "S",
825 low: 174.38,
826 high: 185.62
827}, {
828 name: "South by West",
829 abbr: "SbW",
830 low: 185.63,
831 high: 196.87
832}, {
833 name: "South-SouthWest",
834 abbr: "SSW",
835 low: 196.88,
836 high: 208.12
837}, {
838 name: "SouthWest by South",
839 abbr: "SWbS",
840 low: 208.13,
841 high: 219.37
842}, {
843 name: "SouthWest",
844 abbr: "SW",
845 low: 219.38,
846 high: 230.62
847}, {
848 name: "SouthWest by West",
849 abbr: "SWbW",
850 low: 230.63,
851 high: 241.87
852}, {
853 name: "West-SouthWest",
854 abbr: "WSW",
855 low: 241.88,
856 high: 253.12
857}, {
858 name: "West by South",
859 abbr: "WbS",
860 low: 253.13,
861 high: 264.37
862}, {
863 name: "West",
864 abbr: "W",
865 low: 264.38,
866 high: 275.62
867}, {
868 name: "West by North",
869 abbr: "WbN",
870 low: 275.63,
871 high: 286.87
872}, {
873 name: "West-NorthWest",
874 abbr: "WNW",
875 low: 286.88,
876 high: 298.12
877}, {
878 name: "NorthWest by West",
879 abbr: "NWbW",
880 low: 298.13,
881 high: 309.37
882}, {
883 name: "NorthWest",
884 abbr: "NW",
885 low: 309.38,
886 high: 320.62
887}, {
888 name: "NorthWest by North",
889 abbr: "NWbN",
890 low: 320.63,
891 high: 331.87
892}, {
893 name: "North-NorthWest",
894 abbr: "NNW",
895 low: 331.88,
896 high: 343.12
897}, {
898 name: "North by West",
899 abbr: "NbW",
900 low: 343.13,
901 high: 354.37
902}];
903
904Object.freeze(Compass.Points);
905
906/**
907 * Fires once every N ms, equal to value of `freq`. Defaults to 66ms
908 *
909 * @event
910 * @name read
911 * @memberOf Compass
912 */
913
914
915/**
916 * Fires when the calculated heading has changed
917 *
918 * @event
919 * @name headingchange
920 * @memberOf Compass
921 */
922
923
924/* istanbul ignore else */
925if (!!process.env.IS_TEST_MODE) {
926 Compass.Controllers = Controllers;
927 Compass.purge = function() {
928 priv.clear();
929 };
930}
931
932module.exports = Compass;