diff --git a/lib/merge.js b/lib/merge.js deleted file mode 100644 index 4790b88..0000000 --- a/lib/merge.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -function merge(ext, orig) { - var clone = JSON.parse(JSON.stringify(ext)); - return Object.keys(ext).reduce(function (result, key) { - if('undefined' === typeof clone[key]) { - delete result[key]; - } else { - result[key] = clone[key]; - } - return result; - }, JSON.parse(JSON.stringify(orig))); -} - -module.exports = merge; \ No newline at end of file diff --git a/lib/wii.js b/lib/wii.js index ec1f5d5..dd17520 100644 --- a/lib/wii.js +++ b/lib/wii.js @@ -1,100 +1,35 @@ 'use strict'; -var merge = require('./merge'), - util = require('util'), +var util = require('util'), EventEmitter = require('events').EventEmitter, - serialport = require('serialport'), - SerialPort = serialport.SerialPort, - Q = require('q'); - - -var list = function (callback) { - var deferred = Q.defer(); - serialport.list(function (err, ports) { - if(err) { - deferred.reject(err); - } else { - deferred.resolve(ports); - } - }); - if('function' === typeof callback) { - deferred.promise.nodeify(callback); - } else { - return deferred.promise; - } -}; + when = require('when'); -var defaults = { - baudrate: 115200, - databits: 8, - stopbits: 1, - parity: 'none', - parser: serialport.parsers.raw -}; var sendHeader = [0x24, 0x4D, 0x3C]; - -function Wii(options) { - if(!options) { - options = {}; - } else if('string' === typeof options) { - options = { port: options }; +function Wii(connection, options) { + if (!connection) { + throw new Error("Connection must be provided that implements a stream interface"); } - this.options = merge(options, defaults); - - this.onData = this.onData.bind(this); - this.onError = this.onError.bind(this); + this.options = options || {}; + this.socket = connection; this.messages = {}; this.readMessages = {}; - require('./messages').register(this); -} - -util.inherits(Wii, EventEmitter); - -Wii.prototype.connect = function(port, options) { - if('string' === typeof port) { - port = { comName: port }; - } else { - port = port || this.options.port; - } - if(!port) { - throw new Error('no port specified'); - } - - options = options || {}; - options.port = undefined; - - var deferred = Q.defer(); - - this.port = new SerialPort(port.comName, merge(options, this.options)); + require('./messages').register(this); - this._onConnect = function () { - this.port.removeListener('open', this._onConnect); - delete this._onConnect; - this.onOpen(); - deferred.resolve(this); - }.bind(this); + this.connected = true; - this._onError = function (err) { - this.port.removeListener('error', this._onError); - delete this._onError; - deferred.reject(err); - }.bind(this); + this.onData = this.onData.bind(this); + this.onError = this.onError.bind(this); - this.port.on('error', this._onError); - this.port.on('open', this._onConnect); + this.socket.on('data', this.onData); + this.socket.on('error', this.onError); +} - return deferred.promise; -}; +util.inherits(Wii, EventEmitter); -Wii.prototype.onOpen = function () { - this.connected = true; - this.port.on('error', this.onError); - this.port.on('data', this.onData); -}; Wii.prototype.onError = function (err) { if(err.errno === -1 && err.code === 'UNKNOWN') { @@ -106,7 +41,6 @@ Wii.prototype.onError = function (err) { Wii.prototype.onData = function (buffer) { this.emit('data', buffer); - var len = buffer[3]; var type = buffer[4]; var data = buffer.slice(5, 5+len); @@ -127,14 +61,13 @@ Wii.prototype.addInMessage = function(name, parseFunction) { }.bind(this); }; -Wii.prototype.read = function(messageName) { - return this.send(this.readMessages[messageName]); +Wii.prototype.read = function(messageName, callback) { + this.send(this.readMessages[messageName], callback); }; -Wii.prototype.send = function(message) { +Wii.prototype.send = function(message, callback) { var size, len, msgId, data; - var deferred = Q.defer(); if('number' === typeof message) { msgId = message; @@ -155,24 +88,15 @@ Wii.prototype.send = function(message) { } msg.writeUInt8(this.calculateChecksum(msg), 5 + len); // checksum msg.writeUInt8(0, 6 + len); // derp data to force wii to comply - this.port.write(msg, function (err, result) { - if(err) { - deferred.reject(err); - } else { - deferred.resolve(); - } - }); - - return deferred.promise; + this.socket.write(msg, callback); }; Wii.prototype.disconnect = function () { if(this.connected) { this.connected = false; this.emit('disconnect'); - this.port.removeListener('error', this.onError); - this.port.removeListener('data', this.onData); - this.port.close(); + this.socket.removeListener('error', this.onError); + this.socket.removeListener('data', this.onData); } }; @@ -194,8 +118,4 @@ Wii.prototype.calculateChecksum = function(buffer) { } }; -module.exports = { - Wii: Wii, - list: list, - defaults: defaults -}; \ No newline at end of file +module.exports = Wii; \ No newline at end of file diff --git a/package.json b/package.json index c3dc7ce..d8c287b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "multiwii", - "version": "0.1.0", - "description": "A library for sending and parsing messages to/from a MultiWii device using Node.js", + "version": "1.0.0", + "description": "A library for sending and parsing messages to/from a MultiWii device using Node.js. It is now implemented such that it will use whatever stream you provide it instead of specifically requiring a serialport.", "main": "index.js", "scripts": { "test": "grunt test" @@ -14,15 +14,24 @@ "multiwii", "copter" ], - "author": "Johan Öbrink ", + "author": [ + { + "name": "Johan Öbrink", + "email": "johan.obrink@gmail.com", + "url": "http://terminaln00b.tumblr.com/" + }, + { + "name": "Chris Williams", + "email": "voodootikigod@gmail.com", + "url": "http://www.voodootikigod.com" + } + ], "license": "MIT", "bugs": { "url": "https://github.com/JohanObrink/node-multiwii/issues" }, "homepage": "https://github.com/JohanObrink/node-multiwii", "dependencies": { - "serialport": "~1.3.1", - "q": "~1.0.1" }, "devDependencies": { "sinon-chai": "~2.5.0", diff --git a/test/unit/merge.js b/test/unit/merge.js deleted file mode 100644 index 82b7a56..0000000 --- a/test/unit/merge.js +++ /dev/null @@ -1,33 +0,0 @@ -var chai = require('chai'), - expect = chai.expect, - merge = require('../../lib/merge'); - -describe('\u2b50 merge', function () { - - var defaults; - - beforeEach(function () { - defaults = { - foo: 'bar', - isFoo: true, - something: 42 - }; - }); - - it('adds missing properties to object', function () { - expect(merge({ herp: 'derp' }, defaults)).to.eql({ - foo: 'bar', - isFoo: true, - something: 42, - herp: 'derp' - }); - }); - - it('removes declared undefineds from object', function () { - expect(merge({ herp: 'derp', foo: undefined }, defaults)).to.eql({ - isFoo: true, - something: 42, - herp: 'derp' - }); - }); -}); \ No newline at end of file diff --git a/test/unit/messages.js b/test/unit/messages.js index c8edc4f..8e10987 100644 --- a/test/unit/messages.js +++ b/test/unit/messages.js @@ -3,34 +3,27 @@ var chai = require('chai'), expect = chai.expect, sinon = require('sinon'), - proxyquire = require('proxyquire'); + Wii = require('../../'); chai.use(require('sinon-chai')); describe('\u2b50 messages', function () { - var Wii, multiwii, serialport, sandbox, port, wii; + + + var proxyConnection, sandbox, wii; + beforeEach(function () { - port = { + proxyConnection = { on: sinon.stub(), - removeListener: sinon.stub() + removeListener: sinon.stub(), + write: sinon.stub(), + close: sinon.spy() }; - serialport = { - list: sinon.stub(), - parsers: { - raw: {} - }, - SerialPort: sinon.stub().returns(port) - }; - multiwii = proxyquire('../../lib/wii', { - 'serialport': serialport - }); - Wii = multiwii.Wii; sandbox = sinon.sandbox.create(); - - wii = new Wii(); - wii.connect('/dev/mupp'); - port.on.withArgs('open').yield(); + wii = new Wii(proxyConnection); }); + + afterEach(function () { sandbox.restore(); }); @@ -39,7 +32,7 @@ describe('\u2b50 messages', function () { it('parses the message correctly', function () { var listener = sinon.spy(); wii.on('ident', listener); - port.on.withArgs('data').yield(new Buffer([0, 0, 0, 7, 100, 2, 1, 1, 0, 0, 0, 0])); + proxyConnection.on.withArgs('data').yield(new Buffer([0, 0, 0, 7, 100, 2, 1, 1, 0, 0, 0, 0])); expect(listener).calledWith({ version: 2, multitype: 'TRI', @@ -59,7 +52,7 @@ describe('\u2b50 messages', function () { it('parses the message correctly', function () { var listener = sinon.spy(); wii.on('rc', listener); - port.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 105, + proxyConnection.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 105, 0, 1, // roll 1, 1, // pitch 2, 1, // yaw @@ -86,7 +79,7 @@ describe('\u2b50 messages', function () { it('parses the message correctly', function () { var listener = sinon.spy(); wii.on('status', listener); - port.on.withArgs('data').yield(new Buffer([0, 0, 0, 11, 101, + proxyConnection.on.withArgs('data').yield(new Buffer([0, 0, 0, 11, 101, 0, 1, // cycleTime 1, 1, // i2cErrorCount 2, 1, // sensor @@ -105,7 +98,7 @@ describe('\u2b50 messages', function () { it('parses the message correctly', function () { var listener = sinon.spy(); wii.on('servo', listener); - port.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 103, + proxyConnection.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 103, 0, 1, // servo[0] 1, 1, // servo[1] 2, 1, // servo[2] @@ -123,7 +116,7 @@ describe('\u2b50 messages', function () { it('parses the message correctly', function () { var listener = sinon.spy(); wii.on('motor', listener); - port.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 104, + proxyConnection.on.withArgs('data').yield(new Buffer([0, 0, 0, 16, 104, 0, 1, // motor[0] 1, 1, // motor[1] 2, 1, // motor[2] @@ -136,5 +129,5 @@ describe('\u2b50 messages', function () { expect(listener).calledWith([256, 257, 258, 259, 260, 261, 262, 263]); }); }); - + }); \ No newline at end of file diff --git a/test/unit/wii.js b/test/unit/wii.js index 4aad9e1..b3abfc7 100644 --- a/test/unit/wii.js +++ b/test/unit/wii.js @@ -3,30 +3,19 @@ var chai = require('chai'), expect = chai.expect, sinon = require('sinon'), - proxyquire = require('proxyquire'); + Wii = require('../../'); chai.use(require('sinon-chai')); describe('\u2b50 MultiWii', function () { - var multiwii, Wii, serialport, sandbox, port; + var sandbox, proxyConnection; beforeEach(function () { - port = { + proxyConnection = { on: sinon.stub(), removeListener: sinon.stub(), write: sinon.stub(), close: sinon.spy() }; - serialport = { - list: sinon.stub(), - parsers: { - raw: {} - }, - SerialPort: sinon.stub().returns(port) - }; - multiwii = proxyquire('../../lib/wii', { - 'serialport': serialport - }); - Wii = multiwii.Wii; sandbox = sinon.sandbox.create(); sandbox.stub(process, 'nextTick').yields(); }); @@ -36,280 +25,103 @@ describe('\u2b50 MultiWii', function () { it('returns a reference to Wii', function () { expect(Wii).to.be.a('function'); - expect(new Wii()).to.instanceof(Wii); + expect(new Wii(proxyConnection)).to.instanceof(Wii); }); describe('constructor', function () { - it('sets option values from arg obj', function () { - var wii = new Wii({port:'/dev/device'}); - expect(wii.options.port).to.equal('/dev/device'); - }); - it('sets option port from arg string', function () { - var wii = new Wii('/dev/device'); - expect(wii.options.port).to.equal('/dev/device'); - }); - it('sets defaults', function () { - var wii = new Wii(); - expect(wii.options.baudrate).to.equal(115200); - expect(wii.options.databits).to.equal(8); - expect(wii.options.stopbits).to.equal(1); - expect(wii.options.parity).to.equal('none'); - expect(wii.options.parser).to.eql(serialport.parsers.raw); - }); - it('merges with defaults', function () { - var wii = new Wii({ parity: 'even', parser: 'herro' }); - expect(wii.options.baudrate).to.equal(115200); - expect(wii.options.databits).to.equal(8); - expect(wii.options.stopbits).to.equal(1); - expect(wii.options.parity).to.equal('even'); - expect(wii.options.parser).to.equal('herro'); + it('sets socket from args obj', function () { + var wii = new Wii(proxyConnection); + expect(wii.socket).to.equal(proxyConnection); }); - }); + it('throws an error if no socket is specified', function () { - describe('list', function () { - it('calls serialport to list available devices', function () { - var listener = sinon.spy(); - multiwii.list(listener); - expect(serialport.list).calledOnce; - }); - it('calls callback with error on fail', function () { - var listener = sinon.spy(); - multiwii.list(listener); - serialport.list.yield('error'); - expect(listener).calledWith('error'); - }); - it('calls callback with list on success', function () { - var listener = sinon.spy(); - multiwii.list(listener); - serialport.list.yield(null, []); - expect(listener).calledWith(null, []); - }); - it('returns a promise if no callback is passed in', function () { - var promise = multiwii.list(); - expect(promise).to.be.an('object'); - expect(promise.then).to.be.a('function'); - expect(promise.catch).to.be.a('function'); - expect(promise.finally).to.be.a('function'); - }); - it('resolves the promise when serialport.list succeeds', function () { - var success = sinon.spy(); - var fail = sinon.spy(); - multiwii.list().then(success).catch(fail); - serialport.list.yield(null, []); - expect(success).called; - expect(success).calledWith([]); - expect(fail).not.called; + expect(function () { var wii = new Wii(); }).to.throw(Error, /Connection must be provided/); }); - it('rejects the promise when serialport.list fails', function () { - var success = sinon.spy(); - var fail = sinon.spy(); - multiwii.list().then(success).catch(fail); - serialport.list.yield('error'); - expect(success).not.called; - expect(fail).calledWith('error'); - }); - }); - describe('#connect', function () { - it('uses port from constructor', function () { - var wii = new Wii({ port: { comName: '/foo/bar' }}); - wii.connect(); - expect(serialport.SerialPort).calledOnce; - expect(serialport.SerialPort).calledWithNew; - expect(serialport.SerialPort).calledWith('/foo/bar', { - baudrate: 115200, - databits: 8, - stopbits: 1, - parity: 'none', - parser: serialport.parsers.raw - }); - }); - it('uses port from argument object', function () { - var wii = new Wii({ port: { comName: '/foo/bar' }}); - wii.connect({ comName: '/herp/derp' }); - expect(serialport.SerialPort).calledOnce; - expect(serialport.SerialPort).calledWithNew; - expect(serialport.SerialPort).calledWith('/herp/derp', { - baudrate: 115200, - databits: 8, - stopbits: 1, - parity: 'none', - parser: serialport.parsers.raw - }); - }); - it('uses port from argument string', function () { - var wii = new Wii({ port: { comName: '/foo/bar' }}); - wii.connect('/herp/derp'); - expect(serialport.SerialPort).calledOnce; - expect(serialport.SerialPort).calledWithNew; - expect(serialport.SerialPort).calledWith('/herp/derp', { - baudrate: 115200, - databits: 8, - stopbits: 1, - parity: 'none', - parser: serialport.parsers.raw - }); - }); - it('throws an error if no port is specified', function () { - var wii = new Wii(); - expect(function () { wii.connect(); }).to.throw(Error, /no port specified/); - }); - it('uses options from argument', function () { - var wii = new Wii(); - wii.connect('/herp/derp', { parity: 'even' }); - expect(serialport.SerialPort).calledOnce; - expect(serialport.SerialPort).calledWithNew; - expect(serialport.SerialPort).calledWith('/herp/derp', { - baudrate: 115200, - databits: 8, - stopbits: 1, - parity: 'even', - parser: serialport.parsers.raw - }); - }); - it('returns a promise', function () { - var wii = new Wii(); - var promise = wii.connect('/dev/derp'); - expect(promise).to.be.an('object'); - expect(promise.then).to.be.a('function'); - expect(promise.catch).to.be.a('function'); - expect(promise.finally).to.be.a('function'); - }); it('adds listeners', function () { - var wii = new Wii(); - wii.connect('/dev/derp'); - expect(port.on).calledTwice; - expect(port.on.withArgs('error')).calledOnce; - expect(port.on.withArgs('open')).calledOnce; + var wii = new Wii(proxyConnection); + expect(proxyConnection.on.withArgs('error')).calledOnce; + expect(proxyConnection.on.withArgs('data')).calledOnce; }); - it('resolves promise on open', function () { - var success = sinon.spy(); - var fail = sinon.spy(); - var wii = new Wii(); - wii.connect('/dev/derp').then(success).catch(fail); - port.on.withArgs('open').yield(); - expect(success).calledOnce; - expect(success).calledWith(wii); - expect(fail).not.called; + + it('sets connected property to true', function () { + var wii = new Wii(proxyConnection); + expect(wii.connected).to.be.true; }); - it('clears and removes connect failed listener', function () { - var success = sinon.spy(); - var fail = sinon.spy(); - var wii = new Wii(); - wii.connect('/dev/derp').then(success).catch(fail); - var _onError = wii._onError; - port.on.withArgs('error').yield('bork'); - expect(port.removeListener).calledWith('error', _onError); - expect(wii._onError).to.not.exist; + + }); + + describe('with communication established', function () { + var wii; + beforeEach(function () { + wii = new Wii(proxyConnection); }); - it('rejects promise on error', function () { - var success = sinon.spy(); - var fail = sinon.spy(); - var wii = new Wii(); - wii.connect('/dev/derp').then(success).catch(fail); - port.on.withArgs('error').yield('bork'); - expect(success).not.called; - expect(fail).calledOnce; - expect(fail).calledWith('bork'); + afterEach(function () { + wii = null; }); - describe('#onOpen', function () { - var wii, _onConnect; + describe('\u26a1 data', function () { + var listener; beforeEach(function () { - wii = new Wii(); - wii.connect('/dev/derp'); - _onConnect = wii._onConnect; - port.on.withArgs('open').yield(); + listener = sinon.spy(); + wii.on('data', listener); }); - it('sets connected property to true', function () { - expect(wii.connected).to.be.true; + it('emits raw data', function () { + var buffer = new Buffer([0xff, 0xfe]); + proxyConnection.on.withArgs('data').yield(buffer); + expect(listener).calledOnce; + expect(listener).calledWith(buffer); }); + }); - it('adds listener for data', function () { - expect(port.on.withArgs('data')).calledOnce; - }); + describe('#onError', function () { + var listener; - it('clears and removes connect listener', function () { - expect(port.removeListener).calledWith('open', _onConnect); - expect(wii._onConnect).to.not.exist; + beforeEach(function () { + listener = sinon.spy(); + wii.on('error', listener); }); - describe('#onError', function () { - it('emits error', function () { - var listener = sinon.spy(); - wii.on('error', listener); - var err = new Error(); - port.on.withArgs('error').yield(err); - expect(listener).calledOnce; - }); + it('emits error', function () { + var err = new Error(); + proxyConnection.on.withArgs('error').yield(err); + expect(listener).calledOnce; }); + }); - describe('\u26a1 disconnect', function () { - var listener; - beforeEach(function () { - listener = sinon.spy(); - wii.on('disconnect', listener); - port.on.withArgs('error').yield({ errno: -1, code: 'UNKNOWN' }); - }); - it('emits disconnect', function () { - expect(listener).calledOnce; - }); - it('sets connected to false', function () { - expect(wii.connected).to.be.false; - }); - it('clears listeners', function () { - expect(port.removeListener).calledWith('error', wii.onError); - expect(port.removeListener).calledWith('data', wii.onData); - }); + describe('#send', function () { + it('sends out messages correctly', function () { + wii.send(105); + var expected = Array.prototype.slice.call(new Buffer([0x24, 0x4D, 0x3C, 0, 105, 105, 0])); + var sent = Array.prototype.slice.call(proxyConnection.write.getCall(0).args[0]); + expect(sent).to.eql(expected); }); + }); - describe('\u26a1 data', function () { - var listener; - - beforeEach(function () { - listener = sinon.spy(); - wii.on('data', listener); - }); - it('emits raw data', function () { - var buffer = new Buffer([0xff, 0xfe]); - port.on.withArgs('data').yield(buffer); - expect(listener).calledOnce; - expect(listener).calledWith(buffer); - }); + describe('#calculateChecksum', function () { + it('can calculate the checksum', function () { + var buffer = new Buffer(40); + var data = new Buffer([0x24, 0x4d, 0x3e, 0x0a, 0x65, 0x28, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00]); + data.copy(buffer, 0); + var checksum = wii.calculateChecksum(buffer); + expect(checksum).to.equal(0x6d); }); - - describe('#send', function () { - it('sends out messages correctly', function () { - wii.send(105); - var expected = Array.prototype.slice.call(new Buffer([0x24, 0x4D, 0x3C, 0, 105, 105, 0])); - var sent = Array.prototype.slice.call(port.write.getCall(0).args[0]); - - expect(sent).to.eql(expected); - }); - }); - - describe('#calculateChecksum', function () { - it('can calculate the checksum', function () { - var buffer = new Buffer(40); - var data = new Buffer([0x24, 0x4d, 0x3e, 0x0a, 0x65, 0x28, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00]); - data.copy(buffer, 0); - var checksum = wii.calculateChecksum(buffer); - expect(checksum).to.equal(0x6d); - }); - it('can calculate another checksum', function () { - var buffer = new Buffer(40); - var data = new Buffer([0x24, 0x4D, 0x3C, 0, 105]); - data.copy(buffer, 0); - var checksum = wii.calculateChecksum(buffer); - expect(checksum).to.equal(105); - }); + it('can calculate another checksum', function () { + var buffer = new Buffer(40); + var data = new Buffer([0x24, 0x4D, 0x3C, 0, 105]); + data.copy(buffer, 0); + var checksum = wii.calculateChecksum(buffer); + expect(checksum).to.equal(105); }); - }); + }); + + + }); \ No newline at end of file