UNPKG

64.3 kBJavaScriptView Raw
1// Copyright 2014 Technical Machine, Inc. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10var Peripheral = require('./lib/peripheral');
11var Descriptor = require('./lib/descriptor');
12var Characteristic = require('./lib/characteristic');
13var Service = require('./lib/service');
14var Messenger = require('./lib/messenger');
15var UUID = require('./lib/uuid');
16var Address = require('./lib/address');
17var attributes = require('./lib/attributes.json');
18var profile = require('./lib/profile.json');
19var events = require('events');
20var util = require('util');
21var async = require('async');
22
23// Instantiate a Bluetooth Controller object. Controls all BLE Central and Peripheral methods (depending on role).
24function BluetoothController(hardware, callback) {
25 this.hardware = hardware;
26 this.isAdvertising = false;
27 this.messenger = new Messenger(hardware);
28 this._connectedPeripherals = {};
29
30 this.profile = profile;
31
32 this._firmwareVersionHandle = 17;
33 this._maxNumValues = { "1.0.1" : 12};
34 this._localHandles = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65];
35
36 this._MITMEnabled = false;
37 this._minKeySize = 7;
38
39 this._userAdvData = true;
40
41 this.messenger.on('scanStart', this.onScanStart.bind(this));
42 this.messenger.on('scanStop', this.onScanStop.bind(this));
43 this.messenger.on('discover', this.onDiscover.bind(this));
44 this.messenger.on('connectionStatus', this.onConnectionStatus.bind(this));
45 this.messenger.on('disconnect', this.onDisconnect.bind(this));
46 this.messenger.on('groupFound', this.onGroupFound.bind(this));
47 this.messenger.on('completedProcedure', this.onCompletedProcedure.bind(this));
48 this.messenger.on('findInformationFound', this.onFindInformationFound.bind(this));
49 this.messenger.on('attributeValue', this.onAttributeValue.bind(this));
50 this.messenger.on('remoteWrite', this.onRemoteWrite.bind(this));
51 this.messenger.on('remoteStatus', this.onRemoteStatus.bind(this));
52 this.messenger.on('portStatus', this.onPortStatus.bind(this));
53 this.messenger.on('ADCRead', this.onADCRead.bind(this));
54 this.messenger.on('bondStatus', this.onBondStatus.bind(this));
55 this.messenger.on('indicated', this.onIndicated.bind(this));
56
57 // Once the messenger says we're ready, call callback and emit event
58 this.messenger.once('ready', this.bootSequence.bind(this, callback));
59
60 // If there was an error, let us know
61 this.messenger.once('error', this.bootSequence.bind(this, callback));
62}
63
64util.inherits(BluetoothController, events.EventEmitter);
65
66BluetoothController.prototype.bootSequence = function(callback, err) {
67
68// Tell any ble listeners
69 if (!err) {
70 this.createGPIOs();
71 // Set default adevertising data
72 // LE General Discoverable / BR/EDR not supported
73 // Short device name: Tessel
74 this.setAdvertisingData([0x02, 0x01, 0x06, 0x07, 0x08, 0x54, 0x65, 0x73, 0x73, 0x65, 0x6c], function(){
75 setImmediate(function() {
76 this.emit('ready');
77 // Call the callback
78 if (callback) {
79 callback(null, this);
80 }
81 }.bind(this));
82 }.bind(this));
83 } else {
84 // Emit the error
85 setImmediate(function() {
86 this.emit('error', err);
87 // Call the callback
88 if (callback) {
89 callback(err, this);
90 }
91 }.bind(this));
92 }
93
94 this.messenger.removeAllListeners('error');
95 this.messenger.removeAllListeners('ready');
96};
97
98BluetoothController.prototype.reset = function(callback) {
99 this.messenger.reset(callback);
100};
101/**********************************************************
102 Event Handlers
103**********************************************************/
104BluetoothController.prototype.onScanStart = function(err, result) {
105 this.emit('scanStart', err);
106};
107
108BluetoothController.prototype.onScanStop = function(err, result) {
109 this.emit('scanStop', err);
110};
111
112BluetoothController.prototype.onDiscover = function(peripheralData) {
113 // Try to grab this peripheral from list of previously discovered peripherals
114 this.getPeripheralFromData(peripheralData.rssi, peripheralData.data, peripheralData.sender, peripheralData.address_type, function(peripheral, undiscovered) {
115 // If we are not filtering UUIDs or we are and this is a match
116 if (!this.filteredUUIDs.length || this.matchAdvDataUUID(peripheralData.data)) {
117 // Emit the event
118 setImmediate(function() {
119 this.emit('discover', peripheral);
120 }.bind(this));
121 }
122 }.bind(this));
123};
124
125BluetoothController.prototype.matchAdvDataUUID = function(data) {
126 for (var i in data) {
127 var datum = data[i];
128
129 // For each piece of advertising data
130 if (datum.typeFlag >= 2 && datum.typeFlag <= 7) {
131 // For each uuid in each datum
132 for (var j in datum.data) {
133 // Grab the uuid
134 var uuid = datum.data[j];
135 // For each filter uuid
136 for (var k in this.filteredUUIDs) {
137 // Grab the filter
138 var filter = this.filteredUUIDs[k];
139 // If it's a match
140 if (filter === uuid) {
141 // return true
142 return true;
143 }
144 }
145
146 }
147 }
148 }
149
150 // No matches found
151 return false;
152};
153
154BluetoothController.prototype.onConnectionStatus = function(status) {
155 // If we're advertising in slave mode
156 if (this.advertising) {
157 // Emit that we connected with the connection number
158 setImmediate(function() {
159 this.emit('connect', status.connection);
160 }.bind(this));
161 } else {
162 // If we're in master mode
163 // Grab the peripheral
164 this.getPeripheralFromData(null, null, status.address, status.address_type, function(peripheral, undiscovered) {
165
166 if (peripheral) {
167 // Set the connection number and flags
168 peripheral.connection = status.connection;
169 peripheral.flags = status.flags;
170 peripheral.bondHandle = status.bonding;
171
172 // Save it in our data structure
173 this._connectedPeripherals[peripheral.connection] = peripheral;
174
175 // If this new connection was just made
176 // Let any listeners know
177 if (peripheral.flags & (1 << 2)) {
178 peripheral.connected = true;
179 setImmediate(function() {
180 this.emit('connect', peripheral);
181 }.bind(this));
182 }
183 }
184 }.bind(this));
185 }
186 this.emit('connectionStatus', status);
187};
188
189BluetoothController.prototype.onDisconnect = function(response) {
190
191 var peripheral = this._connectedPeripherals[response.connection];
192
193 // If we have a peripheral (we're acting as master)
194 if (peripheral) {
195 // Set the flags
196 peripheral.flags = 0;
197 peripheral.connection = null;
198 peripheral.connected = false;
199
200 // Emit the event
201 setImmediate(function() {
202 this.emit('disconnect', peripheral, response.reason);
203 }.bind(this));
204 } else {
205 // If we're acting as slave
206 // Emit event with connection param
207 setImmediate(function() {
208 this.emit('disconnect', response.connection, response.reason);
209 }.bind(this));
210 }
211};
212
213/*
214 Called when services or groups found
215*/
216BluetoothController.prototype.onGroupFound = function(group) {
217 this.emit('groupFound', group);
218};
219/*
220 Called when discovery operation completed
221*/
222BluetoothController.prototype.onCompletedProcedure = function(procedure) {
223 this.emit('completedProcedure', procedure);
224};
225
226/*
227 Called when characteristic discovery is complete
228 */
229BluetoothController.prototype.onFindInformationFound = function(info) {
230 this.emit('findInformationFound', info);
231};
232
233/*
234 Called when an attribute value is found
235 */
236BluetoothController.prototype.onAttributeValue = function(value) {
237 // We have a notification
238 if (value.type === 1) {
239 // Grab the peripheral responsible
240 var peripheral = this._connectedPeripherals[value.connection];
241 // If it exists (it better!)
242 if (peripheral) {
243 // Grab the corresponding characteristic
244 var characteristic = peripheral.characteristics[value.atthandle];
245 // If it exists (it better!)
246 if (characteristic) {
247 // Set the value
248 characteristic.value = value.value;
249
250 // Emit events
251 this.emit('notification', characteristic, characteristic.value);
252 peripheral.emit('notification', characteristic, characteristic.value);
253 characteristic.emit('notification', characteristic.value);
254 }
255 }
256 } else if (value.type === 2 || value.type === 5) {
257 // We have an indication
258 // Grab the peripheral responsible
259 var peripheral = this._connectedPeripherals[value.connection];
260 // If it exists (it better!)
261 if (peripheral) {
262 // Grab the corresponding characteristic
263 var characteristic = peripheral.characteristics[value.atthandle];
264 // If it exists (it better!)
265 if (characteristic) {
266 // Set the value
267 characteristic.value = value.value;
268
269 // Emit events
270 this.emit('indication', characteristic, characteristic.value);
271 peripheral.emit('indication', characteristic, characteristic.value);
272 characteristic.emit('indication', characteristic.value);
273 }
274 }
275 }
276
277 this.emit('attributeValue', value);
278};
279
280BluetoothController.prototype.onRemoteWrite = function(value) {
281 // If the master is requesting confirmation
282 if (value.reason === 2) {
283 this.messenger.remoteWriteAck(value.connection, 0x00, function(err, response) {
284 // TODO: Do we need anything with this response?
285 });
286 }
287 var index = this._localHandles.indexOf(value.handle);
288 if (index != -1) {
289 setImmediate(function() {
290 this.emit('remoteWrite', value.connection, index, value.value);
291 }.bind(this));
292 }
293};
294
295BluetoothController.prototype.onRemoteStatus = function(status) {
296 var index = this._localHandles.indexOf(status.handle);
297 if (index != -1) {
298 var action;
299 if (status.flags === 0) {
300 action = "remoteUpdateStop";
301 }
302 if (status.flags === 1) {
303 action = "remoteNotification";
304 } else if (status.flags === 2) {
305 action = "remoteIndication";
306 }
307
308 setImmediate(function() {
309 this.emit(action, status.connection, index);
310 }.bind(this));
311 }
312};
313
314BluetoothController.prototype.onPortStatus = function(portStatus) {
315 // Iterate through gpios
316 for (var id in this.gpios) {
317 var gpio = this.gpios[id];
318 // If it's the right port and pin
319 if (gpio._port == portStatus.port && (portStatus.irq & (1 << gpio._pin))) {
320 // Set the correct type of interrupt
321 var type = (portStatus.state & (1 << gpio._pin)) ? "rise" : "fall";
322 // Emit that type as well as the change type
323 setImmediate(function() {
324 gpio.emit("change", null, portStatus.timestamp, type);
325 gpio.emit(type, null, portStatus.timestamp, type);
326 });
327 }
328 }
329};
330
331BluetoothController.prototype.onADCRead = function(adcRead) {
332 this.emit('ADCRead', adcRead);
333};
334
335BluetoothController.prototype.onBondStatus = function(bondStatus) {
336 this.emit('bondStatus', bondStatus);
337};
338
339BluetoothController.prototype.onIndicated = function(indicated) {
340 var index = this._localHandles.indexOf(indicated.attrhandle);
341 if (index != -1) {
342 this.emit('indicated', indicated.connection, index);
343 }
344};
345
346/**********************************************************
347 Bluetooth API
348**********************************************************/
349BluetoothController.prototype.startScanning = function(options, callback) {
350
351 // If the user just passed in a function, make allow duplicates a null
352 if (typeof options == "function" || (!options && !callback)) {
353 callback = options;
354 options = {};
355 }
356
357 this._allowDuplicates = (options.allowDuplicates ? true : false);
358
359 this.filteredUUIDs = (options.serviceUUIDs ? options.serviceUUIDs : []);
360
361 // Reset discovered peripherals
362 this._discoveredPeripherals = {};
363
364 // Set scan filtering parameters
365 // Accept all advertisments, respond to all masters
366 this.messenger.setFiltering(0, 0, !this._allowDuplicates, function(err, response){
367 if (err) {
368 callback && callback(err)
369 } else {
370 // Start scanning
371 this.messenger.startScanning(this.manageRequestResult.bind(this, 'scanStart', callback));
372 }
373 }.bind(this));
374
375};
376
377BluetoothController.prototype.stopScanning = function(callback) {
378 this.messenger.stopScanning(this.manageRequestResult.bind(this, 'scanStop', callback));
379};
380
381BluetoothController.prototype.manageRequestResult = function(event, callback, err, response) {
382 // If there wasn't an error
383 if (!err) {
384 // Emit the event
385 setImmediate(function() {
386 this.emit(event);
387 }.bind(this));
388 } else {
389 setImmediate(function() {
390 this.emit('error', err);
391 }.bind(this));
392 }
393
394 // Call the callback
395 if (callback) {
396 callback(err);
397 }
398};
399
400BluetoothController.prototype.getPeripheralFromData = function(rssi, data, address, addressType, callback) {
401
402 var addr = new Address(address);
403 // Make a peripheral object from the data
404 peripheral = new Peripheral(
405 this,
406 rssi,
407 data,
408 addr);
409
410 peripheral.addressType = addressType;
411
412 if (callback) {
413 callback(peripheral, true);
414 }
415};
416
417BluetoothController.prototype.connect = function(peripheral, callback) {
418 this.messenger.connect(peripheral.address.toBuffer(), peripheral.addressType, function(err, response) {
419 function connectCallback(connectedPeripheral) {
420 if (peripheral === connectedPeripheral) {
421 // Remove this listener
422 self.removeListener('connect', connectCallback);
423 // Call the callback
424 if (callback) {
425 callback();
426 }
427 setImmediate(function() {
428 // Let any listeners know
429 peripheral.emit('connect');
430 });
431 }
432 }
433 // If there was an error
434 if (err) {
435 // Call the callback
436 if (callback) {
437 callback(err);
438 }
439 return;
440 } else {
441 var self = this;
442 // Wait for a connection Update
443 this.on('connect', connectCallback);
444 }
445 }.bind(this));
446};
447
448BluetoothController.prototype.disconnect = function(peripheral, callback) {
449 this.messenger.disconnect(peripheral.connection, function(err, response) {
450 // If there was an error
451 if (err) {
452 // Call the callback
453 if (callback) {
454 callback(err);
455 }
456 return;
457 } else {
458 // Wait for a connection Update
459 this.on('disconnect', function disconnectCallback(disconnectedPeripheral) {
460 if (disconnectedPeripheral === peripheral) {
461
462 // Remove this listener
463 this.removeListener('disconnect', disconnectCallback);
464
465 // Call the callback
466 if (callback) {
467 callback(reason);
468 }
469
470 setImmediate(function() {
471 // Let any listeners know
472 peripheral.emit('disconnect', reason);
473 }.bind(this));
474 }
475 }.bind(this));
476 }
477 }.bind(this));
478};
479
480BluetoothController.prototype.discoverAllAttributes = function(peripheral, callback) {
481 // Discover all of our services and characteristics
482 this.discoverAllServicesAndCharacteristics(peripheral, function(err, results) {
483 var funcs = [];
484 for (var characteristic in results.characteristics) {
485 funcs.push(this.discoverDescriptorsOfCharacteristic.bind(this, results.characteristics[characteristic]));
486 }
487 // Get an array of arrays of descriptors
488 async.series(funcs, function(err, charDescriptors) {
489 // Make return array
490 var allDescriptors = [];
491 // For each array of descriptors
492 for (var i in charDescriptors) {
493 // For each descriptor in that array
494 for (var descriptor in charDescriptors[i]) {
495 // Push the descriptor
496 allDescriptors.push(charDescriptors[i][descriptor]);
497 }
498 }
499
500 // Add descriptors to the resulting object
501 results.descriptors = allDescriptors;
502
503 // Call the callback
504 if (callback) {
505 callback(err, results);
506 }
507 });
508 }.bind(this));
509};
510
511BluetoothController.prototype.discoverAllServices = function(peripheral, callback)
512{
513 this.discoverServices(peripheral, [], callback);
514};
515
516BluetoothController.prototype.discoverServices = function(peripheral, filter, callback)
517{
518 // Discover the services of this device
519 this.serviceDiscovery(peripheral, false, function(err, allServices) {
520 if (err) {
521 if (callback) {
522 callback(err);
523 }
524 return;
525 } else {
526 this.attributeDiscoveryHandler(err, filter, allServices, function(err, services) {
527
528 // Return the values
529 if (callback) {
530 callback(err, services);
531 }
532
533 if (!err && services.length) {
534 // Set the events to be emitted.
535 setImmediate(function() {
536 this.emit('servicesDiscover', services);
537 peripheral.emit('servicesDiscover', services);
538 }.bind(this));
539 }
540 }.bind(this));
541 }
542 }.bind(this));
543};
544
545// TODO: Implement this function
546BluetoothController.prototype.discoverIncludedServices = function(peripheral, callback) {
547 this.serviceDiscovery(peripheral, true, function(err, services) {
548 if (err) {
549 if (callback) {
550 callback(err);
551 }
552 return;
553 } else {
554 // TODO what goes here?
555 }
556 });
557};
558
559BluetoothController.prototype.serviceDiscovery = function(peripheral, included, callback) {
560
561 var services = [];
562
563 // The 'groupFound' event is called when we find a service
564 var groupFoundListener = this.createServiceFromGroup.bind(this, peripheral, services);
565
566 this.on('groupFound', groupFoundListener);
567
568 var self = this;
569 // The 'completed Procedure' event is called when we're done looking for services
570 this.on('completedProcedure', function serviceDiscoveryComplete(procedure) {
571 // If this was called for this peripheral
572 if (procedure.connection === peripheral.connection) {
573
574 // Remove the listener
575 self.removeListener('groupFound', groupFoundListener);
576 self.removeListener('completedProcedure', serviceDiscoveryComplete);
577
578 // Call the callback
579 if (callback) {
580 callback(null, services);
581 }
582 }
583 });
584
585 var discoveryProcedure;
586
587 if (included) {
588 // Request the messenger to start discovering services
589 this.messenger.discoverIncludedServices(peripheral, function(err, response) {
590 // If there was a problem with the request
591 if (err) {
592 // Call callback immediately
593 return callback && callback(err);
594 }
595 });
596 } else {
597 // Request the messenger to start discovering services
598 this.messenger.discoverServices(peripheral, function(err, response) {
599 // If there was a problem with the request
600 if (err) {
601 // Call callback immediately
602 return callback && callback(err);
603 }
604 });
605 }
606};
607
608BluetoothController.prototype.attributeDiscoveryHandler = function(err, filter, attributes, callback) {
609 // If there was an error, report it
610 if (err) {
611 if (callback) {
612 callback(err);
613 }
614
615 setImmediate(function() {
616 this.emit('error', err);
617 }.bind(this));
618
619 return;
620 } else {
621 // Create a return array
622 var ret = [];
623 // If consumer has requested a subset of services
624 if (filter.length != 0) {
625 // Iterate through the services requested
626 for (var i = 0; i < filter.length; i++) {
627 // Convert filter to lower case
628 var match = filter[i].toLowerCase();
629 // If the service details exist and was returned
630 for (var j = 0; j < attributes.length; j++) {
631 // If the attribute's uuid is the filter
632 if (attributes[j].uuid.toString() == match) {
633 // Add it to the array
634 ret.push(attributes[j]);
635 break;
636 }
637 }
638 }
639 } else {
640 // If the consumer has requested all services
641 ret = attributes;
642 }
643
644 // Return the values
645 if (callback) {
646 callback(null, ret);
647 }
648 }
649};
650
651BluetoothController.prototype.createServiceFromGroup = function(peripheral, ret, groupItem) {
652 // If this is the right peripheral
653 if (groupItem.connection === peripheral.connection) {
654 // Convert the UUID to a string instead of buffer
655 var uuid = new UUID(groupItem.uuid);
656
657 var service;
658
659 // If the service doesn't already exist
660 if (!(service = peripheral[uuid.toString()])) {
661 // Make a new service
662 service = new Service(peripheral, uuid, groupItem.start, groupItem.end);
663 // Add this services to the peripherals data structure
664 peripheral.syncService(service);
665 }
666
667 // Add to the service we will report as having discovered
668 ret.push(service);
669 }
670};
671
672BluetoothController.prototype.discoverAllCharacteristics = function(peripheral, callback) {
673
674 this.discoverAllServices(peripheral, function(err, services) {
675 if (err) {
676 if(callback) {
677 callback(err);
678 }
679 } else {
680 var characteristics = [];
681
682 var self = this;
683
684 var discoveryListener = this.createCharacteristicFromInformationFound.bind(this, peripheral, characteristics);
685
686 this.on('findInformationFound', discoveryListener);
687
688 this.on('completedProcedure', function charDiscoveryComplete(procedure) {
689 if (procedure.connection === peripheral.connection) {
690
691 self.removeListener('findInformationFound', discoveryListener);
692 self.removeListener('completedProcedure', charDiscoveryComplete);
693
694 // If it didn't succeed
695 if (procedure.result != 0) {
696 if (callback) {
697 callback(procedure.result, null);
698 }
699 } else {
700 if (callback) {
701 callback(null, characteristics);
702 }
703
704 setImmediate(function() {
705 self.emit('characteristicsDiscover', characteristics);
706 peripheral.emit('characteristicsDiscover', characteristics);
707 });
708 }
709 }
710 });
711
712 this.messenger.discoverAllAttributes(peripheral, function(err, response) {
713 // If there was a problem with the request
714 if (err) {
715 // Call callback immediately
716 return callback && callback(err);
717 }
718 }.bind(this));
719 }
720 }.bind(this));
721};
722
723// TODO: Make work with actual array
724BluetoothController.prototype.discoverCharacteristics = function(peripheral, uuids, callback) {
725 // Somehow assemble the functions such that the next element is the callback to the previous
726 var funcs = [];
727 for (var i = 0; i < uuids.length; i++) {
728 funcs.push(this.discoverCharacteristic.bind(this, peripheral, new UUID(uuids[i])));
729 }
730
731 async.series(funcs, function(err, characteristics) {
732
733 var ret = [];
734
735 // Weed out any characteristics that weren't found
736 for (var j in characteristics) {
737 if (characteristics[j]) {
738 ret.push(characteristics[j]);
739 }
740 }
741
742 if (callback) {
743 callback(err, ret);
744 }
745
746 if (!err && ret.length) {
747 setImmediate(function() {
748 this.emit('characteristicsDiscover', ret);
749 peripheral.emit('characteristicsDiscover', ret);
750 }.bind(this));
751 }
752
753 }.bind(this));
754};
755
756BluetoothController.prototype.discoverCharacteristic = function(peripheral, characteristicUUID, callback) {
757
758 var self = this;
759 var ret = [];
760 var listener = this.createCharacteristicFromAttributeValue.bind(this, peripheral, ret);
761
762 this.on('attributeValue', listener);
763
764 function charDiscoveryComplete(procedure) {
765 if (procedure.connection === peripheral.connection) {
766 self.removeListener('attributeValue', listener);
767 self.removeListener('completedProcedure', charDiscoveryComplete);
768
769 if (procedure.result != 0) {
770 if (procedure.result.message === "Attribute Not Found") {
771 if (callback) {
772 callback(null, null);
773 }
774 return;
775 } else {
776 if (callback) {
777 callback(procedure.result);
778 }
779 return;
780 }
781 } else {
782
783 if (ret.length != 1) {
784 if (callback) {
785 callback(null, null);
786 }
787 return;
788 } else {
789 self.discoverCharacteristicUUID(peripheral, ret[0], callback);
790 }
791 }
792 }
793 }
794
795 this.on('completedProcedure', charDiscoveryComplete);
796
797 // Request only the value of the characteristic with this handle
798 this.messenger.discoverCharacteristicsInRangeForUUID(peripheral, 0x0001, 0xFFFF, characteristicUUID, function(err, response) {
799 // If there was a problem with the request
800 if (err) {
801 // Call callback immediately
802 return callback && callback(err);
803 }
804 }.bind(this));
805};
806
807BluetoothController.prototype.discoverCharacteristicUUID = function(peripheral, characteristic, callback) {
808
809 // Save reference to self
810 var self = this;
811
812 function setCharacteristicUUID(info) {
813
814 // If this is for the correct connection and char handle
815 if (peripheral.connection === info.connection && characteristic.handle === info.chrhandle) {
816
817 // Set the uuid of this characteristic
818 characteristic.setUUID(new UUID(info.uuid));
819
820 // Sync the new one with the correct service
821 peripheral.syncCharacteristic(characteristic);
822
823 // Remove this listener
824 self.removeListener('findInformationFound', setCharacteristicUUID);
825 }
826 }
827
828 // Once we have found info containing UUID about this char
829 this.on('findInformationFound', setCharacteristicUUID);
830
831 function procedureComplete(procedure) {
832
833 // If this was called for this peripheral
834 if (procedure.connection === peripheral.connection) {
835
836 // Stop listening for more characteristics
837 self.removeListener('completedProcedure', procedureComplete);
838
839 // If it didn't succeed
840 if (procedure.result != 0) {
841
842 // Call the callback with the error
843 if (callback) {
844 callback(procedure.result, null);
845 }
846 } else {
847 // Call the callback with result
848 if (callback){
849 callback(null, characteristic);
850 }
851 }
852 }
853 }
854
855 // Once we complete the search
856 this.on('completedProcedure', procedureComplete);
857
858 // Tell the messenger to begin the search for the uuid of this characteristic
859 this.messenger.discoverCharacteristicUUID(characteristic, function(err, response) {
860 // If there was a problem with the request
861 if (err) {
862 // Call callback immediately
863 if (callback){
864 callback(err);
865 }
866 }
867 }.bind(this));
868};
869
870/*
871* Method for turning a bit of information into a characteristic. Must discover all services before calling. (sucks, I know)
872*/
873BluetoothController.prototype.discoverAllServicesAndCharacteristics = function(peripheral, callback) {
874
875 // Discover all characteristics already requires a discovery of all services
876 this.discoverAllCharacteristics(peripheral, function(err, characteristics) {
877 if (err) {
878 // If there is an error
879 // Call the callback
880 if (callback) {
881 callback(err);
882 }
883 } else {
884 // If there is no error
885 // Services to return
886 var services = [];
887
888 // For each service in this peripheral
889 for (var service in peripheral.services) {
890 // Push it into the array
891 services.push(peripheral.services[service]);
892 }
893 // Call callback with our results
894 if (callback) {
895 callback(err, {services : services, characteristics : characteristics});
896 }
897 }
898 }.bind(this));
899};
900
901BluetoothController.prototype.createCharacteristicFromInformationFound = function(peripheral, ret, info) {
902 if (peripheral.connection === info.connection) {
903 // Turn the uuid into a string
904 var uuid = new UUID(info.uuid);
905
906 // If this uuid isn't a service or a descriptor, or any other generic type
907 if (!peripheral.services[uuid.toString()] &&
908 !Service.isStandardService(uuid.toString()) &&
909 !(Descriptor.isStandardDescriptor(uuid.toString())) &&
910 !attributes[uuid.toString()]) {
911
912 // Make a new one
913 var characteristic = new Characteristic(peripheral, uuid, info.chrhandle);
914
915 // Sync the new one with the correct service
916 peripheral.syncCharacteristic(characteristic);
917
918 // Push it into the return array
919 ret.push(characteristic);
920 }
921 }
922};
923
924BluetoothController.prototype.createCharacteristicFromAttributeValue = function(peripheral, ret, value) {
925 // If this is the correct connection
926 if (peripheral.connection === value.connection) {
927 // Create the characteristic
928 var characteristic = new Characteristic(peripheral, null, value.atthandle, value.value);
929 // Add to the characteristics we will report as having discovered
930 ret.push(characteristic);
931 }
932};
933
934BluetoothController.prototype.discoverAllCharacteristicsOfService = function(service, callback) {
935 this.discoverCharacteristicsOfService(service, [], callback);
936};
937
938// TODO: This currently writes new characteristics over old ones because we have no way of
939// checking which characteristics we should construct and what we shouldn't
940BluetoothController.prototype.discoverCharacteristicsOfService = function(service, filter, callback) {
941
942 // Discover the characteristics of this specific service
943 this.serviceCharacteristicDiscovery(service, function(err, allCharacteristics) {
944 // Format results and report any errors
945 this.attributeDiscoveryHandler(err, filter, allCharacteristics, function(err, characteristics) {
946
947 // Call that callback
948 if (callback) {
949 callback(err, characteristics);
950 }
951 // If we have characteristics to report
952 if (characteristics.length) {
953 // Also emit it from appropriate sources
954 setImmediate(function() {
955 this.emit('characteristicsDiscover', characteristics);
956 service._peripheral.emit('characteristicsDiscover', characteristics);
957 service.emit('characteristicsDiscover', characteristics);
958 }.bind(this));
959 }
960 }.bind(this));
961 }.bind(this));
962};
963
964BluetoothController.prototype.serviceCharacteristicDiscovery = function(service, callback) {
965 // Save reference to self
966 var self = this;
967
968 var characteristics = [];
969
970 var listener = this.createCharacteristicFromInformationFound.bind(this, service._peripheral, characteristics);
971
972 // Once we have found info containing UUID about this char
973 this.on('findInformationFound', listener);
974
975 // Once we complete the search
976 this.on('completedProcedure', function procedureComplete(procedure) {
977
978 // If this was called for this peripheral
979 if (procedure.connection === service._peripheral.connection) {
980
981 // Stop listening for more characteristics
982 self.removeListener('completedProcedure', procedureComplete);
983 self.removeListener('findInformationFound', listener);
984
985 // If it didn't succeed
986 if (procedure.result != 0) {
987
988 // Call the callback with the error
989 if (callback) {
990 callback(procedure.result, null);
991 }
992 } else {
993 // Call the callback with result
994 if (callback){
995 callback(null, characteristics);
996 }
997 }
998 }
999 });
1000
1001 // Tell the messenger to begin the search for the uuid of this characteristic
1002 this.messenger.discoverCharacteristicsInRange(service._peripheral, service._startHandle, service._endHandle, function(err, response) {
1003 // If there was a problem with the request
1004 if (err) {
1005 // Call callback immediately
1006 if (callback) {
1007 callback(err);
1008 }
1009 }
1010 }.bind(this));
1011};
1012
1013BluetoothController.prototype.read = function(characteristic, callback) {
1014 this.readAttribute(characteristic, function(err, value) {
1015 characteristic.value = value;
1016
1017 if (callback) {
1018 callback(err, value);
1019 }
1020
1021 if (value) {
1022 this.emit('characteristicRead', characteristic, value);
1023 characteristic._peripheral.emit('characteristicRead', characteristic, value);
1024 characteristic.emit('characteristicRead', value);
1025 }
1026 }.bind(this));
1027};
1028
1029BluetoothController.prototype.readAttribute = function(attribute, callback) {
1030
1031 var readNum = 0;
1032
1033 var self = this;
1034
1035 var ret;
1036
1037 function valueListener(reading) {
1038 // If this is our first read of several or if this will be the only read
1039 if (readNum === 0 || (reading.type == 0)) {
1040 // Assign the value
1041 ret = reading.value;
1042 } else {
1043 // Concat the buffers
1044 ret = Buffer.concat([ret, reading.value]);
1045 }
1046 readNum++;
1047 }
1048
1049 this.on('attributeValue', valueListener);
1050
1051 function attributeReadComplete(procedure) {
1052
1053 // If this was called for this peripheral
1054 if (procedure.connection === attribute._peripheral.connection) {
1055
1056 // Stop listening for more characteristics
1057 self.removeListener('attributeValue', valueListener);
1058 self.removeListener('completedProcedure', attributeReadComplete);
1059
1060 // If it didn't succeed
1061 if (procedure.result != 0) {
1062 if (callback) {
1063 callback(procedure.result, null);
1064 }
1065 } else {
1066 // Call the callback
1067 if (callback) {
1068 callback(null, ret);
1069 }
1070 }
1071 }
1072 }
1073
1074 // The 'completed Procedure' event is called when we're done looking for services
1075 this.on('completedProcedure', attributeReadComplete);
1076
1077 // Request the messenger to start discovering services
1078 this.messenger.readHandle(attribute._peripheral, attribute, function(err, response) {
1079 // If there was a problem with the request
1080 if (err) {
1081 // Call callback immediately
1082 if (callback) {
1083 callback(err);
1084 }
1085 }
1086 }.bind(this));
1087};
1088
1089
1090BluetoothController.prototype.write = function(characteristic, value, callback) {
1091 this.writeAttribute(characteristic, value, function(err, written) {
1092
1093 if (callback) {
1094 callback(err, written);
1095 }
1096
1097 if (!err) {
1098 setImmediate(function() {
1099 this.emit('characteristicWrite', characteristic, written);
1100 characteristic._peripheral.emit('characteristicWrite', characteristic, written);
1101 characteristic.emit('write', written);
1102 }.bind(this));
1103 }
1104 }.bind(this));
1105};
1106
1107BluetoothController.prototype.writeAttribute = function(attribute, value, callback) {
1108
1109 // if (value.length > 98) {
1110 // return callback && callback(new Error("Writes must be less than or equal to 98 bytes"));
1111 // }
1112
1113 // Write has to be in 20 byte increments
1114 var self = this;
1115 this.splitWriteIntoBuffers(value, function(err, buffers) {
1116 if (err) {
1117 if (callback) {
1118 callback(err);
1119 }
1120 return;
1121 } else {
1122 // If there is only one buffer
1123 if (buffers.length == 1) {
1124 // We can send it immediately
1125 self.writeAttributeImmediately(attribute, buffers[0], callback);
1126 } else {
1127 // If there are multiple buffers, we've got to prepare several writes, then execute
1128 self.prepareAttributeWrite(attribute, buffers, callback);
1129 }
1130 }
1131 });
1132};
1133
1134BluetoothController.prototype.writeAttributeImmediately = function(attribute, singleBuffer, callback) {
1135 // The 'completed Procedure' event is called when we're done writing
1136 this.once('completedProcedure', function(procedure) {
1137
1138 // If this was called for this peripheral
1139 if (procedure.connection === attribute._peripheral.connection &&
1140 procedure.chrhandle === attribute.handle) {
1141
1142 // If it didn't succeed
1143 if (procedure.result != 0) {
1144
1145 if (callback) {
1146 callback(procedure.result, null);
1147 }
1148 } else {
1149 // Call the callback
1150 if (callback) {
1151 callback(null, singleBuffer);
1152 }
1153 }
1154 }
1155 }.bind(this));
1156
1157 this.messenger.writeAttributeImmediately(attribute, singleBuffer, function(err, response) {
1158 // If there was a problem with the request
1159 if (err) {
1160 // Call callback immediately
1161 return callback && callback(err);
1162 }
1163 });
1164};
1165
1166BluetoothController.prototype.prepareAttributeWrite = function(attribute, multipleBuffers, callback) {
1167 var bufSize = 20;
1168 var offset = 0;
1169
1170 // Keep this around
1171 var self = this;
1172
1173 function bufferWriteIterate(procedure) {
1174 // If this is for our connection
1175 if (procedure.connection === attribute._peripheral.connection && procedure.chrhandle === attribute.handle) {
1176 // If there was an error, report it and cancel write
1177 if (procedure.result != 0) {
1178 // Cancel any previous writes
1179 self.messenger.cancelWrite(attribute, function(cancelErr, response) {
1180 // Call callback immediately
1181 return callback && callback(procedure.result);
1182 });
1183 } else {
1184 // If there was no error
1185 // If we've completed the procedure but still have more to write
1186 if (offset < multipleBuffers.length) {
1187
1188 // Send another part of the buffer off
1189 self.messenger.prepareWrite(attribute, multipleBuffers[offset], offset*bufSize, function(err, response) {
1190 // If there was a problem with the request
1191 if (err) {
1192 // Cancel any previous writes
1193 self.messenger.cancelWrite(attribute, function(cancelErr, response) {
1194
1195 // Call callback immediately
1196 return callback && callback(err);
1197 });
1198 }
1199 // Increment offset so we send the next buffer next time
1200 offset++;
1201 });
1202 } else if (offset === multipleBuffers.length) {
1203 // If we've sent all the prepare writes...
1204 // Remove the buffer write iterator listener so we don't send any more packets
1205 self.removeListener('completedProcedure', bufferWriteIterate);
1206
1207 // Once our write execution is complete
1208 self.once('completedProcedure', function(procedure) {
1209 // If there was an error
1210 if (procedure.result != 0) {
1211 // Report the error
1212 if (callback) {
1213 callback(procedure.result);
1214 }
1215 } else {
1216 // If there was no error
1217 //Concat all the buffers into one
1218 var ret = multipleBuffers[0];
1219 for (var i = 1; i < multipleBuffers.length; i++) {
1220 ret = Buffer.concat([ret, multipleBuffers[i]]);
1221 }
1222
1223 // Call callback
1224 if (callback) {
1225 callback(null, ret);
1226 }
1227 }
1228 });
1229 // Execute the write of the packets
1230 self.messenger.executeWrite(attribute, function(err, response) {
1231 // If there was a problem with the request
1232 if (err) {
1233 // Call callback immediately
1234 if (callback) {
1235 callback(err);
1236 }
1237 return;
1238 }
1239 });
1240 }
1241 }
1242 }
1243 }
1244
1245 // Function for writing each subsequent buffer
1246 this.on('completedProcedure', bufferWriteIterate);
1247
1248 this.messenger.prepareWrite(attribute, multipleBuffers[offset], offset * bufSize, function(err, response) {
1249 // If there was a problem with the request
1250 if (err) {
1251 // Call callback immediately
1252 return callback && callback(err);
1253 }
1254 offset++;
1255 });
1256};
1257
1258BluetoothController.prototype.splitWriteIntoBuffers = function(value, callback) {
1259 // If nothing was passed in, just return an error
1260 if (!value) {
1261 return callback && callback(new Error("No value passed to write function"));
1262 } else {
1263 // If something was passed in
1264 var buf;
1265 if (typeof value === "string") {
1266 // If it is a string, make a buf with utf-8 encoding
1267 buf = new Buffer(value);
1268 } else if (Buffer.isBuffer(value)) {
1269 // If it's already a buffer, keep as is
1270 buf = value;
1271 } else if (!isNaN(value)) {
1272 // If it's a number, make a new buffer for the 32 bit number
1273 buf = new Buffer(4);
1274 buf.writeUInt32BE(value, 0);
1275 } else {
1276 // If none of the above, it's invalid. Throw an error
1277 if (callback) {
1278 callback(new Error("Can only write strings, numbers, and buffers.")); // TODO: should this be a new Error?
1279 }
1280 return;
1281 }
1282
1283 // Max number of bytes per buffer
1284 var maxBufLen = 20;
1285 var maxNumBufs = 5;
1286
1287 // Get the number of buffers we'll need
1288 var iter = Math.ceil(buf.length/maxBufLen);
1289
1290 // Prepare array for buffers
1291 var ret = new Array(iter);
1292 // For each buffer
1293 for (var i = 0; i < iter; i++) {
1294 // Grab the start byte
1295 var start = i * maxBufLen;
1296 // Put that start plus next 20 bytes (or if it's the last, just grab remainder)
1297 var end;
1298 if (i == (iter-1)) {
1299 end = buf.length;
1300 } else {
1301 end = start + maxBufLen;
1302 }
1303 // Slice it and throw it into our return array
1304 ret[i] = buf.slice(start, end);
1305 }
1306
1307 // Return array
1308 if (callback){
1309 callback(null, ret);
1310 }
1311 }
1312};
1313
1314// Continues reading and storing subsequent handles of a characteristic until a non-descriptor is found
1315BluetoothController.prototype.discoverDescriptorsOfCharacteristic = function(characteristic, callback) {
1316 var self = this;
1317 var descriptors = [];
1318 var offset = 1;
1319 var done = false;
1320
1321 function findDescriptorInformation(info) {
1322 // If this for the correct connection
1323 if (characteristic._peripheral.connection === info.connection) {
1324 // Turn the uuid into a string
1325 var uuid = new UUID(info.uuid);
1326
1327 // If this uuid isn't of a descriptor
1328 if (Descriptor.isStandardDescriptor(uuid.toString())) {
1329
1330 // Make a new one
1331 var descriptor = new Descriptor(characteristic._peripheral, uuid, info.chrhandle);
1332
1333 // Add it to the characteristic's descriptors
1334 characteristic.descriptors[uuid.toString()] = descriptor;
1335
1336 // Push it into the return array
1337 descriptors.push(descriptor);
1338 } else {
1339 // Set the done flag
1340 done = true;
1341
1342 // Remove this listener
1343 self.removeListener('findInformationFound', findDescriptorInformation);
1344 }
1345 }
1346 }
1347
1348 // Keep reading the next handle until it is not a descriptor
1349 this.on('findInformationFound', findDescriptorInformation);
1350
1351 function descriptorDiscoveryComplete(procedure) {
1352 // If this was called for this peripheral
1353 if (procedure.connection === characteristic._peripheral.connection) {
1354
1355 // If it didn't succeed
1356 if (procedure.result != 0) {
1357 // If the error is not that their is no Attribute found (which will happen
1358 // if the final characteristic is called upon)
1359 if (procedure.result.message != 'Attribute Not Found') {
1360 // Call the callback with the error
1361 if (callback) {
1362 callback(procedure.result, null);
1363 }
1364 // Emit the event
1365 setImmediate(function() {
1366 self.emit('error', procedure.result);
1367 });
1368 return;
1369 } else {
1370 done = true;
1371 }
1372 }
1373
1374 // If we have finished finding all descriptors
1375 if (done) {
1376 // Stop listening for more descriptors
1377 self.removeListener('completedProcedure', descriptorDiscoveryComplete);
1378
1379 // Call the callback with result
1380 if (callback) {
1381 callback(null, descriptors);
1382 }
1383
1384 // Emit events
1385 setImmediate(function() {
1386 self.emit('descriptorsDiscover', descriptors);
1387 characteristic._peripheral.emit('descriptorsDiscover', descriptors);
1388 });
1389 } else {
1390 // If we have not finished finding descriptors
1391
1392 // Increase the offset
1393 offset++;
1394
1395 // And make the call for the next attribute
1396 self.messenger.findHandle(characteristic._peripheral, characteristic.handle + offset, function(err, response) {
1397 // If there was a problem with the request
1398 if (err) {
1399
1400 // Call callback immediately
1401 if (callback) {
1402 callback(err);
1403 }
1404
1405 setImmediate(function() {
1406 self.emit('error', err);
1407 });
1408 }
1409 });
1410 }
1411 }
1412 }
1413
1414 // Once we have finished finding a single descriptor
1415 this.on('completedProcedure', descriptorDiscoveryComplete);
1416
1417 // Read the first subsequent handle
1418 this.messenger.findHandle(characteristic._peripheral, characteristic.handle + offset, function(err, response) {
1419 // If there was a problem with the request
1420 if (err) {
1421
1422 // Call callback immediately
1423 if (callback) {
1424 callback(err);
1425 }
1426
1427 // Emit the error
1428 setImmediate(function() {
1429 this.emit('error', err);
1430 }.bind(this));
1431 }
1432 }.bind(this));
1433};
1434
1435BluetoothController.prototype.readDescriptor = function(descriptor, callback) {
1436 this.readAttribute(descriptor, function(err, value) {
1437 descriptor.value = value;
1438
1439 if (callback) {
1440 callback(err, value);
1441 }
1442
1443 if (value) {
1444 setImmediate(function() {
1445 this.emit('descriptorRead', descriptor, value);
1446 descriptor._peripheral.emit('descriptorRead', descriptor, value);
1447 descriptor.emit('descriptorRead', value);
1448 }.bind(this));
1449 }
1450 }.bind(this));
1451};
1452
1453BluetoothController.prototype.writeDescriptor = function(descriptor, value, callback) {
1454 this.writeAttribute(descriptor, value, function(err, written) {
1455
1456 if (callback) {
1457 callback(err, written);
1458 }
1459
1460 if (!err) {
1461 setImmediate(function() {
1462 this.emit('descriptorWrite', descriptor, written);
1463 descriptor._peripheral.emit('descriptorWrite', descriptor, written);
1464 descriptor.emit('descriptorWrite', written);
1465 }.bind(this));
1466 }
1467 }.bind(this));
1468};
1469
1470BluetoothController.prototype.notify = function(characteristic, notify, callback) {
1471 if (notify) {
1472 this.startNotifications(characteristic, callback);
1473 } else {
1474 this.stopNotifications(characteristic, callback);
1475 }
1476}
1477
1478BluetoothController.prototype.startNotifications = function(characteristic, callback) {
1479 this.writeToConfigDescriptorOfCharacteristic(characteristic, new Buffer([0x01, 0x00]), function(err) {
1480 if (callback) {
1481 callback(err);
1482 }
1483 });
1484};
1485
1486BluetoothController.prototype.stopNotifications = function(characteristic, callback) {
1487 this.stopRemoteUpdates(characteristic, callback);
1488};
1489
1490BluetoothController.prototype.startIndications = function(characteristic, callback) {
1491 this.writeToConfigDescriptorOfCharacteristic(characteristic, new Buffer([0x02, 0x00]), function(err) {
1492 if (callback) {
1493 callback(err);
1494 }
1495 });
1496};
1497
1498BluetoothController.prototype.stopIndications = function(characteristic, callback) {
1499 this.stopRemoteUpdates(characteristic, callback);
1500};
1501
1502BluetoothController.prototype.stopRemoteUpdates = function(characteristic, callback) {
1503 this.writeToConfigDescriptorOfCharacteristic(characteristic, new Buffer([0x00, 0x00]), function(err) {
1504 if (callback) {
1505 callback(err);
1506 }
1507 });
1508};
1509
1510BluetoothController.prototype.writeToConfigDescriptorOfCharacteristic = function(characteristic, value, callback) {
1511 // Check if we've already fetched the config descriptor
1512 this.retrieveConfigDescriptor(characteristic, function(err, descriptor) {
1513 if (err) {
1514 return callback && callback(err);
1515 } else {
1516 if (!descriptor) {
1517 return callback && callback(new Error("Characteristic is not configured for notifications"));
1518 } else {
1519 descriptor.write(value, function(err, written) {
1520 if (callback) {
1521 callback(err);
1522 }
1523 return;
1524 });
1525 }
1526 }
1527 });
1528};
1529
1530BluetoothController.prototype.retrieveConfigDescriptor = function(characteristic, callback) {
1531 // Check if we've already fetched the config descriptor
1532 this.getConfigDescriptorFromFetched(characteristic, function(descriptor) {
1533 // If we haven't
1534 if (!descriptor) {
1535 // Discover all descriptors
1536 this.discoverDescriptorsOfCharacteristic(characteristic, function(err, descriptors) {
1537 if (err) {
1538 if (callback) {
1539 callback(err);
1540 }
1541 return;
1542 } else {
1543 // Now check again for the config descriptor
1544 this.getConfigDescriptorFromFetched(characteristic, function(descriptor) {
1545 // If there is no descriptor, you can't get notifications from this char
1546 if (!descriptor) {
1547 return callback && callback();
1548 } else {
1549 return callback && callback(null, descriptor);
1550 }
1551 }.bind(this));
1552 }
1553 }.bind(this));
1554 } else {
1555 if (callback) {
1556 callback(null, descriptor);
1557 }
1558 return;
1559 }
1560 }.bind(this));
1561};
1562
1563BluetoothController.prototype.getConfigDescriptorFromFetched = function(characteristic, callback) {
1564 for (var d in characteristic.descriptors) {
1565 if (characteristic.descriptors[d].uuid.toString() == "2902") {
1566 return callback && callback(characteristic.descriptors[d]);
1567 }
1568 }
1569 if (callback) {
1570 callback();
1571 }
1572};
1573
1574BluetoothController.prototype.confirmIndication = function(characteristic, callback) {
1575 this.messenger.confirmIndication(characteristic, function(err, response) {
1576 // If there was a problem with the request
1577 if (err) {
1578 // Call callback immediately
1579 if (callback) {
1580 callback(err);
1581 }
1582
1583 // Emit the error
1584 setImmediate(function() {
1585 this.emit('error', err);
1586 }.bind(this));
1587 }
1588 }.bind(this));
1589};
1590
1591BluetoothController.prototype.updateRssi = function(peripheral, callback) {
1592 this.messenger.updateRssi(peripheral, function(err, response) {
1593 if (callback) {
1594 callback(err, response.rssi);
1595 }
1596 if (!err) {
1597 setImmediate(function() {
1598 this.emit('rssiUpdate', response.rssi);
1599 peripheral.emit('rssiUpdate', response.rssi);
1600 }.bind(this));
1601 }
1602 }.bind(this));
1603};
1604
1605// TODO Returns a string... is that appropriate?
1606BluetoothController.prototype.getBluetoothAddress = function(callback) {
1607 this.messenger.getAddress(function(err, response) {
1608 var address;
1609 if (response && !err) {
1610 address = Address.bufToStr(response.address);
1611 }
1612 if (callback) {
1613 callback(err, address);
1614 }
1615 });
1616};
1617
1618BluetoothController.prototype.getMaxConnections = function(callback) {
1619 this.messenger.getMaxConnections(function(err, response) {
1620 if (callback) {
1621 callback(err, response.maxconn);
1622 }
1623 });
1624};
1625
1626BluetoothController.prototype.startAdvertising = function(callback) {
1627 this.messenger.startAdvertising(this._userAdvData, function(err, response) {
1628 if (!err) {
1629 this.advertising = true;
1630 // Emit the error
1631 setImmediate(function() {
1632 this.emit('startAdvertising');
1633 }.bind(this));
1634 } else {
1635 this.advertising = false;
1636 // Emit the error
1637 setImmediate(function() {
1638 this.emit('error', err);
1639 }.bind(this));
1640 }
1641
1642 // Call callback immediately
1643 if (callback) {
1644 callback(err);
1645 }
1646 }.bind(this));
1647};
1648
1649BluetoothController.prototype.stopAdvertising = function(callback) {
1650 this.messenger.stopAdvertising(function(err, response) {
1651 if (!err) {
1652 this.advertising = false;
1653 // Emit the error
1654 setImmediate(function() {
1655 this.emit('stopAdvertising');
1656 }.bind(this));
1657 } else {
1658 this.advertising = true;
1659 // Emit the error
1660 setImmediate(function() {
1661 this.emit('error', err);
1662 }.bind(this));
1663 }
1664
1665 // Call callback immediately
1666 if (callback) {
1667 callback(err);
1668 }
1669 }.bind(this));
1670};
1671
1672BluetoothController.prototype.setAdvertisingData = function(data, callback) {
1673 this.advDataHelper(data, 0, callback);
1674};
1675
1676BluetoothController.prototype.setScanResponseData = function(data, callback) {
1677 this.advDataHelper(data, 1, callback);
1678};
1679
1680BluetoothController.prototype.advDataHelper = function(data, advParam, callback) {
1681 if (data.length > 32){
1682 callback && callback(new Error("Advertisement packet exceeds maximum length of 32 bytes."));
1683 } else {
1684 this.messenger.setAdvertisementData(advParam, data, function(err, response) {
1685
1686 if (err) {
1687 setImmediate(function() {
1688 this.emit('error');
1689 }.bind(this));
1690 this._userAdvData = false;
1691 callback && callback(err);
1692 } else {
1693 this._userAdvData = true;
1694 callback && callback(null);
1695 }
1696
1697 }.bind(this));
1698 }
1699};
1700
1701BluetoothController.prototype.getFirmwareVersion = function(callback) {
1702 this.messenger.readLocalHandle(this._firmwareVersionHandle, 0, function(err, response) {
1703 var version;
1704 if (response.value) {
1705 version = response.value.toString();
1706 }
1707 if (callback) {
1708 callback(err, version);
1709 }
1710 });
1711};
1712
1713BluetoothController.prototype.maxNumValues = function(callback) {
1714 this.getFirmwareVersion(function(err, version) {
1715 var max;
1716 if (!err) {
1717 max = this._maxNumValues[version];
1718 }
1719 if (callback) {
1720 callback(err, max);
1721 }
1722 }.bind(this));
1723};
1724
1725BluetoothController.prototype.readLocalValue = function(index, offset, callback) {
1726 this.readLocalHandle(this._localHandles[index], offset, callback);
1727};
1728
1729BluetoothController.prototype.writeLocalValue = function(index, data, callback) {
1730 if (!Buffer.isBuffer(data)) {
1731 if (callback) {
1732 callback(new Error("Value must be a buffer."));
1733 }
1734 return;
1735 }
1736 this.writeLocalHandle(this._localHandles[index], data, callback);
1737};
1738
1739BluetoothController.prototype.readLocalHandle = function(handle, offset, callback) {
1740 this.messenger.readLocalHandle(handle, offset, function(err, response) {
1741 if (callback) {
1742 callback(err, response.value);
1743 }
1744 });
1745};
1746
1747BluetoothController.prototype.writeLocalHandle = function(handle, data, callback) {
1748 this.messenger.writeLocalHandle(handle, data, function(err, response) {
1749 err = err ? err : response.result;
1750 if (callback) {
1751 callback(err);
1752 }
1753 });
1754};
1755
1756/*
1757* HARDWARE
1758*/
1759
1760BluetoothController.prototype.I2C = function(address) {
1761 return new BluetoothI2C(this.messenger, address);
1762};
1763
1764// TODO What is this global function doing floating around here?
1765function BluetoothI2C(messenger, address) {
1766 this.messenger = messenger;
1767 this.address = address << 1;
1768}
1769
1770BluetoothI2C.prototype.transfer = function(txbuf, rxLen, callback) {
1771 this.send(txbuf, function(err) {
1772 if (err) {
1773 if (callback) {
1774 callback(err);
1775 }
1776 return;
1777 } else {
1778 this.receive(rxLen, function(err, rx) {
1779 if (callback) {
1780 callback(err, rx);
1781 }
1782 return;
1783 });
1784 }
1785 }.bind(this));
1786};
1787
1788BluetoothI2C.prototype.send = function(txbuf, callback) {
1789 // Send off the data
1790 // TODO: Let users decide on stop condition
1791 this.messenger.I2CSend(this.address, 1, txbuf, function(err, response) {
1792
1793 // Return the error
1794 if (callback) {
1795 callback(err);
1796 }
1797 });
1798};
1799
1800BluetoothI2C.prototype.receive = function(length, callback) {
1801 this.messenger.I2CRead(this.address, 1, length, function(err, response) {
1802 if (callback) {
1803 callback(err, response.data);
1804 }
1805 });
1806};
1807
1808BluetoothController.prototype.gpio = function(index) {
1809 if (!this.gpios) {
1810 this.createGPIOs();
1811 }
1812 return this.gpios[index];
1813};
1814
1815BluetoothController.prototype.createGPIOs = function() {
1816 this.gpios = {};
1817 this.gpios.p0_3 = new BluetoothPin(this, 0, 3);
1818 this.gpios.p0_2 = new BluetoothPin(this, 0, 2);
1819};
1820
1821function BluetoothPin(controller, port, pin) {
1822 this._port = port;
1823 this._pin = pin;
1824 this._controller = controller;
1825 this.direction = "input";
1826 this.value = false;
1827 this.interruptOn = null;
1828}
1829
1830util.inherits(BluetoothPin, events.EventEmitter);
1831
1832BluetoothPin.prototype.toString = function() {
1833 return JSON.stringify({
1834 direction: this.direction,
1835 value: this.value,
1836 });
1837};
1838
1839BluetoothPin.prototype.setInput = function(callback) {
1840 this.direction = "input";
1841 this.setPinDirections(callback);
1842};
1843
1844BluetoothPin.prototype.setOutput = function(initial, callback) {
1845 if (typeof initial == 'function') {
1846 next = initial;
1847 initial = null;
1848 }
1849
1850 this.direction = "output";
1851
1852 this.setPinDirections(function(err) {
1853 if (err) {
1854 return callback && callback(err);
1855 } else {
1856 this.write(initial, callback);
1857 }
1858 }.bind(this));
1859};
1860
1861BluetoothPin.prototype.write = function(value, callback) {
1862 if (this.direction === "output") {
1863 this.value = value;
1864 this.setPinValues(value, callback);
1865 }
1866};
1867
1868
1869BluetoothPin.prototype.read = function(callback) {
1870 // Read port 0
1871 this._controller.messenger.readPin(this._port, 1 << this._pin, function(err, response) {
1872 var val;
1873 if (!err) {
1874 val = (response.data >> this._pin);
1875 }
1876 this.value = val;
1877 if (callback) {
1878 callback(err, val);
1879 }
1880 }.bind(this));
1881};
1882
1883BluetoothPin.prototype.setPinDirections = function(callback) {
1884 var mask = 0;
1885
1886 // Iterate through our gpios to construct a bitmask
1887 for (var id in this._controller.gpios) {
1888 // If the gpio is an output
1889 var gpio = this._controller.gpios[id];
1890 if (gpio.direction === "output") {
1891 // Put a one in it's place
1892 mask += (1 << gpio._pin);
1893 }
1894 }
1895
1896 this._controller.messenger.setPinDirections(this._port, mask, function(err, response) {
1897 if (callback) {
1898 callback(err);
1899 }
1900 });
1901};
1902
1903BluetoothPin.prototype.setPinValues = function(value, callback) {
1904 var mask = 0;
1905 var data = 0;
1906
1907 // Iterate through our gpios to construct a bitmask
1908 for (var id in this._controller.gpios) {
1909 // If the gpio is an output
1910 var gpio = this._controller.gpios[id];
1911 if (gpio.direction === "output") {
1912 // Put a one in it's place
1913 mask += (1 << gpio._pin);
1914 // If the value is high
1915 if (gpio.value == true) {
1916 // Put a 1 in it's place
1917 data += (1 << gpio._pin);
1918 }
1919 }
1920 }
1921
1922 this._controller.messenger.writePin(this._port, mask, data, function(err, response) {
1923 if (callback) {
1924 callback(err);
1925 }
1926 });
1927};
1928
1929BluetoothPin.prototype.watch = function(type, callback) {
1930
1931 if (type != "rise" && type != "fall" && type !="change") {
1932 return callback && callback(new Error("Invalid pin watch type. Must be 'rise', 'fall', or 'change'."));
1933 }
1934
1935 // Set an event listener
1936 this.on(type, callback);
1937
1938 // Set the type for the pin
1939 this.onInterrupt = type;
1940
1941 this.setPinWatches(type, function(err) {
1942 if (err) {
1943 if (callback) {
1944 callback(err);
1945 }
1946 }
1947 }.bind(this));
1948};
1949
1950BluetoothPin.prototype.unwatch = function(type, callback) {
1951 if (this.onInterrupt === type) {
1952 this.onInterrupt = null;
1953 this.setPinWatches(type, callback);
1954 } else {
1955 if (callback) {
1956 callback();
1957 }
1958 }
1959};
1960
1961BluetoothPin.prototype.setPinWatches = function(type, callback) {
1962 var mask = 0;
1963 // For each of our gpios
1964 for (var id in this._controller.gpios) {
1965 // Get reference to gpio
1966 var gpio = this._controller.gpios[id];
1967 // If this interrupt type is the kind we are watching for
1968 if (gpio.onInterrupt == type) {
1969 // Add it to the mask
1970 mask += (1 << gpio._pin);
1971 }
1972 }
1973
1974 // Tell the messenger to set the mask
1975 this._controller.messenger.watchPin(0, mask, (type === "rise" ? 0 : 1), function(err, response) {
1976 // If we're looking for a change
1977 if (type === "change") {
1978 // We'll have to set the rise detector as well
1979 this._controller.messenger.watchPin(0, mask, 0, function(err, response) {
1980 if (callback) {
1981 callback(err);
1982 }
1983 });
1984 } else {
1985 if (callback) {
1986 callback(err);
1987 }
1988 }
1989 }.bind(this));
1990};
1991
1992BluetoothController.prototype.readADC = function(callback) {
1993 this.once('ADCRead', function(adc) {
1994 /* From the datasheet:
1995 In the example case of 12 effective bits decimation, you will need to read
1996 the left-most 12 bits of the value to interpret it. It is a 12-bit 2's
1997 complement value left-aligned to the MSB of the 16-bit container.
1998 */
1999 var normalized = (adc.value >> 4) / 0x7ff;
2000 if(callback) {
2001 callback(null, normalized);
2002 }
2003 });
2004 // Read ADC channel 1, with the third option for decimation (12 value) with
2005 // aref as reference
2006 this.messenger.readADC(0x1, 0x3, 0x2, function(err, response) {
2007
2008 // If there was a problem with the request
2009 if (err) {
2010 // Call callback immediately
2011 if (callback) {
2012 callback(err);
2013 }
2014
2015 // Emit the error
2016 setImmediate(function() {
2017 this.emit('error', err);
2018 }.bind(this));
2019 }
2020 }.bind(this));
2021};
2022
2023// Set whether a we can be bonded to
2024BluetoothController.prototype.setBondable = function(bondable, callback) {
2025 this.messenger.setBondable(bondable ? 1 : 0, callback);
2026};
2027
2028// Get bonds with current devices
2029BluetoothController.prototype.getBonds = function(callback) {
2030 var bonds = [];
2031 var numBondsToSatisfy = 0;
2032 this.on('bondStatus', function bondStatus(status) {
2033 bonds.push(status);
2034
2035 if (bonds.length === bumBondsToSatisfy) {
2036 this.removeListener('bondStatus', bondStatus);
2037 if (callback) {
2038 callback(null, bonds);
2039 }
2040 }
2041 }.bind(this));
2042
2043 this.messenger.getBonds(function (err, response) {
2044 if (err) {
2045 if (callback) {
2046 callback(err);
2047 }
2048 } else if (response.bonds === 0) {
2049 if (callback) {
2050 callback(null, bonds);
2051 }
2052 } else {
2053 numBondsToSatisfy = response.bonds;
2054 }
2055 });
2056};
2057
2058// Delete any bonds with devices
2059BluetoothController.prototype.deleteBond = function(peripheral, callback) {
2060 this.messenger.deleteBond(peripheral, function(err) {
2061 if (!err) {
2062 peripheral.bondHandle = 0xff;
2063 }
2064 if (callback){
2065 callback(err);
2066 }
2067 });
2068};
2069
2070BluetoothController.prototype.startEncryption = function(peripheral, callback) {
2071 var self = this;
2072
2073 function successHandler(status) {
2074 if (status.connection === peripheral.connection) {
2075 peripheral.bondHandle = status.bonding;
2076 removeHandlers();
2077 if (callback) {
2078 callback(null, peripheral.bondHandle);
2079 }
2080 }
2081 }
2082
2083 function failHandler(failDetails) {
2084 if (peripheral.connection === failDetails.handle) {
2085 removeHandlers();
2086 if (callback) {
2087 callback(failDetails.reason);
2088 }
2089 }
2090 }
2091
2092 function removeHandlers() {
2093 self.removeListener('connectionStatus', successHandler);
2094 self.removeListener('bondingFail', failHandler);
2095 }
2096
2097 this.on('connectionStatus', successHandler);
2098 this.on('bondingFail', failHandler);
2099
2100 this.messenger.startEncryption(peripheral, true, function(err, response) {
2101 if (err) {
2102 if (callback) {
2103 callback(err);
2104 }
2105 }
2106 });
2107};
2108
2109BluetoothController.prototype.enterPasskey = function(peripheral, passKey, callback) {
2110 if (passKey < 0 || passkey > 999999) {
2111 return callback && callback(new Error("Passkey must be between 0 and 999999 inclusive."));
2112 } else {
2113 this.messenger.enterPasskey(peripheral, passKey, callback);
2114 }
2115};
2116
2117BluetoothController.prototype.setEncryptionSize = function(size, callback) {
2118 // Enable/disable protection, set same key size, no smp io input/output
2119 if (size < 7 || size > 16) {
2120 return callback && callback(new Error("Invalid encryption key size. Must be between 7 and 16 bytes"));
2121 } else {
2122 this.messenger.setSecurityParameters(this._MITMEnabled ? 1 : 0, size, 3, function(err, response) {
2123 if (!err) {
2124 this._minKeySize = size;
2125 }
2126 if (callback) {
2127 callback(err);
2128 }
2129 });
2130 }
2131};
2132
2133BluetoothController.prototype.setOOBData = function(data, callback) {
2134 if (!Buffer.isBuffer(data) || (data.length != 0 && data.length != 16)) {
2135 if (callback) {
2136 callback(new Error("OOB Data must be a buffer of 0 or 16 octets long"));
2137 }
2138 return;
2139 } else {
2140 this.messenger.setOOBData(data, callback);
2141 }
2142};
2143
2144BluetoothController.prototype.enableMITMProtection = function(enable, callback) {
2145 // Enable/disable protection, set same key size, no smp io input/output
2146 this.messenger.setSecurityParameters(enable ? 1 : 0, this._minKeySize, 3, function(err, response) {
2147 if (!err) {
2148 this._MITMEnabled = enable;
2149 }
2150 if (callback) {
2151 callback(err);
2152 }
2153 });
2154};
2155
2156// Set the module port of the Bluetooth Low Energy module to initialize
2157function use(hardware, callback) {
2158 var controller = new BluetoothController(hardware, callback);
2159 return controller;
2160}
2161
2162module.exports.BluetoothController = BluetoothController;
2163module.exports.use = use;
2164module.exports.profile = profile;