@s89/ble-ancs
Version:
An Apple ANCS reciever from Linux. It is a combination of the Bleno, Noble and ANCS projects from Sandeep Mistry
785 lines (613 loc) • 23.6 kB
JavaScript
var debug = require('debug')('hci');
var events = require('events');
var util = require('util');
var BluetoothHciSocket = require('bluetooth-hci-socket');
var ATT_OP_ERROR = 0x01;
var ATT_OP_MTU_REQ = 0x02;
var ATT_OP_MTU_RESP = 0x03;
var ATT_OP_FIND_INFO_REQ = 0x04;
var ATT_OP_FIND_INFO_RESP = 0x05;
var ATT_OP_FIND_BY_TYPE_REQ = 0x06;
var ATT_OP_FIND_BY_TYPE_RESP = 0x07;
var ATT_OP_READ_BY_TYPE_REQ = 0x08;
var ATT_OP_READ_BY_TYPE_RESP = 0x09;
var ATT_OP_READ_REQ = 0x0a;
var ATT_OP_READ_RESP = 0x0b;
var ATT_OP_READ_BLOB_REQ = 0x0c;
var ATT_OP_READ_BLOB_RESP = 0x0d;
var ATT_OP_READ_MULTI_REQ = 0x0e;
var ATT_OP_READ_MULTI_RESP = 0x0f;
var ATT_OP_READ_BY_GROUP_REQ = 0x10;
var ATT_OP_READ_BY_GROUP_RESP = 0x11;
var ATT_OP_WRITE_REQ = 0x12;
var ATT_OP_WRITE_RESP = 0x13;
var ATT_OP_WRITE_CMD = 0x52;
var ATT_OP_PREP_WRITE_REQ = 0x16;
var ATT_OP_PREP_WRITE_RESP = 0x17;
var ATT_OP_EXEC_WRITE_REQ = 0x18;
var ATT_OP_EXEC_WRITE_RESP = 0x19;
var ATT_OP_HANDLE_NOTIFY = 0x1b;
var ATT_OP_HANDLE_IND = 0x1d;
var ATT_OP_HANDLE_CNF = 0x1e;
var ATT_OP_WRITE_CMD = 0x52;
var ATT_OP_SIGNED_WRITE_CMD = 0xd2;
var HCI_COMMAND_PKT = 0x01;
var HCI_ACLDATA_PKT = 0x02;
var HCI_EVENT_PKT = 0x04;
var ACL_START_NO_FLUSH = 0x00;
var ACL_CONT = 0x01;
var ACL_START = 0x02;
var EVT_DISCONN_COMPLETE = 0x05;
var EVT_ENCRYPT_CHANGE = 0x08;
var EVT_CMD_COMPLETE = 0x0e;
var EVT_CMD_STATUS = 0x0f;
var EVT_LE_META_EVENT = 0x3e;
var EVT_LE_CONN_COMPLETE = 0x01;
var EVT_LE_ADVERTISING_REPORT = 0x02;
var EVT_LE_CONN_UPDATE_COMPLETE = 0x03;
var OGF_LINK_CTL = 0x01;
var OCF_DISCONNECT = 0x0006;
var OGF_HOST_CTL = 0x03;
var OCF_SET_EVENT_MASK = 0x0001;
var OGF_INFO_PARAM = 0x04;
var OCF_READ_LOCAL_VERSION = 0x0001;
var OCF_READ_BD_ADDR = 0x0009;
var OGF_STATUS_PARAM = 0x05;
var OCF_READ_RSSI = 0x0005;
var OGF_LE_CTL = 0x08;
var OCF_LE_SET_EVENT_MASK = 0x0001;
var OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006;
var OCF_LE_SET_ADVERTISING_DATA = 0x0008;
var OCF_LE_SET_SCAN_RESPONSE_DATA = 0x0009;
var OCF_LE_SET_ADVERTISE_ENABLE = 0x000a;
var OCF_LE_LTK_NEG_REPLY = 0x001B;
var OCF_LE_SET_SCAN_PARAMETERS = 0x000b;
var OCF_LE_SET_SCAN_ENABLE = 0x000c;
var OCF_LE_CREATE_CONN = 0x000d;
var OCF_LE_START_ENCRYPTION = 0x0019;
var DISCONNECT_CMD = OCF_DISCONNECT | OGF_LINK_CTL << 10;
var SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_HOST_CTL << 10;
var READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10);
var READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10);
var READ_RSSI_CMD = OCF_READ_RSSI | OGF_STATUS_PARAM << 10;
var LE_SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | OGF_LE_CTL << 10;
var LE_SET_SCAN_PARAMETERS_CMD = OCF_LE_SET_SCAN_PARAMETERS | OGF_LE_CTL << 10;
var LE_SET_SCAN_ENABLE_CMD = OCF_LE_SET_SCAN_ENABLE | OGF_LE_CTL << 10;
var LE_CREATE_CONN_CMD = OCF_LE_CREATE_CONN | OGF_LE_CTL << 10;
var LE_START_ENCRYPTION_CMD = OCF_LE_START_ENCRYPTION | OGF_LE_CTL << 10;
var LE_SET_ADVERTISING_PARAMETERS_CMD = OCF_LE_SET_ADVERTISING_PARAMETERS | OGF_LE_CTL << 10;
var LE_SET_ADVERTISING_DATA_CMD = OCF_LE_SET_ADVERTISING_DATA | OGF_LE_CTL << 10;
var LE_SET_SCAN_RESPONSE_DATA_CMD = OCF_LE_SET_SCAN_RESPONSE_DATA | OGF_LE_CTL << 10;
var LE_SET_ADVERTISE_ENABLE_CMD = OCF_LE_SET_ADVERTISE_ENABLE | OGF_LE_CTL << 10;
var LE_LTK_NEG_REPLY_CMD = OCF_LE_LTK_NEG_REPLY | OGF_LE_CTL << 10;
var HCI_OE_USER_ENDED_CONNECTION = 0x13;
var STATUS_MAPPER = require('./hci-status');
var Hci = function() {
this._socket = new BluetoothHciSocket();
this._isDevUp = null;
this._handleBuffers = {};
};
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));
var deviceId = process.env.ABLE_HCI_DEVICE_ID ? parseInt(process.env.ABLE_HCI_DEVICE_ID, 10) : undefined;
this._socket.bindRaw(deviceId);
//this._socket.bindUser(deviceId);
this._socket.start();
this.pollIsDevUp();
};
Hci.prototype.pollIsDevUp = function() {
var isDevUp = this._socket.isDevUp();
if (this._isDevUp !== isDevUp) {
if (isDevUp) {
this.setSocketFilter();
this.setEventMask();
this.setLeEventMask();
this.readLocalVersion();
this.readBdAddr();
} else {
this.emit('stateChange', 'poweredOff');
}
this._isDevUp = isDevUp;
}
setTimeout(this.pollIsDevUp.bind(this), 1000);
};
Hci.prototype.setSocketFilter = function() {
var filter = new Buffer(14);
var typeMask = (1 << HCI_EVENT_PKT)| (1 << HCI_ACLDATA_PKT);
var eventMask1 = (1 << EVT_DISCONN_COMPLETE) | (1 << EVT_ENCRYPT_CHANGE) | (1 << EVT_CMD_COMPLETE) | (1 << EVT_CMD_STATUS);
var eventMask2 = (1 << (EVT_LE_META_EVENT - 32));
var 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() {
var cmd = new Buffer(12);
var eventMask = new Buffer('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.readLocalVersion = function() {
var cmd = new Buffer(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.readBdAddr = function() {
var cmd = new Buffer(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() {
var cmd = new Buffer(12);
var leEventMask = new Buffer('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.setAdvertisingParameters = function() {
var cmd = new Buffer(19);
// header
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
cmd.writeUInt16LE(LE_SET_ADVERTISING_PARAMETERS_CMD, 1);
// length
cmd.writeUInt8(15, 3);
var advertisementInterval = Math.floor((process.env.BLENO_ADVERTISING_INTERVAL ? parseInt(process.env.BLENO_ADVERTISING_INTERVAL) : 20) * 1.6);
// data
cmd.writeUInt16LE(advertisementInterval, 4); // min interval
cmd.writeUInt16LE(advertisementInterval, 6); // max interval
cmd.writeUInt8(0x00, 8); // adv type
cmd.writeUInt8(0x00, 9); // own addr typ
cmd.writeUInt8(0x00, 10); // direct addr type
(new Buffer('000000000000', 'hex')).copy(cmd, 11); // direct addr
cmd.writeUInt8(0x07, 17);
cmd.writeUInt8(0x00, 18);
debug('set advertisement parameters - writing: ' + cmd.toString('hex'));
this._socket.write(cmd);
};
Hci.prototype.setAdvertisingData = function(data) {
var cmd = new Buffer(36);
cmd.fill(0x00);
// header
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
cmd.writeUInt16LE(LE_SET_ADVERTISING_DATA_CMD, 1);
// length
cmd.writeUInt8(32, 3);
// data
cmd.writeUInt8(data.length, 4);
data.copy(cmd, 5);
debug('set advertisement data - writing: ' + cmd.toString('hex'));
this._socket.write(cmd);
};
Hci.prototype.setScanResponseData = function(data) {
var cmd = new Buffer(36);
cmd.fill(0x00);
// header
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
cmd.writeUInt16LE(LE_SET_SCAN_RESPONSE_DATA_CMD, 1);
// length
cmd.writeUInt8(32, 3);
// data
cmd.writeUInt8(data.length, 4);
data.copy(cmd, 5);
debug('set scan response data - writing: ' + cmd.toString('hex'));
this._socket.write(cmd);
};
Hci.prototype.setAdvertiseEnable = function(enabled) {
var cmd = new Buffer(5);
// header
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
cmd.writeUInt16LE(LE_SET_ADVERTISE_ENABLE_CMD, 1);
// length
cmd.writeUInt8(0x01, 3);
// data
cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
debug('set advertise enable - writing: ' + cmd.toString('hex'));
this._socket.write(cmd);
};
Hci.prototype.setScanParameters = function() {
var cmd = new Buffer(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) {
var cmd = new Buffer(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) {
var cmd = new Buffer(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
(new Buffer(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.startLeEncryption = function(handle, random, diversifier, key) {
var cmd = new Buffer(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) {
var cmd = new Buffer(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) {
var cmd = new Buffer(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) {
var pkt = new Buffer(9 + data.length);
// header
pkt.writeUInt8(HCI_ACLDATA_PKT, 0);
pkt.writeUInt16LE(handle | ACL_START_NO_FLUSH << 12, 1);
pkt.writeUInt16LE(data.length + 4, 3); // data length 1
pkt.writeUInt16LE(data.length, 5); // data length 2
pkt.writeUInt16LE(cid, 7);
data.copy(pkt, 9);
debug('write acl data pkt - writing: ' + pkt.toString('hex'));
this._socket.write(pkt);
};
Hci.prototype.debugPacketType = function(type) {
switch(type) {
case ATT_OP_ERROR:
debug('\t\ttype = ATT_OP_ERROR: ');
break;
case ATT_OP_MTU_REQ:
debug('\t\ttype = ATT_OP_MTU_REQ: ');
break;
case ATT_OP_FIND_INFO_REQ:
debug('\t\ttype = ATT_OP_FIND_INFO_REQ: ');
break;
case ATT_OP_FIND_BY_TYPE_REQ:
debug('\t\ttype = ATT_OP_FIND_BY_TYPE_REQ: ');
break;
case ATT_OP_READ_BY_TYPE_REQ:
debug('\t\ttype = ATT_OP_READ_BY_TYPE_REQ:');
break;
case ATT_OP_READ_REQ:
case ATT_OP_READ_BLOB_REQ:
debug('\t\ttype = ATT_OP_READ_REQ: ');
break;
case ATT_OP_READ_BY_GROUP_REQ:
debug('\t\ttype = ATT_OP_READ_BY_GROUP_REQ: ');
break;
case ATT_OP_WRITE_REQ:
case ATT_OP_WRITE_CMD:
debug('\t\ttype = ATT_OP_WRITE_REQ: ');
break;
case ATT_OP_HANDLE_CNF:
debug('\t\ttype = ATT_OP_HANDLE_CNF: ');
break;
case ATT_OP_ERROR:
debug('\t\ttype = ATT_OP_ERROR: ' );
break;
case ATT_OP_READ_BY_TYPE_RESP:
debug('\t\ttype = ATT_OP_READ_BY_TYPE_RESP: ' );
break;
case ATT_OP_READ_BY_GROUP_RESP:
debug('\t\ttype = ATT_OP_READ_BY_GROUP_RESP: ' );
break;
case ATT_OP_HANDLE_NOTIFY:
case ATT_OP_HANDLE_IND:
debug('\t\ttype = ATT_OP_HANDLE_NOTIFY: ' );
default:
case ATT_OP_READ_MULTI_REQ:
case ATT_OP_PREP_WRITE_REQ:
case ATT_OP_EXEC_WRITE_REQ:
case ATT_OP_SIGNED_WRITE_CMD:
debug('\t\ttype = Unhandled: ' + type);
break;
}
};
Hci.prototype.onSocketData = function(data) {
var eventType = data.readUInt8(0);
var handle;
debug('onSocketData: ' + data.toString('hex') + '\tevent type = ' + eventType);
if (HCI_EVENT_PKT === eventType) {
var subEventType = data.readUInt8(1);
debug('\tsub event type = ' + subEventType);
if (subEventType === EVT_DISCONN_COMPLETE) {
handle = data.readUInt16LE(4);
var reason = data.readUInt8(6);
debug('\t\tEVT_DISCONN_COMPLETE');
debug('\t\thandle = ' + handle);
debug('\t\treason = ' + reason);
debug('emitting disconnComplete');
var listened = this.emit('disconnComplete', handle, reason);
debug('Did someone hear: ' + listened );
} else if (subEventType === EVT_ENCRYPT_CHANGE) {
handle = data.readUInt16LE(4);
var encrypt = data.readUInt8(6);
debug('\t\tEVT_ENCRYPT_CHANGE');
debug('\t\thandle = ' + handle);
debug('\t\tencrypt = ' + encrypt);
this.emit('encryptChange', handle, encrypt);
} else if (subEventType === EVT_CMD_COMPLETE) {
var cmd = data.readUInt16LE(4);
var status = data.readUInt8(6);
var result = data.slice(7);
debug('\t\tEVT_CMD_COMPLETE');
debug('\t\tcmd = ' + cmd);
debug('\t\tstatus = ' + status);
debug('\t\tresult = ' + result.toString('hex'));
this.processCmdCompleteEvent(cmd, status, result);
} else if (subEventType === EVT_LE_META_EVENT) {
var leMetaEventType = data.readUInt8(3);
var leMetaEventStatus = data.readUInt8(4);
var leMetaEventData = data.slice(5);
debug('\t\tEVT_LE_META_EVENT');
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 {
debug('\t\tUnknown Command');
}
} else if (HCI_ACLDATA_PKT === eventType) {
var flags = data.readUInt16LE(1) >> 12;
handle = data.readUInt16LE(1) & 0x0fff;
if (ACL_START === flags) {
var cid = data.readUInt16LE(7);
var length = data.readUInt16LE(5);
var pktData = data.slice(9);
debug('\t\tHCI_ACLDATA_PKT - ACL_START');
debug('\t\tcid = ' + cid);
this.debugPacketType(pktData[0]);
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_START_NO_FLUSH === flags) {
var cid = data.readUInt16LE(7);
var length = data.readUInt16LE(5);
var pktData = data.slice(9);
debug('\t\tHCI_ACLDATA_PKT - ACL_START_NO_FLUSH');
debug('\t\tcid = ' + cid);
this.debugPacketType(pktData[0]);
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) {
debug('\t\tHCI_ACLDATA_PKT - ACL_CONT');
if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) {
debug('!\tUnable to find previous packets');
return;
}
this._handleBuffers[handle].data = Buffer.concat([
this._handleBuffers[handle].data,
data.slice(5)
]);
if (this._handleBuffers[handle].data.length === this._handleBuffers[handle].length) {
debug('\t\tCOMPLETE');
this.emit('aclDataPkt', handle, this._handleBuffers[handle].cid, this._handleBuffers[handle].data);
delete this._handleBuffers[handle];
}
} else {
debug('\t\tUnknown Flags for HCI_ACLDATA_PKT: ' + flags)
}
} else {
debug('!\tPacket unhandled');
}
};
Hci.prototype.onSocketError = function(error) {
debug('onSocketError: ' + error.message);
if (error.message === 'Operation not permitted') {
this.emit('stateChange', 'unauthorized');
} else if (error.message === 'Network is down') {
// no-op
}
};
Hci.prototype.processCmdCompleteEvent = function(cmd, status, result) {
var handle;
if (cmd === READ_LOCAL_VERSION_CMD) {
var hciVer = result.readUInt8(0);
var hciRev = result.readUInt16LE(1);
var lmpVer = result.readInt8(3);
var manufacturer = result.readUInt16LE(4);
var lmpSubVer = result.readUInt16LE(6);
if (hciVer < 0x06) {
this.emit('stateChange', 'unsupported');
} else {
this.setScanEnabled(false, true);
this.setScanParameters();
}
debug('\t\tREAD_LOCAL_VERSION_CMD');
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('\t\tREAD_BD_ADDR_CMD');
this.emit('addressChange', this.address);
} else if (cmd === LE_SET_ADVERTISING_PARAMETERS_CMD) {
this.emit('stateChange', 'poweredOn');
debug('\t\tLE_SET_ADVERTISING_PARAMETERS_CMD');
this.emit('leAdvertisingParametersSet', status);
} else if (cmd === LE_SET_SCAN_PARAMETERS_CMD) {
this.emit('stateChange', 'poweredOn');
debug('\t\tLE_SET_SCAN_PARAMETERS_CMD');
this.emit('leScanParametersSet');
} else if (cmd === LE_SET_SCAN_ENABLE_CMD) {
debug('\t\tLE_SET_SCAN_ENABLE_CMD');
this.emit('leScanEnableSet');
} else if (cmd === LE_SET_ADVERTISING_DATA_CMD) {
debug('\t\tLE_SET_ADVERTISING_DATA_CMD');
this.emit('leAdvertisingDataSet', status);
} else if (cmd === LE_SET_SCAN_RESPONSE_DATA_CMD) {
debug('\t\tLE_SET_SCAN_RESPONSE_DATA_CMD');
this.emit('leScanResponseDataSet', status);
} else if (cmd === LE_SET_ADVERTISE_ENABLE_CMD) {
debug('\t\tLE_SET_ADVERTISE_ENABLE_CMD');
this.emit('leAdvertiseEnableSet', status);
} else if (cmd === READ_RSSI_CMD) {
handle = result.readUInt16LE(0);
var rssi = result.readInt8(2);
debug('\t\tREAD_RSSI_CMD');
debug('\t\t\thandle = ' + handle);
debug('\t\t\trssi = ' + rssi);
this.emit('rssiRead', handle, rssi);
} else if (cmd === LE_LTK_NEG_REPLY_CMD) {
handle = result.readUInt16LE(0);
debug('\t\tLE_LTK_NEG_REPLY_CMD');
debug('\t\t\thandle = ' + handle);
this.emit('leLtkNegReply', handle);
} else {
debug('!\tUnhandled Command: ' + cmd);
}
};
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) {
var handle = data.readUInt16LE(0);
var role = data.readUInt8(2);
var addressType = data.readUInt8(3) === 0x01 ? 'random': 'public';
var address = data.slice(4, 10).toString('hex').match(/.{1,2}/g).reverse().join(':');
var interval = data.readUInt16LE(10) * 1.25;
var latency = data.readUInt16LE(12); // TODO: multiplier?
var supervisionTimeout = data.readUInt16LE(14) * 10;
var masterClockAccuracy = data.readUInt8(15); // 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.emit('leConnComplete', status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy);
};
Hci.prototype.processLeAdvertisingReport = function(status, data) {
var type = data.readUInt8(0); // ignore for now
var addressType = data.readUInt8(1) === 0x01 ? 'random' : 'public';
var address = data.slice(2, 8).toString('hex').match(/.{1,2}/g).reverse().join(':');
var eir = data.slice(9, data.length - 1);
var rssi = data.readInt8(data.length - 1);
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', status, type, address, addressType, eir, rssi);
};
Hci.prototype.processLeConnUpdateComplete = function(status, data) {
var handle = data.readUInt16LE(0);
var interval = data.readUInt16LE(2) * 1.25;
var latency = data.readUInt16LE(4); // TODO: multiplier?
var 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);
};
module.exports = Hci;