Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ descriptor.once('valueWrite');
By default, noble will select appropriate Bluetooth device bindings based on your platform. You can provide custom bindings using the `with-bindings` module.

```javascript
var noble = require('noble/with-bindings')(require('./my-custom-bindings'));
var noble = require('@abandonware/noble/with-bindings')(require('./my-custom-bindings'));
```

### Running without root/sudo (Linux-specific)
Expand Down Expand Up @@ -630,6 +630,20 @@ For example, to specify `hci1`:
sudo NOBLE_HCI_DEVICE_ID=1 node <your file>.js
```

If you are using multiple HCI devices in one setup you can run two instances of noble with different binding configurations by initializing them seperatly in code:

```
const HCIBindings = require('@abandonware/noble/lib/hci-socket/bindings');
const Noble = require('@abandonware/noble/lib/noble');

const params = {
deviceId: 0,
userChannel: true
};

const noble = new Noble(new HCIBindings(params));
```

### Reporting all HCI events (Linux-specific)

By default, noble waits for both the advertisement data and scan response data for each Bluetooth address. If your device does not use scan response, the `NOBLE_REPORT_ALL_HCI_EVENTS` environment variable can be used to bypass it.
Expand Down
2 changes: 1 addition & 1 deletion lib/distributed/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,4 @@ NobleBindings.prototype.writeHandle = function (deviceUuid, handle, data, withou
});
};

module.exports = new NobleBindings();
module.exports = NobleBindings;
17 changes: 9 additions & 8 deletions lib/hci-socket/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Gap = require('./gap');
const Hci = require('./hci');
const Signaling = require('./signaling');

const NobleBindings = function () {
const NobleBindings = function (options) {
this._state = null;

this._addresses = {};
Expand All @@ -22,7 +22,7 @@ const NobleBindings = function () {
this._aclStreams = {};
this._signalings = {};

this._hci = new Hci();
this._hci = new Hci(options);
this._gap = new Gap(this._hci);
};

Expand All @@ -42,16 +42,16 @@ NobleBindings.prototype.stopScanning = function () {
this._gap.stopScanning();
};

NobleBindings.prototype.connect = function (peripheralUuid) {
NobleBindings.prototype.connect = function (peripheralUuid, parameters) {
const address = this._addresses[peripheralUuid];
const addressType = this._addresseTypes[peripheralUuid];

if (!this._pendingConnectionUuid) {
this._pendingConnectionUuid = peripheralUuid;

this._hci.createLeConn(address, addressType);
this._hci.createLeConn(address, addressType, parameters);
} else {
this._connectionQueue.push(peripheralUuid);
this._connectionQueue.push({ id: peripheralUuid, params: parameters });
}
};

Expand Down Expand Up @@ -237,14 +237,15 @@ NobleBindings.prototype.onLeConnComplete = function (status, handle, role, addre
this.emit('connect', uuid, error);

if (this._connectionQueue.length > 0) {
const peripheralUuid = this._connectionQueue.shift();
const queueItem = this._connectionQueue.shift();
const peripheralUuid = queueItem.id;

address = this._addresses[peripheralUuid];
addressType = this._addresseTypes[peripheralUuid];

this._pendingConnectionUuid = peripheralUuid;

this._hci.createLeConn(address, addressType);
this._hci.createLeConn(address, addressType, queueItem.params);
} else {
this._pendingConnectionUuid = null;
}
Expand Down Expand Up @@ -556,4 +557,4 @@ NobleBindings.prototype.onConnectionParameterUpdateRequest = function (handle, m
this._hci.connUpdateLe(handle, minInterval, maxInterval, latency, supervisionTimeout);
};

module.exports = new NobleBindings();
module.exports = NobleBindings;
34 changes: 21 additions & 13 deletions lib/hci-socket/hci.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ const HCI_OE_USER_ENDED_CONNECTION = 0x13;

const STATUS_MAPPER = require('./hci-status');

const Hci = function () {
const Hci = function (options) {
options = options || {};
this._socket = new BluetoothHciSocket();
this._isDevUp = null;
this._state = null;
this._deviceId = null;

this._handleBuffers = {};

Expand All @@ -92,26 +92,32 @@ const Hci = function () {

this._aclQueue = [];

this._deviceId = options.deviceId != null
? parseInt(options.deviceId, 10)
: process.env.NOBLE_HCI_DEVICE_ID
? parseInt(process.env.NOBLE_HCI_DEVICE_ID, 10)
: undefined;

this._userChannel = (typeof options.userChannel === 'undefined' && options.userChannel) || process.env.HCI_CHANNEL_USER;

this.on('stateChange', this.onStateChange.bind(this));
};

util.inherits(Hci, events.EventEmitter);

Hci.STATUS_MAPPER = STATUS_MAPPER;

Hci.prototype.init = function () {
Hci.prototype.init = function (options) {
this._socket.on('data', this.onSocketData.bind(this));
this._socket.on('error', this.onSocketError.bind(this));

const deviceId = process.env.NOBLE_HCI_DEVICE_ID ? parseInt(process.env.NOBLE_HCI_DEVICE_ID, 10) : undefined;

if (process.env.HCI_CHANNEL_USER) {
this._deviceId = this._socket.bindUser(deviceId);
if (this._userChannel) {
this._socket.bindUser(this._deviceId);
this._socket.start();

this.reset();
} else {
this._deviceId = this._socket.bindRaw(deviceId);
this._socket.bindRaw(this._deviceId);
this._socket.start();

this.pollIsDevUp();
Expand Down Expand Up @@ -338,7 +344,9 @@ Hci.prototype.setScanEnabled = function (enabled, filterDuplicates) {
this._socket.write(cmd);
};

Hci.prototype.createLeConn = function (address, addressType) {
Hci.prototype.createLeConn = function (address, addressType, parameters = {}) {
const { minInterval = 0x0006, maxInterval = 0x000c, latency = 0x0000, timeout = 0x00c8 } = parameters;

const cmd = Buffer.alloc(29);

// header
Expand All @@ -358,10 +366,10 @@ Hci.prototype.createLeConn = function (address, addressType) {

cmd.writeUInt8(0x00, 16); // own address type

cmd.writeUInt16LE(0x0006, 17); // min interval
cmd.writeUInt16LE(0x000c, 19); // max interval
cmd.writeUInt16LE(0x0000, 21); // latency
cmd.writeUInt16LE(0x00c8, 23); // supervision timeout
cmd.writeUInt16LE(minInterval, 17); // min interval
cmd.writeUInt16LE(maxInterval, 19); // max interval
cmd.writeUInt16LE(latency, 21); // latency
cmd.writeUInt16LE(timeout, 23); // supervision timeout
cmd.writeUInt16LE(0x0004, 25); // min ce length
cmd.writeUInt16LE(0x0006, 27); // max ce length

Expand Down
4 changes: 2 additions & 2 deletions lib/noble.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ Noble.prototype.onDiscover = function (uuid, address, addressType, connectable,
}
};

Noble.prototype.connect = function (peripheralUuid) {
this._bindings.connect(peripheralUuid);
Noble.prototype.connect = function (peripheralUuid, parameters) {
this._bindings.connect(peripheralUuid, parameters);
};

Noble.prototype.onConnect = function (peripheralUuid, error) {
Expand Down
13 changes: 10 additions & 3 deletions lib/peripheral.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ Peripheral.prototype.toString = function () {
});
};

const connect = function (callback) {
const connect = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}

if (callback) {
this.once('connect', error => {
callback(error);
Expand All @@ -42,12 +47,14 @@ const connect = function (callback) {
this.emit('connect', new Error('Peripheral already connected'));
} else {
this.state = 'connecting';
this._noble.connect(this.id);
this._noble.connect(this.id, options);
}
};

Peripheral.prototype.connect = connect;
Peripheral.prototype.connectAsync = util.promisify(connect);
Peripheral.prototype.connectAsync = function (options) {
return util.promisify(callback => this.connect(options, callback))();
};

const disconnect = function (callback) {
if (callback) {
Expand Down
10 changes: 5 additions & 5 deletions lib/resolve-bindings.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
const os = require('os');

module.exports = function () {
module.exports = function (options) {
const platform = os.platform();

if (process.env.NOBLE_WEBSOCKET) {
return require('./websocket/bindings');
return new (require('./websocket/bindings'))(options);
} else if (process.env.NOBLE_DISTRIBUTED) {
return require('./distributed/bindings');
return new (require('./distributed/bindings'))(options);
} else if (platform === 'darwin') {
return require('./mac/bindings');
return new (require('./mac/bindings'))(options);
} else if (platform === 'linux' || platform === 'freebsd' || platform === 'win32') {
return require('./hci-socket/bindings');
return new (require('./hci-socket/bindings'))(options);
} else {
throw new Error('Unsupported platform');
}
Expand Down
4 changes: 1 addition & 3 deletions lib/webbluetooth/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,4 @@ NobleBindings.prototype.writeHandle = function (deviceUuid, handle, data, withou
// this.emit('handleWrite', deviceUuid, handle);
};

const nobleBindings = new NobleBindings();

module.exports = nobleBindings;
module.exports = NobleBindings;
2 changes: 1 addition & 1 deletion lib/websocket/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,4 @@ NobleBindings.prototype.writeHandle = function (deviceUuid, handle, data, withou
});
};

module.exports = new NobleBindings();
module.exports = NobleBindings;
23 changes: 23 additions & 0 deletions test/test-noble.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('Noble', () => {
init: () => {},
on: () => {},
setScanParameters: fake.returns(null),
connect: fake.returns(true),
startScanning: sinon.spy(),
stopScanning: sinon.spy()
};
Expand All @@ -37,6 +38,8 @@ describe('Noble', () => {
await promise;

mockBindings.startScanning.calledWithExactly(expectedServiceUuids, expectedAllowDuplicates).should.equal(true);

assert.notCalled(mockBindings.connect);
});

it('should throw an error if not powered on', async () => {
Expand All @@ -45,6 +48,8 @@ describe('Noble', () => {
noble.emit('scanStart');

await promise.should.be.rejectedWith('Could not start scanning, state is poweredOff (not poweredOn)');

assert.notCalled(mockBindings.connect);
});

it('should resolve', async () => {
Expand All @@ -53,6 +58,8 @@ describe('Noble', () => {
noble.emit('scanStart');

await promise.should.be.resolved();

assert.notCalled(mockBindings.connect);
});
});

Expand All @@ -64,12 +71,28 @@ describe('Noble', () => {
await promise;

mockBindings.stopScanning.calledWithExactly().should.equal(true);

assert.notCalled(mockBindings.connect);
});

it('should resolve', async () => {
const promise = noble.stopScanningAsync();
noble.emit('scanStop');
await promise.should.be.resolved();

assert.notCalled(mockBindings.connect);
});
});

describe('connect', () => {
it('should delegate to binding', () => {
const peripheralUuid = 'peripheral-uuid';
const parameters = {};

noble.connect(peripheralUuid, parameters);

assert.calledOnce(mockBindings.connect);
assert.calledWith(mockBindings.connect, peripheralUuid, parameters);
});
});

Expand Down
36 changes: 34 additions & 2 deletions test/test-peripheral.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require('should');
const sinon = require('sinon');
const { fake, assert } = sinon;

const Peripheral = require('../lib/peripheral');

Expand Down Expand Up @@ -31,6 +32,7 @@ describe('Peripheral', function () {

afterEach(function () {
peripheral = null;
sinon.reset();
});

it('should have a id', function () {
Expand Down Expand Up @@ -67,7 +69,7 @@ describe('Peripheral', function () {
it('should delegate to noble', function () {
peripheral.connect();

mockNoble.connect.calledWithExactly(mockId).should.equal(true);
mockNoble.connect.calledWithExactly(mockId, undefined).should.equal(true);
});

it('should callback', function () {
Expand All @@ -80,6 +82,26 @@ describe('Peripheral', function () {

calledback.should.equal(true);
});

it('with options, no callback', function () {
const options = { options: true };

peripheral.connect(options);
peripheral.emit('connect');

mockNoble.connect.calledWithExactly(peripheral.id, options);
});

it('both options and callback', function () {
const options = { options: true };
const callback = fake.returns(null);

peripheral.connect(options, callback);
peripheral.emit('connect');

mockNoble.connect.calledWithExactly(peripheral.id, options).should.equal(true);
assert.calledOnce(callback);
});
});

describe('connectAsync', () => {
Expand All @@ -105,7 +127,17 @@ describe('Peripheral', function () {
peripheral.emit('connect');
await promise;

mockNoble.connect.calledWithExactly(mockId).should.equal(true);
mockNoble.connect.calledWithExactly(mockId, undefined).should.equal(true);
});

it('with options', async () => {
const options = { options: true };

const promise = peripheral.connectAsync(options);
peripheral.emit('connect');
await promise;

mockNoble.connect.calledWithExactly(peripheral.id, options).should.equal(true);
});
});

Expand Down