johnny-five
Version:
The JavaScript Robotics and Hardware Programming Framework. Use with: Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, TI Launchpad and more!
1,270 lines (1,054 loc) • 31.5 kB
JavaScript
/* istanbul ignore if */
if (!Array.from || !Object.assign || !Map) {
/* istanbul ignore next */
require("es6-shim");
}
/* istanbul ignore if */
if (!Array.prototype.includes) {
/* istanbul ignore next */
require("./array-includes-shim");
}
var IS_TEST_MODE = !!process.env.IS_TEST_MODE;
var Emitter = require("events").EventEmitter;
var util = require("util");
var chalk = require("chalk");
var Collection = require("./mixins/collection");
var Fn = require("./fn");
var Repl = require("./repl");
var Options = require("./board.options");
var Pins = require("./board.pins");
var Expander;
//var temporal = require("temporal");
// Environment Setup
var boards = [];
var rport = /usb|acm|^com/i;
// const things to const when 0.10.x is dropped
// This string appears over 20 times in this file.
var UNDEFINED = "undefined";
var Serial = {
used: [],
attempts: [],
detect: function(callback) {
var serialport;
/* istanbul ignore if */
if (parseFloat(process.versions.nw) >= 0.13) {
serialport = require("browser-serialport");
} else {
serialport = require("serialport");
}
// console.log(require);
// Request a list of available ports, from
// the result set, filter for valid paths
// via known path pattern match.
serialport.list(function(err, result) {
// serialport.list() will never result in an error.
// On failure, an empty array is returned. (#768)
var ports = result.filter(function(val) {
var available = true;
// Match only ports that Arduino cares about
// ttyUSB#, cu.usbmodem#, COM#
if (!rport.test(val.comName)) {
available = false;
}
// Don't allow already used/encountered usb device paths
if (Serial.used.includes(val.comName)) {
available = false;
}
return available;
}).map(function(val) {
return val.comName;
});
// If no ports are detected...
if (!ports.length) {
/* istanbul ignore if */
if (IS_TEST_MODE && this.abort) {
/* istanbul ignore next */
return;
}
// Create an attempt counter
/* istanbul ignore else */
if (!Serial.attempts[Serial.used.length]) {
Serial.attempts[Serial.used.length] = 0;
// Log notification...
this.info("Board", "Looking for connected device");
}
// Set the attempt number
Serial.attempts[Serial.used.length]++;
// Retry Serial connection
Serial.detect.call(this, callback);
return;
}
this.info("Device(s)", chalk.grey(ports));
// Get the first available device path
// from the list of detected ports
callback.call(this, ports[0]);
}.bind(this));
},
connect: function(portOrPath, callback) {
var IO = require("firmata").Board;
var caught = null;
var io, isConnected, path, type;
if (typeof portOrPath === "object" && portOrPath.path) {
//
// Board({ port: SerialPort Object })
//
path = portOrPath.path;
this.info(
(portOrPath.transport || "SerialPort"),
chalk.grey(path)
);
} else {
//
// Board({ port: path String })
//
// Board()
// ie. auto-detected
//
path = portOrPath;
}
// Add the usb device path to the list of device paths that
// are currently in use - this is used by the filter function
// above to remove any device paths that we've already encountered
// or used to avoid blindly attempting to reconnect on them.
Serial.used.push(path);
try {
io = new IO(portOrPath, function(error) {
if (error) {
caught = error;
}
callback.call(this, caught, caught ? "error" : "ready", io);
}.bind(this));
// Extend io instance with special expandos used
// by Johny-Five for the IO Plugin system.
io.name = "Firmata";
io.defaultLed = 13;
io.port = path;
// Made this far, safely connected
isConnected = true;
} catch (error) {
caught = error;
}
if (caught) {
caught = caught.message || caught;
}
// Determine the type of event that will be passed on to
// the board emitter in the callback passed to Serial.detect(...)
type = isConnected ? "connect" : "error";
// Execute "connect" callback
callback.call(this, caught, type, io);
}
};
/**
* Board
* @constructor
*
* @param {Object} opts
*/
function Board(opts) {
if (!(this instanceof Board)) {
return new Board(opts);
}
// Ensure opts is an object
opts = opts || {};
// Used to define the board instance's own
// properties in the REPL's scope.
var replContext = {};
// It's feasible that an IO-Plugin may emit
// "connect" and "ready" events out of order.
// This is used to enforce the order, by
// postponing the "ready" event if the IO-Plugin
// hasn't emitted a "connect" event. Once
// the "connect" event is emitted, the
// postponement is lifted and the board may
// proceed with emitting the events in the
// correct order.
var isPostponed = false;
// Initialize this Board instance with
// param specified properties.
Object.assign(this, opts);
this.timer = null;
this.isConnected = false;
// Easily track state of hardware
this.isReady = false;
// Initialize instance property to reference io board
this.io = this.io || null;
// Registry of components
this.register = [];
// Pins, Addr (alt Pin name), Addresses
this.occupied = [];
// Registry of drivers by address (i.e. I2C Controllers)
this.Drivers = {};
// Identify for connect hardware cache
if (!this.id) {
this.id = Fn.uid();
}
// If no debug flag, default to true
if (typeof this.debug === UNDEFINED) {
this.debug = true;
}
// If no repl flag, default to true
if (typeof this.repl === UNDEFINED) {
this.repl = true;
}
// If no sigint flag, default to true
if (typeof this.sigint === UNDEFINED) {
this.sigint = true;
}
// Specially processed pin capabilities object
// assigned when physical board has reported
// "ready" via Firmata or IO-Plugin.
this.pins = null;
// Create a Repl instance and store as
// instance property of this io/board.
// This will reduce the amount of boilerplate
// code required to _always_ have a Repl
// session available.
//
// If a sesssion exists, use it
// (instead of creating a new session)
//
/* istanbul ignore if */
if (this.repl) {
/* istanbul ignore if */
if (Repl.ref) {
/* istanbul ignore next */
replContext[this.id] = this;
/* istanbul ignore next */
Repl.ref.on("ready", function() {
/* istanbul ignore next */
Repl.ref.inject(replContext);
});
/* istanbul ignore next */
this.repl = Repl.ref;
} else {
replContext[this.id] = replContext.board = this;
this.repl = new Repl(replContext);
}
}
if (opts.io) {
// If you already have a connected io instance
this.io = opts.io;
this.isReady = opts.io.isReady;
this.transport = this.io.transport || null;
this.port = this.io.name;
this.pins = Board.Pins(this);
} else {
if (this.port && opts.port) {
Serial.connect.call(this, this.port, finalizeAndBroadcast);
} else {
Serial.detect.call(this, function(path) {
Serial.connect.call(this, path, finalizeAndBroadcast);
});
}
}
// Either an IO instance was provided or isOnBoard is true
if (!opts.port && this.io !== null) {
/* istanbul ignore next */
this.info("Device(s)", chalk.grey(this.io.name || "unknown"));
["connect", "ready"].forEach(function(type) {
this.io.once(type, function() {
// Since connection and readiness happen asynchronously,
// it's actually possible for Johnny-Five to receive the
// events out of order and that should be ok.
if (type === "ready" && !this.isConnected) {
isPostponed = true;
} else {
// Will emit the "connect" and "ready" events
// if received in order. If out of order, this
// will only emit the "connect" event. The
// "ready" event will be handled in the next
// condition's consequent.
finalizeAndBroadcast.call(this, null, type, this.io);
}
if (type === "connect" && isPostponed) {
finalizeAndBroadcast.call(this, null, "ready", this.io);
}
}.bind(this));
if (this.io.isReady) {
// If the IO instance is reached "ready"
// state, queue tick tasks to emit the
// "connect" and "ready" events
process.nextTick(function() {
this.io.emit(type);
}.bind(this));
}
}, this);
}
this.once("ready", function() {
var hrstart = process.hrtime();
this.millis = function() {
var now = process.hrtime(hrstart);
return (now[1] / 1000000);
};
["close", "disconnect", "error", "string"].forEach(function(type) {
this.io.on(type, function(data) {
this.emit(type, data);
}.bind(this));
}, this);
}.bind(this));
// Cache instance to allow access from module constructors
boards.push(this);
}
function finalizeAndBroadcast(data, type, io) {
var emitted = false;
// Assign found io to instance
if (!this.io) {
this.io = io;
}
// Always Surface errors
if (type === "error") {
/* istanbul ignore else */
if (data && data.message) {
emitted = true;
this.error("Error", data.message);
}
}
if (type === "connect") {
this.isConnected = true;
this.port = io.port || io.name;
this.info(
"Connected",
chalk.grey(this.port)
);
// Unless a "timeout" value has been provided apply 10 Second timeout...
//
// If "ready" hasn't fired and cleared the timer within
// 10 seconds of the connect event, then it's likely
// there is an issue with the device or firmware.
if (!IS_TEST_MODE) {
/* istanbul ignore next */
this.timer = setTimeout(function() {
this.error(
"Device or Firmware Error",
"A timeout occurred while connecting to the Board. \n\n" +
"Please check that you've properly flashed the board with the correct firmware.\n" +
"See: https://github.com/rwaldron/johnny-five/wiki/Getting-Started#trouble-shooting\n\n" +
"If connecting to a Leonardo or Leonardo clone, press the 'Reset' button on the " +
"board, wait approximately 11 seconds for complete reset, then run your program again."
);
this.emit("error", new Error("A timeout occurred while connecting to the Board."));
}.bind(this), this.timeout || 1e4);
}
}
if (type === "ready") {
if (this.timer) {
clearTimeout(this.timer);
}
// Update instance `ready` flag
this.isReady = true;
this.pins = Board.Pins(this);
this.MODES = this.io.MODES;
if (typeof io.debug !== UNDEFINED &&
io.debug === false) {
this.debug = false;
}
if (typeof io.repl !== UNDEFINED &&
io.repl === false) {
this.repl = false;
}
// In multi-board mode, block the REPL from
// activation. This will be started directly
// by the Board.Collection constructor.
//
// In single-board mode, the REPL will not
// be blocked at all.
//
// If the user program has not disabled the
// REPL, initialize it.
if (this.repl) {
this.repl.initialize(this.emit.bind(this, "ready"));
}
if (io.name !== "Mock" && this.sigint) {
process.on("SIGINT", function() {
this.emit("exit");
this.warn("Board", "Closing.");
process.nextTick(process.reallyExit);
}.bind(this));
}
}
// If there is a REPL...
if (this.repl) {
// "ready" will be emitted once repl.initialize
// is complete, so the only event that needs to
// be propagated here is the "connect" event.
if (type === "connect") {
this.emit(type, data);
}
} else {
// The REPL is disabled, propagate all events
if (!emitted) {
this.emit(type, data);
}
}
}
// Inherit event api
util.inherits(Board, Emitter);
/**
* Pass through methods
*/
[
"digitalWrite", "analogWrite",
"analogRead", "digitalRead",
"pinMode", "queryPinState",
"stepperConfig", "stepperStep",
"sendI2CConfig", "sendI2CWriteRequest", "sendI2CReadRequest",
"i2cConfig", "i2cWrite", "i2cWriteReg", "i2cRead", "i2cReadOnce",
"pwmWrite",
"servoConfig", "servoWrite",
"sysexCommand", "sysexResponse",
"serialConfig", "serialWrite", "serialRead", "serialStop", "serialClose", "serialFlush", "serialListen",
].forEach(function(method) {
/* istanbul ignore next */
Board.prototype[method] = function() {
this.io[method].apply(this.io, arguments);
return this;
};
});
Board.prototype.snapshot = function(reducer) {
var blacklist = this.snapshot.blacklist;
var special = this.snapshot.special;
var hasReducer = typeof reducer === "function";
return this.register.reduce(function(accum, component) {
// Don't include collections or multi/imu wrappers
if (typeof component.components === UNDEFINED) {
accum.push(
Object.getOwnPropertyNames(component).reduce(function(data, prop) {
var value = component[prop];
if (!blacklist.includes(prop) && typeof value !== "function") {
if (hasReducer) {
var result = reducer(prop, value, component);
if (result !== undefined) {
data[prop] = result;
}
} else {
data[prop] = special[prop] ?
special[prop](value) : value;
}
}
return data;
}, Object.create(null))
);
}
return accum;
}.bind(this), []);
};
Board.prototype.serialize = function(reducer) {
return JSON.stringify(this.snapshot(reducer));
};
Board.prototype.snapshot.blacklist = [
"board", "io", "_events", "_eventsCount", "state",
];
Board.prototype.samplingInterval = function(ms) {
if (this.io.setSamplingInterval) {
this.io.setSamplingInterval(ms);
} else {
throw new Error("This IO plugin does not implement an interval adjustment method");
}
return this;
};
Board.prototype.snapshot.special = {
mode: function(value) {
return ["INPUT", "OUTPUT", "ANALOG", "PWM", "SERVO"][value] || "unknown";
}
};
/**
* shiftOut
*
*/
Board.prototype.shiftOut = function(dataPin, clockPin, isBigEndian, value) {
if (arguments.length === 3) {
value = isBigEndian;
isBigEndian = true;
}
for (var i = 0; i < 8; i++) {
this.io.digitalWrite(clockPin, 0);
if (isBigEndian) {
this.io.digitalWrite(dataPin, !!(value & (1 << (7 - i))) | 0);
} else {
this.io.digitalWrite(dataPin, !!(value & (1 << i)) | 0);
}
this.io.digitalWrite(clockPin, 1);
}
};
var logging = {
specials: [
"error",
"fail",
"warn",
"info",
],
colors: {
log: "white",
error: "red",
fail: "inverse",
warn: "yellow",
info: "cyan"
}
};
Board.prototype.log = function( /* type, klass, message [, long description] */ ) {
var args = Array.from(arguments);
// If this was a direct call to `log(...)`, make sure
// there is a correct "type" to emit below.
if (!logging.specials.includes(args[0])) {
args.unshift("log");
}
var type = args.shift();
var klass = args.shift();
var message = args.shift();
var color = logging.colors[type];
var now = Date.now();
var event = {
type: type,
timestamp: now,
class: klass,
message: "",
data: null,
};
if (typeof args[args.length - 1] === "object") {
event.data = args.pop();
}
message += " " + args.join(", ");
event.message = message.trim();
/* istanbul ignore if */
if (this.debug) {
/* istanbul ignore next */
console.log([
// Timestamp
chalk.grey(now),
// Module, color matches type of log
chalk.magenta(klass),
// Details
chalk[color](message),
// Miscellaneous args
args.join(", ")
].join(" "));
}
this.emit(type, event);
this.emit("message", event);
};
// Make shortcuts to all logging methods
logging.specials.forEach(function(type) {
Board.prototype[type] = function() {
var args = [].slice.call(arguments);
args.unshift(type);
this.log.apply(this, args);
};
});
/**
* delay, loop, queue
*
* Pass through methods to temporal
*/
/*
[
"delay", "loop", "queue"
].forEach(function( method ) {
Board.prototype[ method ] = function( time, callback ) {
temporal[ method ]( time, callback );
return this;
};
});
// Alias wait to delay to match existing Johnny-five API
Board.prototype.wait = Board.prototype.delay;
*/
// -----THIS IS A TEMPORARY FIX UNTIL THE ISSUES WITH TEMPORAL ARE RESOLVED-----
// Aliasing.
// (temporary, while ironing out API details)
// The idea is to match existing hardware programming apis
// or simply find the words that are most intuitive.
// Eventually, there should be a queuing process
// for all new callbacks added
//
// TODO: Repalce with temporal or compulsive API
Board.prototype.wait = function(time, callback) {
setTimeout(callback, time);
return this;
};
Board.prototype.loop = function(time, callback) {
var handler = function() {
callback(function() {
clearInterval(interval);
});
};
var interval = setInterval(handler, time);
return this;
};
// ----------
// Static API
// ----------
// Board.map( val, fromLow, fromHigh, toLow, toHigh )
//
// Re-maps a number from one range to another.
// Based on arduino map()
Board.map = Fn.map;
Board.fmap = Fn.fmap;
// Board.constrain( val, lower, upper )
//
// Constrains a number to be within a range.
// Based on arduino constrain()
Board.constrain = Fn.constrain;
// Board.range( upper )
// Board.range( lower, upper )
// Board.range( lower, upper, tick )
//
// Returns a new array range
//
Board.range = Fn.range;
// Board.range.prefixed( prefix, upper )
// Board.range.prefixed( prefix, lower, upper )
// Board.range.prefixed( prefix, lower, upper, tick )
//
// Returns a new array range, each value prefixed
//
Board.range.prefixed = Fn.range.prefixed;
// Board.uid()
//
// Returns a reasonably unique id string
//
Board.uid = Fn.uid;
// Board.mount()
// Board.mount( index )
// Board.mount( object )
//
// Return hardware instance, based on type of param:
// @param {arg}
// object, user specified
// number/index, specified in cache
// none, defaults to first in cache
//
// Notes:
// Used to reduce the amount of boilerplate
// code required in any given module or program, by
// giving the developer the option of omitting an
// explicit Board reference in a module
// constructor's options
Board.mount = function(arg) {
var index = typeof arg === "number" && arg,
hardware;
// board was explicitly provided
if (arg && arg.board) {
return arg.board;
}
// index specified, attempt to return
// hardware instance. Return null if not
// found or not available
if (typeof index === "number") {
hardware = boards[index];
return hardware && hardware || null;
}
// If no arg specified and hardware instances
// exist in the cache
if (boards.length) {
return boards[0];
}
// No mountable hardware
return null;
};
/**
* Board.Component
*
* Initialize a new device instance
*
* Board.Component is a |this| sensitive constructor,
* and must be called as:
*
* Board.Component.call( this, opts );
*
*
*
* TODO: Migrate all constructors to use this
* to avoid boilerplate
*/
Board.Component = function(opts, componentOpts) {
if (typeof opts === UNDEFINED) {
opts = {};
}
if (typeof componentOpts === UNDEFINED) {
componentOpts = {};
}
// Board specific properties
this.board = Board.mount(opts);
this.io = this.board.io;
// Component/Module instance properties
this.id = opts.id || Board.uid();
this.custom = opts.custom || {};
var originalPins;
if (typeof opts.pin === "number" || typeof opts.pin === "string") {
originalPins = [opts.pin];
} else {
if (Array.isArray(opts.pins)) {
originalPins = opts.pins.slice();
} else {
if (typeof opts.pins === "object" && opts.pins !== null) {
var pinset = opts.pins || opts.pin;
originalPins = [];
for (var p in pinset) {
originalPins.push(pinset[p]);
}
}
}
}
if (opts.controller) {
if (typeof opts.controller === "string") {
opts.controller = opts.controller.replace(/-/g, "");
}
if (!Expander) {
Expander = require("./expander");
}
if (Expander.hasController(opts.controller)) {
componentOpts = {
normalizePin: false,
requestPin: false,
};
}
}
componentOpts = Board.Component.initialization(componentOpts);
if (componentOpts.normalizePin) {
opts = Board.Pins.normalize(opts, this.board);
}
// var requesting = [];
if (typeof opts.pins !== UNDEFINED) {
this.pins = opts.pins || [];
// if (Array.isArray(this.pins)) {
// requesting = requesting.concat(
// this.pins.map(function(pin) {
// return {
// value: pin,
// type: "pin"
// };
// })
// );
// } else {
// requesting = requesting.concat(
// Object.keys(this.pins).map(function(key) {
// return {
// value: this.pins[key],
// type: "pin"
// };
// }, this)
// );
// }
}
if (typeof opts.pin !== UNDEFINED) {
this.pin = opts.pin;
// requesting.push({
// value: this.pin,
// type: "pin"
// });
}
// TODO: Figure out what is using this
/* istanbul ignore if */
if (typeof opts.emitter !== UNDEFINED) {
/* istanbul ignore next */
this.emitter = opts.emitter;
// requesting.push({
// value: this.emitter,
// type: "emitter"
// });
}
if (typeof opts.address !== UNDEFINED) {
this.address = opts.address;
// requesting.forEach(function(request) {
// request.address = this.address;
// }, this);
}
if (typeof opts.controller !== UNDEFINED) {
this.controller = opts.controller;
// requesting.forEach(function(request) {
// request.controller = this.controller;
// }, this);
}
// TODO: Figure out what is using this
/* istanbul ignore if */
if (typeof opts.bus !== UNDEFINED) {
/* istanbul ignore next */
this.bus = opts.bus;
// requesting.forEach(function(request) {
// request.bus = this.bus;
// }, this);
}
// if (componentOpts.requestPin) {
// // With the pins being requested for use by this component,
// // compare with the list of pins that are already known to be
// // in use by other components. If any are known to be in use,
// // produce a warning for the user.
// requesting.forEach(function(request, index) {
// var hasController = typeof request.controller !== UNDEFINED;
// var hasAddress = typeof request.address !== UNDEFINED;
// var isOccupied = false;
// var message = "";
// request.value = originalPins[index];
// if (this.board.occupied.length) {
// isOccupied = this.board.occupied.some(function(occupied) {
// var isPinOccupied = request.value === occupied.value && request.type === occupied.type;
// if (typeof occupied.controller !== UNDEFINED) {
// if (hasController) {
// return isPinOccupied && (request.controller === occupied.controller);
// }
// return false;
// }
// if (typeof occupied.address !== UNDEFINED) {
// if (hasAddress) {
// return isPinOccupied && (request.address === occupied.address);
// }
// return false;
// }
// return isPinOccupied;
// });
// }
// if (isOccupied) {
// message = request.type + ": " + request.value;
// if (hasController) {
// message += ", controller: " + request.controller;
// }
// if (hasAddress) {
// message += ", address: " + request.address;
// }
// this.board.warn("Component", message + " is already in use");
// } else {
// this.board.occupied.push(request);
// }
// }, this);
// }
this.board.register.push(this);
};
Board.Component.initialization = function(opts) {
var defaults = {
requestPin: true,
normalizePin: true
};
return Object.assign({}, defaults, opts);
};
/**
* Board.Controller
*
* Decorate a Component with a Controller. Must be called
* _AFTER_ a Controller is identified.
*
* Board.Controller is a |this| sensitive constructor,
* and must be called as:
*
* Board.Controller.call( this, controller, opts );
*
*/
Board.Controller = function(controller, options) {
var requirements = controller.requirements && controller.requirements.value;
if (requirements) {
/* istanbul ignore else */
if (requirements.options) {
Object.keys(requirements.options).forEach(function(key) {
/*
requirements: {
value: {
options: {
parameterName: {
throws: false,
message: "...blah blah blah",
typeof: "number",
}
}
}
},
*/
if (typeof options[key] === UNDEFINED ||
typeof options[key] !== requirements.options[key].typeof) {
if (requirements.options[key].throws) {
throw new Error(requirements.options[key].message);
} else {
this.board.warn(this.constructor.name, requirements.options[key].message);
}
}
}, this);
}
}
Object.defineProperties(this, controller);
};
/**
* Pin Capability Signature Mapping
*/
Board.Pins = Pins;
Board.Options = Options;
// Define a user-safe, unwritable hardware cache access
Object.defineProperty(Board, "cache", {
get: function() {
return boards;
}
});
/**
* Board event constructor.
* opts:
* type - event type. eg: "read", "change", "up" etc.
* target - the instance for which the event fired.
* 0..* other properties
*/
Board.Event = function(event) {
if (typeof event === UNDEFINED) {
throw new Error("Board.Event missing Event object");
}
// default event is read
this.type = event.type || "data";
// actual target instance
this.target = event.target || null;
// Initialize this Board instance with
// param specified properties.
Object.assign(this, event);
};
/**
* Boards or Board.Collection; Used when the program must connect to
* more then one board.
*
* @memberof Board
*
* @param {Array} ports List of port objects { id: ..., port: ... }
* List of id strings (initialized in order)
*
* @return {Boards} board object references
*/
function Boards(opts) {
if (!(this instanceof Boards)) {
return new Boards(opts);
}
var ports;
// new Boards([ ...Array of board opts ])
if (Array.isArray(opts)) {
ports = opts.slice();
opts = {
ports: ports,
};
}
// new Boards({ ports: [ ...Array of board opts ], .... })
/* istanbul ignore else */
if (!Array.isArray(opts) && typeof opts === "object" && opts.ports !== undefined) {
ports = opts.ports;
}
// new Boards(non-Array?)
// new Boards({ ports: non-Array? })
/* istanbul ignore if */
if (!Array.isArray(ports)) {
throw new Error("Expected ports to be an array");
}
if (typeof opts.debug === UNDEFINED) {
opts.debug = true;
}
if (typeof opts.repl === UNDEFINED) {
opts.repl = true;
}
var initialized = {};
var noRepl = ports.some(function(port) { return port.repl === false; });
var noDebug = ports.some(function(port) { return port.debug === false; });
this.length = ports.length;
this.debug = opts.debug;
this.repl = opts.repl;
// If any of the port definitions have
// explicitly shut off debug output, bubble up
// to the Boards instance
/* istanbul ignore else */
if (noDebug) {
this.debug = false;
}
// If any of the port definitions have
// explicitly shut off the repl, bubble up
// to the Boards instance
/* istanbul ignore else */
if (noRepl) {
this.repl = false;
}
var expecteds = ports.map(function(port, index) {
var portOpts;
if (typeof port === "string") {
portOpts = {};
// If the string matches a known valid port
// name pattern, then assume this is what
// the user code intended.
if (rport.test(port)) {
portOpts.port = port;
} else {
// Otherwise they expect Johnny-Five to figure
// out what ports to use and intended this
// value to be used an id
portOpts.id = port;
}
} else {
portOpts = port;
}
// Shut off per-board repl instance creation
portOpts.repl = false;
this[index] = initialized[portOpts.id] = new Board(portOpts);
// "error" event is not async, register immediately
this[index].on("error", function(error) {
this.emit("error", error);
}.bind(this));
return new Promise(function(resolve) {
this[index].on("ready", function() {
resolve(initialized[portOpts.id]);
});
}.bind(this));
}, this);
Promise.all(expecteds).then(function(boards) {
Object.assign(this, boards);
this.each(function(board) {
board.info("Board ID: ", chalk.green(board.id));
});
// If the Boards instance requires a REPL,
// make sure it's created before calling "ready"
if (this.repl) {
this.repl = new Repl(
Object.assign({}, initialized, {
board: this
})
);
this.repl.initialize(function() {
this.emit("ready", initialized);
}.bind(this));
} else {
// Otherwise, call ready immediately
this.emit("ready", initialized);
}
}.bind(this));
}
util.inherits(Boards, Emitter);
Object.assign(Boards.prototype, Collection.prototype);
Boards.prototype.byId = function(id) {
for (var i = 0; i < this.length; i++) {
if (this[i].id === id) {
return this[i];
}
}
return null;
};
Boards.prototype.log = Board.prototype.log;
logging.specials.forEach(function(type) {
/* istanbul ignore next */
Boards.prototype[type] = function() {
var args = [].slice.call(arguments);
args.unshift(type);
this.log.apply(this, args);
};
});
/* istanbul ignore else */
if (IS_TEST_MODE) {
Serial.purge = function() {
Serial.used.length = 0;
};
Board.Serial = Serial;
Board.purge = function() {
Board.Pins.normalize.clear();
Repl.isActive = false;
Repl.isBlocked = true;
Repl.ref = null;
boards.length = 0;
};
Board.testMode = function(state) {
if (!arguments.length) {
return IS_TEST_MODE;
} else {
IS_TEST_MODE = state;
}
};
}
// TODO: Eliminate .Array for 1.0.0
Board.Array = Boards;
Board.Collection = Boards;
module.exports = Board;
// References:
// http://arduino.cc/en/Main/arduinoBoardUno