@s89/ble-ancs
Version:
An Apple ANCS reciever from Linux. It is a combination of the Bleno, Noble and ANCS projects from Sandeep Mistry
678 lines (502 loc) • 20.3 kB
JavaScript
var debug = require('debug')('bindings');
var events = require('events');
var util = require('util');
var AclStream = require('./acl-stream');
var Gatt = require('./gatt');
var LocalGatt = require('./local-gatt');
var Gap = require('./gap');
var Hci = require('./hci');
var AbleBindings = function() {
this._state = null;
this._addresses = {};
this._addresseTypes = {};
this._connectable = {};
this._pendingConnection = false;
this._connectionQueue = [];
this._handles = {};
this._gatts = {};
this._aclStreams = {};
this._hci = new Hci();
this._hci.init();
this._gatt = new LocalGatt(this._hci);
this._gap = new Gap(this._hci);
this.onSigIntBinded = this.onSigInt.bind(this);
process.on('SIGINT', this.onSigIntBinded);
process.on('exit', this.onExit.bind(this));
this._gatt.on('handleMtuRequest', this.onMtu.bind(this));
this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this));
this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this));
this._hci.on('addressChange', this.onAddressChange.bind(this));
this._gap.on('scanStart', this.onScanStart.bind(this));
this._gap.on('scanStop', this.onScanStop.bind(this));
this._gap.on('discover', this.onDiscover.bind(this));
this._hci.on('stateChange', this.onStateChange.bind(this));
this._hci.on('leConnComplete', this.onLeConnComplete.bind(this));
this._hci.on('leConnUpdateComplete', this.onLeConnUpdateComplete.bind(this));
this._hci.on('rssiRead', this.onRssiRead.bind(this));
this._hci.on('disconnComplete', this.onDisconnComplete.bind(this));
this._hci.on('encryptChange', this.onEncryptChange.bind(this));
this._hci.on('leLtkNegReply', this.onLeLtkNegReply.bind(this));
this._hci.on('aclDataPkt', this.onAclDataPkt.bind(this));
//debug('registered listeners for disconnComplete: ' + this._hci.listenerCount('disconnComplete'));
};
util.inherits(AbleBindings, events.EventEmitter);
AbleBindings.prototype.startScanning = function(serviceUuids, allowDuplicates) {
this._scanServiceUuids = serviceUuids || [];
this._gap.startScanning(allowDuplicates);
};
AbleBindings.prototype.stopScanning = function() {
this._gap.stopScanning();
};
AbleBindings.prototype.connect = function(peripheralUuid) {
var address = this._addresses[peripheralUuid];
var addressType = this._addresseTypes[peripheralUuid];
console.log("Connect - uuid: " + peripheralUuid + " addres: " + address);
if (!this._pendingConnection) {
this._pendingConnection = true;
this._hci.createLeConn(address, addressType);
} else {
this._connectionQueue.push(peripheralUuid);
}
};
AbleBindings.prototype.disconnect = function(peripheralUuid) {
this._hci.disconnect(this._handles[peripheralUuid]);
};
AbleBindings.prototype.updateRssi = function(peripheralUuid) {
this._hci.readRssi(this._handles[peripheralUuid]);
};
AbleBindings.prototype.init = function() {
// no-op
};
AbleBindings.prototype.onSigInt = function() {
var sigIntListeners = process.listeners('SIGINT');
if (sigIntListeners[sigIntListeners.length - 1] === this.onSigIntBinded) {
// we are the last listener, so exit
// this will trigger onExit, and clean up
process.exit(1);
}
};
AbleBindings.prototype.onExit = function() {
this.stopScanning();
for (var handle in this._aclStreams) {
this._hci.disconnect(handle);
}
//Bleno
this._gap.stopAdvertising();
this.disconnect();
};
AbleBindings.prototype.onStateChange = function(state) {
if (this._state === state) {
return;
}
this._state = state;
if (state === 'unauthorized') {
console.log('able warning: adapter state unauthorized, please run as root or with sudo');
console.log(' or see README for information on running without root/sudo:');
console.log(' https://github.com/sandeepmistry/able#running-on-linux');
} else if (state === 'unsupported') {
console.log('able warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).');
console.log(' Try to run with environment variable:');
console.log(' [sudo] NOBLE_HCI_DEVICE_ID=x node ...');
}
this.emit('stateChange', state);
};
AbleBindings.prototype.onScanStart = function() {
this.emit('scanStart');
};
AbleBindings.prototype.onScanStop = function() {
this.emit('scanStop');
};
AbleBindings.prototype.onDiscover = function(status, address, addressType, connectable, advertisement, rssi) {
if (this._scanServiceUuids === undefined) {
return;
}
var serviceUuids = advertisement.serviceUuids;
var hasScanServiceUuids = (this._scanServiceUuids.length === 0);
if (!hasScanServiceUuids) {
for (var i in serviceUuids) {
hasScanServiceUuids = (this._scanServiceUuids.indexOf(serviceUuids[i]) !== -1);
if (hasScanServiceUuids) {
break;
}
}
}
if (hasScanServiceUuids) {
var uuid = address.split(':').join('');
this._addresses[uuid] = address;
this._addresseTypes[uuid] = addressType;
this._connectable[uuid] = connectable;
this.emit('discover', uuid, address, addressType, connectable, advertisement, rssi);
}
};
AbleBindings.prototype.onLeConnComplete = function(status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) {
var uuid = address.split(':').join('').toLowerCase();
var error = null;
debug("Conn Complete - status: " + status + " role: " + role + " Address: " + address + " Type: " + addressType);
if (status === 0) {
var aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address);
var gatt = new Gatt(address, aclStream);
// Bleno Code
this._address = address;
this._handle = handle;
//end bleno code
this._gatts[uuid] = this._gatts[handle] = gatt;
this._aclStreams[handle] = aclStream;
this._handles[uuid] = handle;
this._handles[handle] = uuid;
this._gatts[handle].on('mtu', this.onMtu.bind(this));
this._gatts[handle].on('servicesDiscover', this.onServicesDiscovered.bind(this));
this._gatts[handle].on('includedServicesDiscover', this.onIncludedServicesDiscovered.bind(this));
this._gatts[handle].on('characteristicsDiscover', this.onCharacteristicsDiscovered.bind(this));
this._gatts[handle].on('read', this.onRead.bind(this));
this._gatts[handle].on('write', this.onWrite.bind(this));
this._gatts[handle].on('broadcast', this.onBroadcast.bind(this));
this._gatts[handle].on('notify', this.onNotify.bind(this));
this._gatts[handle].on('notification', this.onNotification.bind(this));
this._gatts[handle].on('descriptorsDiscover', this.onDescriptorsDiscovered.bind(this));
this._gatts[handle].on('valueRead', this.onValueRead.bind(this));
this._gatts[handle].on('valueWrite', this.onValueWrite.bind(this));
this._gatts[handle].on('handleRead', this.onHandleRead.bind(this));
this._gatts[handle].on('handleWrite', this.onHandleWrite.bind(this));
this._gatts[handle].on('handleNotify', this.onHandleNotify.bind(this));
this._gatts[handle].on('encryptFail', this.onEncryptFail.bind(this));
} else {
error = new Error(Hci.STATUS_MAPPER[status] || ('Unknown (' + status + ')'));
}
if (role == 1) {
//bleno
//this._gatts[handle].setServices([]);
this._gatt.setAclStream(aclStream);
this._gatts[handle]._handles = [];
console.log("Bleno: Handle: " + handle + " UUID: " + uuid + " address " + address);
if (this._connectionQueue.length > 0) {
var peripheralUuid = this._connectionQueue.shift();
console.log("Perh: " + peripheralUuid + " address: " + address);
}
this._addresses[uuid] = address;
this._addresseTypes[uuid] = addressType;
this.emit('accept', uuid, addressType,address);
return;
} else {
this._gatts[handle].exchangeMtu(256);
this.emit('connect', uuid, error);
if (this._connectionQueue.length > 0) {
var peripheralUuid = this._connectionQueue.shift();
address = this._addresses[peripheralUuid];
addressType = this._addresseTypes[peripheralUuid];
this._hci.createLeConn(address, addressType);
} else {
this._pendingConnection = false;
}
}
};
AbleBindings.prototype.onLeConnUpdateComplete = function(status, handle, interval, latency, supervisionTimeout) {
// no-op
debug("onLeConnUpdateComplete - Not sure what to do... not doing anything");
if (status === 0) {
//end bleno code
debug('\t\taddress: ' + this._address);
debug('\t\thandle: ' + this._handle);
debug('\t\tgatt: ' + this._gatts[handle]);
debug('\t\thandles: ' + this._handles[handle]);
} else {
error = new Error(Hci.STATUS_MAPPER[status] || ('Unknown (' + status + ')'));
debug("Error: " + error);
}
};
AbleBindings.prototype.onDisconnComplete = function(handle, reason) {
debug('disconnComplete');
var uuid = this._handles[handle];
console.log('\t\tHande:' + handle + '\tuuid: ' + uuid );
if (uuid) {
this._aclStreams[handle].push(null, null);
this._gatts[handle].removeAllListeners();
delete this._gatts[uuid];
delete this._gatts[handle];
delete this._aclStreams[handle];
delete this._handles[uuid];
delete this._handles[handle];
debug('\t\tEmmitting disconnect');
this.emit('disconnect', uuid); // TODO: handle reason?
} else {
debug('unknown handle ' + handle + ' disconnected!');
}
//This is for Bleno
if (this._aclStream) {
this._aclStream.push(null, null);
}
var address = this._address;
this._address = null;
this._handle = null;
this._aclStream = null;
if (address) {
debug('\t\tEmmitting disconnect');
this.emit('disconnect', address); // TODO: use reason
}
if (this._advertising) {
debug('\t\tRestarting adverstising');
this._gap.restartAdvertising();
}
};
AbleBindings.prototype.onLeLtkNegReply = function(handle) {
if (this._handle === handle && this._aclStream) {
this._aclStream.pushLtkNegReply();
}
};
AbleBindings.prototype.onEncryptFail = function(aclStream) {
/* if (this._handle === handle && this._aclStream) {
this._aclStream.pushEncrypt(encrypt);
}*/
debug("onEncryptFail");
/*
if (aclStream) {
debug("Trying Pairing Again");
aclStream.pushEncrypt(true);
}*/
};
AbleBindings.prototype.onEncryptChange = function(handle, encrypt) {
/* if (this._handle === handle && this._aclStream) {
this._aclStream.pushEncrypt(encrypt);
}*/
var aclStream = this._aclStreams[handle];
debug("onEncryptChange: " + handle);
this.emit("encryptChange");
if (aclStream) {
aclStream.pushEncrypt(encrypt);
}
};
AbleBindings.prototype.onMtu = function(address, mtu) {
this.emit('mtuChange', mtu);
};
AbleBindings.prototype.onRssiRead = function(handle, rssi) {
this.emit('rssiUpdate', this._handles[handle], rssi);
/* Bleno
this.emit('rssiUpdate', rssi);
*/
};
AbleBindings.prototype.onAclDataPkt = function(handle, cid, data) {
var aclStream = this._aclStreams[handle];
if (aclStream) {
aclStream.push(cid, data);
}
/* Bleno
if (this._handle === handle && this._aclStream) {
this._aclStream.push(cid, data);
}
*/
};
AbleBindings.prototype.findByTypeRequest = function(peripheralUuid, startHandle, endHandle, uuid, value) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.findByTypeRequest(startHandle, endHandle, uuid, value);
} else {
console.warn('able warning: FindByTypeRequest unknown peripheral ' + peripheralUuid);
}
}
AbleBindings.prototype.discoverServices = function(peripheralUuid, uuids) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.discoverServices(uuids || []);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onServicesDiscovered = function(address, serviceUuids) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('servicesDiscover', uuid, serviceUuids);
};
AbleBindings.prototype.discoverIncludedServices = function(peripheralUuid, serviceUuid, serviceUuids) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.discoverIncludedServices(serviceUuid, serviceUuids || []);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onIncludedServicesDiscovered = function(address, serviceUuid, includedServiceUuids) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('includedServicesDiscover', uuid, serviceUuid, includedServiceUuids);
};
AbleBindings.prototype.discoverCharacteristics = function(peripheralUuid, serviceUuid, characteristicUuids) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.discoverCharacteristics(serviceUuid, characteristicUuids || []);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onCharacteristicsDiscovered = function(address, serviceUuid, characteristics) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('characteristicsDiscover', uuid, serviceUuid, characteristics);
};
AbleBindings.prototype.read = function(peripheralUuid, serviceUuid, characteristicUuid) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.read(serviceUuid, characteristicUuid);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onRead = function(address, serviceUuid, characteristicUuid, data) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('read', uuid, serviceUuid, characteristicUuid, data, false);
};
AbleBindings.prototype.write = function(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.write(serviceUuid, characteristicUuid, data, withoutResponse);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onWrite = function(address, serviceUuid, characteristicUuid) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('write', uuid, serviceUuid, characteristicUuid);
};
AbleBindings.prototype.broadcast = function(peripheralUuid, serviceUuid, characteristicUuid, broadcast) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.broadcast(serviceUuid, characteristicUuid, broadcast);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onBroadcast = function(address, serviceUuid, characteristicUuid, state) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('broadcast', uuid, serviceUuid, characteristicUuid, state);
};
AbleBindings.prototype.notify = function(peripheralUuid, serviceUuid, characteristicUuid, notify) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.notify(serviceUuid, characteristicUuid, notify);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onNotify = function(address, serviceUuid, characteristicUuid, state) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('notify', uuid, serviceUuid, characteristicUuid, state);
};
AbleBindings.prototype.onNotification = function(address, serviceUuid, characteristicUuid, data) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('read', uuid, serviceUuid, characteristicUuid, data, true);
};
AbleBindings.prototype.discoverDescriptors = function(peripheralUuid, serviceUuid, characteristicUuid) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.discoverDescriptors(serviceUuid, characteristicUuid);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onDescriptorsDiscovered = function(address, serviceUuid, characteristicUuid, descriptorUuids) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('descriptorsDiscover', uuid, serviceUuid, characteristicUuid, descriptorUuids);
};
AbleBindings.prototype.readValue = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.readValue(serviceUuid, characteristicUuid, descriptorUuid);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onValueRead = function(address, serviceUuid, characteristicUuid, descriptorUuid, data) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('valueRead', uuid, serviceUuid, characteristicUuid, descriptorUuid, data);
};
AbleBindings.prototype.writeValue = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.writeValue(serviceUuid, characteristicUuid, descriptorUuid, data);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onValueWrite = function(address, serviceUuid, characteristicUuid, descriptorUuid) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('valueWrite', uuid, serviceUuid, characteristicUuid, descriptorUuid);
};
AbleBindings.prototype.readHandle = function(peripheralUuid, attHandle) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.readHandle(attHandle);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onHandleRead = function(address, handle, data) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('handleRead', uuid, handle, data);
};
AbleBindings.prototype.writeHandle = function(peripheralUuid, attHandle, data, withoutResponse) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
if (gatt) {
gatt.writeHandle(attHandle, data, withoutResponse);
} else {
console.warn('able warning: unknown peripheral ' + peripheralUuid);
}
};
AbleBindings.prototype.onHandleWrite = function(address, handle) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('handleWrite', uuid, handle);
};
AbleBindings.prototype.onHandleNotify = function(address, handle, data) {
var uuid = address.split(':').join('').toLowerCase();
this.emit('handleNotify', uuid, handle, data);
};
AbleBindings.prototype.startAdvertising = function(name, serviceUuids) {
this._advertising = true;
this._gap.startAdvertising(name, serviceUuids);
};
AbleBindings.prototype.startAdvertisingIBeacon = function(data) {
this._advertising = true;
this._gap.startAdvertisingIBeacon(data);
};
AbleBindings.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData) {
this._advertising = true;
this._gap.startAdvertisingWithEIRData(advertisementData, scanData);
};
AbleBindings.prototype.stopAdvertising = function() {
this._advertising = false;
this._gap.stopAdvertising();
};
AbleBindings.prototype.setServices = function(services) {
this._gatt.setServices(services);
debug('Trying to set services');
this.emit('servicesSet');
};
AbleBindings.prototype.disconnect = function() {
if (this._handle) {
debug('disconnect by server');
this._hci.disconnect(this._handle);
}
};
AbleBindings.prototype.updateRssi = function() {
if (this._handle) {
this._hci.readRssi(this._handle);
}
};
AbleBindings.prototype.onAddressChange = function(address) {
this.emit('addressChange', address);
};
AbleBindings.prototype.onAdvertisingStart = function(error) {
this.emit('advertisingStart', error);
};
AbleBindings.prototype.onAdvertisingStop = function() {
this.emit('advertisingStop');
};
module.exports = new AbleBindings();