UNPKG

johnny-five-electron

Version:

Temporary fork to support Electron (to be deprecated)

290 lines (229 loc) 6.65 kB
var Board = require("../lib/board.js"), events = require("events"), util = require("util"), __ = require("../lib/fn.js"), Led = require("../lib/led"), Sensor = require("../lib/sensor"); var CALIBRATED_MIN_VALUE = 0; var CALIBRATED_MAX_VALUE = 1000; var LINE_ON_THRESHOLD = 200; var LINE_NOISE_THRESHOLD = 50; var priv = new Map(); // Private methods function initialize() { var self = this, state = priv.get(this); if (typeof this.opts.emitter === "undefined") { throw new Error("Emitter pin is required"); } if (!this.pins || this.pins.length === 0) { throw new Error("Pins must be defined"); } state.emitter = new Led({ board: this.board, pin: this.opts.emitter }); state.sensorStates = this.pins.map(function(pin) { var sensorState = { sensor: new Sensor({ board: this.board, freq: this.freq, pin: pin }), rawValue: 0, dataReceived: false }; sensorState.sensor.on("data", function() { onData.call(self, sensorState, this.value); }); return sensorState; }, this); } function onData(sensorState, value) { var allRead, state = priv.get(this); sensorState.dataReceived = true; sensorState.rawValue = value; allRead = __.every(state.sensorStates, "dataReceived"); if (allRead) { this.emit("data", null, this.raw); if (state.autoCalibrate) { setCalibration(state.calibration, this.raw); } if (this.isCalibrated) { this.emit("calibratedData", null, this.values); this.emit("line", null, this.line); } state.sensorStates.forEach(function(sensorState) { sensorState.dataReceived = false; }); } } function setCalibration(calibration, values) { values.forEach(function(value, i) { if (calibration.min[i] === undefined || value < calibration.min[i]) { calibration.min[i] = value; } if (calibration.max[i] === undefined || value > calibration.max[i]) { calibration.max[i] = value; } }); } function calibrationIsValid(calibration, sensors) { return calibration && (calibration.max && calibration.max.length === sensors.length) && (calibration.min && calibration.min.length === sensors.length); } function calibratedValues() { return this.raw.map(function(value, i) { var max = this.calibration.max[i], min = this.calibration.min[i]; var scaled = __.scale(value, min, max, CALIBRATED_MIN_VALUE, CALIBRATED_MAX_VALUE); return __.constrain(scaled, CALIBRATED_MIN_VALUE, CALIBRATED_MAX_VALUE); }, this); } function maxLineValue() { return (this.sensors.length - 1) * CALIBRATED_MAX_VALUE; } // Returns a value between 0 and (n-1)*1000 // Given 5 sensors, the value will be between 0 and 4000 function getLine(whiteLine) { var onLine = false; var avg = 0, sum = 0; var state = priv.get(this); whiteLine = !!whiteLine; this.values.forEach(function(value, i) { value = whiteLine ? (CALIBRATED_MAX_VALUE - value) : value; if (value > LINE_ON_THRESHOLD) { onLine = true; } if (value > LINE_NOISE_THRESHOLD) { avg += value * i * CALIBRATED_MAX_VALUE; sum += value; } }); if (!onLine) { var maxPoint = maxLineValue.call(this) + 1; var centerPoint = maxPoint/2; return state.lastLine < centerPoint ? 0 : maxPoint; } return state.lastLine = Math.floor(avg/sum); } // Constructor function ReflectanceArray(opts) { if (!(this instanceof ReflectanceArray)) { return new ReflectanceArray(opts); } this.opts = Board.Options(opts); Board.Component.call( this, this.opts, { requestPin: false } ); // Read event throttling this.freq = opts.freq || 25; // Make private data entry var state = { lastLine: 0, isOn: false, calibration: { min: [], max: [] }, autoCalibrate: opts.autoCalibrate || false }; priv.set(this, state); initialize.call(this); Object.defineProperties(this, { isOn: { get: function() { return state.emitter.isOn; } }, isCalibrated: { get: function() { return calibrationIsValid(this.calibration, this.sensors); } }, isOnLine: { get: function() { var line = this.line; return line > CALIBRATED_MIN_VALUE && line < maxLineValue.call(this); } }, sensors: { get: function() { return __.pluck(state.sensorStates, "sensor"); } }, calibration: { get: function() { return state.calibration; } }, raw: { get: function() { return __.pluck(state.sensorStates, "rawValue"); } }, values: { get: function() { return this.isCalibrated ? calibratedValues.call(this) : this.raw; } }, line: { get: function() { return this.isCalibrated ? getLine.call(this) : 0; } } }); } util.inherits(ReflectanceArray, events.EventEmitter); // Public methods ReflectanceArray.prototype.enable = function() { var state = priv.get(this); state.emitter.on(); return this; }; ReflectanceArray.prototype.disable = function() { var state = priv.get(this); state.emitter.off(); return this; }; // Calibrate will store the min/max values for this sensor array // It should be called many times in order to get a lot of readings // on light and dark areas. See calibrateUntil for a convenience // for looping until a condition is met. ReflectanceArray.prototype.calibrate = function() { var state = priv.get(this); this.once("data", function(err, values) { setCalibration(state.calibration, values); this.emit("calibrated"); }); return this; }; // This will continue to calibrate until the predicate is true. // Allows the user to calibrate n-times, or wait for user input, // or base it on calibration heuristics. However the user wants. ReflectanceArray.prototype.calibrateUntil = function(predicate) { var loop = function() { this.calibrate(); this.once("calibrated", function() { if (!predicate()) { loop(); } }); }.bind(this); loop(); return this; }; // Let the user tell us what the calibration data is // This allows the user to save calibration data and // reload it without needing to calibrate every time. ReflectanceArray.prototype.loadCalibration = function(calibration) { var state = priv.get(this); if (!calibrationIsValid(calibration, this.sensors)) { throw new Error("Calibration data not properly set: {min: [], max: []}"); } state.calibration = calibration; return this; }; module.exports = ReflectanceArray;