UNPKG

bleat

Version:

Abstraction library following Web Bluetooth specification for hiding differences in JavaScript BLE APIs

277 lines (251 loc) 11.4 kB
/* @license * * BLE Abstraction Tool: core functionality - classic specification * * The MIT License (MIT) * * Copyright (c) 2016 Rob Moran * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // https://github.com/umdjs/umd (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['bluetooth.helpers'], factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS module.exports = factory(require('./bluetooth.helpers')); } else { // Browser globals with support for web workers (root is window) root.bleat = factory(root.bleatHelpers); } }(this, function(helpers) { "use strict"; var adapter = null; var adapters = {}; // Helpers function raiseError(errorFn, msg) { return function(error) { if (errorFn) errorFn(msg + ": " + error); }; } function executeFn(fn) { return function() { if (typeof fn === "function") { var args = [].slice.call(arguments); fn.apply(this, args); } }; } function AsyncWait(finishFn, errorFn) { var count = 0; var callbackAdded = false; this.addCallback = function(fn) { count++; callbackAdded = true; return function() { if (fn) fn.apply(null, arguments); if (--count === 0 && finishFn) finishFn(); }; }; this.error = function() { if (errorFn) errorFn.apply(null, arguments); if (--count === 0 && finishFn) finishFn(); }; this.finish = function() { if (!callbackAdded && finishFn) finishFn(); }; } // Device Object var Device = function(deviceInfo) { this._handle = deviceInfo._handle; this.address = deviceInfo._handle; this.name = deviceInfo.name; this.serviceUUIDs = deviceInfo.uuids; this.adData = deviceInfo.adData; this.connected = false; this.services = {}; }; Device.prototype.hasService = function(serviceUUID) { return this.serviceUUIDs.some(function(uuid) { return (uuid === serviceUUID); }); }; Device.prototype.connect = function(connectFn, disconnectFn, errorFn, suppressDiscovery) { adapter.connect(this._handle, function() { this.connected = true; if (typeof errorFn === "boolean") { suppressDiscovery = errorFn; errorFn = null; } if (suppressDiscovery) return executeFn(connectFn)(); this.discoverAll(connectFn, errorFn); }.bind(this), function() { this.connected = false; this.services = {}; executeFn(disconnectFn)(); }.bind(this), raiseError(errorFn, "connect error")); }; Device.prototype.disconnect = function(errorFn) { adapter.disconnect(this._handle, raiseError(errorFn, "disconnect error")); }; Device.prototype.discoverServices = function(serviceUUIDs, completeFn, errorFn) { if (this.connected === false) return raiseError(errorFn, "discovery error")("device not connected"); if (typeof serviceUUIDs === "function") { completeFn = serviceUUIDs; serviceUUIDs = []; } else if (typeof serviceUUIDs === "string") { serviceUUIDs = [serviceUUIDs]; } adapter.discoverServices(this._handle, serviceUUIDs, function(services) { services.forEach(function(serviceInfo) { this.services[serviceInfo.uuid] = new Service(serviceInfo); }, this); if (completeFn) completeFn(); }.bind(this), raiseError(errorFn, "service discovery error")); }; Device.prototype.discoverAll = function(completeFn, errorFn) { if (this.connected === false) return raiseError(errorFn, "discovery error")("device not connected"); var wait = new AsyncWait(completeFn, errorFn); this.discoverServices(wait.addCallback(function() { Object.keys(this.services).forEach(function(serviceUUID) { var service = this.services[serviceUUID]; service.discoverIncludedServices(wait.addCallback(), wait.error); service.discoverCharacteristics(wait.addCallback(function() { Object.keys(service.characteristics).forEach(function(characteristicUUID) { var characteristic = service.characteristics[characteristicUUID]; characteristic.discoverDescriptors(wait.addCallback(), wait.error); }, this); }.bind(this)), wait.error); }, this); }.bind(this)), wait.error); wait.finish(); }; // Service Object var Service = function(serviceInfo) { this._handle = serviceInfo._handle; this.uuid = serviceInfo.uuid; this.primary = serviceInfo.primary; this.includedServices = {}; this.characteristics = {}; }; Service.prototype.discoverIncludedServices = function(serviceUUIDs, completeFn, errorFn) { if (typeof serviceUUIDs === "function") { completeFn = serviceUUIDs; serviceUUIDs = []; } else if (typeof serviceUUIDs === "string") { serviceUUIDs = [serviceUUIDs]; } adapter.discoverIncludedServices(this._handle, serviceUUIDs, function(services) { services.forEach(function(serviceInfo) { this.includedServices[serviceInfo.uuid] = new Service(serviceInfo); }, this); if (completeFn) completeFn(); }.bind(this), raiseError(errorFn, "included service discovery error")); }; Service.prototype.discoverCharacteristics = function(characteristicUUIDs, completeFn, errorFn) { if (typeof characteristicUUIDs === "function") { completeFn = characteristicUUIDs; characteristicUUIDs = []; } else if (typeof characteristicUUIDs === "string") { characteristicUUIDs = [characteristicUUIDs]; } adapter.discoverCharacteristics(this._handle, characteristicUUIDs, function(characteristics) { characteristics.forEach(function(characteristicInfo) { this.characteristics[characteristicInfo.uuid] = new Characteristic(characteristicInfo); }, this); if (completeFn) completeFn(); }.bind(this), raiseError(errorFn, "characteristic discovery error")); }; // Characteristic Object var Characteristic = function(characteristicInfo) { this._handle = characteristicInfo._handle; this.uuid = characteristicInfo.uuid; this.properties = characteristicInfo.properties; this.descriptors = {}; }; Characteristic.prototype.discoverDescriptors = function(descriptorUUIDs, completeFn, errorFn) { if (typeof descriptorUUIDs === "function") { completeFn = descriptorUUIDs; descriptorUUIDs = []; } else if (typeof descriptorUUIDs === "string") { descriptorUUIDs = [descriptorUUIDs]; } adapter.discoverDescriptors(this._handle, descriptorUUIDs, function(descriptors) { descriptors.forEach(function(descriptorInfo) { this.descriptors[descriptorInfo.uuid] = new Descriptor(descriptorInfo); }, this); if (completeFn) completeFn(); }.bind(this), raiseError(errorFn, "descriptor discovery error")); }; Characteristic.prototype.read = function(completeFn, errorFn) { adapter.readCharacteristic(this._handle, executeFn(completeFn), raiseError(errorFn, "read characteristic error")); }; Characteristic.prototype.write = function(dataView, completeFn, errorFn) { adapter.writeCharacteristic(this._handle, dataView, executeFn(completeFn), raiseError(errorFn, "write characteristic error")); }; Characteristic.prototype.enableNotify = function(notifyFn, completeFn, errorFn) { adapter.enableNotify(this._handle, executeFn(notifyFn), executeFn(completeFn), raiseError(errorFn, "enable notify error")); }; Characteristic.prototype.disableNotify = function(completeFn, errorFn) { adapter.disableNotify(this._handle, executeFn(completeFn), raiseError(errorFn, "disable notify error")); }; // Descriptor Object var Descriptor = function(descriptorInfo) { this._handle = descriptorInfo._handle; this.uuid = descriptorInfo.uuid; }; Descriptor.prototype.read = function(completeFn, errorFn) { adapter.readDescriptor(this._handle, executeFn(completeFn), raiseError(errorFn, "read descriptor error")); }; Descriptor.prototype.write = function(dataView, completeFn, errorFn) { adapter.writeDescriptor(this._handle, dataView, executeFn(completeFn), raiseError(errorFn, "write descriptor error")); }; // Main Module return { _addAdapter: function(adapterName, definition) { adapters[adapterName] = definition; adapter = definition; }, startScan: function(serviceUUIDs, foundFn, completeFn, errorFn, allowDuplicates) { if (typeof serviceUUIDs === "function") { // Service UUIDs not present, shift args. allowDuplicates = errorFn; errorFn = completeFn; completeFn = foundFn; foundFn = serviceUUIDs; serviceUUIDs = []; } else if (typeof serviceUUIDs === "string") { serviceUUIDs = [serviceUUIDs]; } adapter.stopScan(raiseError(errorFn, "stop scan error")); var devices = {}; adapter.startScan(serviceUUIDs, function(deviceInfo) { var device = new Device(deviceInfo); if (devices[device.address] && !allowDuplicates) return; devices[device.address] = device; if (foundFn) foundFn(device); }.bind(this), completeFn, raiseError(errorFn, "scan error")); }, stopScan: function(errorFn) { adapter.stopScan(raiseError(errorFn, "stop scan error")); } }; }));