Source: mac/yosemite.js

  1. var events = require('events');
  2. var os = require('os');
  3. var util = require('util');
  4. var debug = require('debug')('yosemite-bindings');
  5. var XpcConnection = require('xpc-connection');
  6. var uuidToAddress = require('./uuid-to-address');
  7. /**
  8. * NobleBindings for mac
  9. */
  10. var NobleBindings = function() {
  11. this._xpcConnection = new XpcConnection('com.apple.blued');
  12. this._xpcConnection.on('error', function(message) {this.emit('xpcError', message);}.bind(this));
  13. this._xpcConnection.on('event', function(event) {this.emit('xpcEvent', event); }.bind(this));
  14. };
  15. util.inherits(NobleBindings, events.EventEmitter);
  16. NobleBindings.prototype.setupXpcConnection = function() {this._xpcConnection.setup();};
  17. NobleBindings.prototype.sendXpcMessage = function(message) {this._xpcConnection.sendMessage(message);};
  18. var nobleBindings = new NobleBindings();
  19. nobleBindings._peripherals = {};
  20. // General xpc message handling
  21. nobleBindings.on('xpcEvent', function(event) {
  22. debug('xpcEvent: ' + JSON.stringify(event, undefined, 2));
  23. var kCBMsgId = event.kCBMsgId;
  24. var kCBMsgArgs = event.kCBMsgArgs;
  25. this.emit('kCBMsgId' + kCBMsgId, kCBMsgArgs);
  26. });
  27. nobleBindings.on('xpcError', function(message) {
  28. console.error('xpcError: ' + message);
  29. });
  30. nobleBindings.sendCBMsg = function(id, args) {
  31. debug('sendCBMsg: ' + id + ', ' + JSON.stringify(args, undefined, 2));
  32. this.sendXpcMessage({kCBMsgId: id,kCBMsgArgs: args});
  33. };
  34. /**
  35. * Init xpc connection to blued
  36. *
  37. * @discussion tested
  38. */
  39. nobleBindings.init = function() {
  40. this.sendCBMsg(1, {
  41. kCBMsgArgName: 'node-' + (new Date()).getTime(),
  42. kCBMsgArgOptions: {
  43. kCBInitOptionShowPowerAlert: 0
  44. },
  45. kCBMsgArgType: 0
  46. });
  47. };
  48. nobleBindings.on('kCBMsgId6', function(args) {
  49. var state = ['unknown', 'resetting', 'unsupported', 'unauthorized', 'poweredOff', 'poweredOn'][args.kCBMsgArgState];
  50. debug('state change ' + state);
  51. this.emit('stateChange', state);
  52. });
  53. /**
  54. * Start scanning
  55. * @param {Array} serviceUuids Scan for these UUIDs, if undefined then scan for all
  56. * @param {Bool} allowDuplicates Scan can return duplicates
  57. *
  58. * @discussion tested
  59. */
  60. nobleBindings.startScanning = function(serviceUuids, allowDuplicates) {
  61. var args = {
  62. kCBMsgArgOptions: {},
  63. kCBMsgArgUUIDs: []
  64. };
  65. if (serviceUuids) {
  66. for(var i = 0; i < serviceUuids.length; i++) {
  67. args.kCBMsgArgUUIDs[i] = new Buffer(serviceUuids[i], 'hex');
  68. }
  69. }
  70. if (allowDuplicates) {
  71. args.kCBMsgArgOptions.kCBScanOptionAllowDuplicates = 1;
  72. }
  73. this.sendCBMsg(29, args);
  74. this.emit('scanStart');
  75. };
  76. /**
  77. * Response message to start scanning
  78. *
  79. * @example
  80. * // For `TI Sensortag` the message lookes like this:
  81. * handleMsg: 37, {
  82. * kCBMsgArgAdvertisementData = {
  83. * kCBAdvDataIsConnectable = 1;
  84. * kCBAdvDataLocalName = SensorTag;
  85. * kCBAdvDataTxPowerLevel = 0;
  86. * };
  87. * kCBMsgArgDeviceUUID = "<__NSConcreteUUID 0x6180000208e0> 53486C7A-DED2-4AA6-8913-387CD22F25D8";
  88. * kCBMsgArgName = SensorTag;
  89. * kCBMsgArgRssi = "-68";
  90. * }
  91. *
  92. * @discussion tested
  93. */
  94. nobleBindings.on('kCBMsgId37', function(args) {
  95. if (Object.keys(args.kCBMsgArgAdvertisementData).length === 0) {
  96. return;
  97. }
  98. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  99. var advertisement = {
  100. localName: args.kCBMsgArgAdvertisementData.kCBAdvDataLocalName || args.kCBMsgArgName,
  101. txPowerLevel: args.kCBMsgArgAdvertisementData.kCBAdvDataTxPowerLevel,
  102. manufacturerData: args.kCBMsgArgAdvertisementData.kCBAdvDataManufacturerData,
  103. serviceData: [],
  104. serviceUuids: []
  105. };
  106. var rssi = args.kCBMsgArgRssi;
  107. var i;
  108. if (args.kCBMsgArgAdvertisementData.kCBAdvDataServiceUUIDs) {
  109. for(i = 0; i < args.kCBMsgArgAdvertisementData.kCBAdvDataServiceUUIDs.length; i++) {
  110. advertisement.serviceUuids.push(args.kCBMsgArgAdvertisementData.kCBAdvDataServiceUUIDs[i].toString('hex'));
  111. }
  112. }
  113. var serviceData = args.kCBMsgArgAdvertisementData.kCBAdvDataServiceData;
  114. if (serviceData) {
  115. for (i = 0; i < serviceData.length; i += 2) {
  116. var serviceDataUuid = serviceData[i].toString('hex');
  117. var data = serviceData[i + 1];
  118. advertisement.serviceData.push({
  119. uuid: serviceDataUuid,
  120. data: data
  121. });
  122. }
  123. }
  124. debug('peripheral ' + deviceUuid + ' discovered');
  125. var uuid = new Buffer(deviceUuid, 'hex');
  126. uuid.isUuid = true;
  127. if (!this._peripherals[deviceUuid]) {
  128. this._peripherals[deviceUuid] = {};
  129. }
  130. this._peripherals[deviceUuid].uuid = uuid;
  131. this._peripherals[deviceUuid].advertisement = advertisement;
  132. this._peripherals[deviceUuid].rssi = rssi;
  133. (function(deviceUuid, advertisement, rssi) {
  134. uuidToAddress(deviceUuid, function(error, address, addressType) {
  135. address = address || 'unknown';
  136. addressType = addressType || 'unknown';
  137. this._peripherals[deviceUuid].address = address;
  138. this._peripherals[deviceUuid].addressType = addressType;
  139. this.emit('discover', deviceUuid, address, addressType, advertisement, rssi);
  140. }.bind(this));
  141. }.bind(this))(deviceUuid, advertisement, rssi);
  142. });
  143. /**
  144. * Stop scanning
  145. *
  146. * @discussion tested
  147. */
  148. nobleBindings.stopScanning = function() {
  149. this.sendCBMsg(30, null);
  150. this.emit('scanStop');
  151. };
  152. /**
  153. * Connect to peripheral
  154. * @param {String} deviceUuid Peripheral uuid to connect to
  155. *
  156. * @discussion tested
  157. */
  158. nobleBindings.connect = function(deviceUuid) {
  159. this.sendCBMsg(31, {
  160. kCBMsgArgOptions: {
  161. kCBConnectOptionNotifyOnDisconnection: 1
  162. },
  163. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid
  164. });
  165. };
  166. nobleBindings.on('kCBMsgId38', function(args) {
  167. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  168. debug('peripheral ' + deviceUuid + ' connected');
  169. this.emit('connect', deviceUuid);
  170. });
  171. /**
  172. * Disconnect
  173. *
  174. * @param {String} deviceUuid Peripheral uuid to disconnect
  175. *
  176. * @discussion tested
  177. */
  178. nobleBindings.disconnect = function(deviceUuid) {
  179. this.sendCBMsg(32, {
  180. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid
  181. });
  182. };
  183. /**
  184. * Response to disconnect
  185. *
  186. * @discussion tested
  187. */
  188. nobleBindings.on('kCBMsgId40', function(args) {
  189. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  190. debug('peripheral ' + deviceUuid + ' disconnected');
  191. this.emit('disconnect', deviceUuid);
  192. });
  193. /**
  194. * Update RSSI
  195. *
  196. * @discussion tested
  197. */
  198. nobleBindings.updateRssi = function(deviceUuid) {
  199. this.sendCBMsg(44, {
  200. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid
  201. });
  202. };
  203. /**
  204. * Response to RSSI update
  205. *
  206. * @discussion tested
  207. */
  208. nobleBindings.on('kCBMsgId55', function(args) {
  209. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  210. var rssi = args.kCBMsgArgData;
  211. this._peripherals[deviceUuid].rssi = rssi;
  212. debug('peripheral ' + deviceUuid + ' RSSI update ' + rssi);
  213. this.emit('rssiUpdate', deviceUuid, rssi);
  214. });
  215. /**
  216. * Discover services
  217. *
  218. * @param {String} deviceUuid Device UUID
  219. * @param {Array} uuids Services to discover, if undefined then all
  220. *
  221. * @discussion tested
  222. */
  223. nobleBindings.discoverServices = function(deviceUuid, uuids) {
  224. var args = {
  225. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  226. kCBMsgArgUUIDs: []
  227. };
  228. if (uuids) {
  229. for(var i = 0; i < uuids.length; i++) {
  230. args.kCBMsgArgUUIDs[i] = new Buffer(uuids[i], 'hex');
  231. }
  232. }
  233. this.sendCBMsg(45, args);
  234. };
  235. /**
  236. * Response to discover service
  237. *
  238. * @discussion tested
  239. */
  240. nobleBindings.on('kCBMsgId56', function(args) {
  241. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  242. var serviceUuids = [];
  243. this._peripherals[deviceUuid].services = {};
  244. if (args.kCBMsgArgServices) {
  245. for(var i = 0; i < args.kCBMsgArgServices.length; i++) {
  246. var service = {
  247. uuid: args.kCBMsgArgServices[i].kCBMsgArgUUID.toString('hex'),
  248. startHandle: args.kCBMsgArgServices[i].kCBMsgArgServiceStartHandle,
  249. endHandle: args.kCBMsgArgServices[i].kCBMsgArgServiceEndHandle
  250. };
  251. this._peripherals[deviceUuid].services[service.uuid] = this._peripherals[deviceUuid].services[service.startHandle] = service;
  252. serviceUuids.push(service.uuid);
  253. }
  254. }
  255. // TODO: result 24 => device not connected
  256. this.emit('servicesDiscover', deviceUuid, serviceUuids);
  257. });
  258. /**
  259. * [discoverIncludedServices description]
  260. *
  261. * @param {String} deviceUuid
  262. * @param {String} serviceUuid
  263. * @param {String} serviceUuids
  264. *
  265. * @dicussion tested
  266. */
  267. nobleBindings.discoverIncludedServices = function(deviceUuid, serviceUuid, serviceUuids) {
  268. var args = {
  269. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  270. kCBMsgArgServiceStartHandle: this._peripherals[deviceUuid].services[serviceUuid].startHandle,
  271. kCBMsgArgServiceEndHandle: this._peripherals[deviceUuid].services[serviceUuid].endHandle,
  272. kCBMsgArgUUIDs: []
  273. };
  274. if (serviceUuids) {
  275. for(var i = 0; i < serviceUuids.length; i++) {
  276. args.kCBMsgArgUUIDs[i] = new Buffer(serviceUuids[i], 'hex');
  277. }
  278. }
  279. this.sendCBMsg(61, args);
  280. };
  281. /**
  282. * Response to dicover included services
  283. *
  284. * @dicussion tested
  285. */
  286. nobleBindings.on('kCBMsgId63', function(args) {
  287. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  288. var serviceStartHandle = args.kCBMsgArgServiceStartHandle;
  289. var serviceUuid = this._peripherals[deviceUuid].services[serviceStartHandle].uuid;
  290. var result = args.kCBMsgArgResult;
  291. var includedServiceUuids = [];
  292. this._peripherals[deviceUuid].services[serviceStartHandle].includedServices = {};
  293. for(var i = 0; i < args.kCBMsgArgServices.length; i++) {
  294. var includedService = {
  295. uuid: args.kCBMsgArgServices[i].kCBMsgArgUUID.toString('hex'),
  296. startHandle: args.kCBMsgArgServices[i].kCBMsgArgServiceStartHandle,
  297. endHandle: args.kCBMsgArgServices[i].kCBMsgArgServiceEndHandle
  298. };
  299. this._peripherals[deviceUuid].services[serviceStartHandle].includedServices[includedServices.uuid] =
  300. this._peripherals[deviceUuid].services[serviceStartHandle].includedServices[includedServices.startHandle] = includedService;
  301. includedServiceUuids.push(includedService.uuid);
  302. }
  303. this.emit('includedServicesDiscover', deviceUuid, serviceUuid, includedServiceUuids);
  304. });
  305. /**
  306. * Discover characteristic
  307. *
  308. * @param {String} deviceUuid Peripheral UUID
  309. * @param {String} serviceUuid Service UUID
  310. * @param {Array} characteristicUuids Characteristics to discover, all if empty
  311. *
  312. * @discussion tested
  313. */
  314. nobleBindings.discoverCharacteristics = function(deviceUuid, serviceUuid, characteristicUuids) {
  315. var args = {
  316. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  317. kCBMsgArgServiceStartHandle: this._peripherals[deviceUuid].services[serviceUuid].startHandle,
  318. kCBMsgArgServiceEndHandle: this._peripherals[deviceUuid].services[serviceUuid].endHandle,
  319. kCBMsgArgUUIDs: []
  320. };
  321. if (characteristicUuids) {
  322. for(var i = 0; i < characteristicUuids.length; i++) {
  323. args.kCBMsgArgUUIDs[i] = new Buffer(characteristicUuids[i], 'hex');
  324. }
  325. }
  326. this.sendCBMsg(62, args);
  327. };
  328. /**
  329. * Response to characteristic discovery
  330. *
  331. * @discussion tested
  332. */
  333. nobleBindings.on('kCBMsgId64', function(args) {
  334. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  335. var serviceStartHandle = args.kCBMsgArgServiceStartHandle;
  336. var serviceUuid = this._peripherals[deviceUuid].services[serviceStartHandle].uuid;
  337. var result = args.kCBMsgArgResult;
  338. var characteristics = [];
  339. this._peripherals[deviceUuid].services[serviceStartHandle].characteristics = {};
  340. for(var i = 0; i < args.kCBMsgArgCharacteristics.length; i++) {
  341. var properties = args.kCBMsgArgCharacteristics[i].kCBMsgArgCharacteristicProperties;
  342. var characteristic = {
  343. uuid: args.kCBMsgArgCharacteristics[i].kCBMsgArgUUID.toString('hex'),
  344. handle: args.kCBMsgArgCharacteristics[i].kCBMsgArgCharacteristicHandle,
  345. valueHandle: args.kCBMsgArgCharacteristics[i].kCBMsgArgCharacteristicValueHandle,
  346. properties: []
  347. };
  348. if (properties & 0x01) {
  349. characteristic.properties.push('broadcast');
  350. }
  351. if (properties & 0x02) {
  352. characteristic.properties.push('read');
  353. }
  354. if (properties & 0x04) {
  355. characteristic.properties.push('writeWithoutResponse');
  356. }
  357. if (properties & 0x08) {
  358. characteristic.properties.push('write');
  359. }
  360. if (properties & 0x10) {
  361. characteristic.properties.push('notify');
  362. }
  363. if (properties & 0x20) {
  364. characteristic.properties.push('indicate');
  365. }
  366. if (properties & 0x40) {
  367. characteristic.properties.push('authenticatedSignedWrites');
  368. }
  369. if (properties & 0x80) {
  370. characteristic.properties.push('extendedProperties');
  371. }
  372. this._peripherals[deviceUuid].services[serviceStartHandle].characteristics[characteristic.uuid] =
  373. this._peripherals[deviceUuid].services[serviceStartHandle].characteristics[characteristic.handle] =
  374. this._peripherals[deviceUuid].services[serviceStartHandle].characteristics[characteristic.valueHandle] = characteristic;
  375. characteristics.push({
  376. uuid: characteristic.uuid,
  377. properties: characteristic.properties
  378. });
  379. }
  380. this.emit('characteristicsDiscover', deviceUuid, serviceUuid, characteristics);
  381. });
  382. /**
  383. * Read value
  384. *
  385. * @param {[type]} deviceUuid [description]
  386. * @param {[type]} serviceUuid [description]
  387. * @param {[type]} characteristicUuid [description]
  388. *
  389. * @discussion tested
  390. */
  391. nobleBindings.read = function(deviceUuid, serviceUuid, characteristicUuid) {
  392. this.sendCBMsg(65 , {
  393. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  394. kCBMsgArgCharacteristicHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].handle,
  395. kCBMsgArgCharacteristicValueHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].valueHandle
  396. });
  397. };
  398. /**
  399. * Response to read value
  400. *
  401. * @discussion tested
  402. */
  403. nobleBindings.on('kCBMsgId71', function(args) {
  404. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  405. var characteristicHandle = args.kCBMsgArgCharacteristicHandle;
  406. var isNotification = args.kCBMsgArgIsNotification ? true : false;
  407. var data = args.kCBMsgArgData;
  408. var peripheral = this._peripherals[deviceUuid];
  409. if (peripheral) {
  410. for(var i in peripheral.services) {
  411. if (peripheral.services[i].characteristics &&
  412. peripheral.services[i].characteristics[characteristicHandle]) {
  413. this.emit('read', deviceUuid, peripheral.services[i].uuid,
  414. peripheral.services[i].characteristics[characteristicHandle].uuid, data, isNotification);
  415. break;
  416. }
  417. }
  418. } else {
  419. console.warn('noble (mac yosemite): received read event from unknown peripheral: ' + deviceUuid + ' !');
  420. }
  421. });
  422. /**
  423. * Write value
  424. * @param {String} deviceUuid
  425. * @param {String} serviceUuid
  426. * @param {String} characteristicUuid
  427. * @param {[Type]} data
  428. * @param {Bool} withoutResponse
  429. *
  430. * @discussion tested
  431. */
  432. nobleBindings.write = function(deviceUuid, serviceUuid, characteristicUuid, data, withoutResponse) {
  433. this.sendCBMsg(66, {
  434. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  435. kCBMsgArgCharacteristicHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].handle,
  436. kCBMsgArgCharacteristicValueHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].valueHandle,
  437. kCBMsgArgData: data,
  438. kCBMsgArgType: (withoutResponse ? 1 : 0)
  439. });
  440. if (withoutResponse) {
  441. this.emit('write', deviceUuid, serviceUuid, characteristicUuid);
  442. }
  443. };
  444. /**
  445. * Response to write
  446. *
  447. * @discussion tested
  448. */
  449. nobleBindings.on('kCBMsgId72', function(args) {
  450. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  451. var characteristicHandle = args.kCBMsgArgCharacteristicHandle;
  452. var result = args.kCBMsgArgResult;
  453. for(var i in this._peripherals[deviceUuid].services) {
  454. if (this._peripherals[deviceUuid].services[i].characteristics &&
  455. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle]) {
  456. this.emit('write', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  457. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].uuid);
  458. break;
  459. }
  460. }
  461. });
  462. /**
  463. * Broadcast
  464. *
  465. * @param {[type]} deviceUuid [description]
  466. * @param {[type]} serviceUuid [description]
  467. * @param {[type]} characteristicUuid [description]
  468. * @param {[type]} broadcast [description]
  469. * @return {[type]} [description]
  470. *
  471. * @discussion The ids were incemented but there seems to be no CoreBluetooth function to call/verify this.
  472. */
  473. nobleBindings.broadcast = function(deviceUuid, serviceUuid, characteristicUuid, broadcast) {
  474. this.sendCBMsg(67, {
  475. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  476. kCBMsgArgCharacteristicHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].handle,
  477. kCBMsgArgCharacteristicValueHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].valueHandle,
  478. kCBMsgArgState: (broadcast ? 1 : 0)
  479. });
  480. };
  481. /**
  482. * Response to broadcast
  483. *
  484. * @discussion The ids were incemented but there seems to be no CoreBluetooth function to call/verify this.
  485. */
  486. nobleBindings.on('kCBMsgId73', function(args) {
  487. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  488. var characteristicHandle = args.kCBMsgArgCharacteristicHandle;
  489. var result = args.kCBMsgArgResult;
  490. var state = args.kCBMsgArgState ? true : false;
  491. for(var i in this._peripherals[deviceUuid].services) {
  492. if (this._peripherals[deviceUuid].services[i].characteristics &&
  493. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle]) {
  494. this.emit('broadcast', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  495. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].uuid, state);
  496. break;
  497. }
  498. }
  499. });
  500. /**
  501. * Register notification hanlder
  502. *
  503. * @param {String} deviceUuid Peripheral UUID
  504. * @param {String} serviceUuid Service UUID
  505. * @param {String} characteristicUuid Charactereistic UUID
  506. * @param {Bool} notify If want to get notification
  507. *
  508. * @discussion tested
  509. */
  510. nobleBindings.notify = function(deviceUuid, serviceUuid, characteristicUuid, notify) {
  511. this.sendCBMsg(68, {
  512. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  513. kCBMsgArgCharacteristicHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].handle,
  514. kCBMsgArgCharacteristicValueHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].valueHandle,
  515. kCBMsgArgState: (notify ? 1 : 0)
  516. });
  517. };
  518. /**
  519. * Response notification
  520. *
  521. * @discussion tested
  522. */
  523. nobleBindings.on('kCBMsgId74', function(args) {
  524. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  525. var characteristicHandle = args.kCBMsgArgCharacteristicHandle;
  526. var result = args.kCBMsgArgResult;
  527. var state = args.kCBMsgArgState ? true : false;
  528. for(var i in this._peripherals[deviceUuid].services) {
  529. if (this._peripherals[deviceUuid].services[i].characteristics &&
  530. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle]) {
  531. this.emit('notify', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  532. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].uuid, state);
  533. break;
  534. }
  535. }
  536. });
  537. /**
  538. * Discover service descriptors
  539. *
  540. * @param {String} deviceUuid
  541. * @param {String} serviceUuid
  542. * @param {String} characteristicUuid
  543. *
  544. * @discussion tested
  545. */
  546. nobleBindings.discoverDescriptors = function(deviceUuid, serviceUuid, characteristicUuid) {
  547. this.sendCBMsg(70, {
  548. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  549. kCBMsgArgCharacteristicHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].handle,
  550. kCBMsgArgCharacteristicValueHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].valueHandle
  551. });
  552. };
  553. /**
  554. * Response to descriptor discovery
  555. *
  556. * @discussion tested
  557. */
  558. nobleBindings.on('kCBMsgId76', function(args) {
  559. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  560. var characteristicHandle = args.kCBMsgArgCharacteristicHandle;
  561. var result = args.kCBMsgArgResult;
  562. var descriptors = []; //args.kCBMsgArgDescriptors;
  563. for(var i in this._peripherals[deviceUuid].services) {
  564. if (this._peripherals[deviceUuid].services[i].characteristics &&
  565. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle]) {
  566. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].descriptors = {};
  567. for(var j = 0; j < args.kCBMsgArgDescriptors.length; j++) {
  568. var descriptor = {
  569. uuid: args.kCBMsgArgDescriptors[j].kCBMsgArgUUID.toString('hex'),
  570. handle: args.kCBMsgArgDescriptors[j].kCBMsgArgDescriptorHandle
  571. };
  572. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].descriptors[descriptor.uuid] =
  573. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].descriptors[descriptor.handle] = descriptor;
  574. descriptors.push(descriptor.uuid);
  575. }
  576. this.emit('descriptorsDiscover', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  577. this._peripherals[deviceUuid].services[i].characteristics[characteristicHandle].uuid, descriptors);
  578. break;
  579. }
  580. }
  581. });
  582. /**
  583. * Read value
  584. *
  585. * @param {[type]} deviceUuid [description]
  586. * @param {[type]} serviceUuid [description]
  587. * @param {[type]} characteristicUuid [description]
  588. * @param {[type]} descriptorUuid [description]
  589. *
  590. * @discussion tested
  591. */
  592. nobleBindings.readValue = function(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid) {
  593. this.sendCBMsg(77, {
  594. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  595. kCBMsgArgDescriptorHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].descriptors[descriptorUuid].handle
  596. });
  597. };
  598. /**
  599. * Response to read value
  600. *
  601. * @discussion tested
  602. */
  603. nobleBindings.on('kCBMsgId79', function(args) {
  604. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  605. var descriptorHandle = args.kCBMsgArgDescriptorHandle;
  606. var result = args.kCBMsgArgResult;
  607. var data = args.kCBMsgArgData;
  608. this.emit('handleRead', deviceUuid, descriptorHandle, data);
  609. for(var i in this._peripherals[deviceUuid].services) {
  610. for(var j in this._peripherals[deviceUuid].services[i].characteristics) {
  611. if (this._peripherals[deviceUuid].services[i].characteristics[j].descriptors &&
  612. this._peripherals[deviceUuid].services[i].characteristics[j].descriptors[descriptorHandle]) {
  613. this.emit('valueRead', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  614. this._peripherals[deviceUuid].services[i].characteristics[j].uuid,
  615. this._peripherals[deviceUuid].services[i].characteristics[j].descriptors[descriptorHandle].uuid, data);
  616. return; // break;
  617. }
  618. }
  619. }
  620. });
  621. /**
  622. * Write value
  623. *
  624. * @param {[type]} deviceUuid [description]
  625. * @param {[type]} serviceUuid [description]
  626. * @param {[type]} characteristicUuid [description]
  627. * @param {[type]} descriptorUuid [description]
  628. * @param {[type]} data [description]
  629. *
  630. * @discussion tested
  631. */
  632. nobleBindings.writeValue = function(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid, data) {
  633. this.sendCBMsg(78, {
  634. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  635. kCBMsgArgDescriptorHandle: this._peripherals[deviceUuid].services[serviceUuid].characteristics[characteristicUuid].descriptors[descriptorUuid].handle,
  636. kCBMsgArgData: data
  637. });
  638. };
  639. /**
  640. * Response to write value
  641. *
  642. * @discussion tested
  643. */
  644. nobleBindings.on('kCBMsgId80', function(args) {
  645. var deviceUuid = args.kCBMsgArgDeviceUUID.toString('hex');
  646. var descriptorHandle = args.kCBMsgArgDescriptorHandle;
  647. var result = args.kCBMsgArgResult;
  648. this.emit('handleWrite', deviceUuid, descriptorHandle);
  649. for(var i in this._peripherals[deviceUuid].services) {
  650. for(var j in this._peripherals[deviceUuid].services[i].characteristics) {
  651. if (this._peripherals[deviceUuid].services[i].characteristics[j].descriptors &&
  652. this._peripherals[deviceUuid].services[i].characteristics[j].descriptors[descriptorHandle]) {
  653. this.emit('valueWrite', deviceUuid, this._peripherals[deviceUuid].services[i].uuid,
  654. this._peripherals[deviceUuid].services[i].characteristics[j].uuid,
  655. this._peripherals[deviceUuid].services[i].characteristics[j].descriptors[descriptorHandle].uuid);
  656. return; // break;
  657. }
  658. }
  659. }
  660. });
  661. /**
  662. * Reade value directly from handle
  663. *
  664. * @param {[type]} deviceUuid [description]
  665. * @param {[type]} handle [description]
  666. *
  667. * @discussion tested
  668. */
  669. nobleBindings.readHandle = function(deviceUuid, handle) {
  670. this.sendCBMsg(77, {
  671. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  672. kCBMsgArgDescriptorHandle: handle
  673. });
  674. };
  675. /**
  676. * Write value directly to handle
  677. *
  678. * @param {[type]} deviceUuid [description]
  679. * @param {[type]} handle [description]
  680. * @param {[type]} data [description]
  681. * @param {[type]} withoutResponse [description]
  682. *
  683. * @discussion tested
  684. */
  685. nobleBindings.writeHandle = function(deviceUuid, handle, data, withoutResponse) {
  686. // TODO: use without response
  687. this.sendCBMsg(78, {
  688. kCBMsgArgDeviceUUID: this._peripherals[deviceUuid].uuid,
  689. kCBMsgArgDescriptorHandle: handle,
  690. kCBMsgArgData: data
  691. });
  692. };
  693. // Exports
  694. nobleBindings.setupXpcConnection();
  695. nobleBindings.init();
  696. module.exports = nobleBindings;