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,195 lines (996 loc) • 29.9 kB
JavaScript
const Emitter = require("./mixins/emitter");
const chalk = require("chalk");
const Collection = require("./mixins/collection");
const Fn = require("./fn");
const Repl = require("./repl");
const Options = require("./board.options");
const Pins = require("./board.pins");
// This may get overridden by the tests
let IS_TEST_MODE = !!process.env.IS_TEST_MODE;
let Expander;
//const temporal = require("temporal");
// Environment Setup
const boards = [];
const rport = /usb|acm|^com/i;
// const things to const when 0.10.x is dropped
// This string appears over 20 times in this file.
const UNDEFINED = "undefined";
const Serial = {
used: [],
attempts: [],
detect(callback) {
// Max number of times listing serial conntections can fail
const maxAttempts = 10;
// Delay (ms) before trying again to list serial connections
const retryDelay = 400;
let serialport;
/* istanbul ignore next */
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().then(results => {
const portPaths = results.reduce((accum, result) => {
let available = true;
// Match only portPaths that Arduino cares about
// ttyUSB#, cu.usbmodem#, COM#
if (!rport.test(result.path)) {
available = false;
}
// Don't allow already used/encountered usb device paths
if (Serial.used.includes(result.path)) {
available = false;
}
if (available) {
accum.push(result.path);
}
return accum;
}, []);
// If no portPaths are detected...
if (!portPaths.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
if (Serial.attempts[Serial.used.length] > maxAttempts) {
this.fail("Board", "No connected device found");
return;
}
setTimeout(() => {
Serial.detect.call(this, callback);
}, retryDelay);
return;
}
this.info("Available", chalk.grey(portPaths));
// Get the first available device path
// from the list of detected portPaths
callback.call(this, portPaths[0]);
});
},
connect(portOrPath, callback) {
const IO = require("firmata").Board;
let caught = null;
let io;
let isConnected;
let path;
let 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, error => {
if (error) {
caught = error;
}
callback.call(this, caught, caught ? "error" : "ready", io);
});
// 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} options
*/
class Board extends Emitter {
constructor(options = {}) {
super();
// Used to define the board instance's own
// properties in the REPL's scope.
const 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.
let isPostponed = false;
// Initialize this Board instance with
// param specified properties.
Object.assign(this, options);
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 (options.io) {
// If you already have a connected io instance
this.io = options.io;
this.isReady = options.io.isReady;
this.transport = this.io.transport || null;
this.port = this.io.name;
this.pins = Board.Pins(this);
this.RESOLUTION = Object.assign({ ADC: 1023, DAC: null, PWM: 255 }, this.io.RESOLUTION || {});
} else {
if (this.port && options.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 (!options.port && this.io !== null) {
/* istanbul ignore next */
this.info("Available", chalk.grey(this.io.name || "unknown"));
["connect", "ready"].forEach((type) => {
this.io.once(type, () => {
// 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);
}
});
if (this.io.isReady) {
// If the IO instance is reached "ready"
// state, queue tick tasks to emit the
// "connect" and "ready" events
process.nextTick(() => this.io.emit(type));
}
});
}
this.once("ready", () => {
const hrstart = process.hrtime();
this.millis = function() {
const now = process.hrtime(hrstart);
return (now[1] / 1000000);
};
["close", "disconnect", "error", "string"].forEach(type => {
this.io.on(type, data => this.emit(type, data));
});
});
// Cache instance to allow access from module constructors
boards.push(this);
}
}
function finalizeAndBroadcast(data, type, io) {
let hasBeenEmitted = 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) {
hasBeenEmitted = 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(() => {
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."));
}, 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("ready"));
}
if (io.name !== "Mock" && this.sigint) {
process.on("SIGINT", () => {
// Time to wait before forcing exit
const failExitTimeout = 1000;
this.emit("exit");
this.warn("Board", "Closing.");
/* istanbul ignore next */
const timeout = setTimeout(() => {
process.reallyExit();
}, failExitTimeout);
const interval = setInterval(() => {
if (!this.io.pending) {
clearInterval(interval);
clearTimeout(timeout);
process.nextTick(process.reallyExit);
}
}, 1);
});
}
// Older versions of Firmata and some IO plugins
// may not have set RESOLUTION.
this.RESOLUTION = Object.assign({ ADC: 1023, DAC: null, PWM: 255 }, io.RESOLUTION || {});
}
// 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 (!hasBeenEmitted) {
this.emit(type, data);
}
}
}
/**
* 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) {
const blacklist = this.snapshot.blacklist;
const special = this.snapshot.special;
const hasReducer = typeof reducer === "function";
return this.register.reduce((cAccum, component) => {
// Don't include collections or multi/imu wrappers
if (typeof component.components === UNDEFINED) {
cAccum.push(
Object.getOwnPropertyNames(component).reduce((pAccum, prop) => {
const value = component[prop];
if (!blacklist.includes(prop) && typeof value !== "function") {
if (hasReducer) {
const result = reducer(prop, value, component);
if (result !== undefined) {
pAccum[prop] = result;
}
} else {
pAccum[prop] = special[prop] ?
special[prop](value) : value;
}
}
return pAccum;
}, Object.create(null))
);
}
return cAccum;
}, []);
};
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 (let 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);
}
};
const 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.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);
}
if (typeof opts.pins !== UNDEFINED) {
this.pins = opts.pins || [];
}
if (typeof opts.pin !== UNDEFINED) {
this.pin = opts.pin;
}
// TODO: Figure out what is using this
/* istanbul ignore if */
if (typeof opts.emitter !== UNDEFINED) {
/* istanbul ignore next */
this.emitter = opts.emitter;
}
if (typeof opts.address !== UNDEFINED) {
this.address = opts.address;
}
if (typeof opts.controller !== UNDEFINED) {
this.controller = opts.controller;
}
// TODO: Figure out what is using this
/* istanbul ignore if */
if (typeof opts.bus !== UNDEFINED) {
/* istanbul ignore next */
this.bus = opts.bus;
}
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(controllers, options) {
let controller;
if (typeof options.controller === "string") {
controller = controllers[options.controller] || controllers[options.controller.toUpperCase()];
} else {
controller = options.controller || controllers.DEFAULT || null;
}
if (controller === null) {
throw new Error("No Valid Controller Found");
}
let 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 = function(options) {
return new Options(options);
};
// Define a user-safe, unwritable hardware cache access
Object.defineProperty(Board, "cache", {
get() {
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
*/
class Boards extends Collection {
constructor(options) {
let ports;
// new Boards([ ...Array of board options ])
if (Array.isArray(options)) {
ports = options.slice();
options = {
ports,
};
}
// new Boards({ ports: [ ...Array of board options ], .... })
/* istanbul ignore else */
if (!Array.isArray(options) && typeof options === "object" && options.ports !== undefined) {
ports = options.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 options.debug === UNDEFINED) {
options.debug = true;
}
if (typeof options.repl === UNDEFINED) {
options.repl = true;
}
const initialized = {};
const noRepl = ports.some(({repl}) => repl === false);
const noDebug = ports.some(({debug}) => debug === false);
const boardObjects = ports.map((port) => {
let 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;
return new Board(portOpts);
});
super(boardObjects);
// Set the base values from the options object.
this.debug = options.debug;
this.repl = options.repl;
// Figure out board specific overrides...
//
// 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;
}
const expecteds = this.map((board, index) => {
initialized[board.id] = board;
return new Promise((resolve) => {
this[index].on("error", error => this.emit("error", error));
this[index].on("fail", event => this.emit("fail", event));
this[index].on("ready", () => resolve(this[index]));
});
});
Promise.all(expecteds).then((/* boards */) => {
this.each(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(() => this.emit("ready", initialized));
} else {
// Otherwise, call ready immediately
this.emit("ready", initialized);
}
}).catch(error => {
console.error(chalk.red(error));
});
}
static get type() {
return Board;
}
}
Collection.installMethodForwarding(
Boards.prototype, Board.prototype
);
Object.assign(
Boards.prototype,
Emitter.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