browser-serialport
Version:
Robots in the browser. Just like node-serialport but for browser/chrome apps.
424 lines (346 loc) • 10.2 kB
JavaScript
'use strict';
var EE = require('events').EventEmitter;
var util = require('util');
var DATABITS = [7, 8];
var STOPBITS = [1, 2];
var PARITY = ['none', 'even', 'mark', 'odd', 'space'];
var FLOWCONTROLS = ['RTSCTS'];
var _options = {
baudrate: 9600,
parity: 'none',
rtscts: false,
databits: 8,
stopbits: 1,
buffersize: 256
};
function convertOptions(options){
switch (options.dataBits) {
case 7:
options.dataBits = 'seven';
break;
case 8:
options.dataBits = 'eight';
break;
}
switch (options.stopBits) {
case 1:
options.stopBits = 'one';
break;
case 2:
options.stopBits = 'two';
break;
}
switch (options.parity) {
case 'none':
options.parity = 'no';
break;
}
return options;
}
function SerialPort(path, options, openImmediately, callback) {
EE.call(this);
var self = this;
var args = Array.prototype.slice.call(arguments);
callback = args.pop();
if (typeof(callback) !== 'function') {
callback = null;
}
options = (typeof options !== 'function') && options || {};
openImmediately = (openImmediately === undefined || openImmediately === null) ? true : openImmediately;
callback = callback || function (err) {
if (err) {
self.emit('error', err);
}
};
var err;
options.baudRate = options.baudRate || options.baudrate || _options.baudrate;
options.dataBits = options.dataBits || options.databits || _options.databits;
if (DATABITS.indexOf(options.dataBits) === -1) {
err = new Error('Invalid "databits": ' + options.dataBits);
callback(err);
return;
}
options.stopBits = options.stopBits || options.stopbits || _options.stopbits;
if (STOPBITS.indexOf(options.stopBits) === -1) {
err = new Error('Invalid "stopbits": ' + options.stopbits);
callback(err);
return;
}
options.parity = options.parity || _options.parity;
if (PARITY.indexOf(options.parity) === -1) {
err = new Error('Invalid "parity": ' + options.parity);
callback(err);
return;
}
if (!path) {
err = new Error('Invalid port specified: ' + path);
callback(err);
return;
}
options.rtscts = _options.rtscts;
if (options.flowControl || options.flowcontrol) {
var fc = options.flowControl || options.flowcontrol;
if (typeof fc === 'boolean') {
options.rtscts = true;
} else {
var clean = fc.every(function (flowControl) {
var fcup = flowControl.toUpperCase();
var idx = FLOWCONTROLS.indexOf(fcup);
if (idx < 0) {
var err = new Error('Invalid "flowControl": ' + fcup + '. Valid options: ' + FLOWCONTROLS.join(', '));
callback(err);
return false;
} else {
// "XON", "XOFF", "XANY", "DTRDTS", "RTSCTS"
switch (idx) {
case 0: options.rtscts = true; break;
}
return true;
}
});
if(!clean){
return;
}
}
}
options.bufferSize = options.bufferSize || options.buffersize || _options.buffersize;
// defaults to chrome.serial if no options.serial passed
// inlined instead of on _options to allow mocking global chrome.serial for optional options test
options.serial = options.serial || (typeof chrome !== 'undefined' && chrome.serial);
if (!options.serial) {
throw new Error('No access to serial ports. Try loading as a Chrome Application.');
}
this.options = convertOptions(options);
this.options.serial.onReceiveError.addListener(function(info){
switch (info.error) {
case 'disconnected':
case 'device_lost':
case 'system_error':
err = new Error('Disconnected');
// send notification of disconnect
if (self.options.disconnectedCallback) {
self.options.disconnectedCallback(err);
} else {
self.emit('disconnect', err);
}
if(self.connectionId >= 0){
self.close();
}
break;
case 'timeout':
break;
}
});
this.path = path;
if (openImmediately) {
process.nextTick(function () {
self.open(callback);
});
}
}
util.inherits(SerialPort, EE);
SerialPort.prototype.connectionId = -1;
SerialPort.prototype.open = function (callback) {
var options = {
bitrate: parseInt(this.options.baudRate, 10),
dataBits: this.options.dataBits,
parityBit: this.options.parity,
stopBits: this.options.stopBits,
ctsFlowControl: this.options.rtscts
};
this.options.serial.connect(this.path, options, this.proxy('onOpen', callback));
};
SerialPort.prototype.onOpen = function (callback, openInfo) {
if(chrome.runtime.lastError){
if(typeof callback === 'function'){
callback(chrome.runtime.lastError);
}else{
this.emit('error', chrome.runtime.lastError);
}
return;
}
this.connectionId = openInfo.connectionId;
if (this.connectionId === -1) {
this.emit('error', new Error('Could not open port.'));
return;
}
this.emit('open', openInfo);
this._reader = this.proxy('onRead');
this.options.serial.onReceive.addListener(this._reader);
if(typeof callback === 'function'){
callback(chrome.runtime.lastError, openInfo);
}
};
SerialPort.prototype.onRead = function (readInfo) {
if (readInfo && this.connectionId === readInfo.connectionId) {
if (this.options.dataCallback) {
this.options.dataCallback(toBuffer(readInfo.data));
} else {
this.emit('data', toBuffer(readInfo.data));
}
}
};
SerialPort.prototype.write = function (buffer, callback) {
if (this.connectionId < 0) {
var err = new Error('Serialport not open.');
if(typeof callback === 'function'){
callback(err);
}else{
this.emit('error', err);
}
return;
}
if (typeof buffer === 'string') {
buffer = str2ab(buffer);
}
//Make sure its not a browserify faux Buffer.
if (buffer instanceof ArrayBuffer === false) {
buffer = buffer2ArrayBuffer(buffer);
}
this.options.serial.send(this.connectionId, buffer, function(info) {
if (typeof callback === 'function') {
callback(chrome.runtime.lastError, info);
}
});
};
SerialPort.prototype.close = function (callback) {
if (this.connectionId < 0) {
var err = new Error('Serialport not open.');
if(typeof callback === 'function'){
callback(err);
}else{
this.emit('error', err);
}
return;
}
this.options.serial.disconnect(this.connectionId, this.proxy('onClose', callback));
};
SerialPort.prototype.onClose = function (callback, result) {
this.connectionId = -1;
this.emit('close');
this.removeAllListeners();
if(this._reader){
this.options.serial.onReceive.removeListener(this._reader);
this._reader = null;
}
if (typeof callback === 'function') {
callback(chrome.runtime.lastError, result);
}
};
SerialPort.prototype.flush = function (callback) {
if (this.connectionId < 0) {
var err = new Error('Serialport not open.');
if(typeof callback === 'function'){
callback(err);
}else{
this.emit('error', err);
}
return;
}
var self = this;
this.options.serial.flush(this.connectionId, function(result) {
if (chrome.runtime.lastError) {
if (typeof callback === 'function') {
callback(chrome.runtime.lastError, result);
} else {
self.emit('error', chrome.runtime.lastError);
}
return;
} else {
callback(null, result);
}
});
};
SerialPort.prototype.drain = function (callback) {
if (this.connectionId < 0) {
var err = new Error('Serialport not open.');
if(typeof callback === 'function'){
callback(err);
}else{
this.emit('error', err);
}
return;
}
if (typeof callback === 'function') {
callback();
}
};
SerialPort.prototype.proxy = function () {
var self = this;
var proxyArgs = [];
//arguments isnt actually an array.
for (var i = 0; i < arguments.length; i++) {
proxyArgs[i] = arguments[i];
}
var functionName = proxyArgs.splice(0, 1)[0];
var func = function() {
var funcArgs = [];
for (var i = 0; i < arguments.length; i++) {
funcArgs[i] = arguments[i];
}
var allArgs = proxyArgs.concat(funcArgs);
self[functionName].apply(self, allArgs);
};
return func;
};
SerialPort.prototype.set = function (options, callback) {
this.options.serial.setControlSignals(this.connectionId, options, function(result){
callback(chrome.runtime.lastError, result);
});
};
SerialPort.prototype.isOpen = function () {
return this.connectionId > -1;
};
function SerialPortList(callback) {
if (typeof chrome != 'undefined' && chrome.serial) {
chrome.serial.getDevices(function(ports) {
var portObjects = new Array(ports.length);
for (var i = 0; i < ports.length; i++) {
portObjects[i] = {
comName: ports[i].path,
manufacturer: ports[i].displayName,
serialNumber: '',
pnpId: '',
locationId:'',
vendorId: '0x' + (ports[i].vendorId||0).toString(16),
productId: '0x' + (ports[i].productId||0).toString(16)
};
}
callback(chrome.runtime.lastError, portObjects);
});
} else {
callback(new Error('No access to serial ports. Try loading as a Chrome Application.'), null);
}
}
// Convert string to ArrayBuffer
function str2ab(str) {
var buf = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buf);
for (var i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
// Convert buffer to ArrayBuffer
function buffer2ArrayBuffer(buffer) {
var buf = new ArrayBuffer(buffer.length);
var bufView = new Uint8Array(buf);
for (var i = 0; i < buffer.length; i++) {
bufView[i] = buffer[i];
}
return buf;
}
function toBuffer(ab) {
var buffer = new Buffer(ab.byteLength);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = view[i];
}
return buffer;
}
module.exports = {
SerialPort: SerialPort,
list: SerialPortList,
buffer2ArrayBuffer: buffer2ArrayBuffer,
used: [] //TODO: Populate this somewhere.
};