UNPKG

@mkerix/noble

Version:

A Node.js BLE (Bluetooth Low Energy) central library.

857 lines (660 loc) 25.3 kB
const debug = require('debug')('hci'); const events = require('events'); const util = require('util'); const BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket'); const HCI_COMMAND_PKT = 0x01; const HCI_ACLDATA_PKT = 0x02; const HCI_EVENT_PKT = 0x04; const ACL_START_NO_FLUSH = 0x00; const ACL_CONT = 0x01; const ACL_START = 0x02; const EVT_DISCONN_COMPLETE = 0x05; const EVT_ENCRYPT_CHANGE = 0x08; const EVT_CMD_COMPLETE = 0x0e; const EVT_CMD_STATUS = 0x0f; const EVT_NUMBER_OF_COMPLETED_PACKETS = 0x13; const EVT_LE_META_EVENT = 0x3e; const EVT_LE_CONN_COMPLETE = 0x01; const EVT_LE_ADVERTISING_REPORT = 0x02; const EVT_LE_CONN_UPDATE_COMPLETE = 0x03; const OGF_LINK_CTL = 0x01; const OCF_DISCONNECT = 0x0006; const OGF_HOST_CTL = 0x03; const OCF_SET_EVENT_MASK = 0x0001; const OCF_RESET = 0x0003; const OCF_READ_LE_HOST_SUPPORTED = 0x006C; const OCF_WRITE_LE_HOST_SUPPORTED = 0x006D; const OGF_INFO_PARAM = 0x04; const OCF_READ_LOCAL_VERSION = 0x0001; const OCF_READ_BUFFER_SIZE = 0x0005; const OCF_READ_BD_ADDR = 0x0009; const OGF_STATUS_PARAM = 0x05; const OCF_READ_RSSI = 0x0005; const OGF_LE_CTL = 0x08; const OCF_LE_SET_EVENT_MASK = 0x0001; const OCF_LE_READ_BUFFER_SIZE = 0x0002; const OCF_LE_SET_SCAN_PARAMETERS = 0x000b; const OCF_LE_SET_SCAN_ENABLE = 0x000c; const OCF_LE_CREATE_CONN = 0x000d; const OCF_LE_CANCEL_CONN = 0x000e; const OCF_LE_CONN_UPDATE = 0x0013; const OCF_LE_START_ENCRYPTION = 0x0019; const DISCONNECT_CMD = OCF_DISCONNECT | OGF_LINK_CTL << 10; const SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_HOST_CTL << 10; const RESET_CMD = OCF_RESET | OGF_HOST_CTL << 10; const READ_LE_HOST_SUPPORTED_CMD = OCF_READ_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; const WRITE_LE_HOST_SUPPORTED_CMD = OCF_WRITE_LE_HOST_SUPPORTED | OGF_HOST_CTL << 10; const READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10); const READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10); const READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10); const READ_RSSI_CMD = OCF_READ_RSSI | OGF_STATUS_PARAM << 10; const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | OGF_LE_CTL << 10; const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | OGF_LE_CTL << 10; const LE_SET_SCAN_PARAMETERS_CMD = OCF_LE_SET_SCAN_PARAMETERS | OGF_LE_CTL << 10; const LE_SET_SCAN_ENABLE_CMD = OCF_LE_SET_SCAN_ENABLE | OGF_LE_CTL << 10; const LE_CREATE_CONN_CMD = OCF_LE_CREATE_CONN | OGF_LE_CTL << 10; const LE_CONN_UPDATE_CMD = OCF_LE_CONN_UPDATE | OGF_LE_CTL << 10; const LE_CANCEL_CONN_CMD = OCF_LE_CANCEL_CONN | OGF_LE_CTL << 10; const LE_START_ENCRYPTION_CMD = OCF_LE_START_ENCRYPTION | OGF_LE_CTL << 10; const HCI_OE_USER_ENDED_CONNECTION = 0x13; const STATUS_MAPPER = require('./hci-status'); const Hci = function () { this._socket = new BluetoothHciSocket(); this._isDevUp = null; this._state = null; this._deviceId = null; this._pollTimeoutId = null; this._handleBuffers = {}; this._aclBuffers = { length: 0, num: 0 }; this._aclConnections = new Map(); this._aclQueue = []; this.on('stateChange', this.onStateChange.bind(this)); }; util.inherits(Hci, events.EventEmitter); Hci.STATUS_MAPPER = STATUS_MAPPER; Hci.prototype.init = function () { this._socket.on('data', this.onSocketData.bind(this)); this._socket.on('error', this.onSocketError.bind(this)); const deviceId = process.env.NOBLE_HCI_DEVICE_ID ? parseInt(process.env.NOBLE_HCI_DEVICE_ID, 10) : undefined; if (process.env.HCI_CHANNEL_USER) { this._deviceId = this._socket.bindUser(deviceId); this._socket.start(); this.reset(); } else { try { this._deviceId = this._socket.bindRaw(deviceId); this._socket.start(); } catch (e) { // EALREADY is acceptable if (!e.message.startsWith('EALREADY')) { throw e; } } this.pollIsDevUp(); } }; Hci.prototype.pollIsDevUp = function () { const isDevUp = this._socket.isDevUp(); if (this._isDevUp !== isDevUp) { if (isDevUp) { if (this._state === 'poweredOff') { this.resetBindings(); return; } this.setSocketFilter(); this.setEventMask(); this.setLeEventMask(); this.readLocalVersion(); this.writeLeHostSupported(); this.readLeHostSupported(); this.readLeBufferSize(); this.readBdAddr(); } else { this.emit('stateChange', 'poweredOff'); } this._isDevUp = isDevUp; } this._pollTimeoutId = setTimeout(this.pollIsDevUp.bind(this), 1000); }; Hci.prototype.setSocketFilter = function () { const filter = Buffer.alloc(14); const typeMask = (1 << HCI_COMMAND_PKT) | (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT); const eventMask1 = (1 << EVT_DISCONN_COMPLETE) | (1 << EVT_ENCRYPT_CHANGE) | (1 << EVT_CMD_COMPLETE) | (1 << EVT_CMD_STATUS) | (1 << EVT_NUMBER_OF_COMPLETED_PACKETS); const eventMask2 = (1 << (EVT_LE_META_EVENT - 32)); const opcode = 0; filter.writeUInt32LE(typeMask, 0); filter.writeUInt32LE(eventMask1, 4); filter.writeUInt32LE(eventMask2, 8); filter.writeUInt16LE(opcode, 12); debug(`setting filter to: ${filter.toString('hex')}`); this._socket.setFilter(filter); }; Hci.prototype.setEventMask = function () { const cmd = Buffer.alloc(12); const eventMask = Buffer.from('fffffbff07f8bf3d', 'hex'); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1); // length cmd.writeUInt8(eventMask.length, 3); eventMask.copy(cmd, 4); debug(`set event mask - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.reset = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(OCF_RESET | OGF_HOST_CTL << 10, 1); // length cmd.writeUInt8(0x00, 3); debug(`reset - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.resetBindings = function () { clearTimeout(this._pollTimeoutId); this._socket.removeAllListeners(); this._state = null; this.init(); }; Hci.prototype.stopSocket = function () { this._socket.stop(); }; Hci.prototype.readLocalVersion = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1); // length cmd.writeUInt8(0x0, 3); debug(`read local version - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.readBufferSize = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1); // length cmd.writeUInt8(0x0, 3); debug(`read buffer size - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.readBdAddr = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1); // length cmd.writeUInt8(0x0, 3); debug(`read bd addr - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.setLeEventMask = function () { const cmd = Buffer.alloc(12); const leEventMask = Buffer.from('1f00000000000000', 'hex'); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1); // length cmd.writeUInt8(leEventMask.length, 3); leEventMask.copy(cmd, 4); debug(`set le event mask - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.readLeBufferSize = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1); // length cmd.writeUInt8(0x0, 3); debug(`le read buffer size - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.readLeHostSupported = function () { const cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1); // length cmd.writeUInt8(0x00, 3); debug(`read LE host supported - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.writeLeHostSupported = function () { const cmd = Buffer.alloc(6); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1); // length cmd.writeUInt8(0x02, 3); // data cmd.writeUInt8(0x01, 4); // le cmd.writeUInt8(0x00, 5); // simul debug(`write LE host supported - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.setScanParameters = function () { const cmd = Buffer.alloc(11); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_SET_SCAN_PARAMETERS_CMD, 1); // length cmd.writeUInt8(0x07, 3); // data cmd.writeUInt8(0x01, 4); // type: 0 -> passive, 1 -> active cmd.writeUInt16LE(0x0010, 5); // internal, ms * 1.6 cmd.writeUInt16LE(0x0010, 7); // window, ms * 1.6 cmd.writeUInt8(0x00, 9); // own address type: 0 -> public, 1 -> random cmd.writeUInt8(0x00, 10); // filter: 0 -> all event types debug(`set scan parameters - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.setScanEnabled = function (enabled, filterDuplicates) { const cmd = Buffer.alloc(6); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_SET_SCAN_ENABLE_CMD, 1); // length cmd.writeUInt8(0x02, 3); // data cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled cmd.writeUInt8(filterDuplicates ? 0x01 : 0x00, 5); // duplicates: 0 -> duplicates, 0 -> duplicates debug(`set scan enabled - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.createLeConn = function (address, addressType) { const cmd = Buffer.alloc(29); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_CREATE_CONN_CMD, 1); // length cmd.writeUInt8(0x19, 3); // data cmd.writeUInt16LE(0x0060, 4); // interval cmd.writeUInt16LE(0x0030, 6); // window cmd.writeUInt8(0x00, 8); // initiator filter cmd.writeUInt8(addressType === 'random' ? 0x01 : 0x00, 9); // peer address type (Buffer.from(address.split(':').reverse().join(''), 'hex')).copy(cmd, 10); // peer address cmd.writeUInt8(0x00, 16); // own address type cmd.writeUInt16LE(0x0006, 17); // min interval cmd.writeUInt16LE(0x000c, 19); // max interval cmd.writeUInt16LE(0x0000, 21); // latency cmd.writeUInt16LE(0x00c8, 23); // supervision timeout cmd.writeUInt16LE(0x0004, 25); // min ce length cmd.writeUInt16LE(0x0006, 27); // max ce length debug(`create le conn - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.connUpdateLe = function (handle, minInterval, maxInterval, latency, supervisionTimeout) { const cmd = Buffer.alloc(18); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_CONN_UPDATE_CMD, 1); // length cmd.writeUInt8(0x0e, 3); // data cmd.writeUInt16LE(handle, 4); cmd.writeUInt16LE(Math.floor(minInterval / 1.25), 6); // min interval cmd.writeUInt16LE(Math.floor(maxInterval / 1.25), 8); // max interval cmd.writeUInt16LE(latency, 10); // latency cmd.writeUInt16LE(Math.floor(supervisionTimeout / 10), 12); // supervision timeout cmd.writeUInt16LE(0x0000, 14); // min ce length cmd.writeUInt16LE(0x0000, 16); // max ce length debug(`conn update le - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.cancelConnect = function () { var cmd = Buffer.alloc(4); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_CANCEL_CONN_CMD, 1); // length cmd.writeUInt8(0x0, 3); debug('cancel le conn - writing: ' + cmd.toString('hex')); this._socket.write(cmd); }; Hci.prototype.startLeEncryption = function (handle, random, diversifier, key) { const cmd = Buffer.alloc(32); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(LE_START_ENCRYPTION_CMD, 1); // length cmd.writeUInt8(0x1c, 3); // data cmd.writeUInt16LE(handle, 4); // handle random.copy(cmd, 6); diversifier.copy(cmd, 14); key.copy(cmd, 16); debug(`start le encryption - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.disconnect = function (handle, reason) { const cmd = Buffer.alloc(7); reason = reason || HCI_OE_USER_ENDED_CONNECTION; // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(DISCONNECT_CMD, 1); // length cmd.writeUInt8(0x03, 3); // data cmd.writeUInt16LE(handle, 4); // handle cmd.writeUInt8(reason, 6); // reason debug(`disconnect - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.readRssi = function (handle) { const cmd = Buffer.alloc(6); // header cmd.writeUInt8(HCI_COMMAND_PKT, 0); cmd.writeUInt16LE(READ_RSSI_CMD, 1); // length cmd.writeUInt8(0x02, 3); // data cmd.writeUInt16LE(handle, 4); // handle debug(`read rssi - writing: ${cmd.toString('hex')}`); this._socket.write(cmd); }; Hci.prototype.writeAclDataPkt = function (handle, cid, data) { const l2capLength = 4 /* l2cap header */ + data.length; const aclLength = Math.min(l2capLength, this._aclBuffers.length); const first = Buffer.alloc(aclLength + 5 /* acl header */); // acl header first.writeUInt8(HCI_ACLDATA_PKT, 0); first.writeUInt16LE(handle | ACL_START_NO_FLUSH << 12, 1); first.writeUInt16LE(aclLength, 3); // l2cap header first.writeUInt16LE(data.length, 5); first.writeUInt16LE(cid, 7); data.copy(first, 9); data = data.slice(first.length - 9); debug(`push to acl queue: ${first.toString('hex')}`); this._aclQueue.push({ handle, packet: first }); while (data.length > 0) { const fragAclLength = Math.min(data.length, this._aclBuffers.length); const frag = Buffer.alloc(fragAclLength + 5 /* acl header */); // acl header frag.writeUInt8(HCI_ACLDATA_PKT, 0); frag.writeUInt16LE(handle | ACL_CONT << 12, 1); frag.writeUInt16LE(fragAclLength, 3); data.copy(frag, 5); data = data.slice(frag.length - 5); debug(`push fragment to acl queue: ${frag.toString('hex')}`); this._aclQueue.push({ handle, packet: frag }); } this.flushAcl(); }; Hci.prototype.flushAcl = function () { const pendingPackets = () => { let totalPending = 0; for (const { pending } of this._aclConnections.values()) { totalPending += pending; } return totalPending; }; debug(`flush - pending: ${pendingPackets()} queue length: ${this._aclQueue.length}`); while (this._aclQueue.length > 0 && pendingPackets() < this._aclBuffers.num) { const { handle, packet } = this._aclQueue.shift(); this._aclConnections.get(handle).pending++; debug(`write acl data packet - writing: ${packet.toString('hex')}`); this._socket.write(packet); } }; Hci.prototype.onSocketData = function (data) { debug(`onSocketData: ${data.toString('hex')}`); const eventType = data.readUInt8(0); let handle; let cmd; let status; debug(`\tevent type = ${eventType}`); if (HCI_EVENT_PKT === eventType) { const subEventType = data.readUInt8(1); debug(`\tsub event type = ${subEventType}`); if (subEventType === EVT_DISCONN_COMPLETE) { handle = data.readUInt16LE(4); const reason = data.readUInt8(6); debug(`\t\thandle = ${handle}`); debug(`\t\treason = ${reason}`); this._aclQueue = this._aclQueue.filter(acl => acl.handle !== handle); this._aclConnections.delete(handle); this.flushAcl(); this.emit('disconnComplete', handle, reason); } else if (subEventType === EVT_ENCRYPT_CHANGE) { handle = data.readUInt16LE(4); const encrypt = data.readUInt8(6); debug(`\t\thandle = ${handle}`); debug(`\t\tencrypt = ${encrypt}`); this.emit('encryptChange', handle, encrypt); } else if (subEventType === EVT_CMD_COMPLETE) { cmd = data.readUInt16LE(4); status = data.readUInt8(6); const result = data.slice(7); debug(`\t\tcmd = ${cmd}`); debug(`\t\tstatus = ${status}`); debug(`\t\tresult = ${result.toString('hex')}`); this.processCmdCompleteEvent(cmd, status, result); } else if (subEventType === EVT_CMD_STATUS) { status = data.readUInt8(3); cmd = data.readUInt16LE(5); debug(`\t\tstatus = ${status}`); debug(`\t\tcmd = ${cmd}`); this.processCmdStatusEvent(cmd, status); } else if (subEventType === EVT_LE_META_EVENT) { const leMetaEventType = data.readUInt8(3); const leMetaEventStatus = data.readUInt8(4); const leMetaEventData = data.slice(5); debug(`\t\tLE meta event type = ${leMetaEventType}`); debug(`\t\tLE meta event status = ${leMetaEventStatus}`); debug(`\t\tLE meta event data = ${leMetaEventData.toString('hex')}`); this.processLeMetaEvent(leMetaEventType, leMetaEventStatus, leMetaEventData); } else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) { const handles = data.readUInt8(3); for (let h = 0; h < handles; h++) { const handle = data.readUInt16LE(4 + h * 4); const pkts = data.readUInt16LE(6 + h * 4); debug(`\thandle = ${handle}`); debug(`\t\tcompleted = ${pkts}`); if (!this._aclConnections.has(handle)) { debug('\t\tclosed'); continue; } const connection = this._aclConnections.get(handle); connection.pending -= pkts; if (connection.pending < 0) { connection.pending = 0; } } this.flushAcl(); } } else if (HCI_ACLDATA_PKT === eventType) { const flags = data.readUInt16LE(1) >> 12; handle = data.readUInt16LE(1) & 0x0fff; if (ACL_START === flags) { var cid = data.readUInt16LE(7); var length = data.readUInt16LE(5); const pktData = data.slice(9); debug(`\t\tcid = ${cid}`); if (length === pktData.length) { debug(`\t\thandle = ${handle}`); debug(`\t\tdata = ${pktData.toString('hex')}`); this.emit('aclDataPkt', handle, cid, pktData); } else { this._handleBuffers[handle] = { length: length, cid: cid, data: pktData }; } } else if (ACL_CONT === flags) { if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) { return; } this._handleBuffers[handle].data = Buffer.concat([ this._handleBuffers[handle].data, data.slice(5) ]); if (this._handleBuffers[handle].data.length === this._handleBuffers[handle].length) { this.emit('aclDataPkt', handle, this._handleBuffers[handle].cid, this._handleBuffers[handle].data); delete this._handleBuffers[handle]; } } } else if (HCI_COMMAND_PKT === eventType) { cmd = data.readUInt16LE(1); const len = data.readUInt8(3); debug(`\t\tcmd = ${cmd}`); debug(`\t\tdata len = ${len}`); if (cmd === LE_SET_SCAN_ENABLE_CMD) { const enable = (data.readUInt8(4) === 0x1); const filterDuplicates = (data.readUInt8(5) === 0x1); debug('\t\t\tLE enable scan command'); debug(`\t\t\tenable scanning = ${enable}`); debug(`\t\t\tfilter duplicates = ${filterDuplicates}`); this.emit('leScanEnableSetCmd', enable, filterDuplicates); } } }; Hci.prototype.onSocketError = function (error) { debug(`onSocketError: ${error.message}`); if (error.code === 'EPERM') { this.emit('stateChange', 'unauthorized'); } else if (error.message === 'Network is down') { // no-op } }; Hci.prototype.processCmdCompleteEvent = function (cmd, status, result) { if (cmd === RESET_CMD) { this.setEventMask(); this.setLeEventMask(); this.readLocalVersion(); this.readBdAddr(); } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) { if (status === 0) { const le = result.readUInt8(0); const simul = result.readUInt8(1); debug(`\t\t\tle = ${le}`); debug(`\t\t\tsimul = ${simul}`); } } else if (cmd === READ_LOCAL_VERSION_CMD) { const hciVer = result.readUInt8(0); const hciRev = result.readUInt16LE(1); const lmpVer = result.readInt8(3); const manufacturer = result.readUInt16LE(4); const lmpSubVer = result.readUInt16LE(6); if (hciVer < 0x06) { this.emit('stateChange', 'unsupported'); } else if (this._state !== 'poweredOn') { this.setScanEnabled(false, true); this.setScanParameters(); } this.emit('readLocalVersion', hciVer, hciRev, lmpVer, manufacturer, lmpSubVer); } else if (cmd === READ_BD_ADDR_CMD) { this.addressType = 'public'; this.address = result.toString('hex').match(/.{1,2}/g).reverse().join(':'); debug(`address = ${this.address}`); this.emit('addressChange', this.address); } else if (cmd === LE_SET_SCAN_PARAMETERS_CMD) { this.emit('stateChange', 'poweredOn'); this.emit('leScanParametersSet'); } else if (cmd === LE_SET_SCAN_ENABLE_CMD) { this.emit('leScanEnableSet', status); } else if (cmd === READ_RSSI_CMD) { const handle = result.readUInt16LE(0); const rssi = result.readInt8(2); debug(`\t\t\thandle = ${handle}`); debug(`\t\t\trssi = ${rssi}`); this.emit('rssiRead', handle, rssi); } else if (cmd === LE_READ_BUFFER_SIZE_CMD) { if (status === 0) { const aclLength = result.readUInt16LE(0); const aclNum = result.readUInt8(2); /* Spec Vol 4 Part E.7.8 /* No dedicated LE Buffer exists. Use the HCI_Read_Buffer_Size command. */ if (aclLength === 0 || aclNum === 0) { debug('using br/edr buffer size'); this.readBufferSize(); } else { debug(`le buffer size: length = ${aclLength}, num = ${aclNum}`); this._aclBuffers.length = aclLength; this._aclBuffers.num = aclNum; } } } else if (cmd === READ_BUFFER_SIZE_CMD) { const aclLength = result.readUInt16LE(0); const aclNum = result.readUInt16LE(3); debug(`buffer size: length = ${aclLength}, num = ${aclNum}`); this._aclBuffers.length = aclLength; this._aclBuffers.num = aclNum; } }; Hci.prototype.processLeMetaEvent = function (eventType, status, data) { if (eventType === EVT_LE_CONN_COMPLETE) { this.processLeConnComplete(status, data); } else if (eventType === EVT_LE_ADVERTISING_REPORT) { this.processLeAdvertisingReport(status, data); } else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) { this.processLeConnUpdateComplete(status, data); } }; Hci.prototype.processLeConnComplete = function (status, data) { const handle = data.readUInt16LE(0); const role = data.readUInt8(2); const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public'; const address = data.slice(4, 10).toString('hex').match(/.{1,2}/g).reverse().join(':'); const interval = data.readUInt16LE(10) * 1.25; const latency = data.readUInt16LE(12); // TODO: multiplier? const supervisionTimeout = data.readUInt16LE(14) * 10; const masterClockAccuracy = data.readUInt8(16); // TODO: multiplier? debug(`\t\t\thandle = ${handle}`); debug(`\t\t\trole = ${role}`); debug(`\t\t\taddress type = ${addressType}`); debug(`\t\t\taddress = ${address}`); debug(`\t\t\tinterval = ${interval}`); debug(`\t\t\tlatency = ${latency}`); debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`); debug(`\t\t\tmaster clock accuracy = ${masterClockAccuracy}`); this._aclConnections.set(handle, { pending: 0 }); this.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy); }; Hci.prototype.processLeAdvertisingReport = function (count, data) { try { for (let i = 0; i < count; i++) { const type = data.readUInt8(0); const addressType = data.readUInt8(1) === 0x01 ? 'random' : 'public'; const address = data.slice(2, 8).toString('hex').match(/.{1,2}/g).reverse().join(':'); const eirLength = data.readUInt8(8); const eir = data.slice(9, eirLength + 9); const rssi = data.readInt8(eirLength + 9); debug(`\t\t\ttype = ${type}`); debug(`\t\t\taddress = ${address}`); debug(`\t\t\taddress type = ${addressType}`); debug(`\t\t\teir = ${eir.toString('hex')}`); debug(`\t\t\trssi = ${rssi}`); this.emit('leAdvertisingReport', 0, type, address, addressType, eir, rssi); data = data.slice(eirLength + 10); } } catch (e) { console.warn(`processLeAdvertisingReport: Caught illegal packet (buffer overflow): ${e}`); } }; Hci.prototype.processLeConnUpdateComplete = function (status, data) { const handle = data.readUInt16LE(0); const interval = data.readUInt16LE(2) * 1.25; const latency = data.readUInt16LE(4); // TODO: multiplier? const supervisionTimeout = data.readUInt16LE(6) * 10; debug(`\t\t\thandle = ${handle}`); debug(`\t\t\tinterval = ${interval}`); debug(`\t\t\tlatency = ${latency}`); debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`); this.emit('leConnUpdateComplete', status, handle, interval, latency, supervisionTimeout); }; Hci.prototype.processCmdStatusEvent = function (cmd, status) { if (cmd === LE_CREATE_CONN_CMD) { if (status !== 0) { this.emit('leConnComplete', status); } } }; Hci.prototype.onStateChange = function (state) { this._state = state; }; module.exports = Hci;