text: /**
   * 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").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 SERIAL_MESSAGE = 0x60;
  var SERIAL_CONFIG = 0x10;
  var SERIAL_WRITE = 0x20;
  var SERIAL_READ = 0x30;
  var SERIAL_REPLY = 0x40;
  var SERIAL_CLOSE = 0x50;
  var SERIAL_FLUSH = 0x60;
  var SERIAL_LISTEN = 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);
  };
  
  /**
   * Handles a SERIAL_REPLY response and emits the "serial-data-"+n event where n is the id of the
   * serial port.
   * The event is passed the buffer of data sent from the serial device
   * @private
   * @param {Board} board the current arduino board we are working with.
   */
  
  SYSEX_RESPONSE[SERIAL_MESSAGE] = function(board) {
    var command = board.currentBuffer[2] & 0xF0;
    var portId = board.currentBuffer[2] & 0x0F;
    var reply = [];
  
    if (command === (SERIAL_REPLY)) {
      for (var i = 3, len = board.currentBuffer.length; i < len - 1; i += 2) {
        //reply.push(((board.currentBuffer[i + 1]& 0x7F) << 7) | (board.currentBuffer[i])&0x7F);  //changed by eric wang for test
  	  reply.push((board.currentBuffer[i + 1] << 7) | board.currentBuffer[i]);
      }
      board.emit("serial-data-" + portId, reply);
    }
  };
  
  
  /**
   * @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 SERIAL_MODES All the Serial modes available.
   * @property SERIAL_PORT_ID ID values to pass as the portId parameter when calling serialConfig.
   * @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,
      SERIAL: 0x0A,
      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.SERIAL_MODES = {
      READ_CONTINUOUS: 0x00,
      STOP_READING: 0x01
    };
  
    // ids for hardware and software serial ports on the board
    this.SERIAL_PORT_IDs = {
      HW_SERIAL0: 0x00,
      HW_SERIAL1: 0x01,
      HW_SERIAL2: 0x02,
      HW_SERIAL3: 0x03,
      SW_SERIAL0: 0x08,
      SW_SERIAL1: 0x09,
      SW_SERIAL2: 0x10,
      SW_SERIAL3: 0x11
    };
  
    // map to the pin resolution value in the capability query response
    this.SERIAL_PIN_TYPES = {
      RES_RX0: 0x00,
      RES_TX0: 0x01,
      RES_RX1: 0x02,
      RES_TX1: 0x03,
      RES_RX2: 0x04,
      RES_TX2: 0x05,
      RES_RX3: 0x06,
      RES_TX3: 0x07
    };
  
    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);
  };
  
  /**
   * Asks the Arduino to configure a hardware or serial port.
   * @param {object} options Options:
   *   portId {number} The serial port to use (HW_SERIAL1, HW_SERIAL2, HW_SERIAL3, SW_SERIAL0,
   *   SW_SERIAL1, SW_SERIAL2, SW_SERIAL3)
   *   baud {number} The baud rate of the serial port.
   *   bytesToRead {number} The number of bytes to read on each iteration of the main loop
   *   rxPin {number} [SoftwareSerial only] The RX pin of the SoftwareSerial instance
   *   txPin {number} [SoftwareSerial only] The TX pin of the SoftwareSerial instance
   */
  
  Board.prototype.serialConfig = function(options) {
  
    var portId;
    var baud;
    var bytesToRead;
    var rxPin;
    var txPin;
  
    if (typeof options === "object" && options !== null) {
      portId = options.portId;
      baud = options.baud;
      bytesToRead = options.bytesToRead;
      rxPin = options.rxPin;
      txPin = options.txPin;
    }
  
    // TODO - if info about the Serial capabilities can be added to the capability query, a
    // reasonable default value can be added and this error can be removed. Until then it is
    // not possible to know if a board supports only HW serial or only SW serial or both so
    // there is really no way to add a default portId value.
    if (typeof portId === "undefined") {
      throw new Error("portId must be specified, see SERIAL_PORT_IDs for options.");
    }
  
    baud = baud || 57600;
    bytesToRead = bytesToRead || 0;
  
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_CONFIG | portId,
      baud & 0x007F,
      (baud >> 7) & 0x007F,
      (baud >> 14) & 0x007F,
      bytesToRead & 0x007F,
      (bytesToRead >> 7) & 0x007F
    ];
    if (portId > 7 && typeof rxPin !== "undefined" && typeof txPin !== "undefined") {
      data.push(rxPin);
      data.push(txPin);
    } else if (portId > 7) {
      throw new Error("Both RX and TX pins must be defined when using Software Serial.");
    }
  
    data.push(END_SYSEX);
    this.transport.write(new Buffer(data));
  };
  
  /**
   * Write an array of bytes to the specified serial port.
   * @param {number} portId The serial port to write to.
   * @param {array} inBytes An array of bytes to write to the serial port.
   */
  
  Board.prototype.serialWrite = function(portId, inBytes) {
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_WRITE | portId
    ];
    for (var i = 0, len = inBytes.length; i < len; i++) {
      data.push(inBytes[i] & 0x007F);
      data.push((inBytes[i] >> 7) & 0x007F);
    }
    data.push(END_SYSEX);
    if (data.length > 0) {
      this.transport.write(new Buffer(data));
    }
  };
  
  /**
   * Start continuous reading of the specified serial port. The port is checked for data each
   * iteration of the main Arduino loop.
   * @param {number} portId The serial port to start reading continuously.
   * @param {function} callback A function to call when we have received the bytes.
   */
  
  Board.prototype.serialRead = function(portId, callback) {
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_READ | portId,
      this.SERIAL_MODES.READ_CONTINUOUS,
      END_SYSEX
    ];
    this.transport.write(new Buffer(data));
  
    this.on("serial-data-" + portId, callback);
  };
  
  /**
   * Stop continuous reading of the specified serial port. This does not close the port, it stops
   * reading it but keeps the port open.
   * @param {number} portId The serial port to stop reading.
   */
  
  Board.prototype.serialStop = function(portId) {
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_READ | portId,
      this.SERIAL_MODES.STOP_READING,
      END_SYSEX
    ];
    this.transport.write(new Buffer(data));
  
    this.removeAllListeners("serial-data-" + portId);
  };
  
  /**
   * Close the specified serial port.
   * @param {number} portId The serial port to close.
   */
  
  Board.prototype.serialClose = function(portId) {
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_CLOSE | portId,
      END_SYSEX
    ];
    this.transport.write(new Buffer(data));
  };
  
  /**
   * Flush the specified serial port. For hardware serial, this waits for the transmission of
   * outgoing serial data to complete. For software serial, this removed any buffered incoming serial
   * data.
   * @param {number} portId The serial port to flush.
   */
  
  Board.prototype.serialFlush = function(portId) {
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_FLUSH | portId,
      END_SYSEX
    ];
    this.transport.write(new Buffer(data));
  };
  
  /**
   * For SoftwareSerial only. Only a single SoftwareSerial instance can read data at a time.
   * Call this method to set this port to be the reading port in the case there are multiple
   * SoftwareSerial instances.
   * @param {number} portId The serial port to listen on.
   */
  
  Board.prototype.serialListen = function(portId) {
    // listen only applies to software serial ports
    if (portId < 8) {
      return;
    }
    var data = [
      START_SYSEX,
      SERIAL_MESSAGE,
      SERIAL_LISTEN | portId,
      END_SYSEX
    ];
    this.transport.write(new Buffer(data));
  };
  
  /**
   * 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;
  