From 65d67fe0a6dd5e0b0d3b2aed37a365239dc34a12 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Fri, 22 Jan 2021 10:50:19 +0100 Subject: [PATCH] bluetooth linux implementation --- docs/bluetooth.html | 24 +++++-- lib/bluetooth.js | 150 +++++++++++++++++++------------------------- lib/index.d.ts | 3 +- lib/osinfo.js | 2 +- lib/util.js | 34 ++++++++++ package.json | 1 + test/si.js | 2 +- 7 files changed, 120 insertions(+), 96 deletions(-) diff --git a/docs/bluetooth.html b/docs/bluetooth.html index 7af5d7d..b310b11 100644 --- a/docs/bluetooth.html +++ b/docs/bluetooth.html @@ -98,13 +98,23 @@ - [0].address + [0].macDevice X X X - address + MAC address device + + + + [0].macHost + X + + X + X + + MAC address host @@ -158,18 +168,20 @@ si.bluetoothDevices().then(data => console.log(data)); Key -// address-- > "device_addr" -// batteryPercent-- > "device_batteryPercent" -// manufacturer-- > "device_manufacturer" -// type(keyboard, ...)-- > ("device_majorClassOfDevice_string"), "device_minorClassOfDevice_string" -// connected - - - -function parseLinuxAudioPciMM(lines, audioPCI) { - const result = {}; - const slotId = util.getValue(lines, 'Slot'); - - const pciMatch = audioPCI.filter(function (item) { return item.slotId === slotId }) - - result.id = slotId; - result.name = util.getValue(lines, 'SDevice'); - // result.type = util.getValue(lines, 'Class'); - result.manufacturer = util.getValue(lines, 'SVendor'); - result.revision = util.getValue(lines, 'Rev'); - result.driver = pciMatch && pciMatch.length === 1 && pciMatch[0].driver ? pciMatch[0].driver : ''; - result.default = null; - result.channel = 'PCIe'; - result.in = null; - result.out = null; - result.status = 'online'; - - return result; -} - -function parseDarwinBluetoothTyoe(str) { +function parseBluetoothTyoe(str) { let result = ''; if (str.indexOf('keyboard') >= 0) { result = 'Keyboard'; } if (str.indexOf('mouse') >= 0) { result = 'Mouse'; } if (str.indexOf('speaker') >= 0) { result = 'Speaker'; } if (str.indexOf('headset') >= 0) { result = 'Headset'; } + if (str.indexOf('phone') >= 0) { result = 'Phone'; } // to be continued ... return result; } -function parseDarwinBluetoothDevices(bluetoothObject) { +function parseLinuxBluetoothInfo(lines, macAddr1, macAddr2) { + const result = {}; + + result.device = null; + result.name = util.getValue(lines, 'name', '='); + result.manufacturer = null; + result.macDevice = macAddr1; + result.macHost = macAddr2; + result.batteryPercent = null; + result.type = parseBluetoothTyoe(result.name.toLowerCase()); + result.connected = false; + + return result; +} + +function parseDarwinBluetoothDevices(bluetoothObject, macAddr2) { const result = {}; const typeStr = ((bluetoothObject.device_minorClassOfDevice_string || bluetoothObject.device_majorClassOfDevice_string || '') + (bluetoothObject.device_name || '')).toLowerCase(); result.device = bluetoothObject.device_services || ''; result.name = bluetoothObject.device_name || ''; result.manufacturer = bluetoothObject.device_manufacturer || ''; - result.address = bluetoothObject.device_addr || ''; + result.macDevice = (bluetoothObject.device_addr || '').toLowerCase().replace(/-/g, ':');; + result.macHost = macAddr2; result.batteryPercent = bluetoothObject.device_batteryPercent || null; - result.tyoe = parseDarwinBluetoothTyoe(typeStr); + result.type = parseBluetoothTyoe(typeStr); result.connected = bluetoothObject.device_isconnected === 'attrib_Yes' || false; return result; @@ -108,19 +75,15 @@ function parseDarwinBluetoothDevices(bluetoothObject) { function parseWindowsBluetooth(lines) { const result = {}; - const status = util.getValue(lines, 'StatusInfo', '='); - // const description = util.getValue(lines, 'Description', '='); result.device = null; result.name = util.getValue(lines, 'name', '='); result.manufacturer = util.getValue(lines, 'manufacturer', '='); - result.address = null; - result.batteryPercent = null - result.default = null - result.in = null - result.out = null - result.interfaceType = null - result.status = status + result.macDevice = null; + result.macHost = null; + result.batteryPercent = null; + result.type = parseBluetoothTyoe(result.name.toLowerCase()); + result.connected = null; return result; } @@ -130,26 +93,35 @@ function bluetoothDevices(callback) { return new Promise((resolve) => { process.nextTick(() => { let result = []; - if (_linux || _freebsd || _openbsd || _netbsd) { - let cmd = 'lspci -vmm 2>/dev/null' - exec(cmd, function (error, stdout) { - // PCI - if (!error) { - const audioPCI = getLinuxAudioPci(); - const parts = stdout.toString().split('\n\n'); - for (let i = 0; i < parts.length; i++) { - const lines = parts[i].split('\n'); - if (util.getValue(lines, 'class', ':', true).toLowerCase().indexOf('audio') >= 0) { - const audio = parseLinuxAudioPciMM(lines, audioPCI); - result.push(audio); - } + if (_linux) { + // get files in /var/lib/bluetooth/ recursive + const btFiles = util.getFilesInPath('/var/lib/bluetooth/'); + for (let i = 0; i < btFiles.length; i++) { + const filename = path.basename(btFiles[i]); + const pathParts = btFiles[i].split('/'); + const macAddr1 = pathParts.length >= 6 ? pathParts[pathParts.length - 2] : null; + const macAddr2 = pathParts.length >= 7 ? pathParts[pathParts.length - 3] : null; + if (filename === 'info') { + const infoFile = fs.readFileSync(btFiles[i], { encoding: 'utf8' }).split('\n'); + result.push(parseLinuxBluetoothInfo(infoFile, macAddr1, macAddr2)); + } + } + // determine "connected" with hcitool con + try { + const hdicon = execSync('hditool con').toString().toLowerCase(); + for (let i = 0; i < result.length; i++) { + if (result[i].macAddr1 && result[i].macAddr1.length > 10 && hdicon.indexOf(result[i].macAddr1) >= 0) { + result[i].connected = true; } } - if (callback) { - callback(result); - } - resolve(result); - }); + } catch (e) { + util.noop(); + } + + if (callback) { + callback(result); + } + resolve(result); } if (_darwin) { let cmd = 'system_profiler SPBluetoothDataType -json' @@ -158,17 +130,21 @@ function bluetoothDevices(callback) { try { const outObj = JSON.parse(stdout.toString()); if (outObj.SPBluetoothDataType && outObj.SPBluetoothDataType.length && outObj.SPBluetoothDataType[0] && outObj.SPBluetoothDataType[0]['device_title'] && outObj.SPBluetoothDataType[0]['device_title'].length) { + // missing: host BT Adapter macAddr () + let macAddr2 = null; + if (outObj.SPBluetoothDataType[0]['local_device_title'] && outObj.SPBluetoothDataType[0].local_device_title.general_address) { + macAddr2 = outObj.SPBluetoothDataType[0].local_device_title.general_address.toLowerCase().replace(/-/g, ':'); + } + for (let i = 0; i < outObj.SPBluetoothDataType[0]['device_title'].length; i++) { const obj = outObj.SPBluetoothDataType[0]['device_title'][i]; const objKey = Object.keys(obj); if (objKey && objKey.length === 1) { const innerObject = obj[objKey[0]]; innerObject.device_name = objKey[0]; - const bluetoothDevice = parseDarwinBluetoothDevices(innerObject); + const bluetoothDevice = parseDarwinBluetoothDevices(innerObject, macAddr2); result.push(bluetoothDevice); - } - } } } catch (e) { @@ -197,7 +173,7 @@ function bluetoothDevices(callback) { resolve(result); }); } - if (_sunos) { + if (_freebsd || _netbsd || _openbsd || _sunos) { resolve(null); } }); diff --git a/lib/index.d.ts b/lib/index.d.ts index 569b861..6843e48 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -746,7 +746,8 @@ export namespace Systeminformation { interface BluetoothDeviceData { device: string; name: string; - address: string; + macDevice: string; + macHost: string; batteryPercent: number; manufacturer: string; type: string; diff --git a/lib/osinfo.js b/lib/osinfo.js index fc97676..4dc5d6f 100644 --- a/lib/osinfo.js +++ b/lib/osinfo.js @@ -978,7 +978,7 @@ function uuid(callback) { if (jsonObj.SPHardwareDataType && jsonObj.SPHardwareDataType.length > 0) { const spHardware = jsonObj.SPHardwareDataType[0]; // result.os = parts.length > 1 ? parts[1].trim().toLowerCase() : ''; - result.os = spHardware.platform_UUID; + result.os = spHardware.platform_UUID.toLowerCase(); result.hardware = spHardware.serial_number; } } catch (e) { diff --git a/lib/util.js b/lib/util.js index 93dd839..476de3b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -15,6 +15,7 @@ const os = require('os'); const fs = require('fs'); +const path = require('path'); const spawn = require('child_process').spawn; const exec = require('child_process').exec; const execSync = require('child_process').execSync; @@ -593,6 +594,38 @@ function hex2bin(hex) { return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8); } +function getFilesInPath(source) { + const lstatSync = fs.lstatSync; + const readdirSync = fs.readdirSync; + const join = path.join; + + function isDirectory(source) { + return lstatSync(source).isDirectory(); + } + function isFile(source) { return lstatSync(source).isFile(); } + + function getDirectories(source) { + return readdirSync(source).map(function (name) { return join(source, name); }).filter(isDirectory) + } + function getFiles(source) { + return readdirSync(source).map(function (name) { return join(source, name); }).filter(isFile) + } + + function getFilesRecursively(source) { + let dirs = getDirectories(source); + let files = dirs + .map(function (dir) { return getFilesRecursively(dir); }) + .reduce(function (a, b) { return a.concat(b); }, []); + return files.concat(getFiles(path)); + } + + if (fs.existsSync(source)) { + return getFilesRecursively(source); + } else { + return []; + } +} + function decodePiCpuinfo(lines) { // https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md @@ -831,3 +864,4 @@ exports.stringToString = stringToString; exports.stringSubstr = stringSubstr; exports.stringTrim = stringTrim; exports.WINDIR = WINDIR; +exports.getFilesInPath = getFilesInPath; diff --git a/package.json b/package.json index 6fbeb45..ad158de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "systeminformation", "version": "4.34.9", + "prereleaseversion": "5.0-alpha.0116", "description": "Simple system and OS information library", "license": "MIT", "author": "Sebastian Hildebrandt (https://plus-innovations.com)", diff --git a/test/si.js b/test/si.js index 354077d..c586421 100644 --- a/test/si.js +++ b/test/si.js @@ -19,7 +19,7 @@ function test(f) { else if (f === 'g') { si.graphics().then(data => { if (data !== null) { resolve({ data, title: 'Graphics' }); } else { resolve('not_supported') } }) } else if (f === 'h') { si.bluetoothDevices().then(data => { if (data !== null) { resolve({ data, title: 'Bluetooth' }); } else { resolve('not_supported') } }) } else if (f === 'i') { si.inetLatency().then(data => { if (data !== null) { resolve({ data, title: 'Internet Latency' }); } else { resolve('not_supported') } }) } - else if (f === 'I') { si.inetChecksite('https://www.plus-innovations.com').then(data => { if (data !== null) { resolve({ data, title: 'Internet Check Site' }); } else { resolve('not_supported') } }) } + else if (f === 'I') { si.inetChecksite('https://systeminformation.io').then(data => { if (data !== null) { resolve({ data, title: 'Internet Check Site' }); } else { resolve('not_supported') } }) } else if (f === 'j') { si.cpuCurrentSpeed().then(data => { if (data !== null) { resolve({ data, title: 'CPU Current Speed' }); } else { resolve('not_supported') } }) } else if (f === 'l') { si.currentLoad().then(data => { if (data !== null) { resolve({ data, title: 'CPU Current Load' }); } else { resolve('not_supported') } }) } else if (f === 'L') { si.fullLoad().then(data => { if (data !== null) { resolve({ data, title: 'CPU Full Load' }); } else { resolve('not_supported') } }) }