cordova-plugin-ble-central
Version:
Bluetooth Low Energy (BLE) Central Plugin
530 lines (447 loc) • 18.8 kB
JavaScript
// (c) 2014-2016 Don Coleman
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* global cordova, module */
;
const exec = require('cordova/exec');
var stringToArrayBuffer = function (str) {
var ret = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
ret[i] = str.charCodeAt(i);
}
return ret.buffer;
};
var base64ToArrayBuffer = function (b64) {
return stringToArrayBuffer(atob(b64));
};
function massageMessageNativeToJs(message) {
if (message.CDVType == 'ArrayBuffer') {
message = base64ToArrayBuffer(message.data);
}
return message;
}
// Cordova 3.6 doesn't unwrap ArrayBuffers in nested data structures
// https://github.com/apache/cordova-js/blob/94291706945c42fd47fa632ed30f5eb811080e95/src/ios/exec.js#L107-L122
function convertToNativeJS(object) {
Object.keys(object).forEach(function (key) {
var value = object[key];
object[key] = massageMessageNativeToJs(value);
if (typeof value === 'object') {
convertToNativeJS(value);
}
});
}
// set of auto-connected device ids
var autoconnected = {};
module.exports = {
scan: function (services, seconds, success, failure) {
module.exports.startScanWithOptions(services, { duration: seconds }, success, failure);
},
startScan: function (services, success, failure) {
module.exports.startScanWithOptions(services, undefined, success, failure);
},
stopScan: function (success, failure) {
exec(success, failure, 'BLE', 'stopScan', []);
},
startScanWithOptions: function (services, options, success, failure) {
var successWrapper = function (peripheral) {
convertToNativeJS(peripheral);
success(peripheral);
};
options = options || {};
exec(successWrapper, failure, 'BLE', 'startScanWithOptions', [services, options]);
},
// iOS only
connectedPeripheralsWithServices: function (services, success, failure) {
exec(success, failure, 'BLE', 'connectedPeripheralsWithServices', [services]);
},
// iOS only
peripheralsWithIdentifiers: function (identifiers, success, failure) {
exec(success, failure, 'BLE', 'peripheralsWithIdentifiers', [identifiers]);
},
// Android only
bondedDevices: function (success, failure) {
exec(success, failure, 'BLE', 'bondedDevices', []);
},
list: function (success, failure) {
exec(success, failure, 'BLE', 'list', []);
},
connect: function (device_id, success, failure) {
// wrap success so nested array buffers in advertising info are handled correctly
var successWrapper = function (peripheral) {
convertToNativeJS(peripheral);
success(peripheral);
};
exec(successWrapper, failure, 'BLE', 'connect', [device_id]);
},
autoConnect: function (deviceId, connectCallback, disconnectCallback) {
var disconnectCallbackWrapper;
autoconnected[deviceId] = true;
// wrap connectCallback so nested array buffers in advertising info are handled correctly
var connectCallbackWrapper = function (peripheral) {
convertToNativeJS(peripheral);
connectCallback(peripheral);
};
// iOS needs to reconnect on disconnect, unless ble.disconnect was called.
if (cordova.platformId === 'ios') {
disconnectCallbackWrapper = function (peripheral) {
// let the app know the peripheral disconnected
disconnectCallback(peripheral);
// reconnect if we have a peripheral.id and the user didn't call disconnect
if (peripheral.id && autoconnected[peripheral.id]) {
exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]);
}
};
} else {
// no wrapper for Android
disconnectCallbackWrapper = disconnectCallback;
}
exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]);
},
disconnect: function (device_id, success, failure) {
try {
delete autoconnected[device_id];
} catch (e) {
// ignore error
}
exec(success, failure, 'BLE', 'disconnect', [device_id]);
},
queueCleanup: function (device_id, success, failure) {
exec(success, failure, 'BLE', 'queueCleanup', [device_id]);
},
setPin: function (pin, success, failure) {
exec(success, failure, 'BLE', 'setPin', [pin]);
},
requestMtu: function (device_id, mtu, success, failure) {
exec(success, failure, 'BLE', 'requestMtu', [device_id, mtu]);
},
requestConnectionPriority: function (device_id, connectionPriority, success, failure) {
exec(success, failure, 'BLE', 'requestConnectionPriority', [device_id, connectionPriority]);
},
refreshDeviceCache: function (deviceId, timeoutMillis, success, failure) {
var successWrapper = function (peripheral) {
convertToNativeJS(peripheral);
success(peripheral);
};
exec(successWrapper, failure, 'BLE', 'refreshDeviceCache', [deviceId, timeoutMillis]);
},
// characteristic value comes back as ArrayBuffer in the success callback
read: function (device_id, service_uuid, characteristic_uuid, success, failure) {
exec(success, failure, 'BLE', 'read', [device_id, service_uuid, characteristic_uuid]);
},
// RSSI value comes back as an integer
readRSSI: function (device_id, success, failure) {
exec(success, failure, 'BLE', 'readRSSI', [device_id]);
},
// value must be an ArrayBuffer
write: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
exec(success, failure, 'BLE', 'write', [device_id, service_uuid, characteristic_uuid, value]);
},
// value must be an ArrayBuffer
writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
exec(success, failure, 'BLE', 'writeWithoutResponse', [
device_id,
service_uuid,
characteristic_uuid,
value,
]);
},
// value must be an ArrayBuffer
writeCommand: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
console.log('WARNING: writeCommand is deprecated, use writeWithoutResponse');
exec(success, failure, 'BLE', 'writeWithoutResponse', [
device_id,
service_uuid,
characteristic_uuid,
value,
]);
},
// success callback is called on notification
notify: function (device_id, service_uuid, characteristic_uuid, success, failure) {
console.log('WARNING: notify is deprecated, use startNotification');
exec(success, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]);
},
// success callback is called on notification
startNotification: function (device_id, service_uuid, characteristic_uuid, success, failure, options) {
const emitOnRegistered = options && options.emitOnRegistered == true;
function onEvent(data) {
if (data === 'registered') {
// For backwards compatibility, don't emit the registered event unless explicitly instructed
if (emitOnRegistered) success(data);
} else {
success(data);
}
}
exec(onEvent, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]);
},
// success callback is called when the descriptor 0x2902 is written
stopNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) {
exec(success, failure, 'BLE', 'stopNotification', [device_id, service_uuid, characteristic_uuid]);
},
isConnected: function (device_id, success, failure) {
exec(success, failure, 'BLE', 'isConnected', [device_id]);
},
isEnabled: function (success, failure) {
exec(success, failure, 'BLE', 'isEnabled', []);
},
// Android only
isLocationEnabled: function (success, failure) {
exec(success, failure, 'BLE', 'isLocationEnabled', []);
},
enable: function (success, failure) {
exec(success, failure, 'BLE', 'enable', []);
},
showBluetoothSettings: function (success, failure) {
exec(success, failure, 'BLE', 'showBluetoothSettings', []);
},
startLocationStateNotifications: function (success, failure) {
exec(success, failure, 'BLE', 'startLocationStateNotifications', []);
},
stopLocationStateNotifications: function (success, failure) {
exec(success, failure, 'BLE', 'stopLocationStateNotifications', []);
},
startStateNotifications: function (success, failure) {
exec(success, failure, 'BLE', 'startStateNotifications', []);
},
stopStateNotifications: function (success, failure) {
exec(success, failure, 'BLE', 'stopStateNotifications', []);
},
restoredBluetoothState: function (success, failure) {
exec(success, failure, 'BLE', 'restoredBluetoothState', []);
},
bond: function (device_id, success, failure, options) {
exec(success, failure, 'BLE', 'bond', [device_id, options || {}]);
},
unbond: function (device_id, success, failure) {
exec(success, failure, 'BLE', 'unbond', [device_id]);
},
readBondState: function (device_id, success, failure) {
exec(success, failure, 'BLE', 'readBondState', [device_id]);
},
};
module.exports.withPromises = {
scan: module.exports.scan,
startScan: module.exports.startScan,
startScanWithOptions: module.exports.startScanWithOptions,
connect: module.exports.connect,
stopScan: function () {
return new Promise(function (resolve, reject) {
module.exports.stopScan(resolve, reject);
});
},
disconnect: function (device_id) {
return new Promise(function (resolve, reject) {
module.exports.disconnect(device_id, resolve, reject);
});
},
bondedDevices: function () {
return new Promise(function (resolve, reject) {
module.exports.bondedDevices(resolve, reject);
});
},
list: function () {
return new Promise(function (resolve, reject) {
module.exports.list(resolve, reject);
});
},
queueCleanup: function (device_id) {
return new Promise(function (resolve, reject) {
module.exports.queueCleanup(device_id, resolve, reject);
});
},
setPin: function (pin) {
return new Promise(function (resolve, reject) {
module.exports.setPin(pin, resolve, reject);
});
},
read: function (device_id, service_uuid, characteristic_uuid) {
return new Promise(function (resolve, reject) {
module.exports.read(device_id, service_uuid, characteristic_uuid, resolve, reject);
});
},
write: function (device_id, service_uuid, characteristic_uuid, value) {
return new Promise(function (resolve, reject) {
module.exports.write(device_id, service_uuid, characteristic_uuid, value, resolve, reject);
});
},
writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value) {
return new Promise(function (resolve, reject) {
module.exports.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, value, resolve, reject);
});
},
startNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) {
return new Promise(function (resolve, reject) {
module.exports.startNotification(
device_id,
service_uuid,
characteristic_uuid,
(data) => {
resolve();
// Filter out registered callback
if (data !== 'registered') success(data);
},
(err) => {
reject(err);
failure(err);
},
{ emitOnRegistered: true }
);
});
},
stopNotification: function (device_id, service_uuid, characteristic_uuid) {
return new Promise(function (resolve, reject) {
module.exports.stopNotification(device_id, service_uuid, characteristic_uuid, resolve, reject);
});
},
isConnected: function (device_id, rejectWhenDisconnected) {
return new Promise(function (resolve, reject) {
if (rejectWhenDisconnected === false) {
module.exports.isConnected(
device_id,
() => resolve(true),
() => resolve(false)
);
} else {
module.exports.isConnected(device_id, resolve, reject);
}
});
},
isEnabled: function () {
return new Promise(function (resolve, reject) {
module.exports.isEnabled(resolve, reject);
});
},
enable: function () {
return new Promise(function (resolve, reject) {
module.exports.enable(resolve, reject);
});
},
showBluetoothSettings: function () {
return new Promise(function (resolve, reject) {
module.exports.showBluetoothSettings(resolve, reject);
});
},
startStateNotifications: function (success, failure) {
return new Promise(function (resolve, reject) {
module.exports.startStateNotifications(
function (state) {
resolve();
success(state);
},
function (err) {
reject(err);
failure(err);
}
);
});
},
stopStateNotifications: function () {
return new Promise(function (resolve, reject) {
module.exports.stopStateNotifications(resolve, reject);
});
},
startLocationStateNotifications: function (change, failure) {
return new Promise(function (resolve, reject) {
module.exports.startLocationStateNotifications(
function (state) {
resolve();
change(state);
},
function (err) {
reject(err);
failure(err);
}
);
});
},
stopLocationStateNotifications: function () {
return new Promise(function (resolve, reject) {
module.exports.stopLocationStateNotifications(resolve, reject);
});
},
readRSSI: function (device_id) {
return new Promise(function (resolve, reject) {
module.exports.readRSSI(device_id, resolve, reject);
});
},
requestMtu: function (device_id, mtu) {
return new Promise(function (resolve, reject) {
module.exports.requestMtu(device_id, mtu, resolve, reject);
});
},
requestConnectionPriority: function (device_id, priority) {
return new Promise(function (resolve, reject) {
module.exports.requestConnectionPriority(device_id, priority, resolve, reject);
});
},
restoredBluetoothState: function () {
return new Promise(function (resolve, reject) {
module.exports.restoredBluetoothState(resolve, reject);
});
},
bond: function (device_id, options) {
return new Promise(function (resolve, reject) {
module.exports.bond(device_id, resolve, reject, options);
});
},
unbond: function (device_id) {
return new Promise(function (resolve, reject) {
module.exports.unbond(device_id, resolve, reject);
});
},
readBondState: function (device_id) {
return new Promise(function (resolve, reject) {
module.exports.readBondState(device_id, resolve, reject);
});
},
};
module.exports.l2cap = {
close(device_id, psm, success, failure) {
exec(success, failure, 'BLE', 'closeL2Cap', [device_id, psm]);
},
open(device_id, psmOrOptions, connectCallback, disconnectCallback) {
var psm = psmOrOptions;
var settings = {};
if (psmOrOptions != undefined && 'psm' in psmOrOptions) {
psm = psmOrOptions.psm;
settings = psmOrOptions;
}
exec(connectCallback, disconnectCallback, 'BLE', 'openL2Cap', [device_id, psm, settings]);
},
receiveData(device_id, psm, receive) {
exec(receive, function () {}, 'BLE', 'receiveDataL2Cap', [device_id, psm]);
},
write(device_id, psm, data, success, failure) {
exec(success, failure, 'BLE', 'writeL2Cap', [device_id, psm, data]);
},
};
module.exports.withPromises.l2cap = {
close(device_id, psm) {
return new Promise(function (resolve, reject) {
module.exports.l2cap.close(device_id, psm, resolve, reject);
});
},
open(device_id, psmOrOptions, disconnectCallback) {
return new Promise(function (resolve, reject) {
module.exports.l2cap.open(device_id, psmOrOptions, resolve, function (e) {
disconnectCallback(e);
reject(e);
});
});
},
write(device_id, psm, data) {
return new Promise(function (resolve, reject) {
module.exports.l2cap.write(device_id, psm, data, resolve, reject);
});
},
};