// 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;
};