UNPKG

@stoprocent/bleno

Version:

A Node.js module for implementing BLE (Bluetooth Low Energy) peripherals

234 lines (178 loc) 6.24 kB
const debug = require('debug')('bindings'); const { EventEmitter } = require('events'); const os = require('os'); const AclStream = require('./acl-stream'); const Hci = require('./hci'); const Gap = require('./gap'); const Gatt = require('./gatt'); class BlenoBindings extends EventEmitter { constructor (options) { super(); this._state = null; this._advertising = false; this._hci = new Hci(options); this._gap = new Gap(this._hci); this._gatt = new Gatt(this._hci); this._connections = new Map(); } setAddress (address) { this._hci.setAddress(address); } startAdvertising (name, serviceUuids) { this._advertising = true; this._gatt.updateName(name); this._gap.startAdvertising(name, serviceUuids); } startAdvertisingIBeacon (data) { this._advertising = true; this._gap.startAdvertisingIBeacon(data); } startAdvertisingWithEIRData (advertisementData, scanData) { this._advertising = true; this._gap.startAdvertisingWithEIRData(advertisementData, scanData); } stopAdvertising () { this._advertising = false; this._gap.stopAdvertising(); } setServices (services) { this._gatt.setServices(services); this.emit('servicesSet'); } disconnect (handle = null) { if (handle) { debug('disconnect by server for handle', handle); this._hci.disconnect(handle); } else { for (const [handle, connection] of this._connections) { debug('disconnect by server for handle', handle, connection.address); this._hci.disconnect(handle); } } } updateRssi () { if (this._handle) { this._hci.readRssi(this._handle); } } init () { this._sigIntHandler = this.onSigInt.bind(this); this._exitHandler = this.onExit.bind(this); process.on('SIGINT', this._sigIntHandler); process.on('exit', this._exitHandler); this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this)); this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this)); this._gatt.on('mtuChange', this.onMtuChange.bind(this)); this._hci.on('stateChange', this.onStateChange.bind(this)); this._hci.on('addressChange', this.onAddressChange.bind(this)); this._hci.on('readLocalVersion', this.onReadLocalVersion.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)); this.emit('platform', os.platform()); this._hci.init(); } stop () { process.removeListener('SIGINT', this._sigIntHandler); process.removeListener('exit', this._exitHandler); this.stopAdvertising(); this.disconnect(); this._hci.stop(); } onStateChange (state) { if (this._state === state) { return; } this._state = state; if (state === 'unauthorized') { console.log('Bleno 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/abandonware/bleno#running-on-linux'); } else if (state === 'unsupported') { console.log('Bleno warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).'); console.log(' Try to run with environment variable:'); console.log(' [sudo] BLENO_HCI_DEVICE_ID=x node ...'); } this.emit('stateChange', state); } onAddressChange (address) { this.emit('addressChange', address); } onReadLocalVersion (hciVer, hciRev, lmpVer, manufacturer, lmpSubVer) { } onAdvertisingStart (error) { this.emit('advertisingStart', error); } onAdvertisingStop () { this.emit('advertisingStop'); } onLeConnComplete (status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) { if (role !== 1) { return; } const aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address); this._connections.set(handle, { address, aclStream }); this._gatt.registerAclStream(aclStream, handle); this.emit('accept', address, handle); if (this._advertising) { this._gap.restartAdvertising(); } } onLeConnUpdateComplete (handle, interval, latency, supervisionTimeout) { // no-op } onDisconnComplete (handle, reason) { if (this._connections.has(handle) === false) { return; } const { address, aclStream } = this._connections.get(handle); if (aclStream) { aclStream.push(handle, null, null); } this._connections.delete(handle); this.emit('disconnect', address, handle); if (this._advertising) { this._gap.restartAdvertising(); } } onEncryptChange (handle, encrypt) { const connection = this._connections.get(handle); if (connection && connection.aclStream) { connection.aclStream.pushEncrypt(encrypt); } } onLeLtkNegReply (handle) { const connection = this._connections.get(handle); if (connection && connection.aclStream) { connection.aclStream.pushLtkNegReply(); } } onMtuChange (mtu) { this.emit('mtuChange', mtu); } onRssiRead (handle, rssi) { this.emit('rssiUpdate', rssi); } onAclDataPkt (handle, cid, data) { const connection = this._connections.get(handle); if (connection && connection.aclStream) { connection.aclStream.push(handle, cid, data); } } onSigInt () { const sigIntListeners = process.listeners('SIGINT'); if (sigIntListeners[sigIntListeners.length - 1] === this._sigIntHandler) { // we are the last listener, so exit // this will trigger onExit, and clean up process.exit(1); } } onExit () { this.stop(); } } module.exports = BlenoBindings;