Source: org/terraswarm/accessor/accessors/web/hosts/node/node_modules/@accessors-modules/serial/serial.js

// Copyright (c) 2015-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

/**
 * Node module supporting serial port access.
 *
 * @module @accessors-modules/serial
 * @author Christopher Brooks, Beth Osyk.  Based on ptolemy/actor/lib/jjs/modules/serial/serial.js by Edward A. Lee, Rene Vivanco, and Christopher Brooks
 * @version $$Id$$
 */

// Stop extra messages from jslint.  Note that there should be no
// space between the / and the * and global.
/*globals console, exports, require */
/*jshint globalstrict: true */
"use strict";

var fs = require('fs');

// We use installIfMissingThenRequire(), which is defined in
// nodeHost.js to install certain packages if they are missing.
// To replicate, use:
//   rm -rf node_modules/serialport/ node_modules/virtual-serialport/
//   (cd $PTII/org/terraswarm/accessor/accessors/web/net/test/auto; node ../../../hosts/node/nodeHostInvoke.js net/test/auto/SerialLoopbackNumber)

var nodeHost = require('@accessors-hosts/node');
//var SerialPort = require('serialport');
var SerialPort = nodeHost.installIfMissingThenRequire('serialport');

var EventEmitter = require('events').EventEmitter;
var util = require('util');

var port = null;

///////////////////////////////////////////////////////////////////////////////
//// hostSerialPorts

/** Get an array of serial port names and invoke the
 *  specified callback with an argument that is that array.
 *  @param callback The function to invoke with the port names
 */
// FIXME:  Need an event here?  SerialPort.list invokes a callback; no return value.
// Or, way to block until callback is invoked?
exports.hostSerialPorts = function (callback) {
    var result = ["loopback"];

    try {
        SerialPort.list(function (err, ports) {
            ports.forEach(function(port) {
                result.push(port.comName);
                // Other fields - of any use?
                //console.log(port.pnpId);
                //console.log(port.manufacturer);
            });
            callback(result);
        });
    } catch (err) {
        callback('loopback');
    }
};

///////////////////////////////////////////////////////////////////////////////
//// SerialPort

/** Construct a serial port object, initialize it with the specified options.
 *  It is an event emitter that emits the following events:
 *  * 'data': Emitted when new data arrives on the serial port.
 *
 *  @param portName The name of the port to open.
 *  @param ownerName The name of the owner assigned to this port, if opening is successful.
 *  @param timeout Time in milliseconds before failing.
 *  @param options Serial port options (FIXME: define).
 *  @return A serial port interface.
 *  @exception If the port is in use or initializing the port fails.
 */
exports.SerialPort = function (portName, ownerName, timeout, options) {
    // FIXME:  Everything ignored except portName
    // TODO:  Error handling.


    this.sendType = options.sendType || 'string'; // Default data type. Strings can hold most content.
    this.receiveType = options.receiveType || 'string';

    this.portName = portName;
    if (portName === 'loopback') {
        // SerialPort = require('virtual-serialport');
        SerialPort = nodeHost.installIfMissingThenRequire('virtual-serialport');
    }

    // The VirtualSerialPort ctor in virtual-serialport/index.js checks
    // options.autoOpen.
    this.port = new SerialPort(portName, {autoOpen: false});
    var self = this;
    
    // Register event listeners.
    this.port.on('close', function() {
        console.log(self.portName + ' is closed.');
    });
    
    this.port.on('data', function (data) {
        console.log('Data: ' + data);
    });
    
    this.port.on("dataToDevice", function(data) {
        // dataToDevice is for virtual-serialport
        // console.log('dataToDevice: ' + data + ", typeof data: " + typeof data + ", receiveType: " + self.receiveType + ", sendType: " + self.sendType);
        if (self.receiveType === 'json') {
            if (typeof data === 'object') {
                self.emit('data', JSON.stringify(data));
            } else {
                self.emit('data', data);
            }
        } else if (self.receiveType === "string") {
            if (typeof data === 'number') {
                self.emit('data', String.fromCharCode(data));
            } else {
                self.emit('data', data.toString());
            }
        } else if (self.receiveType === 'short') {
            if (data > 32767) {
                data = (data & 32767) - 32768;
            }
            self.emit('data', data);
        } else if (self.receiveType === 'unsignedbyte') {
            if (data > 255) {
                data = (data & 255);
            }
            self.emit('data', data);
        } else {
            self.emit('data', data);
        }
    });

    this.port.on('error', function(err) {
        console.log("Serial Port Module Error: " + err.message);
    });
    
    this.port.on('open', function() {
        // console.log(self.portName + ' is open.');
    });

    this.port.on("writeToComputer", function(data) {
        // writeToComputer is for virtual-serialport
        console.log('writeToComputer: ' + data);
    });

};

util.inherits(exports.SerialPort, EventEmitter);

/** Close the port.
 */
exports.SerialPort.prototype.close = function () {
    this.removeAllListeners();
    this.port.close();
};

/** Open the port.
 */
exports.SerialPort.prototype.open = function (callback) {
    // FIXME: Not sure what to do here.
    //console.log("node_modules/serial.js: open()");
    this.port.open(callback);
};

/** Send data over the port.
 */
exports.SerialPort.prototype.send = function (data) {
    // FIXME: What about arrays?
    //console.log("node_modules/serial.js: send(): \"" + data + "\"");
    this.port.write(data);
};

/** Set the receive type. If this is not called, the type defaults to "string".
 *  @param type The name of the receive type.
 */
exports.SerialPort.prototype.setReceiveType = function(type) {
    // FIXME: copied from udpSocket.js
    if (this.RECEIVE_DATA_TYPES.indexOf(type.toLowerCase()) > 0) {
        this.receiveType = type.toLowerCase();
    } else {
        this.emit('error', 'Type ' + type + ' is not a supported receive type.');
    }
};

/** Set the send type. If this is not called, the type defaults to "string".
 *  @param type The name of the send type.
 */
exports.SerialPort.prototype.setSendType = function(type) {
    // FIXME: copied from udpSocket.js
    if (this.SEND_DATA_TYPES.indexOf(type.toLowerCase()) > 0) {
        this.sendType = type.toLowerCase();
    } else {
        this.emit('error', 'Type ' + type + ' is not a supported send type.');
    }
};

///////////////////////////////////////////////////////////////////////////////
//// supportedReceiveTypes

/** Return an array of the types supported by the current host for
 *  receiveType arguments.
 */
exports.supportedReceiveTypes = function () {
    return this.RECEIVE_DATA_TYPES;
};

///////////////////////////////////////////////////////////////////////////////
//// supportedSendTypes

/** Return an array of the types supported by the current host for
 *  sendType arguments.
 */
exports.supportedSendTypes = function () {
    return this.SEND_DATA_TYPES;
};