bluetooth.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. 'use strict';
  2. // @ts-check
  3. // ==================================================================================
  4. // audio.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2022
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 17. bluetooth
  14. // ----------------------------------------------------------------------------------
  15. const exec = require('child_process').exec;
  16. const execSync = require('child_process').execSync;
  17. const path = require('path');
  18. const util = require('./util');
  19. const fs = require('fs');
  20. let _platform = process.platform;
  21. const _linux = (_platform === 'linux' || _platform === 'android');
  22. const _darwin = (_platform === 'darwin');
  23. const _windows = (_platform === 'win32');
  24. const _freebsd = (_platform === 'freebsd');
  25. const _openbsd = (_platform === 'openbsd');
  26. const _netbsd = (_platform === 'netbsd');
  27. const _sunos = (_platform === 'sunos');
  28. function parseBluetoothType(str) {
  29. let result = '';
  30. if (str.indexOf('keyboard') >= 0) { result = 'Keyboard'; }
  31. if (str.indexOf('mouse') >= 0) { result = 'Mouse'; }
  32. if (str.indexOf('speaker') >= 0) { result = 'Speaker'; }
  33. if (str.indexOf('headset') >= 0) { result = 'Headset'; }
  34. if (str.indexOf('phone') >= 0) { result = 'Phone'; }
  35. // to be continued ...
  36. return result;
  37. }
  38. function parseLinuxBluetoothInfo(lines, macAddr1, macAddr2) {
  39. const result = {};
  40. result.device = null;
  41. result.name = util.getValue(lines, 'name', '=');
  42. result.manufacturer = null;
  43. result.macDevice = macAddr1;
  44. result.macHost = macAddr2;
  45. result.batteryPercent = null;
  46. result.type = parseBluetoothType(result.name.toLowerCase());
  47. result.connected = false;
  48. return result;
  49. }
  50. function parseDarwinBluetoothDevices(bluetoothObject, macAddr2) {
  51. const result = {};
  52. const typeStr = ((bluetoothObject.device_minorClassOfDevice_string || bluetoothObject.device_majorClassOfDevice_string || '') + (bluetoothObject.device_name || '')).toLowerCase();
  53. result.device = bluetoothObject.device_services || '';
  54. result.name = bluetoothObject.device_name || '';
  55. result.manufacturer = bluetoothObject.device_manufacturer || '';
  56. result.macDevice = (bluetoothObject.device_addr || '').toLowerCase().replace(/-/g, ':');
  57. result.macHost = macAddr2;
  58. result.batteryPercent = bluetoothObject.device_batteryPercent || null;
  59. result.type = parseBluetoothType(typeStr);
  60. result.connected = bluetoothObject.device_isconnected === 'attrib_Yes' || false;
  61. return result;
  62. }
  63. function parseWindowsBluetooth(lines) {
  64. const result = {};
  65. result.device = null;
  66. result.name = util.getValue(lines, 'name', ':');
  67. result.manufacturer = util.getValue(lines, 'manufacturer', ':');
  68. result.macDevice = null;
  69. result.macHost = null;
  70. result.batteryPercent = null;
  71. result.type = parseBluetoothType(result.name.toLowerCase());
  72. result.connected = null;
  73. return result;
  74. }
  75. function bluetoothDevices(callback) {
  76. return new Promise((resolve) => {
  77. process.nextTick(() => {
  78. let result = [];
  79. if (_linux) {
  80. // get files in /var/lib/bluetooth/ recursive
  81. const btFiles = util.getFilesInPath('/var/lib/bluetooth/');
  82. for (let i = 0; i < btFiles.length; i++) {
  83. const filename = path.basename(btFiles[i]);
  84. const pathParts = btFiles[i].split('/');
  85. const macAddr1 = pathParts.length >= 6 ? pathParts[pathParts.length - 2] : null;
  86. const macAddr2 = pathParts.length >= 7 ? pathParts[pathParts.length - 3] : null;
  87. if (filename === 'info') {
  88. const infoFile = fs.readFileSync(btFiles[i], { encoding: 'utf8' }).split('\n');
  89. result.push(parseLinuxBluetoothInfo(infoFile, macAddr1, macAddr2));
  90. }
  91. }
  92. // determine "connected" with hcitool con
  93. try {
  94. const hdicon = execSync('hcitool con').toString().toLowerCase();
  95. for (let i = 0; i < result.length; i++) {
  96. if (result[i].macDevice && result[i].macDevice.length > 10 && hdicon.indexOf(result[i].macDevice.toLowerCase()) >= 0) {
  97. result[i].connected = true;
  98. }
  99. }
  100. } catch (e) {
  101. util.noop();
  102. }
  103. if (callback) {
  104. callback(result);
  105. }
  106. resolve(result);
  107. }
  108. if (_darwin) {
  109. let cmd = 'system_profiler SPBluetoothDataType -json';
  110. exec(cmd, function (error, stdout) {
  111. if (!error) {
  112. try {
  113. const outObj = JSON.parse(stdout.toString());
  114. if (outObj.SPBluetoothDataType && outObj.SPBluetoothDataType.length && outObj.SPBluetoothDataType[0] && outObj.SPBluetoothDataType[0]['device_title'] && outObj.SPBluetoothDataType[0]['device_title'].length) {
  115. // missing: host BT Adapter macAddr ()
  116. let macAddr2 = null;
  117. if (outObj.SPBluetoothDataType[0]['local_device_title'] && outObj.SPBluetoothDataType[0].local_device_title.general_address) {
  118. macAddr2 = outObj.SPBluetoothDataType[0].local_device_title.general_address.toLowerCase().replace(/-/g, ':');
  119. }
  120. for (let i = 0; i < outObj.SPBluetoothDataType[0]['device_title'].length; i++) {
  121. const obj = outObj.SPBluetoothDataType[0]['device_title'][i];
  122. const objKey = Object.keys(obj);
  123. if (objKey && objKey.length === 1) {
  124. const innerObject = obj[objKey[0]];
  125. innerObject.device_name = objKey[0];
  126. const bluetoothDevice = parseDarwinBluetoothDevices(innerObject, macAddr2);
  127. result.push(bluetoothDevice);
  128. }
  129. }
  130. }
  131. } catch (e) {
  132. util.noop();
  133. }
  134. }
  135. if (callback) {
  136. callback(result);
  137. }
  138. resolve(result);
  139. });
  140. }
  141. if (_windows) {
  142. util.powerShell('Get-WmiObject Win32_PNPEntity | select PNPClass, Name, Manufacturer | fl').then((stdout, error) => {
  143. if (!error) {
  144. const parts = stdout.toString().split(/\n\s*\n/);
  145. for (let i = 0; i < parts.length; i++) {
  146. if (util.getValue(parts[i].split('\n'), 'PNPClass', ':') === 'Bluetooth') {
  147. result.push(parseWindowsBluetooth(parts[i].split('\n')));
  148. }
  149. }
  150. }
  151. if (callback) {
  152. callback(result);
  153. }
  154. resolve(result);
  155. });
  156. }
  157. if (_freebsd || _netbsd || _openbsd || _sunos) {
  158. resolve(null);
  159. }
  160. });
  161. });
  162. }
  163. exports.bluetoothDevices = bluetoothDevices;