web-bluetooth
Advanced tools
Comparing version 0.0.11 to 0.0.12
@@ -1,435 +0,297 @@ | ||
import { errorHandler } from "/utils/errorHandler"; | ||
import { bluetooth } from "/utils/bluetoothMap"; | ||
import { errorHandler } from '/utils/errorHandler'; | ||
import { bluetooth } from '/utils/bluetoothMap'; | ||
/** | ||
* @method connect - Establishes a connection with the device | ||
* NOTE: This method must be triggered by a user gesture to satisfy the native API's permissions | ||
* | ||
* @param {object} filters - Collection of filters for device selection | ||
* 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 | ||
* | ||
*/ | ||
/** | ||
* @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 | ||
* | ||
*/ | ||
export default class BluetoothDevice { | ||
constructor(requestParams) { | ||
this.requestParams = requestParams; | ||
this.apiDevice = null; | ||
this.apiServer = null; | ||
this.cache = {}; | ||
} | ||
constructor(requestParams) { | ||
this.requestParams = requestParams; | ||
this.apiDevice = null; | ||
this.apiServer = null; | ||
this.cache = {}; | ||
} | ||
/** | ||
* checks apiDevice to see whether device is connected | ||
*/ | ||
connected() { | ||
if(!this.apiDevice) return errorHandler('no_device'); | ||
return this.apiDevice.gatt.connected; | ||
} | ||
connected() { | ||
return this.apiDevice ? this.apiDevice.gatt.connected : errorHandler('no_device'); | ||
} | ||
/** | ||
* Establishes a new GATT connection w/ the device | ||
* and stores the return of the promise as this.apiServer | ||
*/ | ||
connect() { | ||
const filters = this.requestParams; | ||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; | ||
if (Object.keys(filters).length) { | ||
const requestParams = { | ||
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) { | ||
let services =[]; | ||
filters.services.forEach(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(service => { | ||
if(!bluetooth.gattServiceList.includes(service)) bluetooth.gattServiceList.push(service); | ||
}); | ||
} | ||
/** 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 | ||
*/ | ||
connect() { | ||
const filters = this.requestParams; | ||
const requestParams = { filters: [], }; | ||
const 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) { | ||
let services = []; | ||
filters.services.forEach(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(service => { | ||
if (!bluetooth.gattServiceList.includes(service)) bluetooth.gattServiceList.push(service); | ||
}); | ||
} else { requestParams.optionalServices = bluetooth.gattServiceList; } | ||
return navigator.bluetooth.requestDevice(requestParams) | ||
.then(device => { | ||
this.apiDevice = device; | ||
return device.gatt.connect(); | ||
}).then(server => { | ||
this.apiServer = server; | ||
return server; | ||
}).catch(err => { | ||
return errorHandler('user_cancelled', err); | ||
}); | ||
} | ||
requestParams.optionalServices = bluetooth.gattServiceList; | ||
/** disconnect - terminates the connection with the device and pauses all data stream subscriptions | ||
* @return {boolean} - success | ||
* | ||
*/ | ||
disconnect() { | ||
this.apiServer.connected ? this.apiServer.disconnect() : errorHandler('not_connected'); | ||
return this.apiServer.connected ? errorHandler('issue_disconnecting') : true; | ||
} | ||
return navigator.bluetooth.requestDevice(requestParams) | ||
.then(device => { | ||
this.apiDevice = device; | ||
return device.gatt.connect() | ||
}).then(server => { | ||
this.apiServer = server; | ||
return server; | ||
}).catch(err => { | ||
errorHandler('user_cancelled',err); | ||
return; | ||
}); | ||
} else { | ||
return errorHandler('no_filters'); | ||
} | ||
}; | ||
/** 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. | ||
*/ | ||
getValue(characteristic_name) { | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
/** | ||
* Attempts to disconnect from BT device | ||
* | ||
* @return {boolean} successfully disconnected | ||
* | ||
* NOTE: All subscribed data streams will resume automatically upon re-connection | ||
*/ | ||
disconnect() { | ||
if (this.apiServer.connected) { | ||
this.apiServer.disconnect(); | ||
//TODO: check if this is asynchronous when retesting. | ||
if (!this.apiServer.connected) { | ||
if (!characteristicObj.includedProperties.includes('read')) { | ||
console.warn(`Attempting to access read property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with an object including | ||
only a rawValue property with the native API return | ||
for an attempt to readValue() of ${characteristic_name}.`); | ||
} | ||
return new Promise((resolve, reject) => { return resolve(this._returnCharacteristic(characteristic_name)); }) | ||
.then(characteristic => { return characteristic.readValue(); }) | ||
.then(value => { | ||
const returnObj = characteristicObj.parseValue ? characteristicObj.parseValue(value) : {}; | ||
returnObj.rawValue = value; | ||
return returnObj; | ||
}).catch(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 | ||
*/ | ||
writeValue(characteristic_name, value) { | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
if (!characteristicObj.includedProperties.includes('write')) { | ||
console.warn(`Attempting to access write property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with native API return | ||
for an attempt to writeValue(${value}) to ${characteristic_name}.`); | ||
} | ||
return new Promise((resolve, reject) => { return resolve(this._returnCharacteristic(characteristic_name)); }) | ||
.then(characteristic => { return characteristic.writeValue(characteristicObj.prepValue ? characteristicObj.prepValue(value) : value); }) | ||
.then(changedChar => { return value; }) | ||
.catch(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 | ||
* | ||
*/ | ||
startNotifications(characteristic_name, transmissionCallback) { | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { return errorHandler('characteristic_error', null, characteristic_name); } | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
if (!characteristicObj.includedProperties.includes('notify')) { | ||
console.warn(`Attempting to access notify property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with an object including | ||
only a rawValue property with the native API return | ||
for an attempt to startNotifications() for ${characteristic_name}.`); | ||
} | ||
return new Promise((resolve, reject) => { return resolve(this._returnCharacteristic(characteristic_name)); }) | ||
.then(characteristic => { | ||
characteristic.startNotifications().then(() => { | ||
this.cache[primary_service_name][characteristic_name].notifying = true; | ||
return characteristic.addEventListener('characteristicvaluechanged', event => { | ||
const eventObj = characteristicObj.parseValue ? characteristicObj.parseValue(event.target.value) : {}; | ||
eventObj.rawValue = event; | ||
return transmissionCallback(eventObj); | ||
}); | ||
}); | ||
}).catch(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 | ||
* | ||
*/ | ||
stopNotifications(characteristic_name) { | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
if (this.cache[primary_service_name][characteristic_name].notifying) { | ||
return new Promise((resolve, reject) => { return resolve(this._returnCharacteristic(characteristic_name)); }) | ||
.then(characteristic => { | ||
characteristic.stopNotifications().then(() => { | ||
this.cache[primary_service_name][characteristic_name].notifying = false; | ||
return true; | ||
} | ||
return errorHandler('issue_disconnecting'); | ||
} | ||
return errorHandler('not_connected'); | ||
} | ||
}); | ||
}).catch(err => { | ||
return errorHandler('stop_notifications_error', err, characteristic_name); | ||
}); | ||
} else { | ||
return errorHandler('stop_notifications_not_notifying', null, characteristic_name); | ||
} | ||
} | ||
/** | ||
* Gets requested characteristic before attempting to read value of that characteristic | ||
* and returning an object with the parsed value (if characterisitc is fully supported) | ||
* and raw value (provided regardles of whether device is fully supported or not). | ||
* | ||
* @param {string} characteristic_name - GATT characteristic name | ||
* @return {Object} 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. | ||
*/ | ||
getValue(characteristic_name) { | ||
// Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping and throw error if not found. | ||
if(!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
// Retrieve characteristic object from bluetooth.gattCharacteristicsMapping | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
/* | ||
* Check characteristic object to see if support for read property is provided. | ||
* If not provided, proceed with attempt to read value of characteristic but | ||
* provide warning indicating such. | ||
*/ | ||
if (!characteristicObj.includedProperties.includes('read')) { | ||
console.warn(`Attempting to access read property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with an object including | ||
only a rawValue property with the native API return | ||
for an attempt to readValue() of ${characteristic_name}.`); | ||
} | ||
// Call returnCharacteristic to retrieve characteristic from which to read | ||
return new Promise((resolve,reject) => { | ||
return resolve(this.returnCharacteristic(characteristic_name)); | ||
}).then(characteristic =>{ | ||
return characteristic.readValue(); | ||
}).then(value =>{ | ||
/* | ||
* Check characteristic object to see if parsing method exists. If present, | ||
* call the parseValue method with value returned from readValue() as the | ||
* argument, and add returned value from readValue() as another parameter to | ||
* the returned object from parseValue before returning. If no parsing method | ||
* is present, return an object with the returned value from readValue() as | ||
* the only parameter. | ||
*/ | ||
const returnObj = characteristicObj.parseValue ? characteristicObj.parseValue(value):{}; | ||
// Always include the raw value returned from readValue() in the object returned | ||
returnObj.rawValue = value; | ||
return returnObj; | ||
}).catch(err => { | ||
return errorHandler('read_error',err); | ||
}); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
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.constuctor !== Array || !propertiesArr.length) { | ||
return errorHandler('improper_properties_format', null, propertiesArr); | ||
} | ||
/** | ||
* Attempts to write a given value to the device for a given characteristic | ||
• | ||
* @param {String} characteristic_name - GATT characteristic name | ||
* @param {String or Number} value - String or Number that will be written to | ||
the requested device characteristic | ||
* | ||
* @return {Boolean} - Result of attempt to write characteristic where true === successfully written | ||
*/ | ||
writeValue(characteristic_name, value) { | ||
/* | ||
* Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping | ||
* and throw error if not found. | ||
*/ | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
// Retrieve characteristic object from bluetooth.gattCharacteristicsMapping | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
/* | ||
* Check characteristic object to see if support for write property is provided. | ||
* If not provided, proceed with attempt to write value to characteristic but | ||
* provide warning indicating such. | ||
*/ | ||
if (!characteristicObj.includedProperties.includes('write')) { | ||
console.warn(`Attempting to access write property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with native API return | ||
for an attempt to writeValue(${value}) to ${characteristic_name}.`); | ||
} | ||
// Call returnCharacteristic to retrieve characteristic from which to read | ||
return new Promise((resolve,reject) => { | ||
return resolve(this.returnCharacteristic(characteristic_name)) | ||
}).then(characteristic => { | ||
/** | ||
* Check characteristic object to see if prepping method exists. If present, | ||
* call the prepValue method with the provided value as the argument. If | ||
* no prepping method is present, attempt to call writeValue() to the | ||
* characteristic with the provided value as the the argument. | ||
*/ | ||
value = characteristicObj.prepValue ? characteristicObj.prepValue(value):value; | ||
return characteristic.writeValue(value); | ||
}).then(changedChar => { | ||
return value; | ||
}).catch(err => { | ||
return errorHandler('write_error',err,characteristic_name); | ||
}) | ||
} | ||
console.warn(`${characteristic_name} is not yet fully supported.`); | ||
bluetooth.gattCharacteristicsMapping[characteristic_name] = { | ||
primaryServices: [primary_service_name], | ||
includedProperties: propertiesArr, | ||
}; | ||
/** | ||
* startNotifications - Attempts to start notifications for changes to device values and adds 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 | ||
* | ||
*/ | ||
startNotifications(characteristic_name, transmissionCallback){ | ||
// Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping and throw error if not found. | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
// Retrieve characteristic object and primary service from bluetooth.gattCharacteristicsMapping | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
/* | ||
* Check characteristic object to see if support for notify property is provided. | ||
* If not provided, proceed with attempt to start notifications from characteristic but | ||
* provide warning indicating such. | ||
*/ | ||
if (!characteristicObj.includedProperties.includes('notify')) { | ||
console.warn(`Attempting to access notify property of ${characteristic_name}, | ||
which is not a included as a supported property of the | ||
characteristic. Attempt will resolve with an object including | ||
only a rawValue property with the native API return | ||
for an attempt to startNotifications() for ${characteristic_name}.`); | ||
} | ||
// Call returnCharacteristic to retrieve characteristic from which to read | ||
return new Promise((resolve,reject) => { | ||
return resolve(this.returnCharacteristic(characteristic_name)) | ||
}).then(characteristic => { | ||
// Start notifications from characteristic and add event listener | ||
characteristic.startNotifications().then(() => { | ||
/* | ||
* After successfully starting notifications from characteristic, update | ||
* cache to reflect current notification status. | ||
*/ | ||
this.cache[primary_service_name][characteristic_name].notifying = true; | ||
// Add listener to subscribe to notifications from device | ||
return characteristic.addEventListener('characteristicvaluechanged', event => { | ||
/* | ||
* Check characteristic object to see if parsing method exists. If present, | ||
* call the parseValue method with value attached to the event object, | ||
* and add the raw event object as another parameter to the returned | ||
* object from parseValue before returning. If no parsing method is | ||
* present, return an object with the raw event object as the only parameter. | ||
*/ | ||
const eventObj = characteristicObj.parseValue ? characteristicObj.parseValue(event.target.value):{}; | ||
// Always include the raw event object in the object returned | ||
eventObj.rawValue = event; | ||
transmissionCallback(eventObj); | ||
}); | ||
}) | ||
}).catch(err => { | ||
return errorHandler('start_notifications_error', err, characteristic_name); | ||
}); | ||
} | ||
return true; | ||
} | ||
} | ||
/** | ||
* Attempts to stop previously started notifications for a provided characteristic | ||
* | ||
* @param {String} characteristic_name - GATT characteristic name | ||
* @return {Boolean} - Result of attempt to stop notifications where true === successfully written | ||
*/ | ||
stopNotifications(characteristic_name) { | ||
// Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping and throws an error if not found. | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
// Retrieve characteristic object and primary service from bluetooth.gattCharacteristicsMapping | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
/* | ||
* Check characteristic object to see if notifications are currently active | ||
* and attempt to stop notifications if active, otherwise throw error. | ||
*/ | ||
if(this.cache[primary_service_name][characteristic_name].notifying) { | ||
// Call returnCharacteristic to retrieve characteristic from which to read | ||
return new Promise((resolve,reject) => { | ||
return resolve(this.returnCharacteristic(characteristic_name)) | ||
}).then(characteristic => { | ||
characteristic.stopNotifications().then(() => { | ||
// After successfully stopping notifications from characteristic, update the cache to reflect current notification status. | ||
this.cache[primary_service_name][characteristic_name].notifying = false; | ||
return true; | ||
}) | ||
}).catch(err => { | ||
return errorHandler('stop_notifications_error', err, characteristic_name); | ||
}) | ||
} | ||
else { | ||
errorHandler('stop_notifications_not_notifying',null,characteristic_name); | ||
} | ||
} | ||
/** | ||
* _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 | ||
*/ | ||
_returnCharacteristic(characteristic_name) { | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
addCharacteristic(characteristic_name, primary_service_name, propertiesArr) { | ||
/* | ||
* Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping | ||
* and throw error if found. | ||
*/ | ||
if(bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('add_characteristic_exists_error', null, characteristic_name); | ||
} | ||
// Check formatting of characteristic_name and throw error if improperly formatted | ||
if (!characteristic_name || characteristic_name.constructor !== String || !characteristic_name.length){ | ||
return errorHandler(`improper_characteristic_format`,null, characteristic_name); | ||
} | ||
/* | ||
* If characteristic does not exist in bluetooth.gattCharacteristicsMapping | ||
* validate presence and format of other required parameters. Throw errors if | ||
* other required parameters are missing or improperly formatted. | ||
*/ | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
// If missing any of other required parameters, throw error | ||
if (!primary_service_name || !propertiesArr) { | ||
return errorHandler(`new_characteristic_missing_params`,null, characteristic_name); | ||
} | ||
// Check formatting of primary_service_name and throw error if improperly formatted | ||
if (primary_service_name.constructor !== String || !primary_service_name.length){ | ||
return errorHandler(`improper_service_format`,null, primary_service_name); | ||
} | ||
// Check formatting of propertiesArr and throw error if improperly formatted | ||
if (propertiesArr.constuctor !== Array || !propertiesArr.length) { | ||
return errorHandler(`improper_properties_format`,null, propertiesArr); | ||
} | ||
/* | ||
* If all parameters are present and properly formatted add new object to | ||
* bluetooth.gattCharacteristicsMapping and provide warning that added | ||
* characteristic is not fully supported. | ||
*/ | ||
console.warn(`Attempting to add ${characteristic_name}. Full support | ||
for this characteristic is not provided.`); | ||
bluetooth.gattCharacteristicsMapping[characteristic_name] = { | ||
primaryServices: [primary_service_name], | ||
includedProperties: propertiesArr | ||
} | ||
return true; | ||
} | ||
} | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
/** | ||
* returnCharacteristic - returns a cached characteristic or resolved characteristic after | ||
* successful connection with device | ||
* | ||
* @param {string} characteristic_name - GATT characteristic name | ||
* @return {object} - If the method successfully retrieves the characteristic, | ||
* that characteristic is returned | ||
*/ | ||
returnCharacteristic(characteristic_name) { | ||
/* | ||
* Check to see if characteristic exists in bluetooth.gattCharacteristicsMapping | ||
* and throw error if not found. | ||
*/ | ||
if (!bluetooth.gattCharacteristicsMapping[characteristic_name]) { | ||
return errorHandler('characteristic_error', null, characteristic_name); | ||
} | ||
/* | ||
* Retrieve characteristic object from bluetooth.gattCharacteristicsMapping | ||
* and establish primary service | ||
*/ | ||
const characteristicObj = bluetooth.gattCharacteristicsMapping[characteristic_name]; | ||
const primary_service_name = characteristicObj.primaryServices[0]; | ||
/* | ||
* Check to see if requested characteristic has been cached from a previous | ||
* interaction of any type to characteristic_name and return if found | ||
*/ | ||
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; | ||
} | ||
/* | ||
* Check to see if requested characteristic's parent primary service has | ||
* been cached from a any previous interaction with that primary service | ||
*/ | ||
else if (this.cache[primary_service_name] && this.cache[primary_service_name].cachedService) { | ||
/* | ||
* If parent primary service has been cached, use getCharacteristic method | ||
* on the cached service and cache resolved characteristic before returning | ||
*/ | ||
this.cache[primary_service_name].cachedService.getCharacteristic(characteristic_name).then(characteristic => { | ||
// Cache characteristic before returning characteristic | ||
this.cache[primary_service_name][characteristic_name] = {'cachedCharacteristic': characteristic}; | ||
return characteristic; | ||
}) | ||
.catch(err => { | ||
return errorHandler('returnCharacteristic_error', err, characteristic_name); | ||
}); | ||
} | ||
/* | ||
* If neither characteristic nor any parent primary service of that characteristic | ||
* has been cached, use cached device server to access and cache both the | ||
* characteristic and primary parent service before returning characteristic | ||
*/ | ||
else { | ||
return this.apiServer.getPrimaryService(primary_service_name).then(service => { | ||
// Cache primary service before attempting to access characteristic | ||
this.cache[primary_service_name] = {'cachedService': service}; | ||
return service.getCharacteristic(characteristic_name); | ||
}).then(characteristic => { | ||
// Cache characteristic before returning | ||
this.cache[primary_service_name][characteristic_name] = {'cachedCharacteristic': characteristic}; | ||
return characteristic; | ||
}).catch(err => { | ||
return errorHandler('returnCharacteristic_error', err, characteristic_name); | ||
}); | ||
} | ||
} | ||
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(characteristic => { | ||
this.cache[primary_service_name][characteristic_name] = { cachedCharacteristic: characteristic }; | ||
return characteristic; | ||
}).catch(err => { return errorHandler('_returnCharacteristic_error', err, characteristic_name); }); | ||
} else { | ||
return this.apiServer.getPrimaryService(primary_service_name) | ||
.then(service => { | ||
this.cache[primary_service_name] = { 'cachedService': service }; | ||
return service.getCharacteristic(characteristic_name); | ||
}).then(characteristic => { | ||
this.cache[primary_service_name][characteristic_name] = { cachedCharacteristic: characteristic }; | ||
return characteristic; | ||
}).catch(err => { | ||
return errorHandler('_returnCharacteristic_error', err, characteristic_name); | ||
}); | ||
} | ||
} | ||
} |
@@ -9,17 +9,17 @@ /** errorHandler - Consolodates error message configuration and logic | ||
function errorHandler(errorKey, nativeError, alternate) { | ||
// Object mapping error codes (keys) to error messages (values) | ||
const errorMessages = { | ||
add_characteristic_exists_error: `Characteristic ${alternateParam} already exists.`, | ||
characteristic_error: `Characteristic ${alternateParam} not found. Add ${alternateParam} to device using addCharacteristic or try another characteristic.`, | ||
connect_gatt: `Error. Could not connect to GATT. Device might be out of range. Also check to see if filters are vaild.`, | ||
connect_server: `Error. Could not connect to server on device.`, | ||
connect_service: `Error. Could not find service.`, | ||
connect_gatt: `Could not connect to GATT. Device might be out of range. Also check to see if filters are vaild.`, | ||
connect_server: `Could not connect to server on device.`, | ||
connect_service: `Could not find service.`, | ||
disconnect_timeout: `Timed out. Could not disconnect.`, | ||
disconnect_error: `Error. Could not disconnect from device.`, | ||
improper_characteristic_format: `Error. ${alternateParam} is not a properly formatted characteristic.`, | ||
improper_properties_format: `Error. ${alternateParam} is not a properly formatted properties array.`, | ||
improper_service_format: `Error. ${alternateParam} is not a properly formatted service.`, | ||
disconnect_error: `Could not disconnect from device.`, | ||
improper_characteristic_format: `${alternateParam} is not a properly formatted characteristic.`, | ||
improper_properties_format: `${alternateParam} is not a properly formatted properties array.`, | ||
improper_service_format: `${alternateParam} is not a properly formatted service.`, | ||
issue_disconnecting: `Issue disconnecting with device.`, | ||
new_characteristic_missing_params: `Error. ${alternateParam} is not a fully supported characteristic. Please provide an associated primary service and at least one property.`, | ||
no_device: `Error. No instance of device found.`, | ||
new_characteristic_missing_params: `${alternateParam} is not a fully supported characteristic. Please provide an associated primary service and at least one property.`, | ||
no_device: `No instance of device found.`, | ||
no_filters: `No filters found on instance of Device. For more information, please visit http://sabertooth.io/#method-newdevice`, | ||
@@ -30,22 +30,18 @@ no_read_property: `No read property on characteristic: ${alternateParam}.`, | ||
parsing_not_supported: `Parsing not supported for characterstic: ${alternateParam}.`, | ||
postValue_error: `Error. Could not post value to device.`, | ||
read_error: `Error. Cannot read value on the characteristic.`, | ||
returnCharacteristic_error: `Error accessing characteristic ${alternateParam}.`, | ||
start_notifications_error: `Error. Not able to read stream of data from characteristic: ${alternateParam}.`, | ||
start_notifications_no_notify: `Error. No notify property found on this characteristic: ${alternateParam}.`, | ||
read_error: `Cannot read value on the characteristic.`, | ||
_returnCharacteristic_error: `Error accessing characteristic ${alternateParam}.`, | ||
start_notifications_error: `Not able to read stream of data from characteristic: ${alternateParam}.`, | ||
start_notifications_no_notify: `No notify property found on this characteristic: ${alternateParam}.`, | ||
stop_notifications_not_notifying: `Notifications not established for characteristic: ${alternateParam} or you have not started notifications.`, | ||
stop_notifications_error: `Issue stopping notifications for characteristic: ${alternateParam} or you have not started notifications.`, | ||
user_cancelled: `User cancelled the permission request.`, | ||
uuid_error: `Error. Invalid UUID. For more information on proper formatting of UUIDs, visit https://webbluetoothcg.github.io/web-bluetooth/#uuids`, | ||
write_error: `Error. Could not change value of characteristic: ${alternateParam}.`, | ||
write_permissions: `Error. ${alternateParam} characteristic does not have a write property.` | ||
uuid_error: `Invalid UUID. For more information on proper formatting of UUIDs, visit https://webbluetoothcg.github.io/web-bluetooth/#uuids`, | ||
write_error: `Could not change value of characteristic: ${alternateParam}.`, | ||
write_permissions: `${alternateParam} characteristic does not have a write property.` | ||
} | ||
if(nativeError) { | ||
throw new Error(`${errorMessages[errorKey]} ${nativeError}`); | ||
} | ||
else { | ||
throw new Error(errorMessages[errorKey]); | ||
} | ||
throw new Error(errorMessages[errorKey]); | ||
return false; | ||
} | ||
module.exports = { errorHandler }; |
{ | ||
"name": "web-bluetooth", | ||
"version": "0.0.11", | ||
"version": "0.0.12", | ||
"description": "A configurable application framework for integrating bluetooth functionality with client-side javascript. Currently in Alpha.", | ||
@@ -48,3 +48,5 @@ "keywords": [ | ||
"eslint-config-airbnb": "^9.0.1", | ||
"eslint-plugin-import": "^1.8.1", | ||
"eslint-plugin-jsx-a11y": "^1.2.2", | ||
"eslint-plugin-react": "^5.1.1", | ||
"mocha": "^2.4.5", | ||
@@ -51,0 +53,0 @@ "sinon": "^1.17.4" |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
0
61952
8
1049