bleat
Version:
Abstraction library following Web Bluetooth specification for hiding differences in JavaScript BLE APIs
81 lines • 25.4 kB
JavaScript
/* @license
*
* BLE Abstraction Tool: bluetooth helpers
*
* 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.
*/
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.bleatHelpers=t()}(this,function(){"use strict";var t={alert_notification:6161,automation_io:6165,battery_service:6159,blood_pressure:6160,body_composition:6171,bond_management:6174,continuous_glucose_monitoring:6175,current_time:6149,cycling_power:6168,cycling_speed_and_cadence:6166,device_information:6154,environmental_sensing:6170,generic_access:6144,generic_attribute:6145,glucose:6152,health_thermometer:6153,heart_rate:6157,human_interface_device:6162,immediate_alert:6146,indoor_positioning:6177,internet_protocol_support:6176,link_loss:6147,location_and_navigation:6169,next_dst_change:6151,phone_alert_status:6158,pulse_oximeter:6178,reference_time_update:6150,running_speed_and_cadence:6164,scan_parameters:6163,tx_power:6148,user_data:6172,weight_scale:6173},r={aerobic_heart_rate_lower_limit:10878,aerobic_heart_rate_upper_limit:10884,aerobic_threshold:10879,age:10880,aggregate:10842,alert_category_id:10819,alert_category_id_bit_mask:10818,alert_level:10758,alert_notification_control_point:10820,alert_status:10815,altitude:10931,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,analog:10840,apparent_wind_direction:10867,apparent_wind_speed:10866,"gap.appearance":10753,barometric_pressure_trend:10915,battery_level:10777,blood_pressure_feature:10825,blood_pressure_measurement:10805,body_composition_feature:10907,body_composition_measurement:10908,body_sensor_location:10808,bond_management_control_point:10916,bond_management_feature:10917,boot_keyboard_input_report:10786,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,"gap.central_address_resolution_support":10918,cgm_feature:10920,cgm_measurement:10919,cgm_session_run_time:10923,cgm_session_start_time:10922,cgm_specific_ops_control_point:10924,cgm_status:10921,csc_feature:10844,csc_measurement:10843,current_time:10795,cycling_power_control_point:10854,cycling_power_feature:10853,cycling_power_measurement:10851,cycling_power_vector:10852,database_change_increment:10905,date_of_birth:10885,date_of_threshold_assessment:10886,date_time:10760,day_date_time:10762,day_of_week:10761,descriptor_value_changed:10877,"gap.device_name":10752,dew_point:10875,digital:10838,dst_offset:10765,elevation:10860,email_address:10887,exact_time_256:10764,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,firmware_revision_string:10790,first_name:10890,five_zone_heart_rate_limits:10891,floor_number:10930,gender:10892,glucose_feature:10833,glucose_measurement:10776,glucose_measurement_context:10804,gust_factor:10868,hardware_revision_string:10791,heart_rate_control_point:10809,heart_rate_max:10893,heart_rate_measurement:10807,heat_index:10874,height:10894,hid_control_point:10828,hid_information:10826,hip_circumference:10895,humidity:10863,"ieee_11073-20601_regulatory_certification_data_list":10794,indoor_positioning_configuration:10925,intermediate_blood_pressure:10806,intermediate_temperature:10782,irradiance:10871,language:10914,last_name:10896,latitude:10926,ln_control_point:10859,ln_feature:10858,"local_east_coordinate.xml":10929,local_north_coordinate:10928,local_time_information:10767,location_and_speed:10855,location_name:10933,longitude:10927,magnetic_declination:10796,magnetic_flux_density_2D:10912,magnetic_flux_density_3D:10913,manufacturer_name_string:10793,maximum_recommended_heart_rate:10897,measurement_interval:10785,model_number_string:10788,navigation:10856,new_alert:10822,"gap.peripheral_preferred_connection_parameters":10756,"gap.peripheral_privacy_flag":10754,plx_continuous_measurement:10847,plx_features:10848,plx_spot_check_measurement:10846,pnp_id:10832,pollen_concentration:10869,position_quality:10857,pressure:10861,protocol_mode:10830,rainfall:10872,"gap.reconnection_address":10755,record_access_control_point:10834,reference_time_information:10772,report:10829,report_map:10827,resting_heart_rate:10898,ringer_control_point:10816,ringer_setting:10817,rsc_feature:10836,rsc_measurement:10835,sc_control_point:10837,scan_interval_window:10831,scan_refresh:10801,sensor_location:10845,serial_number_string:10789,"gatt.service_changed":10757,software_revision_string:10792,sport_type_for_aerobic_and_anaerobic_thresholds:10899,supported_new_alert_category:10823,supported_unread_alert_category:10824,system_id:10787,temperature:10862,temperature_measurement:10780,temperature_type:10781,three_zone_heart_rate_limits:10900,time_accuracy:10770,time_source:10771,time_update_control_point:10774,time_update_state:10775,time_with_dst:10769,time_zone:10766,true_wind_direction:10865,true_wind_speed:10864,two_zone_heart_rate_limit:10901,tx_power_level:10759,uncertainty:10932,unread_alert_status:10821,user_control_point:10911,user_index:10906,uv_index:10870,vo2_max:10902,waist_circumference:10903,weight:10904,weight_measurement:10909,weight_scale_feature:10910,wind_chill:10873},_={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function a(e){return"number"==typeof e&&(e=e.toString(16)),(e=e.toLowerCase()).length<=8&&(e=("00000000"+e).slice(-8)+"-0000-1000-8000-00805f9b34fb"),32===e.length&&(e=e.match(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/).splice(1).join("-")),e}return{Services:t,Characteristics:r,Descriptors:_,getCanonicalUUID:a,getServiceUUID:function(e){return t[e]&&(e=t[e]),a(e)},getCharacteristicUUID:function(e){return r[e]&&(e=r[e]),a(e)},getDescriptorUUID:function(e){return _[e]&&(e=_[e]),a(e)}}});
/* @license
*
* BLE Abstraction Tool: core functionality - web bluetooth specification
*
* The MIT License (MIT)
*
* Copyright (c) 2017 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.
*/
!function(e,t){"function"==typeof define&&define.amd?define(["es6-promise","es6-map","bluetooth.helpers"],t):"object"==typeof exports?module.exports=t(Promise,Map,require("./bluetooth.helpers")):e.bleat=t(e.Promise,e.Map,e.bleatHelpers)}(this,function(t,i,o){"use strict";var u=null,r={};function a(t,i){return function(e){t(i+": "+e)}}function n(i,r){r&&Object.keys(r).forEach(function(t){r[t]&&i.hasOwnProperty(t)&&("[object Object]"===Object.prototype.toString.call(i[t])?n(i[t],r[t]):"[object Map]"===Object.prototype.toString.call(i[t])&&"[object Object]"===Object.prototype.toString.call(r[t])?Object.keys(r[t]).forEach(function(e){i[t].set(e,r[t][e])}):i[t]=r[t])})}function e(r){return function(e,t,i){r.indexOf(e)<0||(this.__events||(this.__events={}),this.__events[e]||(this.__events[e]=[]),this.__events[e].push(t))}}function c(e,t,i){if(this.__events&&this.__events[e]){var r=this.__events[e].indexOf(t);0<=r&&this.__events[e].splice(r,1),0===this.__events[e].length&&delete this.__events[e],0===Object.keys(this.__events).length&&delete this.__events}}function s(t){this.__events&&this.__events[t.type]&&(t.target=this).__events[t.type].forEach(function(e){"function"==typeof e&&e(t)})}var d=null;function h(){return new t(function(e,t){d&&(clearTimeout(d),d=null,u.stopScan()),e()})}function f(e){this._handle=null,this._allowedServices=[],this.id="unknown",this.name=null,this.adData={appearance:null,txPower:null,rssi:null,manufacturerData:new i,serviceData:new i},this.gatt=new v,(this.gatt.device=this).uuids=[],n(this,e)}f.prototype.addEventListener=e(["gattserverdisconnected"]),f.prototype.removeEventListener=c,f.prototype.dispatchEvent=s;var v=function(){this._services=null,this.device=null,this.connected=!1};v.prototype.connect=function(){return new t(function(e,t){if(this.connected)return t("connect error: device already connected");u.connect(this.device._handle,function(){this.connected=!0,e(this)}.bind(this),function(){this._services=null,this.connected=!1,this.device.dispatchEvent({type:"gattserverdisconnected",bubbles:!0})}.bind(this),a(t,"connect error"))}.bind(this))},v.prototype.disconnect=function(){u.disconnect(this.device._handle),this.connected=!1},v.prototype.getPrimaryService=function(e){return new t(function(t,i){return this.connected?e?void this.getPrimaryServices(e).then(function(e){if(1!==e.length)return i("getPrimaryService error: service not found");t(e[0])}).catch(function(e){i(e)}):i("getPrimaryService error: no service specified"):i("getPrimaryService error: device not connected")}.bind(this))},v.prototype.getPrimaryServices=function(n){return new t(function(t,i){if(!this.connected)return i("getPrimaryServices error: device not connected");function r(){if(!n)return t(this._services);var e=this._services.filter(function(e){return e.uuid===o.getServiceUUID(n)});if(1!==e.length)return i("getPrimaryServices error: service not found");t(e)}if(this._services)return r.call(this);u.discoverServices(this.device._handle,this.device._allowedServices,function(e){this._services=e.map(function(e){return e.device=this.device,new l(e)}.bind(this)),r.call(this)}.bind(this),a(i,"getPrimaryServices error"))}.bind(this))};var l=function(e){this._handle=null,this._services=null,this._characteristics=null,this.device=null,this.uuid=null,this.isPrimary=!1,n(this,e),this.dispatchEvent({type:"serviceadded",bubbles:!0})};l.prototype.getCharacteristic=function(e){return new t(function(t,i){return this.device.gatt.connected?e?void this.getCharacteristics(e).then(function(e){if(1!==e.length)return i("getCharacteristic error: characteristic not found");t(e[0])}).catch(function(e){i(e)}):i("getCharacteristic error: no characteristic specified"):i("getCharacteristic error: device not connected")}.bind(this))},l.prototype.getCharacteristics=function(n){return new t(function(t,i){if(!this.device.gatt.connected)return i("getCharacteristics error: device not connected");function r(){if(!n)return t(this._characteristics);var e=this._characteristics.filter(function(e){return e.uuid===o.getCharacteristicUUID(n)});if(1!==e.length)return i("getCharacteristics error: characteristic not found");t(e)}if(this._characteristics)return r.call(this);u.discoverCharacteristics(this._handle,[],function(e){this._characteristics=e.map(function(e){return e.service=this,new p(e)}.bind(this)),r.call(this)}.bind(this),a(i,"getCharacteristics error"))}.bind(this))},l.prototype.getIncludedService=function(e){return new t(function(t,i){return this.device.gatt.connected?e?void this.getIncludedServices(e).then(function(e){if(1!==e.length)return i("getIncludedService error: service not found");t(e[0])}).catch(function(e){i(e)}):i("getIncludedService error: no service specified"):i("getIncludedService error: device not connected")}.bind(this))},l.prototype.getIncludedServices=function(n){return new t(function(t,i){if(!this.device.gatt.connected)return i("getIncludedServices error: device not connected");function r(){if(!n)return t(this._services);var e=this._services.filter(function(e){return e.uuid===o.getServiceUUID(n)});if(1!==e.length)return i("getIncludedServices error: service not found");t(e)}if(this._services)return r.call(this);u.discoverIncludedServices(this._handle,this.device._allowedServices,function(e){this._services=e.map(function(e){return e.device=this.device,new l(e)}.bind(this)),r.call(this)}.bind(this),a(i,"getIncludedServices error"))}.bind(this))},l.prototype.addEventListener=e(["serviceadded","servicechanged","serviceremoved"]),l.prototype.removeEventListener=c,l.prototype.dispatchEvent=s;var p=function(e){this._handle=null,this._descriptors=null,this.service=null,this.uuid=null,this.properties={broadcast:!1,read:!1,writeWithoutResponse:!1,write:!1,notify:!1,indicate:!1,authenticatedSignedWrites:!1,reliableWrite:!1,writableAuxiliaries:!1},this.value=null,n(this,e)};p.prototype.getDescriptor=function(e){return new t(function(t,i){return this.service.device.gatt.connected?e?void this.getDescriptors(e).then(function(e){if(1!==e.length)return i("getDescriptor error: descriptor not found");t(e[0])}).catch(function(e){i(e)}):i("getDescriptor error: no descriptor specified"):i("getDescriptor error: device not connected")}.bind(this))},p.prototype.getDescriptors=function(n){return new t(function(t,i){if(!this.service.device.gatt.connected)return i("getDescriptors error: device not connected");function r(){if(!n)return t(this._descriptors);var e=this._descriptors.filter(function(e){return e.uuid===o.getDescriptorUUID(n)});if(1!==e.length)return i("getDescriptors error: descriptor not found");t(e)}if(this._descriptors)return r.call(this);u.discoverDescriptors(this._handle,[],function(e){this._descriptors=e.map(function(e){return e.characteristic=this,new g(e)}.bind(this)),r.call(this)}.bind(this),a(i,"getDescriptors error"))}.bind(this))},p.prototype.readValue=function(){return new t(function(t,e){if(!this.service.device.gatt.connected)return e("readValue error: device not connected");u.readCharacteristic(this._handle,function(e){this.value=e,t(e),this.dispatchEvent({type:"characteristicvaluechanged",bubbles:!0})}.bind(this),a(e,"readValue error"))}.bind(this))},p.prototype.writeValue=function(n){return new t(function(e,t){if(!this.service.device.gatt.connected)return t("writeValue error: device not connected");var i=n.buffer||n,r=new DataView(i);u.writeCharacteristic(this._handle,r,function(){this.value=r,e()}.bind(this),a(t,"writeValue error"))}.bind(this))},p.prototype.startNotifications=function(){return new t(function(e,t){if(!this.service.device.gatt.connected)return t("startNotifications error: device not connected");u.enableNotify(this._handle,function(e){this.value=e,this.dispatchEvent({type:"characteristicvaluechanged",bubbles:!0})}.bind(this),function(){e(this)}.bind(this),a(t,"startNotifications error"))}.bind(this))},p.prototype.stopNotifications=function(){return new t(function(e,t){if(!this.service.device.gatt.connected)return t("stopNotifications error: device not connected");u.disableNotify(this._handle,function(){e(this)}.bind(this),a(t,"stopNotifications error"))}.bind(this))},p.prototype.addEventListener=e(["characteristicvaluechanged"]),p.prototype.removeEventListener=c,p.prototype.dispatchEvent=s;var g=function(e){this._handle=null,this.characteristic=null,this.uuid=null,this.value=null,n(this,e)};return g.prototype.readValue=function(){return new t(function(t,e){if(!this.characteristic.service.device.gatt.connected)return e("readValue error: device not connected");u.readDescriptor(this._handle,function(e){this.value=e,t(e)}.bind(this),a(e,"readValue error"))}.bind(this))},g.prototype.writeValue=function(n){return new t(function(e,t){if(!this.characteristic.service.device.gatt.connected)return t("writeValue error: device not connected");var i=n.buffer||n,r=new DataView(i);u.writeDescriptor(this._handle,r,function(){this.value=r,e()}.bind(this),a(t,"writeValue error"))}.bind(this))},{_addAdapter:function(e,t){r[e]=t,u=t},requestDevice:function(s){return new t(function(n,e){if(null!==d)return e("requestDevice error: request in progress");if(!s.acceptAllDevices&&!s.deviceFound){if(!s.filters||0===s.filters.length)return e(new TypeError("requestDevice error: no filters specified"));if(s.filters.some(function(e){return 0===Object.keys(e).length}))return e(new TypeError("requestDevice error: empty filter specified"));if(s.filters.some(function(e){return void 0!==e.namePrefix&&""===e.namePrefix}))return e(new TypeError("requestDevice error: empty namePrefix specified"))}var t=[];s.filters&&s.filters.forEach(function(e){e.services&&(t=t.concat(e.services.map(o.getServiceUUID)))}),t=t.filter(function(e,t,i){return i.indexOf(e)===t});var c=!1;u.startScan(t,function(e){var t=[];function i(e){h().then(function(){n(e)})}if(s.filters&&(e=function(e,i,r){var n=!1;return e.filters.forEach(function(e){if(!e.name||e.name===i.name){if(e.namePrefix){if(!i.name||e.namePrefix.length>i.name.length)return;if(e.namePrefix!==i.name.substr(0,e.namePrefix.length))return}if(e.services){var t=e.services.map(o.getServiceUUID);if(!t.every(function(e){return-1<i.uuids.indexOf(e)}))return;r=r.concat(t)}n=!0}}),!!n&&i}(s,e,t)),e){c=!0,s.optionalServices&&(t=t.concat(s.optionalServices.map(o.getServiceUUID))),e._allowedServices=t.filter(function(e,t,i){return i.indexOf(e)===t});var r=new f(e);s.deviceFound&&!s.deviceFound(r,function(){i(r)})||i(r)}},function(){d=setTimeout(function(){h().then(function(){c||e("requestDevice error: no devices found")})},s.scanTime||10240)},a(e,"requestDevice error"))})},cancelRequest:h}});
/* @license
*
* BLE Abstraction Tool: noble adapter
*
* 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.
*/
!function(i,e){"function"==typeof define&&define.amd?define(["noble","bleat","bluetooth.helpers"],e):"object"==typeof exports?module.exports=function(i){return e(require("noble"),i,require("./bluetooth.helpers"))}:e(i.noble,i.bleat,i.bleatHelpers)}(this,function(s,i,c){"use strict";function a(t,n){return function(i){if(i)t(i);else if("function"==typeof n){var e=[].slice.call(arguments,1);n.apply(this,e)}}}function d(i){var e=new Uint8Array(i).buffer;return new DataView(e)}function o(i){var e=new Uint8Array(i.buffer);return new Buffer(e)}i._addAdapter&&s&&i._addAdapter("noble",{deviceHandles:{},serviceHandles:{},characteristicHandles:{},descriptorHandles:{},charNotifies:{},foundFn:null,initialised:!1,init:function(i,e){if(this.initialised)return i();s.on("discover",function(i){if(this.foundFn){var e=i.address&&"unknown"!==i.address?i.address:i.id;this.deviceHandles[e]||(this.deviceHandles[e]=i);var t=[];i.advertisement.serviceUuids&&i.advertisement.serviceUuids.forEach(function(i){t.push(c.getCanonicalUUID(i))});var n={};if(i.advertisement.manufacturerData){var r=i.advertisement.manufacturerData.readUInt16LE(0);r=("0000"+r.toString(16)).slice(-4);var s=i.advertisement.manufacturerData.slice(2);n[r]=d(s)}var a={};i.advertisement.serviceData&&i.advertisement.serviceData.forEach(function(i){a[c.getCanonicalUUID(i.uuid)]=d(i.data)}),this.foundFn({_handle:e,id:e,name:i.advertisement.localName,uuids:t,adData:{manufacturerData:n,serviceData:a,txPower:i.advertisement.txPowerLevel,rssi:i.rssi}})}}.bind(this)),this.initialised=!0,i()},startScan:function(t,n,e,r){this.init(function(){this.deviceHandles={};var i=function(i){"poweredOn"===i?(0===t.length?this.foundFn=n:this.foundFn=function(e){t.forEach(function(i){0<=e.uuids.indexOf(i)&&n(e)})},s.startScanning([],!1,a(r,e))):r("adapter not enabled")}.bind(this);"unknown"===s.state?s.once("stateChange",i.bind(this)):i(s.state)}.bind(this),r)},stopScan:function(i){this.foundFn=null,s.stopScanning()},connect:function(i,e,t,n){var r=this.deviceHandles[i];r.once("connect",e),r.once("disconnect",function(){this.serviceHandles={},this.characteristicHandles={},this.descriptorHandles={},this.charNotifies={},t()}.bind(this)),r.connect(a(n))},disconnect:function(i,e){this.deviceHandles[i].disconnect(a(e))},discoverServices:function(i,n,e,t){this.deviceHandles[i].discoverServices([],a(t,function(i){var t=[];i.forEach(function(i){var e=c.getCanonicalUUID(i.uuid);(0===n.length||0<=n.indexOf(e))&&(this.serviceHandles[e]||(this.serviceHandles[e]=i),t.push({_handle:e,uuid:e,primary:!0}))},this),e(t)}.bind(this)))},discoverIncludedServices:function(i,n,e,t){this.serviceHandles[i].discoverIncludedServices([],a(t,function(i){var t=[];i.forEach(function(i){var e=c.getCanonicalUUID(i.uuid);(0===n.length||0<=n.indexOf(e))&&(this.serviceHandles[e]||(this.serviceHandles[e]=i),t.push({_handle:e,uuid:e,primary:!1}))},this),e(t)}.bind(this)))},discoverCharacteristics:function(i,t,n,e){this.serviceHandles[i].discoverCharacteristics([],a(e,function(i){var e=[];i.forEach(function(i){var n=c.getCanonicalUUID(i.uuid);(0===t.length||0<=t.indexOf(n))&&(this.characteristicHandles[n]||(this.characteristicHandles[n]=i),e.push({_handle:n,uuid:n,properties:{broadcast:0<=i.properties.indexOf("broadcast"),read:0<=i.properties.indexOf("read"),writeWithoutResponse:0<=i.properties.indexOf("writeWithoutResponse"),write:0<=i.properties.indexOf("write"),notify:0<=i.properties.indexOf("notify"),indicate:0<=i.properties.indexOf("indicate"),authenticatedSignedWrites:0<=i.properties.indexOf("authenticatedSignedWrites"),reliableWrite:0<=i.properties.indexOf("reliableWrite"),writableAuxiliaries:0<=i.properties.indexOf("writableAuxiliaries")}}),i.on("data",function(i,e){if(!0===e&&"function"==typeof this.charNotifies[n]){var t=d(i);this.charNotifies[n](t)}}.bind(this)))},this),n(e)}.bind(this)))},discoverDescriptors:function(i,r,e,t){var s=this.characteristicHandles[i];s.discoverDescriptors(a(t,function(i){var n=[];i.forEach(function(i){var e=c.getCanonicalUUID(i.uuid);if(0===r.length||0<=r.indexOf(e)){var t=s.uuid+"-"+i.uuid;this.descriptorHandles[t]||(this.descriptorHandles[t]=i),n.push({_handle:t,uuid:e})}},this),e(n)}.bind(this)))},readCharacteristic:function(i,t,e){this.characteristicHandles[i].read(a(e,function(i){var e=d(i);t(e)}))},writeCharacteristic:function(i,e,t,n){var r=o(e);this.characteristicHandles[i].write(r,!0,a(n,t))},enableNotify:function(e,t,n,r){if(this.charNotifies[e])return this.charNotifies[e]=t,n();this.characteristicHandles[e].once("notify",function(i){if(!0!==i)return r("notify failed to enable");this.charNotifies[e]=t,n()}.bind(this)),this.characteristicHandles[e].notify(!0,a(r))},disableNotify:function(e,t,n){if(!this.charNotifies[e])return t();this.characteristicHandles[e].once("notify",function(i){if(!1!==i)return n("notify failed to disable");this.charNotifies[e]&&delete this.charNotifies[e],t()}.bind(this)),this.characteristicHandles[e].notify(!1,a(n))},readDescriptor:function(i,t,e){this.descriptorHandles[i].readValue(a(e,function(i){var e=d(i);t(e)}))},writeDescriptor:function(i,e,t,n){var r=o(e);this.descriptorHandles[i].writeValue(r,a(n,t))}})});