Comparing version 0.1.1 to 0.1.2
@@ -11,3 +11,3 @@ var os = require('os'); | ||
exec('npm install xpc-connection@0.0.1', function(error, stdout, stderr) { | ||
exec('npm install xpc-connection@~0.0.2', function(error, stdout, stderr) { | ||
console.log('noble install: done'); | ||
@@ -17,8 +17,3 @@ process.exit(error ? -1 : 0); | ||
} else if (platform === 'linux') { | ||
console.log('noble install: installing dbus ...'); | ||
exec('npm install dbus', function(error, stdout, stderr) { | ||
console.log('noble install: done'); | ||
process.exit(error ? -1 : 0); | ||
}); | ||
process.exit(0); | ||
} else { | ||
@@ -25,0 +20,0 @@ console.error('noble install: Your platform is not supported!'); |
@@ -25,4 +25,2 @@ var debug = require('debug')('bindings'); | ||
this._bluez.on('discover', this.onDiscover.bind(this)); | ||
this._bluez.init(); | ||
}; | ||
@@ -114,4 +112,2 @@ | ||
this._gatttool[address].disconnect(); | ||
// TODO: kill gatttool, unbind events | ||
}; | ||
@@ -122,2 +118,8 @@ | ||
this._gatttool[address].kill(); | ||
this._gatttool[address].removeAllListeners(); | ||
delete this._gatttool[address]; | ||
this.emit('disconnect', uuid); | ||
@@ -286,2 +288,2 @@ }; | ||
module.exports = nobleBindings; | ||
module.exports = nobleBindings; |
var debug = require('debug')('bluez'); | ||
var events = require('events'); | ||
var spawn = require('child_process').spawn; | ||
var util = require('util'); | ||
var dbus = require('dbus'); | ||
var BlueZ4 = require('./bluez4'); | ||
var BlueZ5 = require('./bluez5'); | ||
var BlueZ = function() { | ||
var bluezScanner = __dirname + '/../../bin/bluez-scanner.py'; | ||
debug('bluezScanner = ' + bluezScanner); | ||
var BlueZ = function() { | ||
this._bluezScanner = spawn('stdbuf', ['-o', '0', '-e', '0', '-i', '0', bluezScanner]); | ||
this._bluezScanner.on('close', this.onClose.bind(this)); | ||
this._bluezScanner.stdout.on('data', this.onStdoutData.bind(this)); | ||
this._bluezScanner.stderr.on('data', this.onStderrData.bind(this)); | ||
this._bluezScanner.on('error', function() { }); | ||
this._buffer = ""; | ||
}; | ||
@@ -16,41 +26,74 @@ | ||
BlueZ.prototype.init = function() { | ||
this._bluez = null; | ||
BlueZ.prototype.onClose = function(code) { | ||
debug('close = ' + code); | ||
}; | ||
dbus.start(function() { | ||
if (this._bluez === null) { | ||
try { | ||
this._bluez = new BlueZ5(dbus); | ||
} catch(e) { | ||
// console.error(e); | ||
BlueZ.prototype.onStdoutData = function(data) { | ||
this._buffer += data.toString(); | ||
debug('buffer = ' + JSON.stringify(this._buffer)); | ||
var newLineIndex; | ||
while ((newLineIndex = this._buffer.indexOf('\n')) !== -1) { | ||
var line = this._buffer.substring(0, newLineIndex); | ||
this._buffer = this._buffer.substring(newLineIndex + 1); | ||
var splitLine = line.split(': '); | ||
var key = splitLine[0]; | ||
var value = splitLine[1]; | ||
if (key === 'Adapter') { | ||
var state = 'unknown'; | ||
if (value === 'None') { | ||
state = 'unsupported'; | ||
} else if (value === 'PoweredOff') { | ||
state = 'poweredOff'; | ||
} else if (value === 'PoweredOn') { | ||
state = 'poweredOn'; | ||
} | ||
} | ||
if (this._bluez === null) { | ||
try { | ||
this._bluez = new BlueZ4(dbus); | ||
} catch(e) { | ||
// console.error(e); | ||
this.emit('stateChange', state); | ||
} else if (key === 'DeviceFound') { | ||
var splitValue = value.split(','); | ||
var properties = {}; | ||
for (var i in splitValue) { | ||
var splitValueValue = splitValue[i].split(' = '); | ||
properties[splitValueValue[0].trim()] = splitValueValue[1].trim(); | ||
} | ||
} | ||
if (this._bluez === null) { | ||
throw new Error('Could not init BlueZ'); | ||
} else { | ||
this._bluez.on('stateChange', this.onStateChange.bind(this)); | ||
this._bluez.on('discover', this.onDiscover.bind(this)); | ||
var address = properties.Address; | ||
var advertisement = { | ||
localName: properties.Name, | ||
serviceUuids: [] | ||
}; | ||
var rssi = properties.RSSI; | ||
process.nextTick(function() { | ||
this.emit('stateChange', this._bluez._powered ? 'poweredOn' : 'poweredOff'); | ||
}.bind(this)); | ||
var uuids = properties.UUIDs.split(' '); | ||
for (var j in uuids) { | ||
var uuid = uuids[j].split('-').join(''); | ||
if (uuid.match(/^0000.{4}00001000800000805f9b/)) { | ||
uuid = uuid.substring(4, 8); | ||
} | ||
advertisement.serviceUuids.push(uuid); | ||
} | ||
this.emit('discover', address, advertisement, rssi); | ||
} else if (key === 'Error') { | ||
console.error('bluez-scanner error: ' + value); | ||
} | ||
}.bind(this)); | ||
} | ||
}; | ||
BlueZ.prototype.onStateChange = function(state) { | ||
this.emit('stateChange', state); | ||
BlueZ.prototype.onStderrData = function(data) { | ||
console.error('stderr: ' + data); | ||
}; | ||
BlueZ.prototype.startScanning = function() { | ||
this._bluez.startScanning(); | ||
this._bluezScanner.stdin.write('start\n'); | ||
@@ -61,3 +104,3 @@ this.emit('scanStart'); | ||
BlueZ.prototype.stopScanning = function() { | ||
this._bluez.startScanning(); | ||
this._bluezScanner.stdin.write('stop\n'); | ||
@@ -67,7 +110,3 @@ this.emit('scanStop'); | ||
BlueZ.prototype.onDiscover = function(address, advertisement, rssi) { | ||
this.emit('discover', address, advertisement, rssi); | ||
}; | ||
module.exports = BlueZ; | ||
module.exports = BlueZ; |
@@ -33,27 +33,34 @@ /*jshint loopfunc: true */ | ||
Gatttool.prototype.kill = function() { | ||
this._gatttool.kill(); | ||
}; | ||
Gatttool.prototype.onClose = function(code) { | ||
console.log('close = ' + code); | ||
debug('close = ' + code); | ||
}; | ||
Gatttool.prototype.onStdoutData = function(data) { | ||
var string = data.toString().replace(/(\u001b\[C)|(\u001b\[16@)/g, '').replace(/\r/g, '\n'); | ||
var string = data.toString().replace(/(\u001b\[C)|(\u001b\[16@)|(\u001b\[4h)|(\u001b\[4l)/g, '').replace(/\r/g, '\n'); | ||
this._buffer += string; | ||
if (this._buffer === ' Characteristic value was written successfully\n' || | ||
this._buffer === ' Read characteristics by UUID failed: No attribute found within the given range\n') { | ||
if (this._buffer === '\n ' || | ||
this._buffer === ' \n ' || | ||
this._buffer === ' Characteristic value was written successfully\n' || | ||
this._buffer === ' Read characteristics by UUID failed: No attribute found within the given range\n' || | ||
this._buffer === ' Characteristic value/descriptor read failed: Attribute can\'t be read\n') { | ||
this._gatttool.stdin.write('\n'); | ||
} | ||
// console.log('***'); | ||
// console.log(JSON.stringify(this._buffer)); | ||
// console.log('***'); | ||
debug('***'); | ||
debug(JSON.stringify(this._buffer)); | ||
debug('***'); | ||
var found; | ||
while ((found = this._buffer.match(/([\s\S]*)\[(.{3})\]\[(.{17})\]\[(.*)\]>(.*)/)) !== null) { | ||
// console.log(found); | ||
debug(found); | ||
var output = found[1]; | ||
var connected = (found[2] === 'CON'); | ||
var address = found[3]; | ||
//var address = found[3]; | ||
var type = found[4]; | ||
@@ -105,3 +112,3 @@ | ||
this.emit(connected ? 'connect' : 'disconnect', address); | ||
this.emit(connected ? 'connect' : 'disconnect', this._address); | ||
} | ||
@@ -112,3 +119,3 @@ } | ||
Gatttool.prototype.onStderrData = function(data) { | ||
console.log('stderr: ' + data); | ||
console.error('stderr: ' + data); | ||
}; | ||
@@ -129,3 +136,3 @@ | ||
Gatttool.prototype._executeNextCommand = function() { | ||
this._currentCommand = this._commandQueue.pop(); | ||
this._currentCommand = this._commandQueue.shift(); | ||
@@ -137,4 +144,4 @@ this._gatttool.stdin.write(this._currentCommand.command + '\n'); | ||
// TODO: handle 'public' and 'random' | ||
this.executeCommand('connect' /*+ ' ' + this._address + ' random'*/, false, function(output) { | ||
// console.log('connect output: ' + JSON.stringify(output)); | ||
this.executeCommand('connect'/*+ ' ' + this._address + ' random'*/, false, function(output) { | ||
debug('connect output: ' + JSON.stringify(output)); | ||
}.bind(this)); | ||
@@ -145,3 +152,3 @@ }; | ||
this.executeCommand('disconnect', false, function(output) { | ||
// console.log('disconnect output: ' + JSON.stringify(output)); | ||
debug('disconnect output: ' + JSON.stringify(output)); | ||
}.bind(this)); | ||
@@ -152,3 +159,3 @@ }; | ||
this.executeCommand('primary', true, function(output) { | ||
// console.log('primary output: ' + JSON.stringify(output)); | ||
debug('primary output: ' + JSON.stringify(output)); | ||
@@ -170,6 +177,6 @@ var serviceUuids = []; | ||
// console.log(attrHandle); | ||
// console.log(endGrpHandle); | ||
// console.log(uuid); | ||
// console.log(); | ||
debug(attrHandle); | ||
debug(endGrpHandle); | ||
debug(uuid); | ||
debug(); | ||
@@ -198,3 +205,3 @@ this._services[uuid] = { | ||
// console.log(serviceUuids); | ||
debug(serviceUuids); | ||
this.emit('servicesDiscover', this._address, serviceUuids); | ||
@@ -208,3 +215,3 @@ }.bind(this)); | ||
this.executeCommand('char-read-uuid 0x2802 ' + service.attrHandle + ' ' + service.endGrpHandle, true, function(output) { | ||
console.log('discover included services output: ' + JSON.stringify(output)); | ||
debug('discover included services output: ' + JSON.stringify(output)); | ||
@@ -234,3 +241,3 @@ var includedServices = []; | ||
this.executeCommand('characteristics ' + service.attrHandle + ' ' + service.endGrpHandle, true, function(output) { | ||
// console.log('characteristics output: ' + JSON.stringify(output)); | ||
debug('characteristics output: ' + JSON.stringify(output)); | ||
@@ -255,7 +262,7 @@ var characteristics = []; | ||
// console.log(handle); | ||
// console.log(properties); | ||
// console.log(valueHandle); | ||
// console.log(uuid); | ||
// console.log(); | ||
debug(handle); | ||
debug(properties); | ||
debug(valueHandle); | ||
debug(uuid); | ||
debug(); | ||
@@ -330,5 +337,6 @@ this._characteristics[serviceUuid][uuid] = { | ||
this.executeCommand('char-read-hnd ' + characteristic.valueHandle, true, function(output) { | ||
// console.log('read output: ' + JSON.stringify(output)); | ||
debug('read output: ' + JSON.stringify(output)); | ||
var bytes = null; | ||
for (var i in output) { | ||
for (var i in output) { | ||
var line = output[i].trim(); | ||
@@ -338,3 +346,3 @@ | ||
if (found) { | ||
var bytes = found[1].trim().split(' '); | ||
bytes = found[1].trim().split(' '); | ||
@@ -344,6 +352,6 @@ for (var j in bytes) { | ||
} | ||
this.emit('read', this._address, serviceUuid, characteristicUuid, new Buffer(bytes)); | ||
} | ||
} | ||
this.emit('read', this._address, serviceUuid, characteristicUuid, bytes ? new Buffer(bytes) : null); | ||
}.bind(this)); | ||
@@ -362,3 +370,3 @@ }; | ||
this.executeCommand(command, !withoutResponse, function(output) { | ||
// console.log('write output: ' + JSON.stringify(output)); | ||
debug('write output: ' + JSON.stringify(output)); | ||
@@ -396,2 +404,4 @@ this.emit('write', this._address, serviceUuid, characteristicUuid); | ||
this.executeCommand(command, true, function(output) { | ||
debug('broadcast output: ' + JSON.stringify(output)); | ||
for (var i in output) { | ||
@@ -429,2 +439,4 @@ var line = output[i].trim(); | ||
this.executeCommand(command, true, function(output) { | ||
debug('notify output: ' + JSON.stringify(output)); | ||
for (var i in output) { | ||
@@ -466,2 +478,4 @@ var line = output[i].trim(); | ||
this.executeCommand(command, true, function(output) { | ||
debug('descriptors discover output: ' + JSON.stringify(output)); | ||
var descriptorUuids = []; | ||
@@ -494,3 +508,3 @@ for (var j in output) { | ||
this.executeCommand('char-read-hnd ' + descriptor.handle, true, function(output) { | ||
// console.log('value read output: ' + JSON.stringify(output)); | ||
debug('value read output: ' + JSON.stringify(output)); | ||
@@ -524,3 +538,3 @@ for (var i in output) { | ||
this.executeCommand(command, false, function(output) { | ||
// console.log('value write output: ' + JSON.stringify(output)); | ||
debug('value write output: ' + JSON.stringify(output)); | ||
@@ -533,5 +547,6 @@ this.emit('valueWrite', this._address, serviceUuid, characteristicUuid, descriptorUuid); | ||
this.executeCommand('char-read-hnd 0x' + handle.toString(16), true, function(output) { | ||
// console.log('handle read output: ' + JSON.stringify(output)); | ||
debug('handle read output: ' + JSON.stringify(output)); | ||
var bytes = null; | ||
for (var i in output) { | ||
for (var i in output) { | ||
var line = output[i].trim(); | ||
@@ -541,3 +556,3 @@ | ||
if(found) { | ||
var bytes = found[1].trim().split(' '); | ||
bytes = found[1].trim().split(' '); | ||
@@ -547,6 +562,6 @@ for (var j in bytes) { | ||
} | ||
this.emit('handleRead', this._address, handle, new Buffer(bytes)); | ||
} | ||
} | ||
this.emit('handleRead', this._address, handle, bytes ? new Buffer(bytes) : null); | ||
}.bind(this)); | ||
@@ -563,3 +578,3 @@ }; | ||
this.executeCommand(command, !withoutResponse, function(output) { | ||
// console.log('write handle output: ' + JSON.stringify(output)); | ||
debug('write handle output: ' + JSON.stringify(output)); | ||
@@ -566,0 +581,0 @@ this.emit('handleWrite', this._address, handle); |
@@ -65,2 +65,35 @@ var debug = require('debug')('peripheral'); | ||
Peripheral.prototype.discoverSomeServicesAndCharacteristics = function(serviceUuids, characteristicsUuids, callback) { | ||
this.discoverServices(serviceUuids, function(err, services) { | ||
var numDiscovered = 0; | ||
var allCharacteristics = []; | ||
for (var i in services) { | ||
var service = services[i]; | ||
service.discoverCharacteristics(characteristicsUuids, function(error, characteristics) { | ||
numDiscovered++; | ||
if (error === null) { | ||
for (var j in characteristics) { | ||
var characteristic = characteristics[j]; | ||
allCharacteristics.push(characteristic); | ||
} | ||
} | ||
if (numDiscovered === services.length) { | ||
if (callback) { | ||
callback(null, services, allCharacteristics); | ||
} | ||
} | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
}; | ||
Peripheral.prototype.discoverAllServicesAndCharacteristics = function(callback) { | ||
this.discoverSomeServicesAndCharacteristics([], [], callback); | ||
}; | ||
Peripheral.prototype.readHandle = function(handle, callback) { | ||
@@ -67,0 +100,0 @@ if (callback) { |
@@ -5,3 +5,3 @@ { | ||
"description": "A node.js BLE (Bluetooth low energy) library.", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
@@ -13,8 +13,12 @@ noble | ||
* node 0.8.x is required, until an [issue with signals with the npm dbus module](https://github.com/Shouqun/node-dbus/issues/45) is fixed. | ||
* [BlueZ](http://www.bluez.org) 4 or 5 (with ```gatttool``` on path) | ||
* Install BlueZ 5.x (optional, needed if there are issues with the stock BlueZ 4.x) | ||
* ```sudo apt-get install libglib2.0-dev libdbus-1-dev libusb-dev libudev-dev libical-dev libreadline-dev``` (build dependencies) | ||
* extract archive, ```./configure --disable-systemd```, ```make```, ```sudo make install``` | ||
* ```sudo apt-get install g++ libdbus-glib-1-dev libexpat1-dev``` (required for the dbus npm module) | ||
* [BlueZ](http://www.bluez.org) __4.100 or 4.101__ is needed ! | ||
* 4.99 does not support connecting to LE devices | ||
* Install BlueZ 4.101 (if you are running an older unsupported version) | ||
* install build dependencies: ```sudo apt-get install libglib2.0-dev libdbus-1-dev libusb-dev libudev-dev libical-dev libreadline-dev``` | ||
* fetch archive: ```wget https://www.kernel.org/pub/linux/bluetooth/bluez-4.101.tar.bz2``` | ||
* extract archive: ```tar xvjf bluez-4.101.tar.bz2``` | ||
* change directory: ```cd bluez-4.101``` | ||
* configure: ```./configure --disable-systemd``` | ||
* build: ```make``` | ||
* install: ```sudo make install``` | ||
@@ -69,2 +73,12 @@ Install | ||
Peripheral discover all services and characteristics | ||
peripheral.discoverAllServicesAndCharacteristics([callback(error, services, characteristics)); | ||
Peripheral discover some services and characteristics | ||
var serviceUUIDs = ["<service UUID 1>", ...]; | ||
var characteristicUUIDs = ["<characteristic UUID 1>", ...]; | ||
peripheral.discoverSomeServicesAndCharacteristics(serviceUUIDs, characteristicUUIDs, [callback(error, services, characteristics)); | ||
Service discover included services | ||
@@ -285,2 +299,9 @@ | ||
* TDB (most likely Windows 8 only) | ||
Useful Links | ||
------------ | ||
* [Bluetooth Development Portal](http://developer.bluetooth.org) | ||
* [GATT Specifications](http://developer.bluetooth.org/gatt/Pages/default.aspx) | ||
* [Bluetooth: ATT and GATT](http://epx.com.br/artigos/bluetooth_gatt.php) | ||
@@ -287,0 +308,0 @@ License |
@@ -158,2 +158,94 @@ var should = require('should'); | ||
describe('discoverSomeServicesAndCharacteristics', function() { | ||
var mockServiceUuids = []; | ||
var mockCharacteristicUuids = []; | ||
var mockServices = null; | ||
beforeEach(function() { | ||
peripheral.discoverServices = sinon.spy(); | ||
mockServices = [ | ||
{ | ||
uuid: '1', | ||
discoverCharacteristics: sinon.spy() | ||
}, | ||
{ | ||
uuid: '2', | ||
discoverCharacteristics: sinon.spy() | ||
} | ||
]; | ||
}); | ||
it('should call discoverServices', function() { | ||
peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids); | ||
peripheral.discoverServices.calledWith(mockServiceUuids).should.equal(true); | ||
}); | ||
it('should call discoverCharacteristics on each service discovered', function() { | ||
peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids); | ||
var discoverServicesCallback = peripheral.discoverServices.getCall(0).args[1]; | ||
discoverServicesCallback(null, mockServices); | ||
mockServices[0].discoverCharacteristics.calledWith(mockCharacteristicUuids).should.equal(true); | ||
mockServices[1].discoverCharacteristics.calledWith(mockCharacteristicUuids).should.equal(true); | ||
}); | ||
it('should callback', function() { | ||
var calledback = false; | ||
peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids, function() { | ||
calledback = true; | ||
}); | ||
var discoverServicesCallback = peripheral.discoverServices.getCall(0).args[1]; | ||
discoverServicesCallback(null, mockServices); | ||
mockServices[0].discoverCharacteristics.getCall(0).args[1](null, []); | ||
mockServices[1].discoverCharacteristics.getCall(0).args[1](null, []); | ||
calledback.should.equal(true); | ||
}); | ||
it('should callback with the services and characteristics discovered', function() { | ||
var calledbackServices = null; | ||
var calledbackCharacteristics = null; | ||
peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids, function(err, services, characteristics) { | ||
calledbackServices = services; | ||
calledbackCharacteristics = characteristics; | ||
}); | ||
var discoverServicesCallback = peripheral.discoverServices.getCall(0).args[1]; | ||
discoverServicesCallback(null, mockServices); | ||
var mockCharacteristic1 = { uuid: '1' }; | ||
var mockCharacteristic2 = { uuid: '2' }; | ||
var mockCharacteristic3 = { uuid: '3' }; | ||
mockServices[0].discoverCharacteristics.getCall(0).args[1](null, [mockCharacteristic1]); | ||
mockServices[1].discoverCharacteristics.getCall(0).args[1](null, [mockCharacteristic2, mockCharacteristic3]); | ||
calledbackServices.should.equal(mockServices); | ||
calledbackCharacteristics.should.eql([mockCharacteristic1, mockCharacteristic2, mockCharacteristic3]); | ||
}); | ||
}); | ||
describe('discoverAllServicesAndCharacteristics', function() { | ||
it('should call discoverSomeServicesAndCharacteristics', function() { | ||
var mockCallback = sinon.spy(); | ||
peripheral.discoverSomeServicesAndCharacteristics = sinon.spy(); | ||
peripheral.discoverAllServicesAndCharacteristics(mockCallback); | ||
peripheral.discoverSomeServicesAndCharacteristics.calledWithExactly([], [], mockCallback).should.equal(true); | ||
}); | ||
}); | ||
describe('readHandle', function() { | ||
@@ -160,0 +252,0 @@ it('should delegate to noble', function() { |
124685
2753
314
24
3