diff --git a/lib/usb.js b/lib/usb.js index 80ed93c..b6a7045 100644 --- a/lib/usb.js +++ b/lib/usb.js @@ -19,6 +19,7 @@ const VENDOR_DEVICE_LIST = [ { vid: 0x413c, pid: 0x8143 }, // Broadcom BCM20702A0 { vid: 0x0a12, pid: 0x0001 }, // CSR { vid: 0x0b05, pid: 0x17cb }, // ASUS BT400 + { vid: 0x0b05, pid: 0x190e, resetCompleteFallback: true }, // ASUS USB-BT500 (Realtek RTL8761B) { vid: 0x8087, pid: 0x07da }, // Intel 6235 { vid: 0x8087, pid: 0x07dc }, // Intel 7260 { vid: 0x8087, pid: 0x0a2a }, // Intel 7265 @@ -32,6 +33,7 @@ const VENDOR_DEVICE_LIST = [ function BluetoothHciSocket () { this._isUp = false; + this._resetCompleteFallback = false; this._hciEventEndpointBuffer = Buffer.alloc(0); this._aclDataInEndpointBuffer = Buffer.alloc(0); @@ -74,6 +76,8 @@ BluetoothHciSocket.prototype.bindUser = function (devId, params) { throw new Error('No compatible USB Bluetooth 4.0 device found!'); } + this._resetCompleteFallback = this._needsResetCompleteFallback(this._usbDevice); + this._usbDevice.open(); this._usbDeviceInterface = this._usbDevice.interfaces[0]; @@ -155,6 +159,20 @@ BluetoothHciSocket.prototype._findUsbDevice = function (devId, usbParams) { } }; +BluetoothHciSocket.prototype._needsResetCompleteFallback = function (usbDevice) { + const deviceDescriptor = usbDevice && usbDevice.deviceDescriptor; + + if (!deviceDescriptor) { + return false; + } + + return VENDOR_DEVICE_LIST.some(device => { + return device.resetCompleteFallback === true && + deviceDescriptor.idVendor === device.vid && + deviceDescriptor.idProduct === device.pid; + }); +}; + BluetoothHciSocket.prototype.getDeviceList = function () { return getDeviceList() .filter(dev => { @@ -275,8 +293,20 @@ BluetoothHciSocket.prototype.onHciEventEndpointData = function (data) { ])); } - if (this._mode === 'raw' && buf.length === 6 && (buf.toString('hex') === '0e0401030c00' || buf.toString('hex') === '0e0402030c00')) { - debug('reset complete'); + const isStandardResetComplete = buf.length === 6 && + (buf.toString('hex') === '0e0401030c00' || buf.toString('hex') === '0e0402030c00'); + const isFallbackResetComplete = !this._isUp && + this._resetCompleteFallback && + buf.length >= 6 && + buf.readUInt8(0) === 0x0e && + buf.readUInt8(5) === 0x00; + + if (this._mode === 'raw' && (isStandardResetComplete || isFallbackResetComplete)) { + if (isFallbackResetComplete && !isStandardResetComplete) { + debug('reset complete (ASUS USB-BT500 fallback)'); + } else { + debug('reset complete'); + } this._isUp = true; this.emit('state', this._isUp); } diff --git a/package.json b/package.json index 3fa3fc7..b146e03 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "semantic-release": "semantic-release", "pretest": "npm run rebuild", "rebuild": "node-gyp rebuild", - "test": "jshint lib/*.js && node test.js" + "test": "jshint lib/*.js && node test.js && mocha \"test/**/*.test.js\"" }, "jshintConfig": { "esversion": 6 diff --git a/test/usb.test.js b/test/usb.test.js new file mode 100644 index 0000000..f2e8ef7 --- /dev/null +++ b/test/usb.test.js @@ -0,0 +1,63 @@ +const assert = require('assert'); + +const BluetoothHciSocket = require('../lib/usb'); + +function createRawSocket (resetCompleteFallback) { + const socket = new BluetoothHciSocket(); + const states = []; + + socket._mode = 'raw'; + socket._resetCompleteFallback = resetCompleteFallback; + socket.on('state', state => states.push(state)); + + return { socket, states }; +} + +describe('USB reset completion', function () { + it('enables the fallback only for the ASUS BT500', function () { + const socket = new BluetoothHciSocket(); + + assert.strictEqual(socket._needsResetCompleteFallback({ + deviceDescriptor: { idVendor: 0x0b05, idProduct: 0x190e } + }), true); + assert.strictEqual(socket._needsResetCompleteFallback({ + deviceDescriptor: { idVendor: 0x0b05, idProduct: 0x17cb } + }), false); + }); + + it('accepts the standard reset response for every adapter', function () { + const { socket, states } = createRawSocket(false); + + socket.onHciEventEndpointData(Buffer.from('0e0401030c00', 'hex')); + + assert.strictEqual(socket.isDevUp(), true); + assert.deepStrictEqual(states, [true]); + }); + + it('accepts a successful non-standard Command Complete for the ASUS BT500', function () { + const { socket, states } = createRawSocket(true); + + socket.onHciEventEndpointData(Buffer.from('0e0401011000', 'hex')); + + assert.strictEqual(socket.isDevUp(), true); + assert.deepStrictEqual(states, [true]); + }); + + it('ignores a non-reset Command Complete for other adapters', function () { + const { socket, states } = createRawSocket(false); + + socket.onHciEventEndpointData(Buffer.from('0e0401011000', 'hex')); + + assert.strictEqual(socket.isDevUp(), false); + assert.deepStrictEqual(states, []); + }); + + it('ignores a failed Command Complete for the ASUS BT500', function () { + const { socket, states } = createRawSocket(true); + + socket.onHciEventEndpointData(Buffer.from('0e0401011001', 'hex')); + + assert.strictEqual(socket.isDevUp(), false); + assert.deepStrictEqual(states, []); + }); +});