diff --git a/examples/pizza/peripheral.js b/examples/pizza/peripheral.js index b186a6a8..6e1f61d2 100644 --- a/examples/pizza/peripheral.js +++ b/examples/pizza/peripheral.js @@ -24,6 +24,7 @@ var PizzaService = require('./pizza-service'); // var name = 'PizzaSquat'; var pizzaService = new PizzaService(new pizza.Pizza()); +var pizzaService2 = new PizzaService(new pizza.Pizza()); // // Wait until the BLE radio powers on before attempting to advertise. @@ -56,5 +57,16 @@ bleno.on('advertisingStart', function(err) { bleno.setServices([ pizzaService ]); + + var flip = false; + setInterval(function (){ + if(flip){ + bleno.removeService(pizzaService2); + } else { + bleno.addService(pizzaService2); + } + flip = !flip; + }, 1500); + } }); diff --git a/lib/bleno.js b/lib/bleno.js index fd1b0a03..9265c65c 100644 --- a/lib/bleno.js +++ b/lib/bleno.js @@ -199,6 +199,14 @@ Bleno.prototype.onAdvertisingStop = function() { this.emit('advertisingStop'); }; +Bleno.prototype.addService = function(service) { + this._bindings.addService(service); +}; + +Bleno.prototype.removeService = function(service) { + this._bindings.removeService(service); +}; + Bleno.prototype.setServices = function(services, callback) { if (callback) { this.once('servicesSet', callback); diff --git a/lib/hci-socket/bindings.js b/lib/hci-socket/bindings.js index 6b068a52..01a68420 100644 --- a/lib/hci-socket/bindings.js +++ b/lib/hci-socket/bindings.js @@ -48,7 +48,17 @@ BlenoBindings.prototype.stopAdvertising = function() { this._gap.stopAdvertising(); }; -BlenoBindings.prototype.setServices = function(services) { +BlenoBindings.prototype.addService = function(service) { + this._gatt.addService(service); + + this.emit('servicesChanged'); +}; + +BlenoBindings.prototype.removeService = function (service){ + this._gatt.removeService(service); +} + +BlenoBindings.prototype.setServices = function(services, deviceName) { this._gatt.setServices(services); this.emit('servicesSet'); diff --git a/lib/hci-socket/gatt.js b/lib/hci-socket/gatt.js index f7aac3af..0d6f21dc 100644 --- a/lib/hci-socket/gatt.js +++ b/lib/hci-socket/gatt.js @@ -64,6 +64,39 @@ var ATT_ECODE_INSUFF_RESOURCES = 0x11; var ATT_CID = 0x0004; var Gatt = function() { + this._services = [ + { + uuid: '1800', + characteristics: [ + { + uuid: '2a00', + properties: ['read'], + secure: [], + value: null, + descriptors: [] + }, + { + uuid: '2a01', + properties: ['read'], + secure: [], + value: new Buffer([0x80, 0x00]), + descriptors: [] + } + ] + }, + { + uuid: '1801', + characteristics: [ + { + uuid: '2a05', + properties: ['indicate'], + secure: [], + value: new Buffer([0x00, 0x00, 0x00, 0x00]), + descriptors: [] + } + ] + } + ]; this._mtu = 23; this.onAclStreamDataBinded = this.onAclStreamData.bind(this); @@ -72,175 +105,251 @@ var Gatt = function() { util.inherits(Gatt, events.EventEmitter); -Gatt.prototype.setServices = function(services) { - var deviceName = process.env.BLENO_DEVICE_NAME || os.hostname(); +function createATTMessage(op, mtu, valueHandle, data){ + var dataLength = Math.min(data.length, mtu - 3); + var message = new Buffer(3 + dataLength); - // base services and characteristics - var allServices = [ - { - uuid: '1800', - characteristics: [ - { - uuid: '2a00', - properties: ['read'], - secure: [], - value: new Buffer(deviceName), - descriptors: [] - }, - { - uuid: '2a01', - properties: ['read'], - secure: [], - value: new Buffer([0x80, 0x00]), - descriptors: [] - } - ] - }, - { - uuid: '1801', - characteristics: [ - { - uuid: '2a05', - properties: ['indicate'], - secure: [], - value: new Buffer([0x00, 0x00, 0x00, 0x00]), - descriptors: [] - } - ] + message.writeUInt8(parseInt(op, 16), 0); + message.writeUInt16LE(parseInt(valueHandle, 16), 1); + + for (i = 0; i < dataLength; i++) { + message[3 + i] = data[i]; } - ].concat(services); - this._handles = []; + return message; +} - var handle = 0; - var i; - var j; +function createServiceHandles(service, start){ + var handle = start; + var handles = []; - for (i = 0; i < allServices.length; i++) { - var service = allServices[i]; + handle++; + var serviceHandle = handle; - handle++; - var serviceHandle = handle; + handles.push({ + type: 'service', + uuid: service.uuid, + attribute: service, + startHandle: serviceHandle + // endHandle filled in below + }); + service.handle = serviceHandle; + for (var j = 0; j < service.characteristics.length; j++) { + var characteristic = service.characteristics[j]; - this._handles[serviceHandle] = { - type: 'service', - uuid: service.uuid, - attribute: service, - startHandle: serviceHandle - // endHandle filled in below - }; + var properties = 0; + var secure = 0; - for (j = 0; j < service.characteristics.length; j++) { - var characteristic = service.characteristics[j]; + if (characteristic.properties.indexOf('read') !== -1) { + properties |= 0x02; - var properties = 0; - var secure = 0; + if (characteristic.secure.indexOf('read') !== -1) { + secure |= 0x02; + } + } - if (characteristic.properties.indexOf('read') !== -1) { - properties |= 0x02; + if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { + properties |= 0x04; - if (characteristic.secure.indexOf('read') !== -1) { - secure |= 0x02; - } + if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { + secure |= 0x04; } + } - if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { - properties |= 0x04; + if (characteristic.properties.indexOf('write') !== -1) { + properties |= 0x08; - if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { - secure |= 0x04; - } + if (characteristic.secure.indexOf('write') !== -1) { + secure |= 0x08; } + } - if (characteristic.properties.indexOf('write') !== -1) { - properties |= 0x08; + if (characteristic.properties.indexOf('notify') !== -1) { + properties |= 0x10; - if (characteristic.secure.indexOf('write') !== -1) { - secure |= 0x08; - } + if (characteristic.secure.indexOf('notify') !== -1) { + secure |= 0x10; } + } - if (characteristic.properties.indexOf('notify') !== -1) { - properties |= 0x10; + if (characteristic.properties.indexOf('indicate') !== -1) { + properties |= 0x20; - if (characteristic.secure.indexOf('notify') !== -1) { - secure |= 0x10; - } + if (characteristic.secure.indexOf('indicate') !== -1) { + secure |= 0x20; } + } - if (characteristic.properties.indexOf('indicate') !== -1) { - properties |= 0x20; + handle++; + var characteristicHandle = handle; - if (characteristic.secure.indexOf('indicate') !== -1) { - secure |= 0x20; - } - } + handle++; + var characteristicValueHandle = handle; + + handles.push({ + type: 'characteristic', + uuid: characteristic.uuid, + properties: properties, + secure: secure, + attribute: characteristic, + startHandle: characteristicHandle, + valueHandle: characteristicValueHandle + }); + + handles.push({ + type: 'characteristicValue', + handle: characteristicValueHandle, + value: characteristic.value + }); + + if (properties & 0x30) { // notify or indicate + // add client characteristic configuration descriptor handle++; - var characteristicHandle = handle; + var clientCharacteristicConfigurationDescriptorHandle = handle; + handles.push({ + type: 'descriptor', + handle: clientCharacteristicConfigurationDescriptorHandle, + uuid: '2902', + attribute: characteristic, + properties: (0x02 | 0x04 | 0x08), // read/write + secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0, + value: new Buffer([0x00, 0x00]) + }); + } + + var descriptors = characteristic.descriptors; + for (var k = 0, kk = descriptors.length; k < kk; k++) { + var descriptor = descriptors[k]; handle++; - var characteristicValueHandle = handle; + var descriptorHandle = handle; - this._handles[characteristicHandle] = { - type: 'characteristic', - uuid: characteristic.uuid, - properties: properties, - secure: secure, - attribute: characteristic, - startHandle: characteristicHandle, - valueHandle: characteristicValueHandle - }; - - this._handles[characteristicValueHandle] = { - type: 'characteristicValue', - handle: characteristicValueHandle, - value: characteristic.value - }; - - if (properties & 0x30) { // notify or indicate - // add client characteristic configuration descriptor - - handle++; - var clientCharacteristicConfigurationDescriptorHandle = handle; - this._handles[clientCharacteristicConfigurationDescriptorHandle] = { - type: 'descriptor', - handle: clientCharacteristicConfigurationDescriptorHandle, - uuid: '2902', - attribute: characteristic, - properties: (0x02 | 0x04 | 0x08), // read/write - secure: (secure & 0x10) ? (0x02 | 0x04 | 0x08) : 0, - value: new Buffer([0x00, 0x00]) - }; - } + handles.push({ + type: 'descriptor', + handle: descriptorHandle, + uuid: descriptor.uuid, + attribute: descriptor, + properties: 0x02, // read only + secure: 0x00, + value: descriptor.value + }); + } + } + + handles[0].endHandle = handle; - for (var k = 0; k < characteristic.descriptors.length; k++) { - var descriptor = characteristic.descriptors[k]; - - handle++; - var descriptorHandle = handle; - - this._handles[descriptorHandle] = { - type: 'descriptor', - handle: descriptorHandle, - uuid: descriptor.uuid, - attribute: descriptor, - properties: 0x02, // read only - secure: 0x00, - value: descriptor.value - }; + return handles; +} + +Gatt.prototype.addService = function AddGATTService(service){ + this._services.push(service); + var handles = createServiceHandles(service, this._handles.length - 1); + for(var i = 0, ii = handles.length; i < ii; i++){ + var handle = handles[i].startHandle || handles[i].handle; + this._handles[handle] = handles[i]; + } + + // Indicate that services changed. +var serviceHandleObject = handles[0]; + + var serviceChangedValue = new Buffer(4); + serviceChangedValue.writeUInt16LE(parseInt(serviceHandleObject.startHandle, 16), 0); + serviceChangedValue.writeUInt16LE(parseInt(serviceHandleObject.endHandle, 16), 2); + this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; + + var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, serviceChangedValue); + this._lastIndicatedAttribute = this._handles[this._serviceChangedValueHandle - 1].attribute; + + debug('addService: indicate message: ' + indicateMessage.toString('hex')); + this.send(indicateMessage); +}; + +Gatt.prototype.removeAllServices = function RemoveAllGATTServices(){ + var startHandle = null; + var endHandle = null; + for (var i = 0; i < this._handles.length; i++) { + var handle = this._handles[i]; + if(handle.uuid === '1800' || handle.uuid === '1801' || handle.uuid === '2a00' || handle.uuid === '2a01' || + handle.uuid === '2a05'){ + continue; + } else { + if (!startHandle) { + startHandle = handle; } + endHandle = handle; } + } + + var serviceChangedValue = new Buffer(4); + serviceChangedValue.writeUInt16LE(parseInt(startHandle, 16), 0); + serviceChangedValue.writeUInt16LE(parseInt(endHandle, 16), 2); + this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; + + var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, + serviceChangedValue); +}; + +Gatt.prototype.removeService = function RemoveGattService(service){ + var handle = service.handle; + + var startHandle = this._handles[handle].startHandle; + var endHandle = this._handles[handle].endHandle; + for(var handle = startHandle; handle <= endHandle; handle++){ + // Don't slice, indexing will be corrupted. + this._handles[handle] = {}; + } + debug('removeService - removed handles: ' + startHandle + '-' + endHandle); + + var serviceChangedValue = new Buffer(4); + serviceChangedValue.writeUInt16LE(parseInt(startHandle, 16), 0); + serviceChangedValue.writeUInt16LE(parseInt(endHandle, 16), 2); + this._handles[this._serviceChangedValueHandle].value = serviceChangedValue; - this._handles[serviceHandle].endHandle = handle; + var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, this._serviceChangedValueHandle, serviceChangedValue); + this._lastIndicatedAttribute = this._handles[this._serviceChangedValueHandle - 1].attribute; + + debug('removeService: indicate message: ' + indicateMessage.toString('hex')); + this.send(indicateMessage); +}; + +/** + * Set services for the GATT server for discovery. Do not call this function if you are connected to a client, it can have + * negative consequences if the services are in a different order from the time it was initially called. + * @param {Array} services The services that will be available for Primary Service Discovery Ref. Vol 3 GATT 4.13 + * @returns {undefined} No payload data provided for return. + */ +Gatt.prototype.setServices = function SetGATTServices(services) { + var deviceName = process.env.BLENO_DEVICE_NAME || os.hostname(); + this._services[0].characteristics[0].value = new Buffer(deviceName); + services = services || []; + + // base services and characteristics + this._services = this._services.concat(services); + + this._handles = []; + + for (var i = 0, ii = this._services.length; i < ii; i++) { + var service = this._services[i]; + + // A bit hacky, but keeps indexing proper so access can be this._handles[index], concat will leave empty properties + var serviceHandles = createServiceHandles(service, this._handles.length - 1); + for(var j = 0, jj = serviceHandles.length; j < jj; j++){ + var handle = serviceHandles[j].startHandle || serviceHandles[j].handle; + this._handles[handle] = serviceHandles[j]; + + if (this._handles[handle].uuid === '2a05'){ + this._serviceChangedValueHandle = handle; + } + } } var debugHandles = []; - for (i = 0; i < this._handles.length; i++) { + for (var i = 0; i < this._handles.length; i++) { handle = this._handles[i]; debugHandles[i] = {}; - for(j in handle) { + for(var j in handle) { if (Buffer.isBuffer(handle[j])) { debugHandles[i][j] = handle[j] ? 'Buffer(\'' + handle[j].toString('hex') + '\', \'hex\')' : null; } else if (j !== 'attribute') { @@ -273,8 +382,10 @@ Gatt.prototype.onAclStreamEnd = function() { }; Gatt.prototype.send = function(data) { - debug('send: ' + data.toString('hex')); - this._aclStream.write(ATT_CID, data); + if (this._aclStream) { + debug('send: ' + data.toString('hex')); + this._aclStream.write(ATT_CID, data); + } }; Gatt.prototype.errorResponse = function(opcode, handle, status) { @@ -764,7 +875,7 @@ Gatt.prototype.handleReadOrReadBlobRequest = function(request) { return response; }; -Gatt.prototype.handleWriteRequestOrCommand = function(request) { +Gatt.prototype.handleWriteRequestOrCommand = function handleWriteRequestOrCommand(request) { var response = null; var requestType = request[0]; @@ -819,34 +930,19 @@ Gatt.prototype.handleWriteRequestOrCommand = function(request) { if (value & 0x0003) { var updateValueCallback = (function(valueHandle, attribute) { return function(data) { - var dataLength = Math.min(data.length, this._mtu - 3); var useNotify = attribute.properties.indexOf('notify') !== -1; var useIndicate = attribute.properties.indexOf('indicate') !== -1; var i; if (useNotify) { - var notifyMessage = new Buffer(3 + dataLength); - - notifyMessage.writeUInt8(ATT_OP_HANDLE_NOTIFY, 0); - notifyMessage.writeUInt16LE(valueHandle, 1); - - for (i = 0; i < dataLength; i++) { - notifyMessage[3 + i] = data[i]; - } + var notifyMessage = createATTMessage(ATT_OP_HANDLE_NOTIFY, this._mtu, valueHandle, data); debug('notify message: ' + notifyMessage.toString('hex')); this.send(notifyMessage); attribute.emit('notify'); } else if (useIndicate) { - var indicateMessage = new Buffer(3 + dataLength); - - indicateMessage.writeUInt8(ATT_OP_HANDLE_IND, 0); - indicateMessage.writeUInt16LE(valueHandle, 1); - - for (i = 0; i < dataLength; i++) { - indicateMessage[3 + i] = data[i]; - } + var indicateMessage = createATTMessage(ATT_OP_HANDLE_IND, this._mtu, valueHandle, data); this._lastIndicatedAttribute = attribute; diff --git a/lib/hci-socket/hci-status.json b/lib/hci-socket/hci-status.json index 3cef0e76..db4f5945 100644 --- a/lib/hci-socket/hci-status.json +++ b/lib/hci-socket/hci-status.json @@ -64,4 +64,4 @@ "Connection Failed to be Established", "MAC Connection Failed", "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock Dragging" -] \ No newline at end of file +] diff --git a/lib/mac/bindings.js b/lib/mac/bindings.js index da83508a..8304bd73 100644 --- a/lib/mac/bindings.js +++ b/lib/mac/bindings.js @@ -149,110 +149,123 @@ blenoBindings.on('kCBMsgId17', function(args) { this.emit('advertisingStop'); }); -blenoBindings.setServices = function(services) { - this.sendCBMsg(12, null); // remove all services - - services = services || []; - var attributeId = 1; - - this._attributes = []; - this._setServicesError = undefined; - - if (services.length) { - for (var i = 0; i < services.length; i++) { - var service = services[i]; - - var arg = { - kCBMsgArgAttributeID: attributeId, - kCBMsgArgAttributeIDs: [], - kCBMsgArgCharacteristics: [], - kCBMsgArgType: 1, // 1 => primary, 0 => included - kCBMsgArgUUID: new Buffer(service.uuid, 'hex') - }; - - this._attributes[attributeId] = service; +blenoBindings.addService = function AddGATTService(service) { + var attributeId = this._attributes.length ? this._attributes.length - 1 : 1; + var arg = { + kCBMsgArgAttributeID: attributeId, + kCBMsgArgAttributeIDs: [], + kCBMsgArgCharacteristics: [], + kCBMsgArgType: 1, // 1 => primary, 0 => included + kCBMsgArgUUID: new Buffer(service.uuid, 'hex') + }; + this._attributes[attributeId] = service; + service.attributeId = attributeId; - this._lastServiceAttributeId = attributeId; - attributeId++; + this._lastServiceAttributeId = attributeId; + attributeId++; - for (var j = 0; j < service.characteristics.length; j++) { - var characteristic = service.characteristics[j]; + for (var j = 0; j < service.characteristics.length; j++) { + var characteristic = service.characteristics[j]; - var properties = 0; - var permissions = 0; + var properties = 0; + var permissions = 0; - if (characteristic.properties.indexOf('read') !== -1) { - properties |= 0x02; + if (characteristic.properties.indexOf('read') !== -1) { + properties |= 0x02; - if (characteristic.secure.indexOf('read') !== -1) { - permissions |= 0x04; - } else { - permissions |= 0x01; - } - } + if (characteristic.secure.indexOf('read') !== -1) { + permissions |= 0x04; + } else { + permissions |= 0x01; + } + } - if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { - properties |= 0x04; + if (characteristic.properties.indexOf('writeWithoutResponse') !== -1) { + properties |= 0x04; - if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { - permissions |= 0x08; - } else { - permissions |= 0x02; - } - } + if (characteristic.secure.indexOf('writeWithoutResponse') !== -1) { + permissions |= 0x08; + } else { + permissions |= 0x02; + } + } - if (characteristic.properties.indexOf('write') !== -1) { - properties |= 0x08; + if (characteristic.properties.indexOf('write') !== -1) { + properties |= 0x08; - if (characteristic.secure.indexOf('write') !== -1) { - permissions |= 0x08; - } else { - permissions |= 0x02; - } - } - - if (characteristic.properties.indexOf('notify') !== -1) { - if (characteristic.secure.indexOf('notify') !== -1) { - properties |= 0x100; - } else { - properties |= 0x10; - } - } - - if (characteristic.properties.indexOf('indicate') !== -1) { - if (characteristic.secure.indexOf('indicate') !== -1) { - properties |= 0x200; - } else { - properties |= 0x20; - } - } + if (characteristic.secure.indexOf('write') !== -1) { + permissions |= 0x08; + } else { + permissions |= 0x02; + } + } - var characteristicArg = { - kCBMsgArgAttributeID: attributeId, - kCBMsgArgAttributePermissions: permissions, - kCBMsgArgCharacteristicProperties: properties, - kCBMsgArgData: characteristic.value, - kCBMsgArgDescriptors: [], - kCBMsgArgUUID: new Buffer(characteristic.uuid, 'hex') - }; + if (characteristic.properties.indexOf('notify') !== -1) { + if (characteristic.secure.indexOf('notify') !== -1) { + properties |= 0x100; + } else { + properties |= 0x10; + } + } - this._attributes[attributeId] = characteristic; + if (characteristic.properties.indexOf('indicate') !== -1) { + if (characteristic.secure.indexOf('indicate') !== -1) { + properties |= 0x200; + } else { + properties |= 0x20; + } + } - for (var k = 0; k < characteristic.descriptors.length; k++) { - var descriptor = characteristic.descriptors[k]; + var characteristicArg = { + kCBMsgArgAttributeID: attributeId, + kCBMsgArgAttributePermissions: permissions, + kCBMsgArgCharacteristicProperties: properties, + kCBMsgArgData: characteristic.value, + kCBMsgArgDescriptors: [], + kCBMsgArgUUID: new Buffer(characteristic.uuid, 'hex') + }; + this._attributes[attributeId] = characteristic; + + for (var k = 0; k < characteristic.descriptors.length; k++) { + var descriptor = characteristic.descriptors[k]; + + characteristicArg.kCBMsgArgDescriptors.push({ + kCBMsgArgData: descriptor.value, + kCBMsgArgUUID: new Buffer(descriptor.uuid, 'hex') + }); + } + arg.kCBMsgArgCharacteristics.push(characteristicArg); + attributeId++; + } + this.sendCBMsg(10, arg); +}; - characteristicArg.kCBMsgArgDescriptors.push({ - kCBMsgArgData: descriptor.value, - kCBMsgArgUUID: new Buffer(descriptor.uuid, 'hex') - }); - } +blenoBindings.removeService = function RemoveGATTService(service){ + var attributeId = this._attributes[service.attributeId]; + var messageBody = { + kCBMsgArgAttributeID: attributeId + }; + this._attributes[service.attributeId] = {}; + this.sendCBMsg(11, attributeId); +} + +blenoBindings.removeAllServices = function RemoveAllGATTServices(){ + this.sendCBMsg(12, null); +} + +blenoBindings.setServices = function SetGATTServices(services) { + this.sendCBMsg(12, null); + services = services || []; - arg.kCBMsgArgCharacteristics.push(characteristicArg); + this._attributes = []; + this._setServicesError = undefined; - attributeId++; - } + if (services.length) { + for (var i = 0; i < services.length; i++) { + var service = services[i]; - this.sendCBMsg(10, arg); + // Add service, which will send message via XPC + this.addService(service); } } else { this.emit('servicesSet'); @@ -269,12 +282,19 @@ blenoBindings.updateRssi = function() { } }; -blenoBindings.on('kCBMsgId18', function(args) { +/** + * Callback for adding / removing services + * @param {Object} 'kCBMsgId18' Callback string for handle message type 18 + * @param {function} AddOrRemoveServiceCallback callback function with provided arguments for handle message + * @returns {undefined} No payload data provided + */ +blenoBindings.on('kCBMsgId18', function AddOrRemoveServiceCallback(args) { var attributeId = args.kCBMsgArgAttributeID; var result = args.kCBMsgArgResult; if (result) { - var errorMessage = 'failed to set service ' + this._attributes[attributeId].uuid; + var message = this._attributes[attributeId].uuid ? this._attributes[attributeId].uuid : ('attributeId: ' + attributeId); + var errorMessage = 'failed to set / add / remove service ' + message; if (result === 27) { errorMessage += ', UUID not allowed!'; diff --git a/package.json b/package.json index 18c4a0e4..14726d1b 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,7 @@ "devDependencies": { "jshint": "~2.3.0", "should": "~2.0.2", - "mocha": "~1.14.0", - "node-blink1": "~0.1.1" + "mocha": "~1.14.0" }, "dependencies": { "debug": "^2.2.0"