UNPKG

firmata-electron

Version:

A library to control an arduino running firmata

1,427 lines (1,211 loc) 40.7 kB
/** * Global Environment Dependencies */ /* jshint -W079 */ var Map = require("es6-map"); var assign = require("object-assign"); /** * @author Julian Gautier */ /** * Module Dependencies */ var util = require("util"), Emitter = require("events").EventEmitter, chrome = chrome || undefined, Encoder7Bit = require("./encoder7bit"), OneWireUtils = require("./onewireutils"), SerialPort = null, i2cActive = new Map(); try { if (process.browser) { SerialPort = require("browser-serialport").SerialPort; } else { SerialPort = require("serialport-electron").SerialPort; } } catch (err) { SerialPort = null; } if (SerialPort == null) { console.log("It looks like serialport didn't compile properly. This is a common problem and its fix is well documented here https://github.com/voodootikigod/node-serialport#to-install"); throw "Missing serialport dependency"; } /** * constants */ var ANALOG_MAPPING_QUERY = 0x69; var ANALOG_MAPPING_RESPONSE = 0x6A; var ANALOG_MESSAGE = 0xE0; var CAPABILITY_QUERY = 0x6B; var CAPABILITY_RESPONSE = 0x6C; var DIGITAL_MESSAGE = 0x90; var END_SYSEX = 0xF7; var EXTENDED_ANALOG = 0x6F; var I2C_CONFIG = 0x78; var I2C_REPLY = 0x77; var I2C_REQUEST = 0x76; var ONEWIRE_CONFIG_REQUEST = 0x41; var ONEWIRE_DATA = 0x73; var ONEWIRE_DELAY_REQUEST_BIT = 0x10; var ONEWIRE_READ_REPLY = 0x43; var ONEWIRE_READ_REQUEST_BIT = 0x08; var ONEWIRE_RESET_REQUEST_BIT = 0x01; var ONEWIRE_SEARCH_ALARMS_REPLY = 0x45; var ONEWIRE_SEARCH_ALARMS_REQUEST = 0x44; var ONEWIRE_SEARCH_REPLY = 0x42; var ONEWIRE_SEARCH_REQUEST = 0x40; var ONEWIRE_WITHDATA_REQUEST_BITS = 0x3C; var ONEWIRE_WRITE_REQUEST_BIT = 0x20; var PIN_MODE = 0xF4; var PIN_STATE_QUERY = 0x6D; var PIN_STATE_RESPONSE = 0x6E; var PING_READ = 0x75; var PULSE_IN = 0x74; var PULSE_OUT = 0x73; var QUERY_FIRMWARE = 0x79; var REPORT_ANALOG = 0xC0; var REPORT_DIGITAL = 0xD0; var REPORT_VERSION = 0xF9; var SAMPLING_INTERVAL = 0x7A; var SERVO_CONFIG = 0x70; var START_SYSEX = 0xF0; var STEPPER = 0x72; var STRING_DATA = 0x71; var SYSTEM_RESET = 0xFF; var MAX_PIN_COUNT = 128; /** * MIDI_RESPONSE contains functions to be called when we receive a MIDI message from the arduino. * used as a switch object as seen here http://james.padolsey.com/javascript/how-to-avoid-switch-case-syndrome/ * @private */ var MIDI_RESPONSE = {}; /** * Handles a REPORT_VERSION response and emits the reportversion event. Also turns on all pins to start reporting * @private * @param {Board} board the current arduino board we are working with. */ MIDI_RESPONSE[REPORT_VERSION] = function(board) { board.version.major = board.currentBuffer[1]; board.version.minor = board.currentBuffer[2]; board.emit("reportversion"); }; /** * Handles a ANALOG_MESSAGE response and emits "analog-read" and "analog-read-"+n events where n is the pin number. * @private * @param {Board} board the current arduino board we are working with. */ MIDI_RESPONSE[ANALOG_MESSAGE] = function(board) { var value = board.currentBuffer[1] | (board.currentBuffer[2] << 7); var pin = board.currentBuffer[0] & 0x0F; if (board.pins[board.analogPins[pin]]) { board.pins[board.analogPins[pin]].value = value; } board.emit("analog-read-" + pin, value); board.emit("analog-read", { pin: pin, value: value }); }; /** * Handles a DIGITAL_MESSAGE response and emits: * "digital-read" * "digital-read-"+n * * Where n is the pin number. * * @private * @param {Board} board the current arduino board we are working with. */ MIDI_RESPONSE[DIGITAL_MESSAGE] = function(board) { var port = (board.currentBuffer[0] & 0x0F); var portValue = board.currentBuffer[1] | (board.currentBuffer[2] << 7); for (var i = 0; i < 8; i++) { var pinNumber = 8 * port + i; var pin = board.pins[pinNumber]; if (pin && (pin.mode === board.MODES.INPUT)) { pin.value = (portValue >> (i & 0x07)) & 0x01; board.emit("digital-read-" + pinNumber, pin.value); board.emit("digital-read", { pin: pinNumber, value: pin.value }); } } }; /** * SYSEX_RESPONSE contains functions to be called when we receive a SYSEX message from the arduino. * used as a switch object as seen here http://james.padolsey.com/javascript/how-to-avoid-switch-case-syndrome/ * @private */ var SYSEX_RESPONSE = {}; /** * Handles a QUERY_FIRMWARE response and emits the "queryfirmware" event * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[QUERY_FIRMWARE] = function(board) { var firmwareBuf = []; board.firmware.version = {}; board.firmware.version.major = board.currentBuffer[2]; board.firmware.version.minor = board.currentBuffer[3]; for (var i = 4, length = board.currentBuffer.length - 2; i < length; i += 2) { firmwareBuf.push((board.currentBuffer[i] & 0x7F) | ((board.currentBuffer[i + 1] & 0x7F) << 7)); } board.firmware.name = new Buffer(firmwareBuf).toString("utf8", 0, firmwareBuf.length); board.emit("queryfirmware"); }; /** * Handles a CAPABILITY_RESPONSE response and emits the "capability-query" event * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[CAPABILITY_RESPONSE] = function(board) { var supportedModes = 0; function pushModes(modesArray, mode) { if (supportedModes & (1 << board.MODES[mode])) { modesArray.push(board.MODES[mode]); } } // Only create pins if none have been previously created on the instance. if (!board.pins.length) { for (var i = 2, n = 0; i < board.currentBuffer.length - 1; i++) { if (board.currentBuffer[i] === 127) { var modesArray = []; Object.keys(board.MODES).forEach(pushModes.bind(null, modesArray)); board.pins.push({ supportedModes: modesArray, mode: board.MODES.UNKNOWN, value: 0, report: 1 }); supportedModes = 0; n = 0; continue; } if (n === 0) { supportedModes |= (1 << board.currentBuffer[i]); } n ^= 1; } } board.emit("capability-query"); }; /** * Handles a PIN_STATE response and emits the 'pin-state-'+n event where n is the pin number. * * Note about pin state: For output modes, the state is any value that has been * previously written to the pin. For input modes, the state is the status of * the pullup resistor. * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[PIN_STATE_RESPONSE] = function (board) { var pin = board.currentBuffer[2]; board.pins[pin].mode = board.currentBuffer[3]; board.pins[pin].state = board.currentBuffer[4]; if (board.currentBuffer.length > 6) { board.pins[pin].state |= (board.currentBuffer[5] << 7); } if (board.currentBuffer.length > 7) { board.pins[pin].state |= (board.currentBuffer[6] << 14); } board.emit("pin-state-" + pin); }; /** * Handles a ANALOG_MAPPING_RESPONSE response and emits the "analog-mapping-query" event. * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[ANALOG_MAPPING_RESPONSE] = function(board) { var pin = 0; var currentValue; for (var i = 2; i < board.currentBuffer.length - 1; i++) { currentValue = board.currentBuffer[i]; board.pins[pin].analogChannel = currentValue; if (currentValue !== 127) { board.analogPins.push(pin); } pin++; } board.emit("analog-mapping-query"); }; /** * Handles a I2C_REPLY response and emits the "I2C-reply-"+n event where n is the slave address of the I2C device. * The event is passed the buffer of data sent from the I2C Device * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[I2C_REPLY] = function(board) { var reply = []; var address = (board.currentBuffer[2] & 0x7F) | ((board.currentBuffer[3] & 0x7F) << 7); var register = (board.currentBuffer[4] & 0x7F) | ((board.currentBuffer[5] & 0x7F) << 7); for (var i = 6, length = board.currentBuffer.length - 1; i < length; i += 2) { reply.push(board.currentBuffer[i] | (board.currentBuffer[i + 1] << 7)); } board.emit("I2C-reply-" + address + "-" + register, reply); }; SYSEX_RESPONSE[ONEWIRE_DATA] = function(board) { var subCommand = board.currentBuffer[2]; if (!SYSEX_RESPONSE[subCommand]) { return; } SYSEX_RESPONSE[subCommand](board); }; SYSEX_RESPONSE[ONEWIRE_SEARCH_REPLY] = function(board) { var pin = board.currentBuffer[3]; var replyBuffer = board.currentBuffer.slice(4, board.currentBuffer.length - 1); board.emit("1-wire-search-reply-" + pin, OneWireUtils.readDevices(replyBuffer)); }; SYSEX_RESPONSE[ONEWIRE_SEARCH_ALARMS_REPLY] = function(board) { var pin = board.currentBuffer[3]; var replyBuffer = board.currentBuffer.slice(4, board.currentBuffer.length - 1); board.emit("1-wire-search-alarms-reply-" + pin, OneWireUtils.readDevices(replyBuffer)); }; SYSEX_RESPONSE[ONEWIRE_READ_REPLY] = function(board) { var encoded = board.currentBuffer.slice(4, board.currentBuffer.length - 1); var decoded = Encoder7Bit.from7BitArray(encoded); var correlationId = (decoded[1] << 8) | decoded[0]; board.emit("1-wire-read-reply-" + correlationId, decoded.slice(2)); }; /** * Handles a STRING_DATA response and logs the string to the console. * @private * @param {Board} board the current arduino board we are working with. */ SYSEX_RESPONSE[STRING_DATA] = function(board) { var string = new Buffer(board.currentBuffer.slice(2, -1)).toString("utf8").replace(/\0/g, ""); board.emit("string", string); }; /** * Response from pingRead */ SYSEX_RESPONSE[PING_READ] = function(board) { var pin = (board.currentBuffer[2] & 0x7F) | ((board.currentBuffer[3] & 0x7F) << 7); var durationBuffer = [ (board.currentBuffer[4] & 0x7F) | ((board.currentBuffer[5] & 0x7F) << 7), (board.currentBuffer[6] & 0x7F) | ((board.currentBuffer[7] & 0x7F) << 7), (board.currentBuffer[8] & 0x7F) | ((board.currentBuffer[9] & 0x7F) << 7), (board.currentBuffer[10] & 0x7F) | ((board.currentBuffer[11] & 0x7F) << 7) ]; var duration = ((durationBuffer[0] << 24) + (durationBuffer[1] << 16) + (durationBuffer[2] << 8) + (durationBuffer[3])); board.emit("ping-read-" + pin, duration); }; /** * Handles the message from a stepper completing move * @param {Board} board */ SYSEX_RESPONSE[STEPPER] = function(board) { var deviceNum = board.currentBuffer[2]; board.emit("stepper-done-" + deviceNum, true); }; /** * @class The Board object represents an arduino board. * @augments EventEmitter * @param {String} port This is the serial port the arduino is connected to. * @param {function} function A function to be called when the arduino is ready to communicate. * @property MODES All the modes available for pins on this arduino board. * @property I2C_MODES All the I2C modes available. * @property HIGH A constant to set a pins value to HIGH when the pin is set to an output. * @property LOW A constant to set a pins value to LOW when the pin is set to an output. * @property pins An array of pin object literals. * @property analogPins An array of analog pins and their corresponding indexes in the pins array. * @property version An object indicating the major and minor version of the firmware currently running. * @property firmware An object indicateon the name, major and minor version of the firmware currently running. * @property currentBuffer An array holding the current bytes received from the arduino. * @property {SerialPort} sp The serial port object used to communicate with the arduino. */ var Board = function(port, options, callback) { Emitter.call(this); if (typeof options === "function" || typeof options === "undefined") { callback = options; options = {}; } var board = this; var defaults = { reportVersionTimeout: 5000, samplingInterval: 19, serialport: { baudRate: 57600, bufferSize: 1 } }; var settings = assign({}, defaults, options); this.isReady = false; this.MODES = { INPUT: 0x00, OUTPUT: 0x01, ANALOG: 0x02, PWM: 0x03, SERVO: 0x04, SHIFT: 0x05, I2C: 0x06, ONEWIRE: 0x07, STEPPER: 0x08, IGNORE: 0x7F, UNKOWN: 0x10 }; this.I2C_MODES = { WRITE: 0x00, READ: 1, CONTINUOUS_READ: 2, STOP_READING: 3 }; this.STEPPER = { TYPE: { DRIVER: 1, TWO_WIRE: 2, FOUR_WIRE: 4 }, RUNSTATE: { STOP: 0, ACCEL: 1, DECEL: 2, RUN: 3 }, DIRECTION: { CCW: 0, CW: 1 } }; this.HIGH = 1; this.LOW = 0; this.pins = []; this.analogPins = []; this.version = {}; this.firmware = {}; this.currentBuffer = []; this.versionReceived = false; this.name = "Firmata"; this.settings = settings; if (typeof port === "object") { this.transport = port; } else { this.transport = new SerialPort(port, settings.serialport); } // For backward compat this.sp = this.transport; this.transport.on("open", function() { this.emit("connect"); }.bind(this)); this.transport.on("error", function(string) { if (typeof callback === "function") { callback(string); } }); this.transport.on("data", function(data) { var byt, cmd; if (!this.versionReceived && data[0] !== REPORT_VERSION) { return; } else { this.versionReceived = true; } for (var i = 0; i < data.length; i++) { byt = data[i]; // we dont want to push 0 as the first byte on our buffer if (this.currentBuffer.length === 0 && byt === 0) { continue; } else { this.currentBuffer.push(byt); // [START_SYSEX, ... END_SYSEX] if (this.currentBuffer[0] === START_SYSEX && SYSEX_RESPONSE[this.currentBuffer[1]] && this.currentBuffer[this.currentBuffer.length - 1] === END_SYSEX) { SYSEX_RESPONSE[this.currentBuffer[1]](this); this.currentBuffer.length = 0; } else if (this.currentBuffer[0] !== START_SYSEX) { // Check if data gets out of sync: first byte in buffer // must be a valid command if not START_SYSEX // Identify command on first byte cmd = this.currentBuffer[0] < 240 ? this.currentBuffer[0] & 0xF0 : this.currentBuffer[0]; // Check if it is not a valid command if (cmd !== REPORT_VERSION && cmd !== ANALOG_MESSAGE && cmd !== DIGITAL_MESSAGE) { // console.log("OUT OF SYNC - CMD: "+cmd); // Clean buffer this.currentBuffer.length = 0; } } // There are 3 bytes in the buffer and the first is not START_SYSEX: // Might have a MIDI Command if (this.currentBuffer.length === 3 && this.currentBuffer[0] !== START_SYSEX) { //commands under 0xF0 we have a multi byte command if (this.currentBuffer[0] < 240) { cmd = this.currentBuffer[0] & 0xF0; } else { cmd = this.currentBuffer[0]; } if (MIDI_RESPONSE[cmd]) { MIDI_RESPONSE[cmd](this); this.currentBuffer.length = 0; } else { // A bad serial read must have happened. // Reseting the buffer will allow recovery. this.currentBuffer.length = 0; } } } } }.bind(this)); // if we have not received the version within the alotted // time specified by the reportVersionTimeout (user or default), // then send an explicit request for it. this.reportVersionTimeoutId = setTimeout(function() { if (this.versionReceived === false) { this.reportVersion(function() {}); this.queryFirmware(function() {}); } }.bind(this), settings.reportVersionTimeout); function ready() { board.isReady = true; board.emit("ready"); if (typeof callback === "function") { callback(); } } // Await the reported version. this.once("reportversion", function() { clearTimeout(this.reportVersionTimeoutId); this.versionReceived = true; this.once("queryfirmware", function() { // Only preemptively set the sampling interval if `samplingInterval` // property was _explicitly_ set as a constructor option. if (options.samplingInterval !== undefined) { this.setSamplingInterval(options.samplingInterval); } if (settings.skipCapabilities) { this.analogPins = settings.analogPins || this.analogPins; this.pins = settings.pins || this.pins; if (!this.pins.length) { for (var i = 0; i < (settings.pinCount || MAX_PIN_COUNT); i++) { var analogChannel = this.analogPins.indexOf(i); if (analogChannel < 0) { analogChannel = 127; } this.pins.push({supportedModes: [], analogChannel: analogChannel}); } } ready(); } else { this.queryCapabilities(function() { this.queryAnalogMapping(ready); }); } }); }); i2cActive.set(this, false); }; util.inherits(Board, Emitter); /** * Asks the arduino to tell us its version. * @param {function} callback A function to be called when the arduino has reported its version. */ Board.prototype.reportVersion = function(callback) { this.once("reportversion", callback); this.transport.write(new Buffer([REPORT_VERSION])); }; /** * Asks the arduino to tell us its firmware version. * @param {function} callback A function to be called when the arduino has reported its firmware version. */ Board.prototype.queryFirmware = function(callback) { this.once("queryfirmware", callback); this.transport.write(new Buffer([START_SYSEX, QUERY_FIRMWARE, END_SYSEX])); }; /** * Asks the arduino to read analog data. Turn on reporting for this pin. * @param {number} pin The pin to read analog data * @param {function} callback A function to call when we have the analag data. */ Board.prototype.analogRead = function(pin, callback) { this.reportAnalogPin(pin, 1); this.addListener("analog-read-" + pin, callback); }; /** * Asks the arduino to write an analog message. * @param {number} pin The pin to write analog data to. * @param {nubmer} value The data to write to the pin between 0 and 255. */ Board.prototype.analogWrite = function(pin, value) { var data = []; this.pins[pin].value = value; if (pin > 15) { data[0] = START_SYSEX; data[1] = EXTENDED_ANALOG; data[2] = pin; data[3] = value & 0x7F; data[4] = (value >> 7) & 0x7F; if (value > 0x00004000) { data[data.length] = (value >> 14) & 0x7F; } if (value > 0x00200000) { data[data.length] = (value >> 21) & 0x7F; } if (value > 0x10000000) { data[data.length] = (value >> 28) & 0x7F; } data[data.length] = END_SYSEX; } else { data.push(ANALOG_MESSAGE | pin, value & 0x7F, (value >> 7) & 0x7F); } this.transport.write(new Buffer(data)); }; Board.prototype.pwmWrite = Board.prototype.analogWrite; /** * Set a pin to SERVO mode with an explicit PWM range. * * @param {number} pin The pin the servo is connected to * @param {number} min A 14-bit signed int. * @param {number} max A 14-bit signed int. */ Board.prototype.servoConfig = function(pin, min, max) { // [0] START_SYSEX (0xF0) // [1] SERVO_CONFIG (0x70) // [2] pin number (0-127) // [3] minPulse LSB (0-6) // [4] minPulse MSB (7-13) // [5] maxPulse LSB (0-6) // [6] maxPulse MSB (7-13) // [7] END_SYSEX (0xF7) var data = [ START_SYSEX, SERVO_CONFIG, pin, min & 0x7F, (min >> 7) & 0x7F, max & 0x7F, (max >> 7) & 0x7F, END_SYSEX ]; this.pins[pin].mode = this.MODES.SERVO; this.transport.write(new Buffer(data)); }; /** * Asks the arduino to move a servo * @param {number} pin The pin the servo is connected to * @param {number} value The degrees to move the servo to. */ Board.prototype.servoWrite = function(pin, value) { // Values less than 544 will be treated as angles in degrees // (valid values in microseconds are handled as microseconds) this.analogWrite.apply(this, arguments); }; /** * Asks the arduino to set the pin to a certain mode. * @param {number} pin The pin you want to change the mode of. * @param {number} mode The mode you want to set. Must be one of board.MODES */ Board.prototype.pinMode = function(pin, mode) { this.pins[pin].mode = mode; this.transport.write(new Buffer([PIN_MODE, pin, mode])); }; /** * Asks the arduino to write a value to a digital pin * @param {number} pin The pin you want to write a value to. * @param {value} value The value you want to write. Must be board.HIGH or board.LOW */ Board.prototype.digitalWrite = function(pin, value) { var port = Math.floor(pin / 8); var portValue = 0; var pinRecord; this.pins[pin].value = value; for (var i = 0; i < 8; i++) { pinRecord = this.pins[8 * port + i]; if (pinRecord && pinRecord.value) { portValue |= (1 << i); } } this.transport.write(new Buffer([DIGITAL_MESSAGE | port, portValue & 0x7F, (portValue >> 7) & 0x7F])); }; /** * Asks the arduino to read digital data. Turn on reporting for this pin's port. * * @param {number} pin The pin to read data from * @param {function} callback The function to call when data has been received */ Board.prototype.digitalRead = function(pin, callback) { this.reportDigitalPin(pin, 1); this.addListener("digital-read-" + pin, callback); }; /** * Asks the arduino to tell us its capabilities * @param {function} callback A function to call when we receive the capabilities */ Board.prototype.queryCapabilities = function(callback) { this.once("capability-query", callback); this.transport.write(new Buffer([START_SYSEX, CAPABILITY_QUERY, END_SYSEX])); }; /** * Asks the arduino to tell us its analog pin mapping * @param {function} callback A function to call when we receive the pin mappings. */ Board.prototype.queryAnalogMapping = function(callback) { this.once("analog-mapping-query", callback); this.transport.write(new Buffer([START_SYSEX, ANALOG_MAPPING_QUERY, END_SYSEX])); }; /** * Asks the arduino to tell us the current state of a pin * @param {number} pin The pin we want to the know the state of * @param {function} callback A function to call when we receive the pin state. */ Board.prototype.queryPinState = function(pin, callback) { this.once("pin-state-" + pin, callback); this.transport.write(new Buffer([START_SYSEX, PIN_STATE_QUERY, pin, END_SYSEX])); }; /** * Sends a string to the arduino * @param {String} string to send to the device */ Board.prototype.sendString = function(string) { var bytes = new Buffer(string + "\0", "utf8"); var data = []; data.push(START_SYSEX); data.push(STRING_DATA); for (var i = 0, length = bytes.length; i < length; i++) { data.push(bytes[i] & 0x7F); data.push((bytes[i] >> 7) & 0x7F); } data.push(END_SYSEX); this.transport.write(data); }; function i2cRequest(board, buffer) { if (!i2cActive.get(board)) { throw new Error("I2C is not enabled for this board. To enable, call the i2cConfig() method."); } board.transport.write(buffer); } /** * Sends a I2C config request to the arduino board with an optional * value in microseconds to delay an I2C Read. Must be called before * an I2C Read or Write * @param {number} delay in microseconds to set for I2C Read */ Board.prototype.sendI2CConfig = function(delay) { return this.i2cConfig(delay); }; /** * Enable I2C with an optional read delay. Must be called before * an I2C Read or Write * * Supersedes sendI2CConfig * * @param {number} delay in microseconds to set for I2C Read */ Board.prototype.i2cConfig = function(options) { var delay; if (typeof options === "number") { delay = options; } else { if (typeof options === "object" && options !== null) { delay = options.delay; } } delay = delay || 0; i2cActive.set(this, true); i2cRequest(this, new Buffer([ START_SYSEX, I2C_CONFIG, delay & 0xFF, (delay >> 8) & 0xFF, END_SYSEX ]) ); return this; }; /** * Asks the arduino to send an I2C request to a device * @param {number} slaveAddress The address of the I2C device * @param {Array} bytes The bytes to send to the device */ Board.prototype.sendI2CWriteRequest = function(slaveAddress, bytes) { var data = []; bytes = bytes || []; data.push( START_SYSEX, I2C_REQUEST, slaveAddress, this.I2C_MODES.WRITE << 3 ); for (var i = 0, length = bytes.length; i < length; i++) { data.push( bytes[i] & 0x7F, (bytes[i] >> 7) & 0x7F ); } data.push(END_SYSEX); i2cRequest(this, new Buffer(data)); }; /** * Write data to a register * * @param {number} address The address of the I2C device. * @param {array} cmdRegOrData An array of bytes * * Write a command to a register * * @param {number} address The address of the I2C device. * @param {number} cmdRegOrData The register * @param {array} inBytes An array of bytes * */ Board.prototype.i2cWrite = function(address, registerOrData, inBytes) { /** * registerOrData: * [... arbitrary bytes] * * or * * registerOrData, inBytes: * command [, ...] * */ var bytes; var data = [ START_SYSEX, I2C_REQUEST, address, this.I2C_MODES.WRITE << 3 ]; // If i2cWrite was used for an i2cWriteReg call... if (arguments.length === 3 && !Array.isArray(registerOrData) && !Array.isArray(inBytes)) { return this.i2cWriteReg(address, registerOrData, inBytes); } // Fix arguments if called with Firmata.js API if (arguments.length === 2) { if (Array.isArray(registerOrData)) { inBytes = registerOrData.slice(); registerOrData = inBytes.shift(); } else { inBytes = []; } } bytes = new Buffer([registerOrData].concat(inBytes)); for (var i = 0, length = bytes.length; i < length; i++) { data.push( bytes[i] & 0x7F, (bytes[i] >> 7) & 0x7F ); } data.push(END_SYSEX); i2cRequest(this, new Buffer(data)); return this; }; /** * Write data to a register * * @param {number} address The address of the I2C device. * @param {number} register The register. * @param {number} byte The byte value to write. * */ Board.prototype.i2cWriteReg = function(address, register, byte) { i2cRequest(this, new Buffer([ START_SYSEX, I2C_REQUEST, address, this.I2C_MODES.WRITE << 3, // register register & 0x7F, (register >> 7) & 0x7F, // byte byte & 0x7F, (byte >> 7) & 0x7F, END_SYSEX ]) ); return this; }; /** * Asks the arduino to request bytes from an I2C device * @param {number} slaveAddress The address of the I2C device * @param {number} numBytes The number of bytes to receive. * @param {function} callback A function to call when we have received the bytes. */ Board.prototype.sendI2CReadRequest = function(address, numBytes, callback) { i2cRequest(this, new Buffer([ START_SYSEX, I2C_REQUEST, address, this.I2C_MODES.READ << 3, numBytes & 0x7F, (numBytes >> 7) & 0x7F, END_SYSEX ]) ); this.once("I2C-reply-" + address + "-0" , callback); }; // TODO: Refactor i2cRead and i2cReadOnce // to share most operations. /** * Initialize a continuous I2C read. * * @param {number} address The address of the I2C device * @param {number} register Optionally set the register to read from. * @param {number} numBytes The number of bytes to receive. * @param {function} callback A function to call when we have received the bytes. */ Board.prototype.i2cRead = function(address, register, bytesToRead, callback) { if (arguments.length === 3 && typeof register === "number" && typeof bytesToRead === "function") { callback = bytesToRead; bytesToRead = register; register = null; } var event = "I2C-reply-" + address + "-"; var data = [ START_SYSEX, I2C_REQUEST, address, this.I2C_MODES.CONTINUOUS_READ << 3, ]; if (register !== null) { data.push( register & 0x7F, (register >> 7) & 0x7F ); } else { register = 0; } event += register; data.push( bytesToRead & 0x7F, (bytesToRead >> 7) & 0x7F, END_SYSEX ); this.on(event, callback); i2cRequest(this, new Buffer(data)); return this; }; /** * Perform a single I2C read * * Supersedes sendI2CReadRequest * * Read bytes from address * * @param {number} address The address of the I2C device * @param {number} register Optionally set the register to read from. * @param {number} numBytes The number of bytes to receive. * @param {function} callback A function to call when we have received the bytes. * */ Board.prototype.i2cReadOnce = function(address, register, bytesToRead, callback) { if (arguments.length === 3 && typeof register === "number" && typeof bytesToRead === "function") { callback = bytesToRead; bytesToRead = register; register = null; } var event = "I2C-reply-" + address + "-"; var data = [ START_SYSEX, I2C_REQUEST, address, this.I2C_MODES.READ << 3, ]; if (register !== null) { data.push( register & 0x7F, (register >> 7) & 0x7F ); } else { register = 0; } event += register; data.push( bytesToRead & 0x7F, (bytesToRead >> 7) & 0x7F, END_SYSEX ); this.once(event, callback); i2cRequest(this, new Buffer(data)); return this; }; // CONTINUOUS_READ /** * Configure the passed pin as the controller in a 1-wire bus. * Pass as enableParasiticPower true if you want the data pin to power the bus. * @param pin * @param enableParasiticPower */ Board.prototype.sendOneWireConfig = function(pin, enableParasiticPower) { this.transport.write(new Buffer([START_SYSEX, ONEWIRE_DATA, ONEWIRE_CONFIG_REQUEST, pin, enableParasiticPower ? 0x01 : 0x00, END_SYSEX])); }; /** * Searches for 1-wire devices on the bus. The passed callback should accept * and error argument and an array of device identifiers. * @param pin * @param callback */ Board.prototype.sendOneWireSearch = function(pin, callback) { this._sendOneWireSearch(ONEWIRE_SEARCH_REQUEST, "1-wire-search-reply-" + pin, pin, callback); }; /** * Searches for 1-wire devices on the bus in an alarmed state. The passed callback * should accept and error argument and an array of device identifiers. * @param pin * @param callback */ Board.prototype.sendOneWireAlarmsSearch = function(pin, callback) { this._sendOneWireSearch(ONEWIRE_SEARCH_ALARMS_REQUEST, "1-wire-search-alarms-reply-" + pin, pin, callback); }; Board.prototype._sendOneWireSearch = function(type, event, pin, callback) { this.transport.write(new Buffer([START_SYSEX, ONEWIRE_DATA, type, pin, END_SYSEX])); var searchTimeout = setTimeout(function() { callback(new Error("1-Wire device search timeout - are you running ConfigurableFirmata?")); }, 5000); this.once(event, function(devices) { clearTimeout(searchTimeout); callback(null, devices); }); }; /** * Reads data from a device on the bus and invokes the passed callback. * * N.b. ConfigurableFirmata will issue the 1-wire select command internally. * @param pin * @param device * @param numBytesToRead * @param callback */ Board.prototype.sendOneWireRead = function(pin, device, numBytesToRead, callback) { var correlationId = Math.floor(Math.random() * 255); var readTimeout = setTimeout(function() { callback(new Error("1-Wire device read timeout - are you running ConfigurableFirmata?")); }, 5000); this._sendOneWireRequest(pin, ONEWIRE_READ_REQUEST_BIT, device, numBytesToRead, correlationId, null, null, "1-wire-read-reply-" + correlationId, function(data) { clearTimeout(readTimeout); callback(null, data); }); }; /** * Resets all devices on the bus. * @param pin */ Board.prototype.sendOneWireReset = function(pin) { this._sendOneWireRequest(pin, ONEWIRE_RESET_REQUEST_BIT); }; /** * Writes data to the bus to be received by the passed device. The device * should be obtained from a previous call to sendOneWireSearch. * * N.b. ConfigurableFirmata will issue the 1-wire select command internally. * @param pin * @param device * @param data */ Board.prototype.sendOneWireWrite = function(pin, device, data) { this._sendOneWireRequest(pin, ONEWIRE_WRITE_REQUEST_BIT, device, null, null, null, Array.isArray(data) ? data : [data]); }; /** * Tells firmata to not do anything for the passed amount of ms. For when you * need to give a device attached to the bus time to do a calculation. * @param pin */ Board.prototype.sendOneWireDelay = function(pin, delay) { this._sendOneWireRequest(pin, ONEWIRE_DELAY_REQUEST_BIT, null, null, null, delay); }; /** * Sends the passed data to the passed device on the bus, reads the specified * number of bytes and invokes the passed callback. * * N.b. ConfigurableFirmata will issue the 1-wire select command internally. * @param pin * @param device * @param data * @param numBytesToRead * @param callback */ Board.prototype.sendOneWireWriteAndRead = function(pin, device, data, numBytesToRead, callback) { var correlationId = Math.floor(Math.random() * 255); var readTimeout = setTimeout(function() { callback(new Error("1-Wire device read timeout - are you running ConfigurableFirmata?")); }, 5000); this._sendOneWireRequest(pin, ONEWIRE_WRITE_REQUEST_BIT | ONEWIRE_READ_REQUEST_BIT, device, numBytesToRead, correlationId, null, Array.isArray(data) ? data : [data], "1-wire-read-reply-" + correlationId, function(data) { clearTimeout(readTimeout); callback(null, data); }); }; // see http://firmata.org/wiki/Proposals#OneWire_Proposal Board.prototype._sendOneWireRequest = function(pin, subcommand, device, numBytesToRead, correlationId, delay, dataToWrite, event, callback) { var bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; if (device || numBytesToRead || correlationId || delay || dataToWrite) { subcommand = subcommand | ONEWIRE_WITHDATA_REQUEST_BITS; } if (device) { bytes.splice.apply(bytes, [0, 8].concat(device)); } if (numBytesToRead) { bytes[8] = numBytesToRead & 0xFF; bytes[9] = (numBytesToRead >> 8) & 0xFF; } if (correlationId) { bytes[10] = correlationId & 0xFF; bytes[11] = (correlationId >> 8) & 0xFF; } if (delay) { bytes[12] = delay & 0xFF; bytes[13] = (delay >> 8) & 0xFF; bytes[14] = (delay >> 16) & 0xFF; bytes[15] = (delay >> 24) & 0xFF; } if (dataToWrite) { dataToWrite.forEach(function(byte) { bytes.push(byte); }); } var output = [START_SYSEX, ONEWIRE_DATA, subcommand, pin]; output = output.concat(Encoder7Bit.to7BitArray(bytes)); output.push(END_SYSEX); this.transport.write(new Buffer(output)); if (event && callback) { this.once(event, callback); } }; /** * Set sampling interval in millis. Default is 19 ms * @param {number} interval The sampling interval in ms > 10 */ Board.prototype.setSamplingInterval = function(interval) { var safeint = interval < 10 ? 10 : (interval > 65535 ? 65535 : interval); // constrained this.settings.samplingInterval = safeint; this.transport.write(new Buffer([START_SYSEX, SAMPLING_INTERVAL, (safeint & 0x7F), ((safeint >> 7) & 0x7F), END_SYSEX])); }; /** * Get sampling interval in millis. Default is 19 ms */ Board.prototype.getSamplingInterval = function(interval) { return this.settings.samplingInterval; }; /** * Set reporting on pin * @param {number} pin The pin to turn on/off reporting * @param {number} value Binary value to turn reporting on/off */ Board.prototype.reportAnalogPin = function(pin, value) { if (value === 0 || value === 1) { this.pins[this.analogPins[pin]].report = value; this.transport.write(new Buffer([REPORT_ANALOG | pin, value])); } }; /** * Set reporting on pin * @param {number} pin The pin to turn on/off reporting * @param {number} value Binary value to turn reporting on/off */ Board.prototype.reportDigitalPin = function(pin, value) { var port = Math.floor(pin / 8); if (value === 0 || value === 1) { this.pins[pin].report = value; this.transport.write(new Buffer([REPORT_DIGITAL | port, value])); } }; /** * * */ Board.prototype.pingRead = function(opts, callback) { var pin = opts.pin; var value = opts.value; var pulseOut = opts.pulseOut || 0; var timeout = opts.timeout || 1000000; var pulseOutArray = [ ((pulseOut >> 24) & 0xFF), ((pulseOut >> 16) & 0xFF), ((pulseOut >> 8) & 0XFF), ((pulseOut & 0xFF)) ]; var timeoutArray = [ ((timeout >> 24) & 0xFF), ((timeout >> 16) & 0xFF), ((timeout >> 8) & 0XFF), ((timeout & 0xFF)) ]; var data = [ START_SYSEX, PING_READ, pin, value, pulseOutArray[0] & 0x7F, (pulseOutArray[0] >> 7) & 0x7F, pulseOutArray[1] & 0x7F, (pulseOutArray[1] >> 7) & 0x7F, pulseOutArray[2] & 0x7F, (pulseOutArray[2] >> 7) & 0x7F, pulseOutArray[3] & 0x7F, (pulseOutArray[3] >> 7) & 0x7F, timeoutArray[0] & 0x7F, (timeoutArray[0] >> 7) & 0x7F, timeoutArray[1] & 0x7F, (timeoutArray[1] >> 7) & 0x7F, timeoutArray[2] & 0x7F, (timeoutArray[2] >> 7) & 0x7F, timeoutArray[3] & 0x7F, (timeoutArray[3] >> 7) & 0x7F, END_SYSEX ]; this.transport.write(new Buffer(data)); this.once("ping-read-" + pin, callback); }; /** * Stepper functions to support AdvancedFirmata"s asynchronous control of stepper motors * https://github.com/soundanalogous/AdvancedFirmata */ /** * Asks the arduino to configure a stepper motor with the given config to allow asynchronous control of the stepper * @param {number} deviceNum Device number for the stepper (range 0-5, expects steppers to be setup in order from 0 to 5) * @param {number} type One of this.STEPPER.TYPE.* * @param {number} stepsPerRev Number of steps motor takes to make one revolution * @param {number} dirOrMotor1Pin If using EasyDriver type stepper driver, this is direction pin, otherwise it is motor 1 pin * @param {number} stepOrMotor2Pin If using EasyDriver type stepper driver, this is step pin, otherwise it is motor 2 pin * @param {number} [motor3Pin] Only required if type == this.STEPPER.TYPE.FOUR_WIRE * @param {number} [motor4Pin] Only required if type == this.STEPPER.TYPE.FOUR_WIRE */ Board.prototype.stepperConfig = function(deviceNum, type, stepsPerRev, dirOrMotor1Pin, stepOrMotor2Pin, motor3Pin, motor4Pin) { var data = [ START_SYSEX, STEPPER, 0x00, // STEPPER_CONFIG from firmware deviceNum, type, stepsPerRev & 0x7F, (stepsPerRev >> 7) & 0x7F, dirOrMotor1Pin, stepOrMotor2Pin ]; if (type === this.STEPPER.TYPE.FOUR_WIRE) { data.push(motor3Pin, motor4Pin); } data.push(END_SYSEX); this.transport.write(new Buffer(data)); }; /** * Asks the arduino to move a stepper a number of steps at a specific speed * (and optionally with and acceleration and deceleration) * speed is in units of .01 rad/sec * accel and decel are in units of .01 rad/sec^2 * TODO: verify the units of speed, accel, and decel * @param {number} deviceNum Device number for the stepper (range 0-5) * @param {number} direction One of this.STEPPER.DIRECTION.* * @param {number} steps Number of steps to make * @param {number} speed * @param {number|function} accel Acceleration or if accel and decel are not used, then it can be the callback * @param {number} [decel] * @param {function} [callback] */ Board.prototype.stepperStep = function(deviceNum, direction, steps, speed, accel, decel, callback) { if (typeof accel === "function") { callback = accel; accel = 0; decel = 0; } var data = [ START_SYSEX, STEPPER, 0x01, // STEPPER_STEP from firmware deviceNum, direction, // one of this.STEPPER.DIRECTION.* steps & 0x7F, (steps >> 7) & 0x7F, (steps >> 14) & 0x7f, speed & 0x7F, (speed >> 7) & 0x7F ]; if (accel > 0 || decel > 0) { data.push( accel & 0x7F, (accel >> 7) & 0x7F, decel & 0x7F, (decel >> 7) & 0x7F ); } data.push(END_SYSEX); this.transport.write(new Buffer(data)); this.once("stepper-done-" + deviceNum, callback); }; /** * Send SYSTEM_RESET to arduino */ Board.prototype.reset = function() { this.transport.write(new Buffer([SYSTEM_RESET])); }; // For backwards compatibility Board.Board = Board; Board.SYSEX_RESPONSE = SYSEX_RESPONSE; Board.MIDI_RESPONSE = MIDI_RESPONSE; module.exports = Board;