Show:

File: src/core/Serial.js

/**
 * Copyright (c) 2016 Jeff Hoefs <soundanalogous@gmail.com>
 * Released under the MIT license. See LICENSE file for details.
 */

JSUTILS.namespace('BO.Serial');

/**
 * @namespace BO
 */
BO.Serial = (function() {
  "use strict";

  var Serial;

  var SERIAL_MESSAGE = 0x60;
  var CONFIG = 0x10;
  var WRITE = 0x20;
  var READ = 0x30;
  var REPLY = 0x40;
  var CLOSE = 0x50;
  var FLUSH = 0x60;
  var LISTEN = 0x70;

  var READ_CONTINUOUS = 0x00;
  var STOP_READING = 0x01;

  // dependencies
  var EventDispatcher = JSUTILS.EventDispatcher;
  var IOBoardEvent = BO.IOBoardEvent;
  var SerialEvent = BO.SerialEvent;

  /**
   * Enables use of Hardware (UART) and Software serial ports on the board.
   *
   * @class Serial
   * @constructor
   * @uses JSUTILS.EventDispatcher
   * @param {Object} opts Options:
   * <ul>
   * <li><strong>board</strong> {IOBoard} A reference to the IOBoard instance.</li>
   * <li><strong>port</strong> {Number} The serial port to use (HW_SERIAL1, HW_SERIAL2, HW_SERIAL3, SW_SERIAL0,
   *   SW_SERIAL1, SW_SERIAL2, SW_SERIAL3)</li>
   * <li><strong>baud</strong> {Number} The baud rate of the serial port. Default = 57600.</li>
   * <li><strong>rxPin</strong> {Number} [SoftwareSerial only] The RX pin of the SoftwareSerial instance</li>
   * <li><strong>txPin</strong> {Number} [SoftwareSerial only] The TX pin of the SoftwareSerial instance</li>
   * </ul>
   *
   * @example
   *     // Use a SoftwareSerial instance
   *     var serial = new BO.Serial({
   *       board: arduino,
   *       port: BO.Serial.SW_SERIAL0,
   *       baud: 57600,
   *       txPin: 10,
   *       rxPin: 11
   *     });
   *     serial.addEventListener(BO.SerialEvent.DATA, function (event) {
   *       console.log(event.data);
   *     });
   *     serial.startReading();
   *
   * @example
   *     // Use a HardwareSerial instance (pins RX1, TX1 on Leonardo, Mega, Due, etc)
   *     var serial = new BO.Serial({
   *       board: arduino,
   *       port: BO.Serial.HW_SERIAL1,
   *       baud: 57600
   *     });
   *     serial.addEventListener(BO.SerialEvent.DATA, function (event) {
   *       console.log(event.data);
   *     });
   *     serial.startReading();
   */
  Serial = function(opts) {
    if (typeof opts === "undefined" ||
      typeof opts.board === "undefined" ||
      typeof opts.port === "undefined") {
      throw new Error("Serial options board and port must be defined.");
    }

    this.name = "Serial";
    this.board = opts.board;
    this.port = opts.port;
    this.baud = opts.baud || 57600;
    this.txPin = opts.txPin;
    this.rxPin = opts.rxPin;

    this._evtDispatcher = new EventDispatcher(this);

    this.board.addEventListener(IOBoardEvent.SYSEX_MESSAGE, this.onSysExMessage.bind(this));

    var configData = [
      CONFIG | this.port,
      this.baud & 0x007F, (this.baud >> 7) & 0x007F, (this.baud >> 14) & 0x007F
    ];
    if (this.port > 7 && typeof this.txPin !== "undefined" && typeof this.rxPin !== "undefined") {
      configData.push(this.rxPin);
      configData.push(this.txPin);
    } else if (this.port > 7) {
      throw new Error("Both RX and TX pins must be defined when using SoftwareSerial.");
    }

    this.board.sendSysex(SERIAL_MESSAGE, configData);
  };

  Serial.prototype = {

    constructor: Serial,

    /**
     * Handle incoming sysex message.
     * @private
     */
    onSysExMessage: function(event) {
      var message = event.message;
      var data = [];

      if (message[0] !== SERIAL_MESSAGE) {
        return;
      } else {
        if (message[1] === (REPLY | this.port)) {
          for (var i = 2, len = message.length; i < len; i += 2) {
            data.push(this.board.getValueFromTwo7bitBytes(message[i], message[i + 1]));
          }
          this.dispatchEvent(new SerialEvent(SerialEvent.DATA), {
            data: data,
            portId: this.port
          });
        }
      }
    },

    /**
     * Write an array of data.
     * @method write
     * @param {Number|Array} val A single byte or an array of bytes to write
     */
    write: function(val) {
      var txData = [];
      var data = [];
      if (!Array.isArray(val)) {
        data.push(val);
      } else {
        data = val;
      }
      txData.push(WRITE | this.port);
      for (var i = 0, len = data.length; i < len; i++) {
        txData.push(data[i] & 0x007F);
        txData.push((data[i] >> 7) & 0x007F);
      }
      if (txData.length > 0) {
        this.board.sendSysex(SERIAL_MESSAGE, txData);
      }
    },

    /**
     * Start reading the serial port.
     * @method startReading
     * @param {Number} maxBytesToRead [optional] The number of bytes to read on each iteration
     * of the main loop.
     */
    startReading: function(maxBytesToRead) {
      var data = [];
      if (typeof maxBytesToRead === "undefined") {
        maxBytesToRead = 0;
      }
      data.push(READ | this.port);
      data.push(READ_CONTINUOUS);
      data.push(maxBytesToRead & 0x007F);
      data.push((maxBytesToRead >> 7) & 0x007F);
      this.board.sendSysex(SERIAL_MESSAGE, data);
    },

    /**
     * Stop reading the serial port.
     * @method stopReading
     */
    stopReading: function() {
      this.board.sendSysex(SERIAL_MESSAGE, [READ | this.port, STOP_READING]);
    },

    /**
     * Close the serial port. A new instance must be created in order
     * to reopen the port.
     * @method close
     */
    close: function() {
      this.board.sendSysex(SERIAL_MESSAGE, [CLOSE | this.port]);
    },

    /**
     * For HardwareSerial, waits for the transmission of outgoing serial data
     * to complete.
     * For SoftwareSerial, removes any buffered incoming serial data.
     * @method flush
     */
    flush: function() {
      this.board.sendSysex(SERIAL_MESSAGE, [FLUSH | this.port]);
    },

    /**
     * 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.
     * @method listen
     */
    listen: function() {
      if (this.port < 8) {
        return;
      }
      this.board.sendSysex(SERIAL_MESSAGE, [LISTEN | this.port]);
    },

    /* implement EventDispatcher */

    /**
     * @param {String} type The event type
     * @param {Function} listener The function to be called when the event is fired
     */
    addEventListener: function(type, listener) {
      this._evtDispatcher.addEventListener(type, listener);
    },

    /**
     * @param {String} type The event type
     * @param {Function} listener The function to be called when the event is fired
     */
    removeEventListener: function(type, listener) {
      this._evtDispatcher.removeEventListener(type, listener);
    },

    /**
     * @param {String} type The event type
     * return {boolean} True is listener exists for this type, false if not.
     */
    hasEventListener: function(type) {
      return this._evtDispatcher.hasEventListener(type);
    },

    /**
     * @param {Event} type The Event object
     * @param {Object} optionalParams Optional parameters to assign to the event object.
     * return {boolean} True if dispatch is successful, false if not.
     */
    dispatchEvent: function(event, optionalParams) {
      return this._evtDispatcher.dispatchEvent(event, optionalParams);
    }
  };

  /**
   * Not currently used. Corresponds to the default Arduino Serial port
   * @property Serial.HW_SERIAL0
   * @static
   */
  Serial.HW_SERIAL0 = 0x00;
  /**
   * Pins RX1 and TX1 on the board
   * @property Serial.HW_SERIAL1
   * @static
   */
  Serial.HW_SERIAL1 = 0x01;
  /**
   * Pins RX2 and TX2 on the board
   * @property Serial.HW_SERIAL2
   * @static
   */
  Serial.HW_SERIAL2 = 0x02;
  /**
   * Pins RX3 and TX3 on the board
   * @property Serial.HW_SERIAL3
   * @static
   */
  Serial.HW_SERIAL3 = 0x03;

  /**
   * One of four software serial instances
   * @property Serial.SW_SERIAL0
   * @static
   */
  Serial.SW_SERIAL0 = 0x08;
  /**
   * One of four software serial instances
   * @property Serial.SW_SERIAL1
   * @static
   */
  Serial.SW_SERIAL1 = 0x09;
  /**
   * One of four software serial instances
   * @property Serial.SW_SERIAL2
   * @static
   */
  Serial.SW_SERIAL2 = 0x10;
  /**
   * One of four software serial instances
   * @property Serial.SW_SERIAL3
   * @static
   */
  Serial.SW_SERIAL3 = 0x11;

  return Serial;

}());