UNPKG

johnny-five

Version:

The JavaScript Arduino Programming Framework.

349 lines (288 loc) 7.71 kB
var Board = require("../lib/board.js"), events = require("events"), util = require("util"), __ = require("../lib/fn.js"); function calculate(method, y, x) { return Math[method](y, x) * 180 / Math.PI; } function acceleration(axis, vRange, sensitivity) { var voltage; // convert raw reading to voltage // (read * vRange / 1024) - (vRange / 2) voltage = (axis * vRange / 1024) - (vRange / 2); // 800mV sensitivity return voltage / sensitivity; } // Filter for change events // Pass in data by type // e.g. accel.get("smooth") function axisChange(data, threshold) { var changed, axes; axes = Object.keys(data); changed = axes.map(function(axis) { var extent = data[axis].length - 1; return data[axis][extent] - data[axis][extent - 1]; }); return changed.some(function(element) { return Math.abs(element) > threshold; }); } // relatively terse updating function // vs. this.data.thing.axis.whatever = value // queue is the array on whatever object we want to update function update(queue, value, extent) { queue.push(value); if (queue.length > extent) { queue.shift(); } return queue; } // build data based on types and axes passed in // initialize at zero function dataStr(types, axes) { var container = {}; types.forEach(function(type) { container[type] = {}; axes.forEach(function(axis) { container[type][axis] = [0]; }); }); return container; } /* Expects an options argument e.g. { accel: this.data.accel, smooth: this.data.smooth, alpha: this.alpha, extent: this.extent } */ function exponential(smopts) { var smooth, accel; smooth = smopts.smooth; accel = smopts.accel; this.axes.forEach(function(axis) { var acper, smper, currsm; // Exponential smoothing // see http://people.duke.edu/~rnau/411avg.htm acper = accel[axis]; smper = smooth[axis]; // bootstrap for initial state if (smper.length < smopts.extent) { smooth[axis] = update(smper, acper[0], smopts.extent); } else { // convolves past smoothed/nonsmooth readings currsm = smper[0] + smopts.alpha * (acper[0] - smper[0]); // update current smoothed values smooth[axis] = update(smper, currsm, smopts.extent); } }); return smooth; } /** * Accelerometer * @constructor * * five.Accelerometer([ x, y[, z] ]); * * five.Accelerometer({ * pins: [ x, y[, z] ] * freq: ms * }); * * * @param {Object} opts [description] * */ function Accelerometer(opts) { if (!(this instanceof Accelerometer)) { return new Accelerometer(opts); } var err = null; // Initialize a Device instance on a Board Board.Device.call( this, opts = Board.Options(opts) ); this.mode = this.io.MODES.ANALOG; // Accelerometer instance properties this.voltage = opts.voltage || 3.3; this.sensitivity = opts.sensitivity || 0.8; this.freq = opts.freq || 50; // Threshold needs to be tested this.threshold = opts.threshold || 0.5; // axis keys this.axes = opts.axes || ["x", "y", "z"]; // types (not really important unless you need them) this.types = opts.types || ["smooth", "accel", "trend"]; // how many past values to store this.extent = opts.extent || 2; // some smoothing methods require bootstrapping this.initial = opts.initial || true; // build data based on types and axes passed in // initialize at zero this.data = dataStr(this.types, this.axes); // Blending property for the smoother. // Smaller is less filtering this.alpha = opts.alpha || 0.2; // default smoother is an exponential smoother this.smoother = opts.smoother || exponential; // Setup read listeners for each pin, update instance // properties as they are received. Special values are // calculated during the throttled event emit phase this.pins.forEach(function(pin, index) { // Set the pin to input mode to ANALOG this.io.pinMode(pin, this.mode); this.io.analogRead(pin, function(data) { var paxis, accel, sink, magnitude; paxis = this.axes[index]; accel = acceleration(data, this.voltage, this.sensitivity); sink = this.data.accel[paxis]; // The output we're interested in most of the time this.data.accel[paxis] = update(sink, accel, this.extent); // this will trigger a [[Get]] which will // process the the acceleration data and // keep the magnitude value current. magnitude = this.magnitude; }.bind(this)); }, this); // Throttle event emitter setInterval(function() { this.data.smooth = this.smoother({ accel: this.data.accel, smooth: this.data.smooth, alpha: this.alpha, extent: this.extent }); var data = { smooth: this.data.smooth, rough: this.data.accel }; // Check each axis for change above some threshold if (axisChange(this.data.accel, this.threshold)) { ["axischange", "change"].forEach(function(type) { this.emit(type, err, data); }, this); } this.emit("acceleration", err, data); this.emit("data", err, data); }.bind(this), this.freq); /** * raw x, y, z data * @property raw * @type Object */ /** * smooth x, y, z data * @property smooth * @type Object */ /** * accel calculated x, y, z data * @property axis * @type Object */ var old = { x: 0, y: 0, z: 0 }; Object.defineProperties(this, { /** * [read-only] Calculated pitch value * @property pitch * @type Number */ pitch: { get: function() { var x, y, z, accel; accel = this.data.accel; x = accel.x[1]; y = accel.y[1]; z = this.axes.indexOf("z") > -1 ? accel.z[1] : 0; return Math.abs( Math.atan2( x, Math.sqrt(Math.pow(y, 2) + Math.pow(z, 2)) ) ); } }, /** * [read-only] Calculated roll value * @property roll * @type Number */ roll: { get: function() { var x, y, z, accel; accel = this.data.accel; x = accel.x[1]; y = accel.y[1]; z = this.axes.indexOf("z") > -1 ? accel.z[1] : 0; return Math.abs( Math.atan2( y, Math.sqrt(Math.pow(x, 2) + Math.pow(z, 2)) ) ); } }, /** * [read-only] Calculated magnitude value * @property magnitude * @type Number */ magnitude: { get: function() { var accel, x, y, z, deltax, deltay, deltaz; accel = this.data.accel; x = accel.x[1]; y = accel.y[1]; z = this.axes.indexOf("z") > -1 ? accel.z[1] : 0; deltax = x - old.x; deltay = y - old.y; deltaz = z - old.z; old.x = x; old.y = y; old.z = z; return Math.sqrt( (deltax * deltax) + (deltay * deltay) + (deltaz * deltaz) ); } } }); } util.inherits(Accelerometer, events.EventEmitter); Object.defineProperty(Accelerometer, "G", { /** * [read-only] One g is the acceleration due to gravity at the Earth's surface * * meters/s ^ 2 * * @property G * @type Number */ value: 9.81 }); /** * Fires once every N ms, equal to value of `freq`. Defaults to 500ms * * @event * @name acceleration * @memberOf Accelerometer */ /** * Fires only when X, Y or Z has changed * * @event * @name axischange * @memberOf Accelerometer */ module.exports = Accelerometer; // References // // http://www.instructables.com/id/Accelerometer-Gyro-Tutorial/ // // Images // // http://www.instructables.com/image/F7NMMPEG4PBOJPY/The-Accelerometer.jpg