UNPKG

@s89/ble-ancs

Version:

An Apple ANCS reciever from Linux. It is a combination of the Bleno, Noble and ANCS projects from Sandeep Mistry

672 lines (526 loc) 21.2 kB
var debug = require('debug')('Able'); var events = require('events'); var os = require('os'); var util = require('util'); var Peripheral = require('./peripheral'); var PrimaryService = require('./primary-service'); var Service = require('./service'); var RemoteCharacteristic = require('./remote-characteristic'); var RemoteDescriptor = require('./remote-descriptor'); var Characteristic = require('./local-characteristic'); var LocalDescriptor = require('./local-descriptor'); var bindings = null; var platform = os.platform(); if (platform === 'linux' || platform === 'win32') { bindings = require('./hci-socket/bindings'); } else { throw new Error('Unsupported platform'); } function Able() { this.state = 'unknown'; this._bindings = bindings; this._peripherals = {}; this._services = {}; this._characteristics = {}; this._descriptors = {}; this._discoveredPeripheralUUids = []; this._allowDuplicates = true; this._bindings._scanServiceUuids = []; this._bindings.on('stateChange', this.onStateChange.bind(this)); this._bindings.on('addressChange', this.onAddressChange.bind(this)); this._bindings.on('advertisingStart', this.onAdvertisingStart.bind(this)); this._bindings.on('advertisingStop', this.onAdvertisingStop.bind(this)); this._bindings.on('servicesSet', this.onServicesSet.bind(this)); this._bindings.on('accept', this.onAccept.bind(this)); this._bindings.on('mtuChange', this.onMtuChange.bind(this)); this._bindings.on('disconnect', this.onDisconnect.bind(this)); this._bindings.on('scanStart', this.onScanStart.bind(this)); this._bindings.on('scanStop', this.onScanStop.bind(this)); this._bindings.on('discover', this.onDiscover.bind(this)); this._bindings.on('connect', this.onConnect.bind(this)); this._bindings.on('rssiUpdate', this.onRssiUpdate.bind(this)); this._bindings.on('servicesDiscover', this.onServicesDiscover.bind(this)); this._bindings.on('includedServicesDiscover', this.onIncludedServicesDiscover.bind(this)); this._bindings.on('characteristicsDiscover', this.onCharacteristicsDiscover.bind(this)); this._bindings.on('read', this.onRead.bind(this)); this._bindings.on('write', this.onWrite.bind(this)); this._bindings.on('broadcast', this.onBroadcast.bind(this)); this._bindings.on('notify', this.onNotify.bind(this)); this._bindings.on('encryptChange', this.onEncryptChange.bind(this)); this._bindings.on('encryptFail', this.onEncryptFail.bind(this)); this._bindings.on('descriptorsDiscover', this.onDescriptorsDiscover.bind(this)); this._bindings.on('valueRead', this.onValueRead.bind(this)); this._bindings.on('valueWrite', this.onValueWrite.bind(this)); this._bindings.on('handleRead', this.onHandleRead.bind(this)); this._bindings.on('handleWrite', this.onHandleWrite.bind(this)); this._bindings.on('handleNotify', this.onHandleNotify.bind(this)); this.on('warning', function(message) { if (this.listeners('warning').length === 1) { console.warn('Able: ' + message); } }.bind(this)); } Able.prototype.PrimaryService = PrimaryService; Able.prototype.Characteristic = Characteristic; Able.prototype.Descriptor = LocalDescriptor; util.inherits(Able, events.EventEmitter); Able.prototype.onStateChange = function(state) { debug('stateChange ' + state); this.state = state; this.emit('stateChange', state); }; Able.prototype.onEncryptChange = function() { debug('encryptChange' ); this.emit('encryptChange'); }; Able.prototype.onEncryptFail = function() { debug('encryptFail ' ); this.emit('encryptFail'); }; Able.prototype.onAddressChange = function(address) { debug('addressChange ' + address); this.address = address; }; Able.prototype.onAccept = function(uuid, address, addressType) { debug('accept ' + address); var peripheral = this._peripherals[uuid]; var connectable = true; var advertisement = { localName: undefined, txPowerLevel: undefined, manufacturerData: undefined, serviceData: [], serviceUuids: [] }; var rssi = 127; if (!peripheral) { peripheral = new Peripheral(this, uuid, address, addressType, connectable, advertisement, rssi); peripheral.state = 'connected'; this._peripherals[uuid] = peripheral; this._services[uuid] = {}; this._characteristics[uuid] = {}; this._descriptors[uuid] = {}; } else { // "or" the advertisment data with existing /* for (var i in advertisement) { if (advertisement[i] !== undefined) { peripheral.advertisement[i] = advertisement[i]; } }*/ peripheral.rssi = rssi; } var previouslyDiscoverd = (this._discoveredPeripheralUUids.indexOf(uuid) !== -1); if (!previouslyDiscoverd) { this._discoveredPeripheralUUids.push(uuid); } this.emit('accept', peripheral); }; Able.prototype.onMtuChange = function(mtu) { debug('mtu ' + mtu); this.mtu = mtu; this.emit('mtuChange', mtu); }; Able.prototype.startScanning = function(serviceUuids, allowDuplicates, callback) { debug("Starting SCcanning"); if (this.state !== 'poweredOn') { var error = new Error('Could not start scanning, state is ' + this.state + ' (not poweredOn)'); if (typeof callback === 'function') { callback(error); } else { throw error; } } else { if (callback) { this.once('scanStart', callback); } this._discoveredPeripheralUUids = []; this._allowDuplicates = allowDuplicates; this._bindings.startScanning(serviceUuids, allowDuplicates); } }; Able.prototype.onScanStart = function() { debug('scanStart'); this.emit('scanStart'); }; Able.prototype.stopScanning = function(callback) { if (callback) { this.once('scanStop', callback); } this._bindings.stopScanning(); }; Able.prototype.onScanStop = function() { debug('scanStop'); this.emit('scanStop'); }; Able.prototype.onDiscover = function(uuid, address, addressType, connectable, advertisement, rssi) { var peripheral = this._peripherals[uuid]; if (!peripheral) { peripheral = new Peripheral(this, uuid, address, addressType, connectable, advertisement, rssi); this._peripherals[uuid] = peripheral; this._services[uuid] = {}; this._characteristics[uuid] = {}; this._descriptors[uuid] = {}; } else { // "or" the advertisment data with existing for (var i in advertisement) { if (advertisement[i] !== undefined) { peripheral.advertisement[i] = advertisement[i]; } } peripheral.rssi = rssi; } var previouslyDiscoverd = (this._discoveredPeripheralUUids.indexOf(uuid) !== -1); if (!previouslyDiscoverd) { this._discoveredPeripheralUUids.push(uuid); } if (this._allowDuplicates || !previouslyDiscoverd) { this.emit('discover', peripheral); } }; Able.prototype.connect = function(peripheralUuid) { this._bindings.connect(peripheralUuid); }; Able.prototype.onConnect = function(peripheralUuid, error) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { peripheral.state = error ? 'error' : 'connected'; peripheral.emit('connect', error); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' connected!'); } }; Able.prototype.disconnect = function(peripheralUuid) { this._bindings.disconnect(peripheralUuid); }; Able.prototype.onDisconnect = function(peripheralUuid) { var peripheral = this._peripherals[peripheralUuid]; debug("recieved disconnect"); this.emit('disconnect'); if (peripheral) { peripheral.state = 'disconnected'; peripheral.emit('disconnect'); } else { debug('unknown peripheral ' + peripheralUuid + ' disconnected!'); this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' disconnected!'); } }; Able.prototype.updateRssi = function(peripheralUuid) { this._bindings.updateRssi(peripheralUuid); }; Able.prototype.onRssiUpdate = function(peripheralUuid, rssi) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { peripheral.rssi = rssi; peripheral.emit('rssiUpdate', rssi); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' RSSI update!'); } }; Able.prototype.findHandlesForUuid = function(peripheralUuid, uuid) { var uuidBuf = new Buffer(uuid, 'hex'); this._bindings.findByTypeRequest(peripheralUuid, 0x0001,0xffff, 0x2800, uuidBuf); } Able.prototype.findService = function(peripheralUuid, uuid) { var uuidBuf = new Buffer(uuid, 'hex'); this._bindings.findByTypeRequest(peripheralUuid, 0x0001,0xffff, 0x2800, uuidBuf); } Able.prototype.discoverServices = function(peripheralUuid, uuids) { this._bindings.discoverServices(peripheralUuid, uuids); }; Able.prototype.onServicesDiscover = function(peripheralUuid, serviceUuids) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { var services = []; for (var i = 0; i < serviceUuids.length; i++) { var serviceUuid = serviceUuids[i]; var service = new Service(this, peripheralUuid, serviceUuid); this._services[peripheralUuid][serviceUuid] = service; this._characteristics[peripheralUuid][serviceUuid] = {}; this._descriptors[peripheralUuid][serviceUuid] = {}; services.push(service); } peripheral.services = services; peripheral.emit('servicesDiscover', services); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' services discover!'); } }; Able.prototype.discoverIncludedServices = function(peripheralUuid, serviceUuid, serviceUuids) { this._bindings.discoverIncludedServices(peripheralUuid, serviceUuid, serviceUuids); }; Able.prototype.onIncludedServicesDiscover = function(peripheralUuid, serviceUuid, includedServiceUuids) { var service = this._services[peripheralUuid][serviceUuid]; if (service) { service.includedServiceUuids = includedServiceUuids; service.emit('includedServicesDiscover', includedServiceUuids); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ' included services discover!'); } }; Able.prototype.discoverCharacteristics = function(peripheralUuid, serviceUuid, characteristicUuids) { this._bindings.discoverCharacteristics(peripheralUuid, serviceUuid, characteristicUuids); }; Able.prototype.onCharacteristicsDiscover = function(peripheralUuid, serviceUuid, characteristics) { var service = this._services[peripheralUuid][serviceUuid]; if (service) { var characteristics_ = []; for (var i = 0; i < characteristics.length; i++) { var characteristicUuid = characteristics[i].uuid; var characteristic = new RemoteCharacteristic( this, peripheralUuid, serviceUuid, characteristicUuid, characteristics[i].properties ); this._characteristics[peripheralUuid][serviceUuid][characteristicUuid] = characteristic; this._descriptors[peripheralUuid][serviceUuid][characteristicUuid] = {}; characteristics_.push(characteristic); } service.characteristics = characteristics_; service.emit('characteristicsDiscover', characteristics_); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ' characteristics discover!'); } }; Able.prototype.read = function(peripheralUuid, serviceUuid, characteristicUuid) { this._bindings.read(peripheralUuid, serviceUuid, characteristicUuid); }; Able.prototype.onRead = function(peripheralUuid, serviceUuid, characteristicUuid, data, isNotification) { var characteristic = this._characteristics[peripheralUuid][serviceUuid][characteristicUuid]; if (characteristic) { characteristic.emit('data', data, isNotification); characteristic.emit('read', data, isNotification); // for backwards compatbility } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ' read!'); } }; Able.prototype.write = function(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse) { this._bindings.write(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse); }; Able.prototype.onWrite = function(peripheralUuid, serviceUuid, characteristicUuid) { var characteristic = this._characteristics[peripheralUuid][serviceUuid][characteristicUuid]; if (characteristic) { characteristic.emit('write'); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ' write!'); } }; Able.prototype.broadcast = function(peripheralUuid, serviceUuid, characteristicUuid, broadcast) { this._bindings.broadcast(peripheralUuid, serviceUuid, characteristicUuid, broadcast); }; Able.prototype.onBroadcast = function(peripheralUuid, serviceUuid, characteristicUuid, state) { var characteristic = this._characteristics[peripheralUuid][serviceUuid][characteristicUuid]; if (characteristic) { characteristic.emit('broadcast', state); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ' broadcast!'); } }; Able.prototype.notify = function(peripheralUuid, serviceUuid, characteristicUuid, notify) { this._bindings.notify(peripheralUuid, serviceUuid, characteristicUuid, notify); }; Able.prototype.onNotify = function(peripheralUuid, serviceUuid, characteristicUuid, state) { var characteristic = this._characteristics[peripheralUuid][serviceUuid][characteristicUuid]; if (characteristic) { characteristic.emit('notify', state); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ' notify!'); } }; Able.prototype.discoverDescriptors = function(peripheralUuid, serviceUuid, characteristicUuid) { this._bindings.discoverDescriptors(peripheralUuid, serviceUuid, characteristicUuid); }; Able.prototype.onDescriptorsDiscover = function(peripheralUuid, serviceUuid, characteristicUuid, descriptors) { var characteristic = this._characteristics[peripheralUuid][serviceUuid][characteristicUuid]; if (characteristic) { var descriptors_ = []; for (var i = 0; i < descriptors.length; i++) { var descriptorUuid = descriptors[i]; var descriptor = new Descriptor( this, peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid ); this._descriptors[peripheralUuid][serviceUuid][characteristicUuid][descriptorUuid] = descriptor; descriptors_.push(descriptor); } characteristic.descriptors = descriptors_; characteristic.emit('descriptorsDiscover', descriptors_); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ' descriptors discover!'); } }; Able.prototype.readValue = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid) { this._bindings.readValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid); }; Able.prototype.onValueRead = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data) { var descriptor = this._descriptors[peripheralUuid][serviceUuid][characteristicUuid][descriptorUuid]; if (descriptor) { descriptor.emit('valueRead', data); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ', ' + descriptorUuid + ' value read!'); } }; Able.prototype.writeValue = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data) { this._bindings.writeValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data); }; Able.prototype.onValueWrite = function(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid) { var descriptor = this._descriptors[peripheralUuid][serviceUuid][characteristicUuid][descriptorUuid]; if (descriptor) { descriptor.emit('valueWrite'); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ', ' + serviceUuid + ', ' + characteristicUuid + ', ' + descriptorUuid + ' value write!'); } }; Able.prototype.readHandle = function(peripheralUuid, handle) { this._bindings.readHandle(peripheralUuid, handle); }; Able.prototype.onHandleRead = function(peripheralUuid, handle, data) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { peripheral.emit('handleRead' + handle, data); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' handle read!'); } }; Able.prototype.writeHandle = function(peripheralUuid, handle, data, withoutResponse) { this._bindings.writeHandle(peripheralUuid, handle, data, withoutResponse); }; Able.prototype.onHandleWrite = function(peripheralUuid, handle) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { peripheral.emit('handleWrite' + handle); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' handle write!'); } }; Able.prototype.onHandleNotify = function(peripheralUuid, handle, data) { var peripheral = this._peripherals[peripheralUuid]; if (peripheral) { peripheral.emit('handleNotify', handle, data); } else { this.emit('warning', 'unknown peripheral ' + peripheralUuid + ' handle notify!'); } }; /*Bleno start */ Able.prototype.startAdvertising = function(name, serviceUuids, callback) { if (this.state !== 'poweredOn') { var error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); if (typeof callback === 'function') { callback(error); } else { throw error; } } else { if (callback) { this.once('advertisingStart', callback); } var undashedServiceUuids = []; if (serviceUuids && serviceUuids.length) { for (var i = 0; i < serviceUuids.length; i++) { undashedServiceUuids[i] = UuidUtil.removeDashes(serviceUuids[i]); } } this._bindings.startAdvertising(name, undashedServiceUuids); } }; Able.prototype.startAdvertisingIBeacon = function(uuid, major, minor, measuredPower, callback) { if (this.state !== 'poweredOn') { var error = new Error('Could not start advertising, state is ' + this.state + ' (not poweredOn)'); if (typeof callback === 'function') { callback(error); } else { throw error; } } else { var undashedUuid = UuidUtil.removeDashes(uuid); var uuidData = new Buffer(undashedUuid, 'hex'); var uuidDataLength = uuidData.length; var iBeaconData = new Buffer(uuidData.length + 5); for (var i = 0; i < uuidDataLength; i++) { iBeaconData[i] = uuidData[i]; } iBeaconData.writeUInt16BE(major, uuidDataLength); iBeaconData.writeUInt16BE(minor, uuidDataLength + 2); iBeaconData.writeInt8(measuredPower, uuidDataLength + 4); if (callback) { this.once('advertisingStart', callback); } debug('iBeacon data = ' + iBeaconData.toString('hex')); this._bindings.startAdvertisingIBeacon(iBeaconData); } }; Able.prototype.onAdvertisingStart = function(error) { debug('advertisingStart: ' + error); if (error) { this.emit('advertisingStartError', error); } this.emit('advertisingStart', error); }; if (platform === 'linux') { // Linux only API Able.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData, callback) { if (this.state !== 'poweredOn') { var error = new Error('Could not advertising scanning, state is ' + this.state + ' (not poweredOn)'); if (typeof callback === 'function') { callback(error); } else { throw error; } } else { if (callback) { this.once('advertisingStart', callback); } this._bindings.startAdvertisingWithEIRData(advertisementData, scanData); } }; } Able.prototype.stopAdvertising = function(callback) { if (callback) { this.once('advertisingStop', callback); } this._bindings.stopAdvertising(); }; Able.prototype.onAdvertisingStop = function() { debug('advertisingStop'); this.emit('advertisingStop'); }; Able.prototype.setServices = function(services, callback) { if (callback) { this.once('servicesSet', callback); } this._bindings.setServices(services); }; Able.prototype.onServicesSet = function(error) { debug('servicesSet'); if (error) { this.emit('servicesSetError', error); } this.emit('servicesSet', error); }; if (platform === 'linux') { // Linux only API Able.prototype.disconnect = function() { debug('disconnect'); this._bindings.disconnect(); }; } Able.prototype.updateRssi = function(callback) { if (callback) { this.once('rssiUpdate', function(rssi) { callback(null, rssi); }); } this._bindings.updateRssi(); }; Able.prototype.onRssiUpdate = function(rssi) { this.emit('rssiUpdate', rssi); }; /* Able End */ module.exports = Able;