UNPKG

web-bluetooth

Version:

Library for interacting with Bluetooth 4.0 devices through the browser.

360 lines (309 loc) 16.4 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var bluetooth = require('./bluetoothMap'); var errorHandler = require('./errorHandler'); /** BluetoothDevice - * * @method connect - Establishes a connection with the device * @method connected - checks apiDevice to see whether device is connected * @method disconnect - terminates the connection with the device and pauses all data stream subscriptions * @method getValue - reads the value of a specified characteristic * @method writeValue - writes data to a specified characteristic of the device * @method startNotifications - attempts to start notifications for changes to device values and attaches an event listener for each data transmission * @method stopNotifications - attempts to stop previously started notifications for a provided characteristic * @method addCharacteristic - adds a new characteristic object to bluetooth.gattCharacteristicsMapping * @method _returnCharacteristic - _returnCharacteristic - returns the value of a cached or resolved characteristic or resolved characteristic * * @param {object} filters - collection of filters for device selectin. All filters are optional, but at least 1 is required. * .name {string} * .namePrefix {string} * .uuid {string} * .services {array} * .optionalServices {array} - defaults to all available services, use an empty array to get no optional services * * @return {object} Returns a new instance of BluetoothDevice * */ var BluetoothDevice = function () { function BluetoothDevice(requestParams) { _classCallCheck(this, BluetoothDevice); this.requestParams = requestParams; this.apiDevice = null; this.apiServer = null; this.cache = {}; } _createClass(BluetoothDevice, [{ key: 'connected', value: function connected() { return this.apiDevice ? this.apiDevice.gatt.connected : errorHandler('no_device'); } /** connect - establishes a connection with the device * * NOTE: This method must be triggered by a user gesture to satisfy the native API's permissions * * @return {object} - native browser API device server object */ }, { key: 'connect', value: function connect() { var _this = this; var filters = this.requestParams; var requestParams = { filters: [] }; var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]/; if (!Object.keys(filters).length) { return errorHandler('no_filters'); } if (filters.name) requestParams.filters.push({ name: filters.name }); if (filters.namePrefix) requestParams.filters.push({ namePrefix: filters.namePrefix }); if (filters.uuid) { if (!filters.uuid.match(uuidRegex)) { errorHandler('uuid_error'); } else { requestParams.filters.push({ uuid: filters.uuid }); } } if (filters.services) { (function () { var services = []; filters.services.forEach(function (service) { if (!bluetooth.gattServiceList.includes(service)) { console.warn(service + ' is not a valid service. Please check the service name.'); } else { services.push(service); } }); requestParams.filters.push({ services: services }); })(); } if (filters.optional_services) { filters.optional_services.forEach(function (service) { if (!bluetooth.gattServiceList.includes(service)) bluetooth.gattServiceList.push(service); }); } else { requestParams.optionalServices = bluetooth.gattServiceList; } return navigator.bluetooth.requestDevice(requestParams).then(function (device) { _this.apiDevice = device; return device.gatt.connect(); }).then(function (server) { _this.apiServer = server; return server; }).catch(function (err) { return errorHandler('user_cancelled', err); }); } /** disconnect - terminates the connection with the device and pauses all data stream subscriptions * @return {boolean} - success * */ }, { key: 'disconnect', value: function disconnect() { this.apiServer.connected ? this.apiServer.disconnect() : errorHandler('not_connected'); return this.apiServer.connected ? errorHandler('issue_disconnecting') : true; } /** getValue - reads the value of a specified characteristic * * @param {string} characteristic_name - GATT characteristic name * @return {promise} - resolves with an object that includes key-value pairs for each of the properties * successfully read and parsed from the device, as well as the * raw value object returned by a native readValue request to the * device characteristic. */ }, { key: 'getValue', value: function getValue(characteristic_name) { var _this2 = this; if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } var characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; if (!characteristicObj.includedProperties.includes('read')) { console.warn('Attempting to access read property of ' + characteristic_name + ',\n which is not a included as a supported property of the\n characteristic. Attempt will resolve with an object including\n only a rawValue property with the native API return\n for an attempt to readValue() of ' + characteristic_name + '.'); } return new Promise(function (resolve, reject) { return resolve(_this2._returnCharacteristic(characteristic_name)); }).then(function (characteristic) { return characteristic.readValue(); }).then(function (value) { var returnObj = characteristicObj.parseValue ? characteristicObj.parseValue(value) : {}; returnObj.rawValue = value; return returnObj; }).catch(function (err) { return errorHandler('read_error', err); }); } /** writeValue - writes data to a specified characteristic of the device * * @param {string} characteristic_name - name of the GATT characteristic * https://www.bluetooth.com/specifications/assigned-numbers/generic-attribute-profile * * @param {string|number} value - value to write to the requested device characteristic * * * @return {boolean} - Result of attempt to write characteristic where true === successfully written */ }, { key: 'writeValue', value: function writeValue(characteristic_name, value) { var _this3 = this; if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } var characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; if (!characteristicObj.includedProperties.includes('write')) { console.warn('Attempting to access write property of ' + characteristic_name + ',\n which is not a included as a supported property of the\n characteristic. Attempt will resolve with native API return\n for an attempt to writeValue(' + value + ') to ' + characteristic_name + '.'); } return new Promise(function (resolve, reject) { return resolve(_this3._returnCharacteristic(characteristic_name)); }).then(function (characteristic) { return characteristic.writeValue(characteristicObj.prepValue ? characteristicObj.prepValue(value) : value); }).then(function (changedChar) { return true; }).catch(function (err) { return errorHandler('write_error', err, characteristic_name); }); } /** startNotifications - attempts to start notifications for changes to device values and attaches an event listener for each data transmission * * @param {string} characteristic_name - GATT characteristic name * @param {callback} transmissionCallback - callback function to apply to each event while notifications are active * * @return * */ }, { key: 'startNotifications', value: function startNotifications(characteristic_name, transmissionCallback) { var _this4 = this; if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } var characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; var primary_service_name = characteristicObj.primaryServices[0]; if (!characteristicObj.includedProperties.includes('notify')) { console.warn('Attempting to access notify property of ' + characteristic_name + ',\n which is not a included as a supported property of the\n characteristic. Attempt will resolve with an object including\n only a rawValue property with the native API return\n for an attempt to startNotifications() for ' + characteristic_name + '.'); } return new Promise(function (resolve, reject) { return resolve(_this4._returnCharacteristic(characteristic_name)); }).then(function (characteristic) { characteristic.startNotifications().then(function () { _this4.cache[primary_service_name][characteristic_name].notifying = true; return characteristic.addEventListener('characteristicvaluechanged', function (event) { var eventObj = characteristicObj.parseValue ? characteristicObj.parseValue(event.target.value) : {}; eventObj.rawValue = event; return transmissionCallback(eventObj); }); }); }).catch(function (err) { return errorHandler('start_notifications_error', err, characteristic_name); }); } /** stopNotifications - attempts to stop previously started notifications for a provided characteristic * * @param {string} characteristic_name - GATT characteristic name * * @return {boolean} success * */ }, { key: 'stopNotifications', value: function stopNotifications(characteristic_name) { var _this5 = this; if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } var characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; var primary_service_name = characteristicObj.primaryServices[0]; if (this.cache[primary_service_name][characteristic_name].notifying) { return new Promise(function (resolve, reject) { return resolve(_this5._returnCharacteristic(characteristic_name)); }).then(function (characteristic) { characteristic.stopNotifications().then(function () { _this5.cache[primary_service_name][characteristic_name].notifying = false; return true; }); }).catch(function (err) { return errorHandler('stop_notifications_error', err, characteristic_name); }); } else { return errorHandler('stop_notifications_not_notifying', null, characteristic_name); } } /** * addCharacteristic - adds a new characteristic object to bluetooth.gattCharacteristicsMapping * * @param {string} characteristic_name - GATT characteristic name or other characteristic * @param {string} primary_service_name - GATT primary service name or other parent service of characteristic * @param {array} propertiesArr - Array of GATT properties as Strings * * @return {boolean} - Result of attempt to add characteristic where true === successfully added */ }, { key: 'addCharacteristic', value: function addCharacteristic(characteristic_name, primary_service_name, propertiesArr) { if (bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('add_characteristic_exists_error', null, characteristic_name); } if (!characteristic_name || characteristic_name.constructor !== String || !characteristic_name.length) { return errorHandler('improper_characteristic_format', null, characteristic_name); } if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { if (!primary_service_name || !propertiesArr) { return errorHandler('new_characteristic_missing_params', null, characteristic_name); } if (primary_service_name.constructor !== String || !primary_service_name.length) { return errorHandler('improper_service_format', null, primary_service_name); } if (propertiesArr.constructor !== Array || !propertiesArr.length) { return errorHandler('improper_properties_format', null, propertiesArr); } console.warn(characteristic_name + ' is not yet fully supported.'); bluetooth.gattCharacteristicsMapping[characteristic_name] = { primaryServices: [primary_service_name], includedProperties: propertiesArr }; return true; } } /** * _returnCharacteristic - returns the value of a cached or resolved characteristic or resolved characteristic * * @param {string} characteristic_name - GATT characteristic name * @return {object|false} - the characteristic object, if successfully obtained */ }, { key: '_returnCharacteristic', value: function _returnCharacteristic(characteristic_name) { var _this6 = this; if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } var characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; var primary_service_name = characteristicObj.primaryServices[0]; if (this.cache[primary_service_name] && this.cache[primary_service_name][characteristic_name] && this.cache[primary_service_name][characteristic_name].cachedCharacteristic) { return this.cache[primary_service_name][characteristic_name].cachedCharacteristic; } else if (this.cache[primary_service_name] && this.cache[primary_service_name].cachedService) { this.cache[primary_service_name].cachedService.getCharacteristic(characteristic_name).then(function (characteristic) { _this6.cache[primary_service_name][characteristic_name] = { cachedCharacteristic: characteristic }; return characteristic; }).catch(function (err) { return errorHandler('_returnCharacteristic_error', err, characteristic_name); }); } else { return this.apiServer.getPrimaryService(primary_service_name).then(function (service) { _this6.cache[primary_service_name] = { 'cachedService': service }; return service.getCharacteristic(characteristic_name); }).then(function (characteristic) { _this6.cache[primary_service_name][characteristic_name] = { cachedCharacteristic: characteristic }; return characteristic; }).catch(function (err) { return errorHandler('_returnCharacteristic_error', err, characteristic_name); }); } } }]); return BluetoothDevice; }(); module.exports = BluetoothDevice;