web-bluetooth
Version:
Library for interacting with Bluetooth 4.0 devices through the browser.
360 lines (309 loc) • 16.4 kB
JavaScript
'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;