// File eddystone.dist.js 2016-08-15T11:27:33Z // This file was generated by buildDistFiles.rb // -------------------------------------------------- // File: evothings-dist-base.js // // Set up definitions needed by Evothings JavaScript libraries // distribution files. // // Global holding everything. window.evothings = window.evothings || {}; // Define an empty No Operation function. This function is called // in place of async script loading, since we build a single merged file. evothings.__NOOP_FUN__ = function() {}; // -------------------------------------------------- // File: evothings.js // // Here we define common function such as async script loading and OS detection. ;(function() { window.evothings = window.evothings || {}; /** * @namespace * @description

Functions for loading scripts asynchronously, * detecting platform, and other common application functionality.

* @alias evothings * @public */ var evothings = window.evothings; /* ------------------ Script loading ------------------ */ var mScriptLoadingCounter = 0; var mLoadedScripts = {}; var mScriptsLoadedCallbacks = []; /** * Make sure to catch any DOMContentLoaded events occurring before * asynchronous loading of scripts. Those scripts, like ui.js, should check * this variable before listening for the event. */ evothings.gotDOMContentLoaded = false; window.addEventListener('DOMContentLoaded', function(e) { evothings.gotDOMContentLoaded = true; }) /** * Load a script. * @param {string} url - URL or path to the script. Relative paths are * relative to the HTML file that initiated script loading. * @param {function} successCallback - Optional parameterless function that * will be called when the script has loaded. * @param {function} errorCallback - Optional function that will be called * if loading the script fails, takes an error object as parameter. * @public */ evothings.loadScript = function(url, successCallback, errorCallback) { // If script is already loaded call callback directly and return. if (mLoadedScripts[url] == 'loadingcomplete') { successCallback && successCallback(); return; } // Add script to dictionary of loaded scripts. mLoadedScripts[url] = 'loadingstarted'; ++mScriptLoadingCounter; // Create script tag. var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; // Bind the onload event. script.onload = function() { // Mark as loaded. mLoadedScripts[url] = 'loadingcomplete'; --mScriptLoadingCounter; // Call success callback if given. successCallback && successCallback(); // Call scripts loaded callbacks if this was the last script loaded. if (0 == mScriptLoadingCounter) { for (var i = 0; i < mScriptsLoadedCallbacks.length; ++i) { var loadedCallback = mScriptsLoadedCallbacks[i]; loadedCallback && loadedCallback(); } // Clear callbacks - should we do this??? mScriptsLoadedCallbacks = []; } }; // onerror fires for things like malformed URLs and 404's. // If this function is called, the matching onload will not be called and // scriptsLoaded will not fire. script.onerror = function(error) { errorCallback && errorCallback(error); }; // Attaching the script tag to the document starts loading the script. document.head.appendChild(script); }; /** * Load array of scripts. * @param {array} array - Array of URL or path name stringa. * Relative paths are relative to the HTML file that initiated * script loading. * @param {function} loadedCallback - Optional parameterless * function called when all scripts in the array has loaded. * @public */ evothings.loadScripts = function(array, loadedCallback) { var lib = array.shift(); if (!lib) { // Array is empty and all scripts are loaded. loadedCallback && loadedCallback(); } else { // Load next script. evothings.loadScript(lib, function() { evothings.loadScripts(array, loadedCallback); }); } }; /** * Experimental. * Mark a script as loaded. This is useful if a script is designed * to be included both in HTML and in JavaScript. * @param {string} pathOrURL - URL or path to the script. Relative paths are * relative to the HTML file that initiated script loading. * @public */ evothings.markScriptAsLoaded = function(pathOrURL) { mLoadedScripts[url] = 'loadingcomplete'; }; /** *

Add a callback that will be called when all scripts are loaded.

*

It is good practise to always use this function when * loading script asynchronously or using a library that does so.

* @param {function} callback - Parameterless function that will * be called when all scripts have finished loading. * @public */ evothings.scriptsLoaded = function(callback) { // If scripts are already loaded call the callback directly, // else add the callback to the callbacks array. if (0 != Object.keys(mLoadedScripts).length && 0 == mScriptLoadingCounter) { callback && callback(); } else { mScriptsLoadedCallbacks.push(callback); } }; /* ------------------ Debugging ------------------ */ /** * Print a JavaScript object (dictionary). For debugging. * * @param {Object} obj - Object to print. * @param {function} printFun - print function (optional - defaults to * console.log if not given). * * @example * var obj = { company: 'Evothings', field: 'IoT' }; * evothings.printObject(obj); * evothings.printObject(obj, console.log); * * @public */ evothings.printObject = function(obj, printFun) { printFun = printFun || console.log; function print(obj, level) { var indent = new Array(level + 1).join(' '); for (var prop in obj) { if (obj.hasOwnProperty(prop)) { var value = obj[prop]; if (typeof value == 'object') { printFun(indent + prop + ':'); print(value, level + 1); } else { printFun(indent + prop + ': ' + value); } } } } print(obj, 0); }; /* ------------------ Platform check ------------------ */ /** * @namespace * @description Namespace for platform check functions. */ evothings.os = {}; /** * Returns true if current platform is iOS, false if not. * @return {boolean} true if platform is iOS, false if not. * @public */ evothings.os.isIOS = function() { return /iP(hone|ad|od)/.test(navigator.userAgent); }; /** * Returns true if current platform is iOS 7, false if not. * @return {boolean} true if platform is iOS 7, false if not. * @public */ evothings.os.isIOS7 = function() { return /iP(hone|ad|od).*OS 7/.test(navigator.userAgent); }; /** * Returns true if current platform is Android, false if not. * @return {boolean} true if platform is Android, false if not. * @public */ evothings.os.isAndroid = function() { return /Android|android/.test(navigator.userAgent); }; /** * Returns true if current platform is Windows Phone, false if not. * @return {boolean} true if platform is Windows Phone, false if not. * @public */ evothings.os.isWP = function() { return /Windows Phone/.test(navigator.userAgent); }; })(); // -------------------------------------------------- // File: util.js evothings = window.evothings || {}; /** * @namespace * @author Aaron Ardiri * @author Fredrik Eldh * @description Utilities for byte arrays. */ evothings.util = {}; ;(function() { /** * Interpret byte buffer as little endian 8 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.littleEndianToInt8 = function(data, offset) { var x = evothings.util.littleEndianToUint8(data, offset) if (x & 0x80) x = x - 256 return x } /** * Interpret byte buffer as unsigned little endian 8 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.littleEndianToUint8 = function(data, offset) { return data[offset] } /** * Interpret byte buffer as little endian 16 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.littleEndianToInt16 = function(data, offset) { return (evothings.util.littleEndianToInt8(data, offset + 1) << 8) + evothings.util.littleEndianToUint8(data, offset) } /** * Interpret byte buffer as unsigned little endian 16 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.littleEndianToUint16 = function(data, offset) { return (evothings.util.littleEndianToUint8(data, offset + 1) << 8) + evothings.util.littleEndianToUint8(data, offset) } /** * Interpret byte buffer as unsigned little endian 32 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.littleEndianToUint32 = function(data, offset) { return (evothings.util.littleEndianToUint8(data, offset + 3) << 24) + (evothings.util.littleEndianToUint8(data, offset + 2) << 16) + (evothings.util.littleEndianToUint8(data, offset + 1) << 8) + evothings.util.littleEndianToUint8(data, offset) } /** * Interpret byte buffer as signed big endian 16 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.bigEndianToInt16 = function(data, offset) { return (evothings.util.littleEndianToInt8(data, offset) << 8) + evothings.util.littleEndianToUint8(data, offset + 1) } /** * Interpret byte buffer as unsigned big endian 16 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.bigEndianToUint16 = function(data, offset) { return (evothings.util.littleEndianToUint8(data, offset) << 8) + evothings.util.littleEndianToUint8(data, offset + 1) } /** * Interpret byte buffer as unsigned big endian 32 bit integer. * Returns converted number. * @param {ArrayBuffer} data - Input buffer. * @param {number} offset - Start of data. * @return Converted number. * @public */ evothings.util.bigEndianToUint32 = function(data, offset) { return (evothings.util.littleEndianToUint8(data, offset) << 24) + (evothings.util.littleEndianToUint8(data, offset + 1) << 16) + (evothings.util.littleEndianToUint8(data, offset + 2) << 8) + evothings.util.littleEndianToUint8(data, offset + 3) } /** * Converts a single Base64 character to a 6-bit integer. * @private */ function b64ToUint6(nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; } /** * Decodes a Base64 string. Returns a Uint8Array. * nBlocksSize is optional. * @param {String} sBase64 * @param {int} nBlocksSize * @return {Uint8Array} * @public */ evothings.util.base64DecToArr = function(sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""); var nInLen = sB64Enc.length; var nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2; var taBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return taBytes; } /** * Returns the integer i in hexadecimal string form, * with leading zeroes, such that * the resulting string is at least byteCount*2 characters long. * @param {int} i * @param {int} byteCount * @public */ evothings.util.toHexString = function(i, byteCount) { var string = (new Number(i)).toString(16); while(string.length < byteCount*2) { string = '0'+string; } return string; } /** * Takes a ArrayBuffer or TypedArray and returns its hexadecimal representation. * No spaces or linebreaks. * @param data * @public */ evothings.util.typedArrayToHexString = function(data) { // view data as a Uint8Array, unless it already is one. if(data.buffer) { if(!(data instanceof Uint8Array)) data = new Uint8Array(data.buffer); } else if(data instanceof ArrayBuffer) { data = new Uint8Array(data); } else { throw "not an ArrayBuffer or TypedArray."; } var str = ''; for(var i=0; iLibrary for making BLE programming easier.

*

An all-in-one file with this library and helper libraries included is * available in file easyble.dist.js. Include this file in index.html (recommended).

*

If you include easyble.js rather than easyble.dist.js 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).

*/ 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 Deprecated. 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; lServices must be read be able to access characteristics and * descriptors. 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); } }; /** * Deprecated. * 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); } }; /** * Deprecated. * 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); } }; /** * Deprecated. * 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); }; /** * Deprecated. * 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); } }; /** * Deprecated. * 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. *

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.

* @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); } }; /** * Deprecated. * 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. *

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.

* @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); } }; /** * Deprecated. * 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(); }; })(); // -------------------------------------------------- // File: eddystone.js // This library scans for Eddystone beacons and translates their // advertisements into user-friendly variables. // The protocol specification is available at: // https://github.com/google/eddystone ;(function() { // prerequisites evothings.__NOOP_FUN__([ 'libs/evothings/easyble/easyble.js', ]) /** * @namespace * @description

Library for Eddystone beacons.

*

It is safe practise to call function {@link evothings.scriptsLoaded} * to ensure dependent libraries are loaded before calling functions * in this library.

*/ evothings.eddystone = {}; // constants var BLUETOOTH_BASE_UUID = '-0000-1000-8000-00805f9b34fb'; // false when scanning is off. true when on. var isScanning = false; /** * @description Starts scanning for Eddystone devices. *

Found devices and errors will be reported to the supplied callbacks.

*

Will keep scanning indefinitely until you call stopScan().

*

To conserve energy, call stopScan() as soon as you've found the device * you're looking for.

*

Calling startScan() while scanning is in progress will produce an error.

* * @param {evothings.eddystone.scanCallback} - Success function called * when a beacon is found. * @param {evothings.eddystone.failCallback} - Error callback: fail(error). * * @public * * @example * evothings.eddystone.startScan( * function(beacon) * { * console.log('Found beacon: ' + beacon.url); * }, * function(error) * { * console.log('Scan error: ' + error); * }); */ evothings.eddystone.startScan = function(scanCallback, failCallback) { // Internal callback variable names. var win = scanCallback; var fail = failCallback; // If scanning is already in progress, fail. if(isScanning) { fail("Scanning already in progress!"); return; } isScanning = true; // The device object given in this callback is reused by easyble. // Therefore we can store data in it and expect to have the data still be there // on the next callback with the same device. evothings.easyble.startScan( // Scan for Eddystone Service UUID. // This enables background scanning on iOS (and Android). ['0000FEAA-0000-1000-8000-00805F9B34FB'], function(device) { // A device might be an Eddystone if it has advertisementData... var ad = device.advertisementData; if(!ad) return; // With serviceData... var sd = ad.kCBAdvDataServiceData; if(!sd) return; // And the 0xFEAA service. var base64data = sd['0000feaa'+BLUETOOTH_BASE_UUID]; if(!base64data) return; var byteArray = evothings.util.base64DecToArr(base64data); // If the data matches one of the Eddystone frame formats, // we can forward it to the user. if(parseFrameUID(device, byteArray, win, fail)) return; if(parseFrameURL(device, byteArray, win, fail)) return; if(parseFrameTLM(device, byteArray, win, fail)) return; }, function(error) { fail(error); }); } /** * @description This function is a parameter to startScan() and * is called when a beacons is discovered/updated. * @callback evothings.eddystone.scanCallback * @param {evothings.eddystone.EddystoneDevice} beacon - Beacon * found during scanning. */ /** * @description This function is called when an operation fails. * @callback evothings.eddystone.failCallback * @param {string} errorString - A human-readable string that * describes the error that occurred. */ /** * @description Object representing a BLE device. Inherits from * {@link evothings.easyble.EasyBLEDevice}. * Which properties are available depends on which packets types broadcasted * by the beacon. Properties may be undefined. Typically properties are populated * as scanning processes. * @typedef {Object} evothings.eddystone.EddystoneDevice * @property {string} url - An Internet URL. * @property {number} txPower - A signed integer, the signal strength in decibels, * factory-measured at a range of 0 meters. * @property {Uint8Array} nid - 10-byte namespace ID. * @property {Uint8Array} bid - 6-byte beacon ID. * @property {number} voltage - Device's battery voltage, in millivolts, * or 0 (zero) if device is not battery-powered. * @property {number} temperature - Device's ambient temperature in 256:ths of * degrees Celcius, or 0x8000 if device has no thermometer. * @property {number} adv_cnt - Count of advertisement frames sent since device's startup. * @property {number} dsec_cnt - Time since device's startup, in deci-seconds * (10 units equals 1 second). */ /** * @description Stop scanning for Eddystone devices. * @public * @example * evothings.eddystone.stopScan(); */ evothings.eddystone.stopScan = function() { evothings.easyble.stopScan(); isScanning = false; } /** * @description Calculate the accuracy (distance in meters) of the beacon. *

The beacon distance calculation uses txPower at 1 meters, but the * Eddystone protocol reports the value at 0 meters. 41dBm is the signal * loss that occurs over 1 meter, this value is subtracted by default * from the reported txPower. You can tune the calculation by adding * or subtracting to param txPower.

*

Note that the returned distance value is not accurate, and that * it fluctuates over time. Sampling/filtering over time is recommended * to obtain a stable value.

* @public * @param txPower The txPower of the beacon. * @param rssi The RSSI of the beacon, subtract or add to this value to * tune the dBm strength. 41dBm is subtracted from this value in the * distance algorithm used by calculateAccuracy. * @return Distance in meters, or null if unable to compute distance * (occurs for example when txPower or rssi is undefined). * @example * // Note that beacon.txPower and beacon.rssi many be undefined, * // in which case calculateAccuracy returns null. This happens * // before txPower and rssi have been reported by the beacon. * var distance = evothings.eddystone.calculateAccuracy( * beacon.txPower, beacon.rssi); */ evothings.eddystone.calculateAccuracy = function(txPower, rssi) { if (!rssi || rssi >= 0 || !txPower) { return null } // Algorithm // http://developer.radiusnetworks.com/2014/12/04/fundamentals-of-beacon-ranging.html // http://stackoverflow.com/questions/21338031/radius-networks-ibeacon-ranging-fluctuation // The beacon distance formula uses txPower at 1 meters, but the Eddystone // protocol reports the value at 0 meters. 41dBm is the signal loss that // occurs over 1 meter, so we subtract that from the reported txPower. var ratio = rssi * 1.0 / (txPower - 41) if (ratio < 1.0) { return Math.pow(ratio, 10) } else { var accuracy = (0.89976) * Math.pow(ratio, 7.7095) + 0.111 return accuracy } } /** * Create a low-pass filter. * @param cutOff The filter cut off value. * @return Object with two functions: filter(value), value() * @example * // Create filter with cut off 0.8 * var lowpass = evothings.eddystone.createLowPassFilter(0.8) * // Filter value (returns current filter value) * distance = lowpass.filter(distance) * // Get current value * distance = lowpass.value() */ evothings.eddystone.createLowPassFilter = function(cutOff, state) { // Filter cut off. if (undefined === cutOff) { cutOff = 0.8 } // Current value of the filter. if (undefined === state) { state = 0.0 } // Return object with filter functions. return { // This function will filter the given value. // Returns the current value of the filter. filter: function(value) { state = (value * (1.0 - cutOff)) + (state * cutOff) return state }, // This function returns the current value of the filter. value: function() { return state } } } // Return true on frame type recognition, false otherwise. function parseFrameUID(device, data, win, fail) { if(data[0] != 0x00) return false; // The UID frame has 18 bytes + 2 bytes reserved for future use // https://github.com/google/eddystone/tree/master/eddystone-uid // Check that we got at least 18 bytes. if(data.byteLength < 18) { fail("UID frame: invalid byteLength: "+data.byteLength); return true; } device.txPower = evothings.util.littleEndianToInt8(data, 1); device.nid = data.subarray(2, 12); // Namespace ID. device.bid = data.subarray(12, 18); // Beacon ID. win(device); return true; } function parseFrameURL(device, data, win, fail) { if(data[0] != 0x10) return false; if(data.byteLength < 4) { fail("URL frame: invalid byteLength: "+data.byteLength); return true; } device.txPower = evothings.util.littleEndianToInt8(data, 1); // URL scheme prefix var url; switch(data[2]) { case 0: url = 'http://www.'; break; case 1: url = 'https://www.'; break; case 2: url = 'http://'; break; case 3: url = 'https://'; break; default: fail("URL frame: invalid prefix: "+data[2]); return true; } // Process each byte in sequence. var i = 3; while(i < data.byteLength) { var c = data[i]; // A byte is either a top-domain shortcut, or a printable ascii character. if(c < 14) { switch(c) { case 0: url += '.com/'; break; case 1: url += '.org/'; break; case 2: url += '.edu/'; break; case 3: url += '.net/'; break; case 4: url += '.info/'; break; case 5: url += '.biz/'; break; case 6: url += '.gov/'; break; case 7: url += '.com'; break; case 8: url += '.org'; break; case 9: url += '.edu'; break; case 10: url += '.net'; break; case 11: url += '.info'; break; case 12: url += '.biz'; break; case 13: url += '.gov'; break; } } else if(c < 32 || c >= 127) { // Unprintables are not allowed. fail("URL frame: invalid character: "+data[2]); return true; } else { url += String.fromCharCode(c); } i += 1; } // Set URL field of the device. device.url = url; win(device); return true; } function parseFrameTLM(device, data, win, fail) { if(data[0] != 0x20) return false; if(data[1] != 0x00) { fail("TLM frame: unknown version: "+data[1]); return true; } if(data.byteLength != 14) { fail("TLM frame: invalid byteLength: "+data.byteLength); return true; } device.voltage = evothings.util.bigEndianToUint16(data, 2); var temp = evothings.util.bigEndianToUint16(data, 4); if(temp == 0x8000) { device.temperature = 0x8000; } else { device.temperature = evothings.util.bigEndianToInt16(data, 4) / 256.0; } device.adv_cnt = evothings.util.bigEndianToUint32(data, 6); device.dsec_cnt = evothings.util.bigEndianToUint32(data, 10); win(device); return true; } var isAdvertising = false; function isValidHex(str, len) { return typeof str === "string" && str.length === len * 2 && /^[0-9A-F]+$/.test(str) } function txPowerLevel() { // ADVERTISE_TX_POWER_MEDIUM taken from // txeddystone_uid/MainActivity.java#L324-L340 (https://goo.gl/MqWqUu) return -26 } // From [txeddystone_uid/MainActivity.java](https://goo.gl/VGtnN1) function toByteArray(hexString) { var out = []; for (var i = 0; i < hexString.length; i += 2) { out.push((parseInt(hexString.charAt(i), 16) << 4) + parseInt(hexString.charAt(i + 1), 16)); } return out; } // Variable that points to the Cordova Base64 module, loaded lazily. var base64; function buildServiceData(namespace, instance) { if (!base64) { base64 = cordova.require('cordova/base64'); } var data = [0, txPowerLevel()]; Array.prototype.push.apply(data, toByteArray(namespace)); Array.prototype.push.apply(data, toByteArray(instance)); return base64.fromArrayBuffer(new Uint8Array(data)); } /** * @description Start Advertising Eddystone Beacon. *

Namespace may only be 10 hex bytes

*

Instance may only be 6 hex bytes

*

Will keep advertising indefinitely until you call stopAdvertise().

* @public * @param {string} namespace - Hex namespace string (10 bytes, 20 chars) * @param {string} instance - Hex instance string (6 bytes, 12 chars) * @param {evothings.eddystone.advertiseCallback} - Success function called when advertising started * @param {evothings.eddystone.failCallback} - Error callback: fail(error). * @example * evothings.eddystone.startAdvertise("0123456789ABCDEF0123", "0123456789AB"); */ evothings.eddystone.startAdvertise = function(namespace, instance, advertiseCallback, failCallback) { // Internal callback variable names. var win = advertiseCallback; var fail = failCallback; if(isAdvertising) { fail("Advertising already in progress!"); return; } if (!isValidHex(namespace, 10)) { fail("Invalid namespace, must be 10 hex bytes"); return; } if (!isValidHex(instance, 6)) { fail("Invalid instance, must be 6 hex bytes"); return; } var serviceData = buildServiceData(namespace, instance); var transmitData = { serviceUUIDs: ["0000FEAA-0000-1000-8000-00805F9B34FB"], serviceData: { "0000FEAA-0000-1000-8000-00805F9B34FB": serviceData } }; var settings = { broadcastData: transmitData, scanResponseData: transmitData }; evothings.ble.peripheral.startAdvertise( settings, function() { isAdvertising = true; win() }, function(error) { isAdvertising = false; fail(error) }); } /** * @description Stop Advertising Eddystone Beacon. * @public * @param {Function} onSuccess - Success function called when advertising stoppped * @param {evothings.eddystone.failCallback} - Error callback: fail(error). * @example * evothings.eddystone.stopAdvertise(); */ evothings.eddystone.stopAdvertise = function(onSuccess, failCallback) { isAdvertising = false; evothings.ble.peripheral.stopAdvertise(onSuccess, failCallback); } })(); // --------------------------------------------------