UNPKG

johnny-five

Version:

The JavaScript Arduino Programming Framework.

745 lines (639 loc) 15 kB
var Board = require("../lib/board.js"), events = require("events"), util = require("util"), __ = require("../lib/fn.js"); var priv = new Map(), Devices; /** * Compass * @constructor * * five.Compass(); * * five.Compass({ * device: "HMC5883L", * freq: 50, * }); * * * Device Shorthands: * * "HMC5883L": new five.Magnetometer() * * * @param {Object} opts [description] * */ function Compass(opts) { if (!(this instanceof Compass)) { return new Compass(opts); } var address, bytes, data, descriptor, device, delay, last, properties, read, setup, preread; // Initialize a Device instance on a Board Board.Device.call( this, opts = Board.Options(opts) ); device = Devices[opts.device]; address = device.address; bytes = device.bytes; data = device.data; delay = device.delay; setup = device.setup; preread = device.preread[0]; properties = device.properties; // Read event throttling this.freq = opts.freq || 500; // Make private data entry priv.set(this, { x: 0, y: 0, z: 0 }); // Correct value set in scale() this.scale = 1; this.register = 0x00; Compass.scale.call(this, opts.gauss); // Set up I2C data connection this.io.sendI2CConfig(); // Enumerate and write each set of setup instructions setup.forEach(function(byteArray) { this.io.sendI2CWriteRequest(address, byteArray); }, this); this.setMaxListeners(100); // Read Request Loop setInterval(function() { // Set pointer to X most signficant byte this.io.sendI2CWriteRequest(address, preread); // Read from register this.io.sendI2CReadRequest(address, bytes, data.bind(this)); // Emit "headingchange" events whenever the calculated // heading accessor changes // TODO: handling for 360/0 if (__.range(last - 1, last + 1).indexOf(Math.floor(this.heading)) === -1) { this.emit("headingchange", { now: this.heading, previous: last }); } last = Math.floor(this.heading); }.bind(this), delay); // "read" throttle loop setInterval(function() { // @DEPRECATE this.emit("read"); // The "read" event has been deprecated in // favor of a "data" event. this.emit("data"); }.bind(this), this.freq); descriptor = __.extend({}, { /** * raw x, y, z data * @name raw * @property * @type Object */ raw: { get: function() { var raw = priv.get(this); return { x: raw.x, y: raw.y, z: raw.z || null }; } }, /** * scaled x, y, z data * @name scaled * @property * @type Object */ scaled: { get: function() { var raw = priv.get(this); return { x: raw.x * this.scale, y: raw.y * this.scale, z: raw.z ? raw.z * this.scale : null }; } } }); // If this compass device has it's own properties // then merge with the defaults; if (properties) { descriptor = __.extend(descriptor, properties); } // Define instance accessors with merged descriptor properties Object.defineProperties(this, descriptor); } /** * Compass.scale Set the scale gauss for compass readings * @param {Number} gauss [description] * @return {register} [description] * * Ported from: * http://bildr.org/2012/02/hmc5883l_arduino/ */ Compass.scale = function(gauss) { if (!(this instanceof Compass)) { return; } if (gauss === 0.88) { this.register = 0x00; this.scale = 0.73; } else if (gauss === 1.3) { this.register = 0x01; this.scale = 0.92; } else if (gauss === 1.9) { this.register = 0x02; this.scale = 1.22; } else if (gauss === 2.5) { this.register = 0x03; this.scale = 1.52; } else if (gauss === 4.0) { this.register = 0x04; this.scale = 2.27; } else if (gauss === 4.7) { this.register = 0x05; this.scale = 2.56; } else if (gauss === 5.6) { this.register = 0x06; this.scale = 3.03; } else if (gauss === 8.1) { this.register = 0x07; this.scale = 4.35; } else { this.register = 0x00; this.scale = 1; } // Setting is in the top 3 bits of the register. this.register = this.register << 5; }; /** * Compass.Points * * 32 Point Compass * +1 for North * */ Compass.Points = [{ point: "North", abbr: "N", low: 354.38, mid: 360, high: 360 }, { point: "North", abbr: "N", low: 0, mid: 0, high: 5.62 }, { point: "North by East", abbr: "NbE", low: 5.63, mid: 11.25, high: 16.87 }, { point: "North-NorthEast", abbr: "NNE", low: 16.88, mid: 22.5, high: 28.12 }, { point: "NorthEast by North", abbr: "NEbN", low: 28.13, mid: 33.75, high: 39.37 }, { point: "NorthEast", abbr: "NE", low: 39.38, mid: 45, high: 50.62 }, { point: "NorthEast by East", abbr: "NEbE", low: 50.63, mid: 56.25, high: 61.87 }, { point: "East-NorthEast", abbr: "ENE", low: 61.88, mid: 67.5, high: 73.12 }, { point: "East by North", abbr: "EbN", low: 73.13, mid: 78.75, high: 84.37 }, { point: "East", abbr: "E", low: 84.38, mid: 90, high: 95.62 }, { point: "East by South", abbr: "EbS", low: 95.63, mid: 101.25, high: 106.87 }, { point: "East-SouthEast", abbr: "ESE", low: 106.88, mid: 112.5, high: 118.12 }, { point: "SouthEast by East", abbr: "SEbE", low: 118.13, mid: 123.75, high: 129.37 }, { point: "SouthEast", abbr: "SE", low: 129.38, mid: 135, high: 140.62 }, { point: "SouthEast by South", abbr: "SEbS", low: 140.63, mid: 146.25, high: 151.87 }, { point: "South-SouthEast", abbr: "SSE", low: 151.88, mid: 157.5, high: 163.12 }, { point: "South by East", abbr: "SbE", low: 163.13, mid: 168.75, high: 174.37 }, { point: "South", abbr: "S", low: 174.38, mid: 180, high: 185.62 }, { point: "South by West", abbr: "SbW", low: 185.63, mid: 191.25, high: 196.87 }, { point: "South-SouthWest", abbr: "SSW", low: 196.88, mid: 202.5, high: 208.12 }, { point: "SouthWest by South", abbr: "SWbS", low: 208.13, mid: 213.75, high: 219.37 }, { point: "SouthWest", abbr: "SW", low: 219.38, mid: 225, high: 230.62 }, { point: "SouthWest by West", abbr: "SWbW", low: 230.63, mid: 236.25, high: 241.87 }, { point: "West-SouthWest", abbr: "WSW", low: 241.88, mid: 247.5, high: 253.12 }, { point: "West by South", abbr: "WbS", low: 253.13, mid: 258.75, high: 264.37 }, { point: "West", abbr: "W", low: 264.38, mid: 270, high: 275.62 }, { point: "West by North", abbr: "WbN", low: 275.63, mid: 281.25, high: 286.87 }, { point: "West-NorthWest", abbr: "WNW", low: 286.88, mid: 292.5, high: 298.12 }, { point: "NorthWest by West", abbr: "NWbW", low: 298.13, mid: 303.75, high: 309.37 }, { point: "NorthWest", abbr: "NW", low: 309.38, mid: 315.00, high: 320.62 }, { point: "NorthWest by North", abbr: "NWbN", low: 320.63, mid: 326.25, high: 331.87 }, { point: "North-NorthWest", abbr: "NNW", low: 331.88, mid: 337.5, high: 343.12 }, { point: "North by West", abbr: "NbW", low: 343.13, mid: 348.75, high: 354.37 }]; // Add ranges to each compass point record Compass.Points.forEach(function(point, k) { this[k].range = __.range(Math.floor(point.low), Math.floor(point.high)); }, Compass.Points); util.inherits(Compass, events.EventEmitter); Devices = { /** * HMC5883L: 3-Axis Compass Module * 0x1E * * https://sites.google.com/site/parallaxinretailstores/home/compass-module-3-axis-hmc5883l * * http://www51.honeywell.com/aero/common/documents/myaerospacecatalog-documents/Defense_Brochures-documents/HMC5883L_3-Axis_Digital_Compass_IC.pdf * P. 10,11,12,13 * * http://www.memsense.com/docs/MTD-0801_1_0_Calculating_Heading_Elevation_Bank_Angle.pdf * * https://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial * */ "HMC5883L": { address: 0x1E, bytes: 6, delay: 100, // read request data handler data: function(data) { var raw = priv.get(this); // console.log( data ); raw.x = (data[0] << 8) | data[1]; raw.y = (data[4] << 8) | data[5]; raw.z = (data[2] << 8) | data[3]; // Negative number, nT spike correction // Derived and adapted from: // Jeff Hoefs' Breakout.js > MagnetoMeterHMC5883.js Object.keys(raw).forEach(function(key) { var val = raw[key]; raw[key] = val >> 15 ? ((val ^ 0xFFFF) + 1) * -1 : val; }); }, // These are added to the property descriptors defined // within the constructor // Reference: // http://casanovasadventures.com/catalog/compass/p1409.htm // http://en.wikipedia.org/wiki/Boxing_the_compass // http://en.wikipedia.org/wiki/File:Compass_Card.png // http://en.wikipedia.org/wiki/Boxing_the_compass#Compass_point_names properties: { /** * [read-only] Bearing information * @name bearing * @property * @type Object * * name abbr low mid high heading * */ bearing: { get: function() { var k, len, heading, point; k = 0; len = Compass.Points.length; heading = Math.floor(this.heading); for (; k < len; k++) { point = Compass.Points[k]; if (point.range.indexOf(heading) !== -1) { // Specify fields to return to avoid returning the // range array (too much noisy data) return { name: point.point, abbr: point.abbr, low: point.low, mid: point.mid, high: point.high, heading: heading }; } } } }, /** * [read-only] Heading (azimuth) * @name heading * @property * @type number */ heading: { get: function() { var heading, raw, x, y, z; // Aquire raw x, y, z data from private data map raw = priv.get(this); x = raw.x; y = raw.y; z = raw.z; /** * * Applications of Magnetoresistive Sensors in Navigation Systems * by Michael J. Caruso of Honeywell Inc. * http://www.ssec.honeywell.com/position-sensors/datasheets/sae.pdf * * * Azimuth (x=0, y<0) = 90.0 (3) * Azimuth (x=0, y>0) = 270.0 * Azimuth (x<0) = 180 - [arcTan(y/x)]*180/PI * Azimuth (x>0, y<0) = - [arcTan(y/x)]*180/PI * Azimuth (x>0, y>0) = 360 - [arcTan(y/x)]*180/PI * * * * * */ /** * * * http://bildr.org/2012/02/hmc5883l_arduino/ * @type {[type]} * Copyright (C) 2011 Love Electronics (loveelectronics.co.uk) 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ heading = Math.atan2(y, x); if (heading < 0) { heading += 2 * Math.PI; } if (heading > 2 * Math.PI) { heading -= 2 * Math.PI; } return heading * (180 / Math.PI); } } }, // http://torrentula.to.funpic.de/tag/i2c/ setup: [ // CRA [0x00, 0x70], // CRB [0x01, 0x40], // Continuous measurement mode [0x02, 0x00] ], preread: [ [0x03] ] }, /** * HMC6352: 2-Axis Compass Module * 0x42 * * http://www.sparkfun.com/datasheets/Components/HMC6352.pdf * http://bildr.org/2011/01/hmc6352/ */ "HMC6352": { // "The Wire library uses 7 bit addresses throughout. // If you have a datasheet or sample code that uses 8 bit address, // you'll want to drop the low bit (i.e. shift the value one bit to // the right),yielding an address between 0 and 127." address: 0x42 >> 1, bytes: 2, delay: 100, data: function(data) { var raw = priv.get(this); var headingSum, headingInt; headingSum = (data[0] << 8) + data[1]; headingInt = headingSum / 10; headingInt = parseInt(headingInt, 10); raw.x = headingInt; }, properties: { /** * [read-only] Bearing information * @name bearing * @property * @type Object * * name abbr low mid high heading * */ bearing: { get: function() { var k, len, heading, point; k = 0; len = Compass.Points.length; heading = Math.floor(this.heading); for (; k < len; k++) { point = Compass.Points[k]; if (point.range.indexOf(heading) !== -1) { // Specify fields to return to avoid returning the // range array (too much noisy data) return { name: point.point, abbr: point.abbr, low: point.low, mid: point.mid, high: point.high, heading: heading }; } } } }, /** * [read-only] Heading (azimuth) * @name heading * @property * @type number */ heading: { get: function() { var heading, raw, x; // Aquire raw x, y, z data from private data map raw = priv.get(this); //this compass is flat so all we use is x x = raw.x; heading = x; if (heading < 0) { heading += 2 * Math.PI; } if (heading > 2 * Math.PI) { heading -= 2 * Math.PI; } return heading; } } }, setup: [], preread: [ [0x41] ] } }; /** * Fires once every N ms, equal to value of `freq`. Defaults to 66ms * * @event * @name read * @memberOf Compass */ /** * Fires when the calculated heading has changed * * @event * @name headingchange * @memberOf Compass */ module.exports = Compass; // http://en.wikipedia.org/wiki/Relative_direction