// File: easyble.js updated 160713
;(function()
{
// Load script used by this file.
evothings.loadScript('libs/evothings/util/util.js');
/**
* @namespace
* @description <p>Library for making BLE programming easier.</p>
* <p>An all-in-one file with this library and helper libraries included is
* available in file <a href=""https://github.com/evothings/evothings-libraries/blob/master/libs/evothings/easyble/easyble.dist.js>easyble.dist.js</a>. Include this file in index.html (recommended).</p>
* <p>If you include <code>easyble.js</code> rather than <code>easyble.dist.js</code> it is safe practise to call function {@link evothings.scriptsLoaded}
* to ensure dependent libraries are loaded before calling functions
* in this library (in this case you also need to have the dependent library folders).</p>
*/
evothings.easyble = {};
/**
* @namespace
* @description Error string.
*/
evothings.easyble.error = {};
/**
* @description BLE device already connected.
*/
evothings.easyble.error.DEVICE_ALREADY_CONNECTED = 'EASYBLE_ERROR_DEVICE_ALREADY_CONNECTED';
/**
* @description BLE device was disconnected.
*/
evothings.easyble.error.DISCONNECTED = 'EASYBLE_ERROR_DISCONNECTED';
/**
* @description BLE service was not found.
*/
evothings.easyble.error.SERVICE_NOT_FOUND = 'EASYBLE_ERROR_SERVICE_NOT_FOUND';
/**
* @description BLE characteristic was not found.
*/
evothings.easyble.error.CHARACTERISTIC_NOT_FOUND = 'EASYBLE_ERROR_CHARACTERISTIC_NOT_FOUND';
/**
* @description BLE descriptor was not found.
*/
evothings.easyble.error.DESCRIPTOR_NOT_FOUND = 'EASYBLE_ERROR_DESCRIPTOR_NOT_FOUND';
/**
* @private
* This variable is set "lazily", because when this script is loaded
* the Base64 Cordova module may not be loaded yet.
*/
var base64;
/**
* Set to true to report found devices only once,
* set to false to report continuously.
* @private
*/
var reportDeviceOnce = false;
/**
* @private
*/
var serviceFilter = false;
/**
* @private
*/
var isScanning = false;
/**
* Internal properties and functions.
* @private
*/
var internal = {};
/**
* Internal variable used to track reading of service data.
* @private
*/
var readCounter = 0;
/**
* Table of discovered devices.
* @private
*/
internal.knownDevices = {};
/**
* Table of connected devices.
* @private
*/
internal.connectedDevices = {};
/**
* @description <strong>Deprecated.</strong> Set whether to report devices once or continuously during scan.
* The default is to report continously.
* @deprecated Use the options parameter {@link evothings.easyble.ScanOptions} in
* function {@link evothings.easyble.startScan}.
* @param {boolean} reportOnce - Set to true to report found devices only once.
* Set to false to report continuously.
* @public
*/
evothings.easyble.reportDeviceOnce = function(reportOnce)
{
reportDeviceOnce = reportOnce;
};
/**
* @description Set to an Array of UUID strings to enable filtering of devices
* found by startScan().
* @param services - Array of UUID strings. Set to false to disable filtering.
* The default is no filtering. An empty array will cause no devices to be reported.
* @public
*/
evothings.easyble.filterDevicesByService = function(services)
{
serviceFilter = services;
};
/**
* @description Called during scanning when a BLE device is found.
* @callback evothings.easyble.scanCallback
* @param {evothings.easyble.EasyBLEDevice} device - EasyBLE device object
* found during scanning.
*/
/**
* @description This callback indicates that an operation was successful,
* without specifying and additional information.
* @callback evothings.easyble.emptyCallback - Callback that takes no parameters.
*/
/**
* @description This function is called when an operation fails.
* @callback evothings.easyble.failCallback
* @param {string} errorString - A human-readable string that
* describes the error that occurred.
*/
/**
* @description Called when successfully connected to a device.
* @callback evothings.easyble.connectCallback
* @param {evothings.easyble.EasyBLEDevice} device - EasyBLE devices object.
*/
/**
* @description Called when services are successfully read.
* @callback evothings.easyble.servicesCallback
* @param {evothings.easyble.EasyBLEDevice} device - EasyBLE devices object.
*/
/**
* @description Function when data is available.
* @callback evothings.easyble.dataCallback
* @param {ArrayBuffer} data - The data is an array buffer.
* Use an ArrayBufferView to access the data.
*/
/**
* @description Called with RSSI value.
* @callback evothings.easyble.rssiCallback
* @param {number} rssi - A negative integer, the signal strength in decibels.
* This value may be 127 which indicates an unknown value.
*/
/**
* @typedef {Object} evothings.easyble.ScanOptions
* @description Options for function {evothings.easyble.startScan}
* @property {array} serviceUUIDs - Array with strings of service UUIDs
* to scan for. When providing one service UUID, behaviour is the same on
* Android and iOS, when providing multiple UUIDs behaviour differs between
* platforms.
* On iOS multiple UUIDs are scanned for using logical OR operator,
* any UUID that matches any of the UUIDs adverticed by the device
* will count as a match. On Android, multiple UUIDs are scanned for
* using AND logic, the device must advertise all of the given UUIDs
* to produce a match. Leaving out this parameter or setting it to null
* will scan for all devices regardless of advertised services (default
* behaviour).
* @property {boolean} allowDuplicates - If true same device will be reported
* repeatedly during scanning, if false it will only be reported once.
* Default is true.
*/
/**
* Start scanning for devices. Note that the optional parameter serviceUUIDs
* has been deprecated. Please use the options parmameter
* {@link evothings.easyble.ScanOptions} instead to specify any specific
* service UUID to scan for.
* @param {evothings.easyble.scanCallback} success - Success function called when a
* device is found.
* Format: success({@link evothings.easyble.EasyBLEDevice}).
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @param {evothings.easyble.ScanOptions} [options] - Object with scan options.
* @public
* @example
* // Scan for all services.
* evothings.easyble.startScan(
* function(device)
* {
* console.log('Found device named: ' + device.name);
* },
* function(errorCode)
* {
* console.log('startScan error: ' + errorCode);
* }
* );
*
* // Scan for specific service.
* evothings.easyble.startScan(
* function(device)
* {
* console.log('Found device named: ' + device.name);
* },
* function(errorCode)
* {
* console.log('startScan error: ' + errorCode);
* },
* // Eddystone service UUID specified in options.
* { serviceUUIDs: ['0000FEAA-0000-1000-8000-00805F9B34FB'] }
* );
*/
evothings.easyble.startScan = function(arg1, arg2, arg3, arg4)
{
// Stop ongoing scan.
evothings.easyble.stopScan();
// Clear list of found devices.
internal.knownDevices = {};
// Scanning parameters.
var serviceUUIDs;
var success;
var fail;
var options;
var allowDuplicates = undefined;
// Determine parameters.
if (Array.isArray(arg1))
{
// First param is an array of serviceUUIDs.
serviceUUIDs = arg1;
success = arg2;
fail = arg3;
options = arg4;
}
else if ('function' == typeof arg1)
{
// First param is a function.
serviceUUIDs = null;
success = arg1;
fail = arg2;
options = arg3;
}
// Set options.
if (options)
{
if (Array.isArray(options.serviceUUIDs))
{
serviceUUIDs = options.serviceUUIDs;
}
if (options.allowDuplicates === true)
{
allowDuplicates = true;
}
else if (options.allowDuplicates === false)
{
allowDuplicates = false;
}
}
// Start scanning.
isScanning = true;
if (Array.isArray(serviceUUIDs))
{
evothings.ble.startScan(serviceUUIDs, onDeviceFound, onError);
}
else
{
evothings.ble.startScan(onDeviceFound, onError);
}
function onDeviceFound(device)
{
// Don't report devices unless the isScanning flag is true.
// This is to prevent devices being reported after stopScanning
// has been called (this can happen since scanning does not stop
// instantly when evothings.ble.stopScan is called).
if (!isScanning) return;
// Ensure we have advertisementData.
internal.ensureAdvertisementData(device);
// Check if the device matches the filter, if we have a filter.
if (!internal.deviceMatchesServiceFilter(device))
{
return;
}
// Check if we already have got the device.
var existingDevice = internal.knownDevices[device.address]
if (existingDevice)
{
// Do not report device again if flag is set.
if (allowDuplicates === false || reportDeviceOnce === true) { return; }
// Duplicates allowed, report device again.
existingDevice.rssi = device.rssi;
existingDevice.name = device.name;
existingDevice.scanRecord = device.scanRecord;
existingDevice.advertisementData = device.advertisementData;
success(existingDevice);
return;
}
// New device, add to known devices.
internal.knownDevices[device.address] = device;
// Set connect status.
device.__isConnected = false;
// Add methods to the device info object.
internal.addMethodsToDeviceObject(device);
// Call callback function with device info.
success(device);
}
function onError(errorCode)
{
fail(errorCode);
}
};
/**
* Stop scanning for devices.
* @example
* evothings.easyble.stopScan();
*/
evothings.easyble.stopScan = function()
{
isScanning = false;
evothings.ble.stopScan();
};
/**
* Disconnect and close all connected BLE devices.
* @example
* evothings.easyble.closeConnectedDevices();
*/
evothings.easyble.closeConnectedDevices = function()
{
for (var key in internal.connectedDevices)
{
var device = internal.connectedDevices[key];
device && device.close();
internal.connectedDevices[key] = null;
}
};
/**
* If device already has advertisementData, does nothing.
* If device instead has scanRecord, creates advertisementData.
* See ble.js for AdvertisementData reference.
* @param device - Device object.
* @private
*/
internal.ensureAdvertisementData = function(device)
{
if (!base64) { base64 = cordova.require('cordova/base64'); }
// If device object already has advertisementData we
// do not need to parse the scanRecord.
if (device.advertisementData) { return; }
// Must have scanRecord yo continue.
if (!device.scanRecord) { return; }
// Here we parse BLE/GAP Scan Response Data.
// See the Bluetooth Specification, v4.0, Volume 3, Part C, Section 11,
// for details.
var byteArray = evothings.util.base64DecToArr(device.scanRecord);
var pos = 0;
var advertisementData = {};
var serviceUUIDs;
var serviceData;
// The scan record is a list of structures.
// Each structure has a length byte, a type byte, and (length-1) data bytes.
// The format of the data bytes depends on the type.
// Malformed scanRecords will likely cause an exception in this function.
while (pos < byteArray.length)
{
var length = byteArray[pos++];
if (length == 0)
{
break;
}
length -= 1;
var type = byteArray[pos++];
// Parse types we know and care about.
// Skip other types.
var BLUETOOTH_BASE_UUID = '-0000-1000-8000-00805f9b34fb'
// Convert 16-byte Uint8Array to RFC-4122-formatted UUID.
function arrayToUUID(array, offset)
{
var k=0;
var string = '';
var UUID_format = [4, 2, 2, 2, 6];
for (var l=0; l<UUID_format.length; l++)
{
if (l != 0)
{
string += '-';
}
for (var j=0; j<UUID_format[l]; j++, k++)
{
string += evothings.util.toHexString(array[offset+k], 1);
}
}
return string;
}
if (type == 0x02 || type == 0x03) // 16-bit Service Class UUIDs.
{
serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
for(var i=0; i<length; i+=2)
{
serviceUUIDs.push(
'0000' +
evothings.util.toHexString(
evothings.util.littleEndianToUint16(byteArray, pos + i),
2) +
BLUETOOTH_BASE_UUID);
}
}
if (type == 0x04 || type == 0x05) // 32-bit Service Class UUIDs.
{
serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
for (var i=0; i<length; i+=4)
{
serviceUUIDs.push(
evothings.util.toHexString(
evothings.util.littleEndianToUint32(byteArray, pos + i),
4) +
BLUETOOTH_BASE_UUID);
}
}
if (type == 0x06 || type == 0x07) // 128-bit Service Class UUIDs.
{
serviceUUIDs = serviceUUIDs ? serviceUUIDs : [];
for (var i=0; i<length; i+=16)
{
serviceUUIDs.push(arrayToUUID(byteArray, pos + i));
}
}
if (type == 0x08 || type == 0x09) // Local Name.
{
advertisementData.kCBAdvDataLocalName = evothings.ble.fromUtf8(
new Uint8Array(byteArray.buffer, pos, length));
}
if (type == 0x0a) // TX Power Level.
{
advertisementData.kCBAdvDataTxPowerLevel =
evothings.util.littleEndianToInt8(byteArray, pos);
}
if (type == 0x16) // Service Data, 16-bit UUID.
{
serviceData = serviceData ? serviceData : {};
var uuid =
'0000' +
evothings.util.toHexString(
evothings.util.littleEndianToUint16(byteArray, pos),
2) +
BLUETOOTH_BASE_UUID;
var data = new Uint8Array(byteArray.buffer, pos+2, length-2);
serviceData[uuid] = base64.fromArrayBuffer(data);
}
if (type == 0x20) // Service Data, 32-bit UUID.
{
serviceData = serviceData ? serviceData : {};
var uuid =
evothings.util.toHexString(
evothings.util.littleEndianToUint32(byteArray, pos),
4) +
BLUETOOTH_BASE_UUID;
var data = new Uint8Array(byteArray.buffer, pos+4, length-4);
serviceData[uuid] = base64.fromArrayBuffer(data);
}
if (type == 0x21) // Service Data, 128-bit UUID.
{
serviceData = serviceData ? serviceData : {};
var uuid = arrayToUUID(byteArray, pos);
var data = new Uint8Array(byteArray.buffer, pos+16, length-16);
serviceData[uuid] = base64.fromArrayBuffer(data);
}
if (type == 0xff) // Manufacturer-specific Data.
{
// Annoying to have to transform base64 back and forth,
// but it has to be done in order to maintain the API.
advertisementData.kCBAdvDataManufacturerData =
base64.fromArrayBuffer(new Uint8Array(byteArray.buffer, pos, length));
}
pos += length;
}
advertisementData.kCBAdvDataServiceUUIDs = serviceUUIDs;
advertisementData.kCBAdvDataServiceData = serviceData;
device.advertisementData = advertisementData;
/*
// Log raw data for debugging purposes.
console.log("scanRecord: "+evothings.util.typedArrayToHexString(byteArray));
console.log(JSON.stringify(advertisementData));
*/
}
/**
* Returns true if the device matches the serviceFilter, or if there is no filter.
* Returns false otherwise.
* @private
*/
internal.deviceMatchesServiceFilter = function(device)
{
if (!serviceFilter) { return true; }
var advertisementData = device.advertisementData;
if (advertisementData)
{
var serviceUUIDs = advertisementData.kCBAdvDataServiceUUIDs;
if (serviceUUIDs)
{
for (var i in serviceUUIDs)
{
for (var j in serviceFilter)
{
if (serviceUUIDs[i].toLowerCase() ==
serviceFilter[j].toLowerCase())
{
return true;
}
}
}
}
}
return false;
}
/**
* Add functions to the device object to allow calling them
* in an object-oriented style.
* @private
*/
internal.addMethodsToDeviceObject = function(deviceObject)
{
/**
* @namespace
* @alias evothings.easyble.EasyBLEDevice
* @description This is the BLE DeviceInfo object obtained from the
* underlying Cordova plugin.
* @property {string} address - Uniquely identifies the device.
* The form of the address depends on the host platform.
* @property {number} rssi - A negative integer, the signal strength in decibels.
* @property {string} name - The device's name, or nil.
* @property {string} scanRecord - Base64-encoded binary data. Its meaning is
* device-specific. Not available on iOS.
* @property {evothings.easyble.AdvertisementData} advertisementData -
* Object containing some of the data from the scanRecord.
*/
var device = deviceObject;
/**
* @typedef {Object} evothings.easyble.AdvertisementData
* @description Information extracted from a scanRecord. Some or all of the fields may be
* undefined. This varies between BLE devices.
* Depending on OS version and BLE device, additional fields, not documented
* here, may be present.
* @property {string} kCBAdvDataLocalName - The device's name. Use this field
* rather than device.name, since on iOS the device name is cached and changes
* are not reflected in device.name.
* @property {number} kCBAdvDataTxPowerLevel - Transmission power level as
* advertised by the device.
* @property {boolean} kCBAdvDataIsConnectable - True if the device accepts
* connections. False if it doesn't.
* @property {array} kCBAdvDataServiceUUIDs - Array of strings, the UUIDs of
* services advertised by the device. Formatted according to RFC 4122,
* all lowercase.
* @property {object} kCBAdvDataServiceData - Dictionary of strings to strings.
* The keys are service UUIDs. The values are base-64-encoded binary data.
* @property {string} kCBAdvDataManufacturerData - Base-64-encoded binary data.
* This field is used by BLE devices to advertise custom data that don't fit
* into any of the other fields.
*/
/**
* Get device name. If there is a device name present in
* advertisement data, this is returned. Otherwise the value of
* the device.name field is returned. Note that iOS caches the
* device.name field, but not the name in advertisement data.
* If you change name of the device, it is more reliable to use
* the field in advertisement data (this name is set in the device
* firmware code).
* @return Name of the device.
* @public
* @instance
* @example
* var name = device.getName();
*/
device.getName = function()
{
// If there is a device name present in advertisement data,
// check if this matches. (This name is not cached by iOS.)
var deviceName = device.advertisementData ?
device.advertisementData.kCBAdvDataLocalName : false;
if (deviceName)
{
return deviceName;
}
else
{
return device.name;
}
};
/**
* Match device name. First checks the device name present in
* advertisement data, if not present checks device.name field.
* @param name The name to match.
* @return true if device has the given name, false if not.
* @public
* @instance
* @example
* device.hasName('MyBLEDevice');
*/
device.hasName = function(name)
{
// If there is a device name present in advertisement data,
// check if this matches. (This name is not cached by iOS.)
var deviceName = device.advertisementData ?
device.advertisementData.kCBAdvDataLocalName : false;
if (deviceName)
{
// TODO: This should be a comparison, but there has been
// instances of the kCBAdvDataLocalName field ending with
// a non-printable character, using indexOf is a quick
// fix for this.
return 0 == deviceName.indexOf(name);
}
// Otherwise check if device.name matches (cached by iOS,
// might not match if device name is updated).
return name == device.name;
};
/**
* Connect to the device.
* @param {evothings.easyble.connectCallback} success -
* Called when connected: success(device).
* @param {evothings.easyble.failCallback} fail -
* Called on error and if a disconnect happens.
* Format: error(errorMessage)
* @public
* @instance
* @example
* device.connect(
* function(device)
* {
* console.log('device connected.');
* // Read services here.
* },
* function(errorCode)
* {
* console.log('connect error: ' + errorCode);
* });
*/
device.connect = function(success, fail)
{
internal.connectToDevice(device, success, fail);
};
/**
* Check if device is connected.
* @return true if connected, false if not connected.
* @public
* @instance
* @example
* var connected = device.isConnected();
*/
device.isConnected = function()
{
return device.__isConnected;
};
/**
* Close the device. This disconnects from the BLE device.
* @public
* @instance
* @example
* device.close();
*/
device.close = function()
{
if (device.deviceHandle)
{
device.__isConnected = false;
evothings.ble.close(device.deviceHandle);
}
};
/**
* Read devices RSSI. Device must be connected.
* @param {evothings.easyble.rssiCallback} success - Callback called with
* RSSI value: success(rssi).
* @param {evothings.easyble.failCallback} fail - Called on error: fail(error).
* @public
* @instance
*/
device.readRSSI = function(success, fail)
{
evothings.ble.rssi(device.deviceHandle, success, fail);
};
/**
* @typedef {Object} evothings.easyble.ReadServicesOptions
* @description Options object for function
* {@link evothings.easyble.EasyBLEDevice#readServices}
* @property {array} serviceUUIDs - Array of service UUID strings.
*/
/**
* Read services, characteristics and descriptors for the
* specified service UUIDs.
* <strong>Services must be read be able to access characteristics and
* descriptors</strong>. Call this function before reading and writing
* characteristics/descriptors. (This function took an array of service
* UUIDs as first parameter in previous versions of this library, that
* is still supported for backwards compatibility but has ben deprecated.)
* @param {evothings.easyble.servicesCallback} success -
* Called when services are read: success(device)
* @param {evothings.easyble.failCallback} fail - error callback:
* error(errorMessage)
* @param {evothings.easyble.ReadServicesOptions} [options] - Optional
* object with setting that allow specification of which services to
* read. If left out, all services and related characteristics and
* descriptors are read (this can be time-consuming compared to
* reading selected services).
* @public
* @instance
* @example
* // Read all services
* device.readServices(
* function(device)
* {
* console.log('Services available.');
* // Read/write/enable notifications here.
* },
* function(errorCode)
* {
* console.log('readServices error: ' + errorCode);
* });
*
* // Read specific service
* device.readServices(
* function(device)
* {
* console.log('Services available.');
* // Read/write/enable notifications here.
* },
* function(errorCode)
* {
* console.log('readServices error: ' + errorCode);
* },
* { serviceUUIDs: ['19b10000-e8f2-537e-4f6c-d104768a1214'] });
*/
device.readServices = function(arg1, arg2, arg3, arg4)
{
// Parameters.
var serviceUUIDs;
var success;
var fail;
var options;
// For backwards compatibility when first arg specified
// an array of service UUIDs.
if (Array.isArray(arg1))
{
serviceUUIDs = arg1;
success = arg2;
fail = arg3;
options = arg4;
}
// Previously you could set first param to null to read all services.
// Here we handle this case for backwards compatibility.
else if (arg1 === undefined || arg1 === null)
{
serviceUUIDs = null;
success = arg2;
fail = arg3;
options = arg4;
}
else
{
success = arg1;
fail = arg2;
options = arg3;
}
if (options && Array.isArray(options.serviceUUIDs))
{
serviceUUIDs = options.serviceUUIDs;
}
internal.readServices(device, serviceUUIDs, success, fail);
};
/**
* Read value of characteristic.
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param {string} characteristicUUID - UUID of characteristic to read.
* @param {evothings.easyble.dataCallback} success - Success callback:
* success(data).
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @public
* @instance
* @example
* device.readCharacteristic(
* serviceUUID,
* characteristicUUID,
* function(data)
* {
* console.log('characteristic data: ' + evothings.ble.fromUtf8(data));
* },
* function(errorCode)
* {
* console.log('readCharacteristic error: ' + errorCode);
* });
*/
device.readCharacteristic = function(arg1, arg2, arg3, arg4)
{
if ('function' == typeof arg2)
{
// Service UUID is missing.
internal.readCharacteristic(device, arg1, arg2, arg3);
}
else
{
// Service UUID is present.
internal.readServiceCharacteristic(device, arg1, arg2, arg3, arg4);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#readCharacteristic}
* @deprecated
* @instance
*/
device.readServiceCharacteristic = function(
serviceUUID, characteristicUUID, success, fail)
{
internal.readServiceCharacteristic(
device, serviceUUID, characteristicUUID, success, fail);
};
/**
* Read value of descriptor.
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param {string} characteristicUUID - UUID of characteristic for descriptor.
* @param {string} descriptorUUID - UUID of descriptor to read.
* @param {evothings.easyble.dataCallback} success - Success callback:
* success(data).
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @public
* @instance
* @example
* device.readDescriptor(
* serviceUUID,
* characteristicUUID,
* descriptorUUID,
* function(data)
* {
* console.log('descriptor data: ' + evothings.ble.fromUtf8(data));
* },
* function(errorCode)
* {
* console.log('readDescriptor error: ' + errorCode);
* });
*/
device.readDescriptor = function(arg1, arg2, arg3, arg4, arg5)
{
if ('function' == typeof arg3)
{
// Service UUID is missing.
internal.readDescriptor(device, arg1, arg2, arg3, arg4);
}
else
{
// Service UUID is present.
internal.readServiceDescriptor(device, arg1, arg2, arg3, arg4, arg5);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#readDescriptor}
* @deprecated
* @instance
*/
device.readServiceDescriptor = function(
serviceUUID, characteristicUUID, descriptorUUID, success, fail)
{
internal.readServiceDescriptor(
device, serviceUUID, characteristicUUID, descriptorUUID, success, fail);
};
/**
* Write value of characteristic.
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param {string} characteristicUUID - UUID of characteristic to write to.
* @param {ArrayBufferView} value - Value to write.
* @param {evothings.easyble.emptyCallback} success - Success callback: success().
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @public
* @instance
* @example
* device.writeCharacteristic(
* serviceUUID,
* characteristicUUID,
* new Uint8Array([1]), // Write byte with value 1.
* function()
* {
* console.log('characteristic written.');
* },
* function(errorCode)
* {
* console.log('writeCharacteristic error: ' + errorCode);
* });
*/
device.writeCharacteristic = function(arg1, arg2, arg3, arg4, arg5)
{
if ('function' == typeof arg3)
{
// Service UUID is missing.
internal.writeCharacteristic(device, arg1, arg2, arg3, arg4);
}
else
{
// Service UUID is present.
internal.writeServiceCharacteristic(device, arg1, arg2, arg3, arg4, arg5);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#writeCharacteristic}
* @deprecated
* @instance
*/
device.writeServiceCharacteristic = function(
serviceUUID, characteristicUUID, value, success, fail)
{
internal.writeServiceCharacteristic(
device, serviceUUID, characteristicUUID, value, success, fail);
};
/**
* Write value of a characteristic for a specific service without response.
* This faster but not as fail safe as writing with response.
* Asks the remote device to NOT send a confirmation message.
* Experimental, implemented on Android.
* @param {string} serviceUUID - UUID of service that has the characteristic.
* @param {string} characteristicUUID - UUID of characteristic to write to.
* @param {ArrayBufferView} value - Value to write.
* @param {evothings.easyble.emptyCallback} success - Success callback: success().
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @public
* @instance
* @example
* device.writeCharacteristicWithoutResponse(
* serviceUUID,
* characteristicUUID,
* new Uint8Array([1]), // Write byte with value 1.
* function()
* {
* console.log('data sent.');
* },
* function(errorCode)
* {
* console.log('writeCharacteristicWithoutResponse error: ' + errorCode);
* });
*/
device.writeCharacteristicWithoutResponse = function(
serviceUUID, characteristicUUID, value, success, fail)
{
internal.writeServiceCharacteristicWithoutResponse(
device, serviceUUID, characteristicUUID, value, success, fail);
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#writeCharacteristicWithoutResponse}
* @deprecated
* @instance
*/
device.writeServiceCharacteristicWithoutResponse = function(
serviceUUID, characteristicUUID, value, success, fail)
{
internal.writeServiceCharacteristicWithoutResponse(
device, serviceUUID, characteristicUUID, value, success, fail);
};
/**
* Write value of descriptor.
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param {string} characteristicUUID - UUID of characteristic for descriptor.
* @param {string} descriptorUUID - UUID of descriptor to write to.
* @param {ArrayBufferView} value - Value to write.
* @param {evothings.easyble.emptyCallback} success - Success callback: success().
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @public
* @instance
* @example
* device.writeDescriptor(
* serviceUUID,
* characteristicUUID,
* descriptorUUID,
* new Uint8Array([1]), // Write byte with value 1.
* function()
* {
* console.log('descriptor written.');
* },
* function(errorCode)
* {
* console.log('writeDescriptor error: ' + errorCode);
* });
*/
device.writeDescriptor = function(arg1, arg2, arg3, arg4, arg5, arg6)
{
if ('function' == typeof arg4)
{
// Service UUID is missing.
internal.writeDescriptor(device, arg1, arg2, arg3, arg4, arg5);
}
else
{
// Service UUID is present.
internal.writeServiceDescriptor(device, arg1, arg2, arg3, arg4, arg5, arg6);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#writeDescriptor}
* @deprecated
* @instance
*/
device.writeServiceDescriptor = function(
serviceUUID, characteristicUUID, descriptorUUID, value, success, fail)
{
internal.writeServiceDescriptor(
device,
serviceUUID,
characteristicUUID,
descriptorUUID,
value,
success,
fail);
};
/**
* @typedef {Object} evothings.easyble.NotificationOptions
* @description Options object for functions
* {@link evothings.easyble.EasyBLEDevice#enableNotification}
* and {@link evothings.easyble.EasyBLEDevice#disableNotification}.
* @property {boolean} writeConfigDescriptor - Supported on Android, ignored on iOS.
* Set to false to disable automatic writing of notification or indication
* config descriptor value. This is useful in special cases when full control
* of writing the config descriptor is needed.
*/
/**
* Subscribe to value updates of a characteristic.
* The success function will be called repeatedly whenever there
* is new data available.
* <p>On Android you can disable automatic write of notify/indicate and write
* the configuration descriptor yourself, supply an options object as
* last parameter, see example below.</p>
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param {string} characteristicUUID - UUID of characteristic to subscribe to.
* @param {evothings.easyble.dataCallback} success - Success callback:
* success(data).
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error).
* @param {evothings.easyble.NotificationOptions} [options] - Optional settings.
* @public
* @instance
* @example
* // Example call:
* device.enableNotification(
* serviceUUID,
* characteristicUUID,
* function(data)
* {
* console.log('characteristic data: ' + evothings.ble.fromUtf8(data));
* },
* function(errorCode)
* {
* console.log('enableNotification error: ' + errorCode);
* });
*
* // Turn off automatic writing of the config descriptor (for special cases):
* device.enableNotification(
* serviceUUID,
* characteristicUUID,
* function(data)
* {
* console.log('characteristic data: ' + evothings.ble.fromUtf8(data));
* },
* function(errorCode)
* {
* console.log('enableNotification error: ' + errorCode);
* },
* { writeConfigDescriptor: false });
*/
device.enableNotification = function(arg1, arg2, arg3, arg4, arg5)
{
if ('function' == typeof arg2)
{
// Service UUID is missing.
internal.enableNotification(device, arg1, arg2, arg3, arg4);
}
else
{
// Service UUID is present.
internal.enableServiceNotification(device, arg1, arg2, arg3, arg4, arg5);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#enableNotification}
* @deprecated
* @instance
*/
device.enableServiceNotification = function(
serviceUUID, characteristicUUID, success, fail, options)
{
internal.enableServiceNotification(
device,
serviceUUID,
characteristicUUID,
success,
fail,
options);
};
/**
* Unsubscribe from characteristic updates to stop notifications.
* <p>On Android you can disable automatic write of notify/indicate and write
* the configuration descriptor yourself, supply an options object as
* last parameter, see example below.</p>
* @param {string} serviceUUID - UUID of service that has the given
* characteristic (previous versions of this library allowed leaving out
* the service UUID, this is unsafe practice and has been deprecated, always
* specify the service UUID).
* @param serviceUUID - UUID of service that has the given characteristic.
* @param characteristicUUID - UUID of characteristic to unsubscribe from.
* @param {evothings.easyble.emptyCallback} success - Success callback: success()
* @param {evothings.easyble.failCallback} fail - Error callback: fail(error)
* @param {evothings.easyble.NotificationOptions} [options] - Optional settings.
* @public
* @instance
* @example
* // Example call:
* device.disableNotification(
* serviceUUID,
* characteristicUUID,
* function()
* {
* console.log('characteristic notification disabled');
* },
* function(errorCode)
* {
* console.log('disableNotification error: ' + errorCode);
* });
*/
device.disableNotification = function(arg1, arg2, arg3, arg4, arg5)
{
if ('function' == typeof arg2)
{
// Service UUID is missing.
internal.disableNotification(device, arg1, arg2, arg3, arg4);
}
else
{
// Service UUID is present.
internal.disableServiceNotification(device, arg1, arg2, arg3, arg4, arg5);
}
};
/**
* <strong>Deprecated</strong>.
* Use function {@link evothings.easyble.EasyBLEDevice#disableNotification}
* @deprecated
* @instance
*/
device.disableServiceNotification = function(
serviceUUID, characteristicUUID, success, fail, options)
{
internal.disableServiceNotification(
device, serviceUUID, characteristicUUID, success, fail, options);
};
};
/**
* Connect to a device.
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.connectToDevice = function(device, success, fail)
{
// Check that device is not already connected.
if (device.__isConnected)
{
fail(evothings.easyble.error.DEVICE_ALREADY_CONNECTED);
return;
}
evothings.ble.connect(
device.address,
// Success callback.
function(connectInfo)
{
// DEBUG LOG
console.log('BLE connect state: ' + connectInfo.state);
if (connectInfo.state == 2) // connected
{
device.deviceHandle = connectInfo.deviceHandle;
device.__uuidMap = {};
device.__serviceMap = {};
device.__isConnected = true;
internal.connectedDevices[device.address] = device;
success(device);
}
else if (connectInfo.state == 0) // disconnected
{
device.__isConnected = false;
internal.connectedDevices[device.address] = null;
// TODO: Perhaps this should be redesigned, as disconnect is
// more of a status change than an error? What do you think?
fail && fail(evothings.easyble.error.DISCONNECTED);
}
},
// Error callback.
function(errorCode)
{
// DEBUG LOG
console.log('BLE connect error: ' + errorCode);
// Set isConnected to false on error.
device.__isConnected = false;
internal.connectedDevices[device.address] = null;
fail(errorCode);
});
};
/**
* Obtain device services, them read characteristics and descriptors
* for the services with the given uuid(s).
* If serviceUUIDs is null, info is read for all services.
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.readServices = function(device, serviceUUIDs, success, fail)
{
// Read services.
evothings.ble.services(
device.deviceHandle,
function(services)
{
// Array that stores services.
device.__services = [];
for (var i = 0; i < services.length; ++i)
{
var service = services[i];
service.uuid = service.uuid.toLowerCase();
device.__services.push(service);
device.__uuidMap[service.uuid] = service;
}
internal.readCharacteristicsForServices(
device, serviceUUIDs, success, fail);
},
function(errorCode)
{
fail(errorCode);
});
};
/**
* Read characteristics and descriptors for the services with the given uuid(s).
* If serviceUUIDs is null, info for all services are read.
* Internal function.
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.readCharacteristicsForServices = function(device, serviceUUIDs, success, fail)
{
var characteristicsCallbackFun = function(service)
{
// Array with characteristics for service.
service.__characteristics = [];
return function(characteristics)
{
--readCounter; // Decrements the count added by services.
readCounter += characteristics.length;
for (var i = 0; i < characteristics.length; ++i)
{
var characteristic = characteristics[i];
characteristic.uuid = characteristic.uuid.toLowerCase();
service.__characteristics.push(characteristic);
device.__uuidMap[characteristic.uuid] = characteristic;
device.__serviceMap[service.uuid + ':' + characteristic.uuid] = characteristic;
// DEBUG LOG
//console.log('storing service:characteristic key: ' + service.uuid + ':' + characteristic.uuid);
//if (!characteristic)
//{
// console.log(' --> characteristic is null!')
//}
// Read descriptors for characteristic.
evothings.ble.descriptors(
device.deviceHandle,
characteristic.handle,
descriptorsCallbackFun(service, characteristic),
function(errorCode)
{
fail(errorCode);
});
}
};
};
/**
* @private
*/
var descriptorsCallbackFun = function(service, characteristic)
{
// Array with descriptors for characteristic.
characteristic.__descriptors = [];
return function(descriptors)
{
--readCounter; // Decrements the count added by characteristics.
for (var i = 0; i < descriptors.length; ++i)
{
var descriptor = descriptors[i];
descriptor.uuid = descriptor.uuid.toLowerCase();
characteristic.__descriptors.push(descriptor);
device.__uuidMap[characteristic.uuid + ':' + descriptor.uuid] = descriptor;
device.__serviceMap[service.uuid + ':' + characteristic.uuid + ':' + descriptor.uuid] = descriptor;
}
if (0 == readCounter)
{
// Everything is read.
success(device);
}
};
};
// Initialize read counter.
readCounter = 0;
if (null != serviceUUIDs)
{
// Read info for service UUIDs.
readCounter = serviceUUIDs.length;
for (var i = 0; i < serviceUUIDs.length; ++i)
{
var uuid = serviceUUIDs[i].toLowerCase();
var service = device.__uuidMap[uuid];
if (!service)
{
fail(evothings.easyble.error.SERVICE_NOT_FOUND + ' ' + uuid);
return;
}
// Read characteristics for service. Will also read descriptors.
evothings.ble.characteristics(
device.deviceHandle,
service.handle,
characteristicsCallbackFun(service),
function(errorCode)
{
fail(errorCode);
});
}
}
else
{
// Read info for all services.
readCounter = device.__services.length;
for (var i = 0; i < device.__services.length; ++i)
{
// Read characteristics for service. Will also read descriptors.
var service = device.__services[i];
evothings.ble.characteristics(
device.deviceHandle,
service.handle,
characteristicsCallbackFun(service),
function(errorCode)
{
fail(errorCode);
});
}
}
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.readCharacteristic = function(device, characteristicUUID, success, fail)
{
characteristicUUID = characteristicUUID.toLowerCase();
var characteristic = device.__uuidMap[characteristicUUID];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' +
characteristicUUID);
return;
}
evothings.ble.readCharacteristic(
device.deviceHandle,
characteristic.handle,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.readServiceCharacteristic = function(
device, serviceUUID, characteristicUUID, success, fail)
{
var key = serviceUUID.toLowerCase() + ':' + characteristicUUID.toLowerCase();
var characteristic = device.__serviceMap[key];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.readCharacteristic(
device.deviceHandle,
characteristic.handle,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.readDescriptor = function(
device, characteristicUUID, descriptorUUID, success, fail)
{
characteristicUUID = characteristicUUID.toLowerCase();
descriptorUUID = descriptorUUID.toLowerCase();
var descriptor = device.__uuidMap[characteristicUUID + ':' + descriptorUUID];
if (!descriptor)
{
fail(evothings.easyble.error.DESCRIPTOR_NOT_FOUND + ' ' + descriptorUUID);
return;
}
evothings.ble.readDescriptor(
device.deviceHandle,
descriptor.handle,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.readServiceDescriptor = function(
device, serviceUUID, characteristicUUID, descriptorUUID, success, fail)
{
var key = serviceUUID.toLowerCase() + ':' +
characteristicUUID.toLowerCase() + ':' +
descriptorUUID.toLowerCase();
var descriptor = device.__serviceMap[key];
if (!descriptor)
{
fail(evothings.easyble.error.DESCRIPTOR_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.readDescriptor(
device.deviceHandle,
descriptor.handle,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.writeCharacteristic = function(
device, characteristicUUID, value, success, fail)
{
characteristicUUID = characteristicUUID.toLowerCase();
var characteristic = device.__uuidMap[characteristicUUID];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' +
characteristicUUID);
return;
}
evothings.ble.writeCharacteristic(
device.deviceHandle,
characteristic.handle,
value,
function()
{
success();
},
function(errorCode)
{
fail(errorCode);
});
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.writeServiceCharacteristic = function(
device, serviceUUID, characteristicUUID, value, success, fail)
{
var key = serviceUUID.toLowerCase() + ':' + characteristicUUID.toLowerCase();
var characteristic = device.__serviceMap[key];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.writeCharacteristic(
device.deviceHandle,
characteristic.handle,
value,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.writeServiceCharacteristicWithoutResponse = function(
device, serviceUUID, characteristicUUID, value, success, fail)
{
var key = serviceUUID.toLowerCase() + ':' + characteristicUUID.toLowerCase();
// DEBUG LOG
//console.log('internal.writeServiceCharacteristicWithoutResponse key: ' + key)
//console.log('internal.writeServiceCharacteristicWithoutResponse serviceMap:')
for (var theKey in device.__serviceMap)
{
console.log(' ' + theKey);
}
var characteristic = device.__serviceMap[key];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.writeCharacteristicWithoutResponse(
device.deviceHandle,
characteristic.handle,
value,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.writeDescriptor = function(
device, characteristicUUID, descriptorUUID, value, success, fail)
{
characteristicUUID = characteristicUUID.toLowerCase();
descriptorUUID = descriptorUUID.toLowerCase();
var descriptor = device.__uuidMap[characteristicUUID + ':' + descriptorUUID];
if (!descriptor)
{
fail(evothings.easyble.error.DESCRIPTOR_NOT_FOUND + ' ' + descriptorUUID);
return;
}
evothings.ble.writeDescriptor(
device.deviceHandle,
descriptor.handle,
value,
function()
{
success();
},
function(errorCode)
{
fail(errorCode);
});
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.writeServiceDescriptor = function(
device, serviceUUID, characteristicUUID, descriptorUUID, value, success, fail)
{
var key = serviceUUID.toLowerCase() + ':' +
characteristicUUID.toLowerCase() + ':' +
descriptorUUID.toLowerCase();
var descriptor = device.__serviceMap[key];
if (!descriptor)
{
fail(evothings.easyble.error.DESCRIPTOR_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.writeDescriptor(
device.deviceHandle,
descriptor.handle,
value,
success,
fail);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.enableNotification = function(
device, characteristicUUID, success, fail, options)
{
characteristicUUID = characteristicUUID.toLowerCase();
var characteristic = device.__uuidMap[characteristicUUID];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' +
characteristicUUID);
return;
}
evothings.ble.enableNotification(
device.deviceHandle,
characteristic.handle,
success,
fail,
options);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.enableServiceNotification = function(
device, serviceUUID, characteristicUUID, success, fail, options)
{
var key = serviceUUID.toLowerCase() + ':' + characteristicUUID.toLowerCase();
var characteristic = device.__serviceMap[key];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.enableNotification(
device.deviceHandle,
characteristic.handle,
success,
fail,
options);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @deprecated Naming is a bit confusing, internally functions
* named "xxxServiceYYY" are the "future-safe" onces, but in
* the public API functions "xxxYYY" are new "future-safe"
* (and backwards compatible).
* @private
*/
internal.disableNotification = function(
device, characteristicUUID, success, fail, options)
{
characteristicUUID = characteristicUUID.toLowerCase();
var characteristic = device.__uuidMap[characteristicUUID];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' +
characteristicUUID);
return;
}
evothings.ble.disableNotification(
device.deviceHandle,
characteristic.handle,
success,
fail,
options);
};
/**
* Called from evothings.easyble.EasyBLEDevice.
* @private
*/
internal.disableServiceNotification = function(
device, serviceUUID, characteristicUUID, success, fail, options)
{
var key = serviceUUID.toLowerCase() + ':' + characteristicUUID.toLowerCase();
var characteristic = device.__serviceMap[key];
if (!characteristic)
{
fail(evothings.easyble.error.CHARACTERISTIC_NOT_FOUND + ' ' + key);
return;
}
evothings.ble.disableNotification(
device.deviceHandle,
characteristic.handle,
success,
fail,
options);
};
/**
* Prints and object for debugging purposes.
* @deprecated. Defined here for backwards compatibility.
* Use evothings.printObject().
* @public
*/
evothings.easyble.printObject = evothings.printObject;
/**
* Reset the BLE hardware. Can be time consuming.
* @public
*/
evothings.easyble.reset = function()
{
evothings.ble.reset();
};
})();