UNPKG

browser-serialport

Version:

Robots in the browser. Just like node-serialport but for browser/chrome apps.

424 lines (346 loc) 10.2 kB
'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. };