bluetooth linux implementation

This commit is contained in:
Sebastian Hildebrandt 2021-01-22 10:50:19 +01:00
parent 142a0b5557
commit 65d67fe0a6
7 changed files with 120 additions and 96 deletions

View File

@ -98,13 +98,23 @@
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td>[0].address</td> <td>[0].macDevice</td>
<td>X</td> <td>X</td>
<td></td> <td></td>
<td>X</td> <td>X</td>
<td>X</td> <td>X</td>
<td></td> <td></td>
<td>address</td> <td>MAC address device</td>
</tr>
<tr>
<td></td>
<td>[0].macHost</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>MAC address host</td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
@ -158,18 +168,20 @@ si.bluetoothDevices().then(data => console.log(data));</code></pre class="exampl
device: 'Magic Mouse 2', device: 'Magic Mouse 2',
name: 'My Maus', name: 'My Maus',
manufacturer: 'Broadcom (0x5, 0x240C)', manufacturer: 'Broadcom (0x5, 0x240C)',
address: '10-XX-XX-XX-XX-XX', macDevice: '10-XX-XX-XX-XX-XX',
macHost: null,
batteryPercent: '74%', batteryPercent: '74%',
tyoe: 'Mouse', type: 'Mouse',
connected: true connected: true
}, },
{ {
device: 'Magic Keyboard with Numeric Keypad', device: 'Magic Keyboard with Numeric Keypad',
name: 'My Keyboard', name: 'My Keyboard',
manufacturer: 'Broadcom (0x5, 0x240C)', manufacturer: 'Broadcom (0x5, 0x240C)',
address: '10-XX-XX-XX-XX-XX', macDevice: '10-XX-XX-XX-XX-XX',
macHost: null,
batteryPercent: '75%', batteryPercent: '75%',
tyoe: 'Keyboard', type: 'Keyboard',
connected: true connected: true
}, },
]</pre> ]</pre>

View File

@ -15,8 +15,9 @@
const exec = require('child_process').exec; const exec = require('child_process').exec;
const execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
const path = require('path');
const util = require('./util'); const util = require('./util');
// const fs = require('fs'); const fs = require('fs');
let _platform = process.platform; let _platform = process.platform;
@ -28,79 +29,45 @@ const _openbsd = (_platform === 'openbsd');
const _netbsd = (_platform === 'netbsd'); const _netbsd = (_platform === 'netbsd');
const _sunos = (_platform === 'sunos'); const _sunos = (_platform === 'sunos');
function getLinuxAudioPci() { function parseBluetoothTyoe(str) {
let cmd = 'lspci -v 2>/dev/null'
let result = [];
try {
const parts = execSync(cmd).toString().split('\n\n');
for (let i = 0; i < parts.length; i++) {
const lines = parts[i].split('\n');
if (lines && lines.length && lines[0].toLowerCase().indexOf('audio') >= 0) {
const audio = {};
audio.slotId = lines[0].split(' ')[0];
audio.driver = util.getValue(lines, 'Kernel driver in use', ':', true) || util.getValue(lines, 'Kernel modules', ':', true)
result.push(audio);
}
}
return result;
} catch (e) {
return result;
}
}
// device("device_services")
// name-- > 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) {
let result = ''; let result = '';
if (str.indexOf('keyboard') >= 0) { result = 'Keyboard'; } if (str.indexOf('keyboard') >= 0) { result = 'Keyboard'; }
if (str.indexOf('mouse') >= 0) { result = 'Mouse'; } if (str.indexOf('mouse') >= 0) { result = 'Mouse'; }
if (str.indexOf('speaker') >= 0) { result = 'Speaker'; } if (str.indexOf('speaker') >= 0) { result = 'Speaker'; }
if (str.indexOf('headset') >= 0) { result = 'Headset'; } if (str.indexOf('headset') >= 0) { result = 'Headset'; }
if (str.indexOf('phone') >= 0) { result = 'Phone'; }
// to be continued ... // to be continued ...
return result; 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 result = {};
const typeStr = ((bluetoothObject.device_minorClassOfDevice_string || bluetoothObject.device_majorClassOfDevice_string || '') + (bluetoothObject.device_name || '')).toLowerCase(); const typeStr = ((bluetoothObject.device_minorClassOfDevice_string || bluetoothObject.device_majorClassOfDevice_string || '') + (bluetoothObject.device_name || '')).toLowerCase();
result.device = bluetoothObject.device_services || ''; result.device = bluetoothObject.device_services || '';
result.name = bluetoothObject.device_name || ''; result.name = bluetoothObject.device_name || '';
result.manufacturer = bluetoothObject.device_manufacturer || ''; 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.batteryPercent = bluetoothObject.device_batteryPercent || null;
result.tyoe = parseDarwinBluetoothTyoe(typeStr); result.type = parseBluetoothTyoe(typeStr);
result.connected = bluetoothObject.device_isconnected === 'attrib_Yes' || false; result.connected = bluetoothObject.device_isconnected === 'attrib_Yes' || false;
return result; return result;
@ -108,19 +75,15 @@ function parseDarwinBluetoothDevices(bluetoothObject) {
function parseWindowsBluetooth(lines) { function parseWindowsBluetooth(lines) {
const result = {}; const result = {};
const status = util.getValue(lines, 'StatusInfo', '=');
// const description = util.getValue(lines, 'Description', '=');
result.device = null; result.device = null;
result.name = util.getValue(lines, 'name', '='); result.name = util.getValue(lines, 'name', '=');
result.manufacturer = util.getValue(lines, 'manufacturer', '='); result.manufacturer = util.getValue(lines, 'manufacturer', '=');
result.address = null; result.macDevice = null;
result.batteryPercent = null result.macHost = null;
result.default = null result.batteryPercent = null;
result.in = null result.type = parseBluetoothTyoe(result.name.toLowerCase());
result.out = null result.connected = null;
result.interfaceType = null
result.status = status
return result; return result;
} }
@ -130,26 +93,35 @@ function bluetoothDevices(callback) {
return new Promise((resolve) => { return new Promise((resolve) => {
process.nextTick(() => { process.nextTick(() => {
let result = []; let result = [];
if (_linux || _freebsd || _openbsd || _netbsd) { if (_linux) {
let cmd = 'lspci -vmm 2>/dev/null' // get files in /var/lib/bluetooth/ recursive
exec(cmd, function (error, stdout) { const btFiles = util.getFilesInPath('/var/lib/bluetooth/');
// PCI for (let i = 0; i < btFiles.length; i++) {
if (!error) { const filename = path.basename(btFiles[i]);
const audioPCI = getLinuxAudioPci(); const pathParts = btFiles[i].split('/');
const parts = stdout.toString().split('\n\n'); const macAddr1 = pathParts.length >= 6 ? pathParts[pathParts.length - 2] : null;
for (let i = 0; i < parts.length; i++) { const macAddr2 = pathParts.length >= 7 ? pathParts[pathParts.length - 3] : null;
const lines = parts[i].split('\n'); if (filename === 'info') {
if (util.getValue(lines, 'class', ':', true).toLowerCase().indexOf('audio') >= 0) { const infoFile = fs.readFileSync(btFiles[i], { encoding: 'utf8' }).split('\n');
const audio = parseLinuxAudioPciMM(lines, audioPCI); result.push(parseLinuxBluetoothInfo(infoFile, macAddr1, macAddr2));
result.push(audio); }
} }
// 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) { } catch (e) {
callback(result); util.noop();
} }
resolve(result);
}); if (callback) {
callback(result);
}
resolve(result);
} }
if (_darwin) { if (_darwin) {
let cmd = 'system_profiler SPBluetoothDataType -json' let cmd = 'system_profiler SPBluetoothDataType -json'
@ -158,17 +130,21 @@ function bluetoothDevices(callback) {
try { try {
const outObj = JSON.parse(stdout.toString()); 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) { 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++) { for (let i = 0; i < outObj.SPBluetoothDataType[0]['device_title'].length; i++) {
const obj = outObj.SPBluetoothDataType[0]['device_title'][i]; const obj = outObj.SPBluetoothDataType[0]['device_title'][i];
const objKey = Object.keys(obj); const objKey = Object.keys(obj);
if (objKey && objKey.length === 1) { if (objKey && objKey.length === 1) {
const innerObject = obj[objKey[0]]; const innerObject = obj[objKey[0]];
innerObject.device_name = objKey[0]; innerObject.device_name = objKey[0];
const bluetoothDevice = parseDarwinBluetoothDevices(innerObject); const bluetoothDevice = parseDarwinBluetoothDevices(innerObject, macAddr2);
result.push(bluetoothDevice); result.push(bluetoothDevice);
} }
} }
} }
} catch (e) { } catch (e) {
@ -197,7 +173,7 @@ function bluetoothDevices(callback) {
resolve(result); resolve(result);
}); });
} }
if (_sunos) { if (_freebsd || _netbsd || _openbsd || _sunos) {
resolve(null); resolve(null);
} }
}); });

3
lib/index.d.ts vendored
View File

@ -746,7 +746,8 @@ export namespace Systeminformation {
interface BluetoothDeviceData { interface BluetoothDeviceData {
device: string; device: string;
name: string; name: string;
address: string; macDevice: string;
macHost: string;
batteryPercent: number; batteryPercent: number;
manufacturer: string; manufacturer: string;
type: string; type: string;

View File

@ -978,7 +978,7 @@ function uuid(callback) {
if (jsonObj.SPHardwareDataType && jsonObj.SPHardwareDataType.length > 0) { if (jsonObj.SPHardwareDataType && jsonObj.SPHardwareDataType.length > 0) {
const spHardware = jsonObj.SPHardwareDataType[0]; const spHardware = jsonObj.SPHardwareDataType[0];
// result.os = parts.length > 1 ? parts[1].trim().toLowerCase() : ''; // 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; result.hardware = spHardware.serial_number;
} }
} catch (e) { } catch (e) {

View File

@ -15,6 +15,7 @@
const os = require('os'); const os = require('os');
const fs = require('fs'); const fs = require('fs');
const path = require('path');
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const exec = require('child_process').exec; const exec = require('child_process').exec;
const execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
@ -593,6 +594,38 @@ function hex2bin(hex) {
return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8); 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) { function decodePiCpuinfo(lines) {
// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md // https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md
@ -831,3 +864,4 @@ exports.stringToString = stringToString;
exports.stringSubstr = stringSubstr; exports.stringSubstr = stringSubstr;
exports.stringTrim = stringTrim; exports.stringTrim = stringTrim;
exports.WINDIR = WINDIR; exports.WINDIR = WINDIR;
exports.getFilesInPath = getFilesInPath;

View File

@ -1,6 +1,7 @@
{ {
"name": "systeminformation", "name": "systeminformation",
"version": "4.34.9", "version": "4.34.9",
"prereleaseversion": "5.0-alpha.0116",
"description": "Simple system and OS information library", "description": "Simple system and OS information library",
"license": "MIT", "license": "MIT",
"author": "Sebastian Hildebrandt <hildebrandt@plus-innovations.com> (https://plus-innovations.com)", "author": "Sebastian Hildebrandt <hildebrandt@plus-innovations.com> (https://plus-innovations.com)",

View File

@ -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 === '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 === '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.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 === '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.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') } }) } else if (f === 'L') { si.fullLoad().then(data => { if (data !== null) { resolve({ data, title: 'CPU Full Load' }); } else { resolve('not_supported') } }) }