UNPKG

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