Back to project page ble.
The source code is released under:
Apache License
If you think the Android project ble listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.randdusing.bluetoothle; /*w w w . ja v a 2 s. c o m*/ import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.util.Base64; import android.util.Log; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter.LeScanCallback; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class BluetoothLePlugin extends CordovaPlugin { //private final static String TAG = BluetoothLePlugin.class.getSimpleName(); //Callback variables private CallbackContext initCallbackContext; private CallbackContext scanCallbackContext; private CallbackContext connectCallbackContext; private CallbackContext operationCallbackContext; //Initialization related variables private final int REQUEST_BT_ENABLE = 59627; /*Random integer*/ private BluetoothAdapter bluetoothAdapter; //Connection related variables private BluetoothGatt bluetoothGatt; private int connectionState = BluetoothProfile.STATE_DISCONNECTED; //Discovery related variables private final int STATE_UNDISCOVERED = 0; private final int STATE_DISCOVERING = 1; private final int STATE_DISCOVERED = 2; private int discoveredState = STATE_UNDISCOVERED; //Action Name Strings private final String initializeActionName = "initialize"; private final String startScanActionName = "startScan"; private final String stopScanActionName = "stopScan"; private final String connectActionName = "connect"; private final String reconnectActionName = "reconnect"; private final String disconnectActionName = "disconnect"; private final String closeActionName = "close"; private final String discoverActionName = "discover"; private final String servicesActionName = "services"; private final String characteristicsActionName = "characteristics"; private final String descriptorsActionName = "descriptors"; private final String readActionName = "read"; private final String subscribeActionName = "subscribe"; private final String unsubscribeActionName = "unsubscribe"; private final String writeActionName = "write"; private final String readDescriptorActionName = "readDescriptor"; private final String writeDescriptorActionName = "writeDescriptor"; private final String rssiActionName = "rssi"; private final String isInitializedActionName = "isInitialized"; private final String isScanningActionName = "isScanning"; private final String isDiscoveredActionName = "isDiscovered"; private final String isConnectedActionName = "isConnected"; //Object keys private final String keyStatus = "status"; private final String keyError = "error"; private final String keyMessage = "message"; private final String keyName = "name"; private final String keyAddress = "address"; private final String keyRssi = "rssi"; private final String keyAdvertisement = "advertisement"; private final String keyServiceUuids = "serviceUuids"; private final String keyServiceUuid = "serviceUuid"; private final String keyCharacteristicUuid = "characteristicUuid"; private final String keyDescriptorUuid = "descriptorUuid"; private final String keyServices = "services"; private final String keyCharacteristics = "characteristics"; private final String keyDescriptors = "descriptors"; private final String keyValue = "value"; private final String keyIsInitialized = "isInitalized"; private final String keyIsScanning = "isScanning"; private final String keyIsConnected = "isConnected"; private final String keyIsDiscovered = "isDiscovered"; private final String keyIsNotification = "isNotification"; //Status Types private final String statusInitialized = "initialized"; private final String statusScanStarted = "scanStarted"; private final String statusScanStopped = "scanStopped"; private final String statusScanResult = "scanResult"; private final String statusConnected = "connected"; private final String statusConnecting = "connecting"; private final String statusDisconnected = "disconnected"; private final String statusDisconnecting = "disconnecting"; private final String statusClosed = "closed"; private final String statusDiscovered = "discovered"; private final String statusRead = "read"; private final String statusSubscribed = "subscribed"; private final String statusSubscribedResult = "subscribedResult"; private final String statusUnsubscribed = "unsubscribed"; private final String statusWritten = "written"; private final String statusReadDescriptor = "readDescriptor"; private final String statusWrittenDescriptor = "writtenDescriptor"; private final String statusRssi = "rssi"; //Error Types private final String errorInitialize = "initialize"; private final String errorArguments = "arguments"; private final String errorStartScan = "startScan"; private final String errorStopScan = "stopScan"; private final String errorConnect = "connect"; private final String errorReconnect = "reconnect"; private final String errorDiscover = "discover"; private final String errorRead = "read"; private final String errorSubscription = "subscription"; private final String errorWrite = "write"; private final String errorReadDescriptor = "readDescriptor"; private final String errorWriteDescriptor = "writeDescriptor"; private final String errorRssi = "rssi"; private final String errorNeverConnected = "neverConnected"; private final String errorIsNotDisconnected = "isNotDisconnected"; private final String errorIsNotConnected = "isNotConnected"; private final String errorIsDisconnected = "isDisconnected"; private final String errorService = "service"; private final String errorCharacteristic = "characteristic"; private final String errorDescriptor = "descriptor"; //Error Messages //Initialization private final String logNotEnabled = "Bluetooth not enabled"; private final String logNotEnabledUser = "Bluetooth not enabled by user"; private final String logNotSupported = "Hardware doesn't support Bluetooth LE"; private final String logNotInit = "Bluetooth not initialized"; //Scanning private final String logAlreadyScanning = "Scanning already in progress"; private final String logScanStartFail = "Scan failed to start"; private final String logNotScanning = "Not scanning"; //Connection private final String logPreviouslyConnected = "Device previously connected, reconnect or close for new device"; private final String logNeverConnected = "Never connected to device"; private final String logIsNotConnected = "Device isn't connected"; private final String logIsNotDisconnected = "Device isn't disconnected"; private final String logIsDisconnected = "Device is disconnected"; private final String logNoAddress = "No device address"; private final String logNoDevice = "Device not found"; private final String logReconnectFail = "Reconnection to device failed"; //Discovery private final String logAlreadyDiscovering = "Already discovering device"; private final String logDiscoveryFail = "Unable to discover device"; //Read/write private final String logNoArgObj = "Argument object not found"; private final String logNoService = "Service not found"; private final String logNoCharacteristic = "Characteristic not found"; private final String logNoDescriptor = "Descriptor not found"; private final String logReadFail = "Unable to read"; private final String logReadFailReturn = "Unable to read on return"; private final String logSubscribeFail = "Unable to subscribe"; private final String logUnsubscribeFail = "Unable to unsubscribe"; private final String logWriteFail = "Unable to write"; private final String logWriteFailReturn = "Unable to write on return"; private final String logWriteValueNotFound = "Write value not found"; private final String logWriteValueNotSet = "Write value not set"; private final String logReadDescriptorFail = "Unable to read descriptor"; private final String logReadDescriptorFailReturn = "Unable to read descriptor on return"; private final String logWriteDescriptorNotAllowed = "Unable to write client configuration descriptor"; private final String logWriteDescriptorFail = "Unable to write descriptor"; private final String logWriteDescriptorValueNotFound = "Write descriptor value not found"; private final String logWriteDescriptorValueNotSet = "Write descriptor value not set"; private final String logWriteDescriptorFailReturn = "Descriptor not written on return"; private final String logRssiFail = "Unable to read RSSI"; private final String logRssiFailReturn = "Unable to read RSSI on return"; private final String baseUuidStart = "0000"; private final String baseUuidEnd = "-0000-1000-8000-00805f9b34fb"; //Client Configuration UUID for notifying/indicating private final UUID clientConfigurationDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); //Actions @Override public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { //Execute the specified action if (initializeActionName.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { initializeAction(callbackContext); } }); return true; } else if (startScanActionName.equals(action)) { startScanAction(args, callbackContext); return true; } else if (stopScanActionName.equals(action)) { stopScanAction(callbackContext); return true; } else if (connectActionName.equals(action)) { connectAction(args, callbackContext); return true; } else if (reconnectActionName.equals(action)) { reconnectAction(callbackContext); return true; } else if (disconnectActionName.equals(action)) { disconnectAction(callbackContext); return true; } else if (servicesActionName.equals(action)) { callbackContext.success(); return true; } else if (characteristicsActionName.equals(action)) { callbackContext.success(); return true; } else if (descriptorsActionName.equals(action)) { callbackContext.success(); return true; } else if (closeActionName.equals(action)) { closeAction(callbackContext); return true; } else if (discoverActionName.equals(action)) { discoverAction(callbackContext); return true; } else if (readActionName.equals(action)) { readAction(args, callbackContext); return true; } else if (subscribeActionName.equals(action)) { subscribeAction(args, callbackContext); return true; } else if (unsubscribeActionName.equals(action)) { unsubscribeAction(args, callbackContext); return true; } else if (writeActionName.equals(action)) { writeAction(args, callbackContext); return true; } else if (readDescriptorActionName.equals(action)) { readDescriptorAction(args, callbackContext); return true; } else if (writeDescriptorActionName.equals(action)) { writeDescriptorAction(args, callbackContext); return true; } else if (rssiActionName.equals(action)) { rssiAction(callbackContext); return true; } else if (isInitializedActionName.equals(action)) { isInitializedAction(callbackContext); return true; } else if (isScanningActionName.equals(action)) { isScanningAction(callbackContext); return true; } else if (isConnectedActionName.equals(action)) { isConnectedAction(callbackContext); return true; } else if (isDiscoveredActionName.equals(action)) { isDiscoveredAction(callbackContext); return true; } return false; } private void initializeAction(CallbackContext callbackContext) { JSONObject returnObj = new JSONObject(); //If Bluetooth is already enabled, return success if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { addProperty(returnObj, keyStatus, statusInitialized); callbackContext.success(returnObj); return; } //Check whether the device supports Bluetooth LE //Not necessary if app manifest contains: <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> if (!cordova.getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { addProperty(returnObj, keyError, errorInitialize); addProperty(returnObj, keyMessage, logNotSupported); callbackContext.error(returnObj); return; } //Get Bluetooth adapter via Bluetooth Manager BluetoothManager bluetoothManager = (BluetoothManager) cordova.getActivity().getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); //If adapter is null or disabled... if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { //Request Bluetooth to be enabled initCallbackContext = callbackContext; Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); cordova.startActivityForResult(this, enableBtIntent, REQUEST_BT_ENABLE); } //Else successful else { addProperty(returnObj, keyStatus, statusInitialized); callbackContext.success(returnObj); return; } } private void startScanAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); //If the adapter is already scanning, don't call another scan. if (scanCallbackContext != null) { addProperty(returnObj, keyError, errorStartScan); addProperty(returnObj, keyMessage, logAlreadyScanning); callbackContext.error(returnObj); return; } //Get the service UUIDs from the arguments JSONObject obj = getArgsObject(args); UUID[] serviceUuids = null; if (obj != null) { serviceUuids = getServiceUuids(obj); } //Save the callback context for reporting back found devices. Also the isScanning flag scanCallbackContext = callbackContext; //Start the scan with or without service UUIDs boolean result; if (serviceUuids == null || serviceUuids.length == 0) { result = bluetoothAdapter.startLeScan(scanCallback); } else { result = bluetoothAdapter.startLeScan(serviceUuids, scanCallback); } //If the scan didn't start... if (!result) { addProperty(returnObj, keyError, errorStartScan); addProperty(returnObj, keyMessage, logScanStartFail); callbackContext.error(returnObj); scanCallbackContext = null; return; } //Notify user of started scan and save callback addProperty(returnObj, keyStatus, statusScanStarted); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); } private void stopScanAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); //Check if already scanning if (scanCallbackContext == null) { addProperty(returnObj, keyError, errorStopScan); addProperty(returnObj, keyMessage, logNotScanning); callbackContext.error(returnObj); return; } //Stop the scan bluetoothAdapter.stopLeScan(scanCallback); //Set scanning state scanCallbackContext = null; //Inform user addProperty(returnObj, keyStatus, statusScanStopped); callbackContext.success(returnObj); } private void connectAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); if (bluetoothGatt != null) { addProperty(returnObj, keyError, errorConnect); addProperty(returnObj, keyMessage, logPreviouslyConnected); callbackContext.error(returnObj); return; } //Get the address string JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } //Get address String address = getAddress(obj); if (address == null) { addProperty(returnObj, keyError, errorConnect); addProperty(returnObj, keyMessage, logNoAddress); callbackContext.error(returnObj); return; } //Get the device BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); //If device wasn't found... if (device == null) { addProperty(returnObj, keyError, errorConnect); addProperty(returnObj, keyMessage, logNoDevice); callbackContext.error(returnObj); return; } //Connect! connectCallbackContext = callbackContext; connectionState = BluetoothProfile.STATE_CONNECTING; bluetoothGatt = device.connectGatt(cordova.getActivity().getApplicationContext(), false, gattCallback); //Return connecting status addProperty(returnObj, keyStatus, statusConnecting); addProperty(returnObj, keyName, device.getName()); addProperty(returnObj, keyAddress, device.getAddress()); //Keep the callback PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); } private void reconnectAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotDisconnected(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); connectCallbackContext = callbackContext; boolean result = bluetoothGatt.connect(); if (!result) { addProperty(returnObj, keyError, errorReconnect); addProperty(returnObj, keyMessage, logReconnectFail); callbackContext.error(returnObj); connectCallbackContext = null; return; } connectionState = BluetoothProfile.STATE_CONNECTING; BluetoothDevice device = bluetoothGatt.getDevice(); //Return connecting status and keep callback addProperty(returnObj, keyStatus, statusConnecting); addProperty(returnObj, keyName, device.getName()); addProperty(returnObj, keyAddress, device.getAddress()); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); } private void disconnectAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isDisconnected(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); BluetoothDevice device = bluetoothGatt.getDevice(); //Return disconnecting status and keep callback addProperty(returnObj, keyName, device.getName()); addProperty(returnObj, keyAddress, device.getAddress()); //If it's connecting, cancel attempt and return disconnect if (connectionState == BluetoothProfile.STATE_CONNECTING) { addProperty(returnObj, keyStatus, statusDisconnected); connectionState = BluetoothProfile.STATE_DISCONNECTED; PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(false); callbackContext.sendPluginResult(pluginResult); } //Very unlikely that this is DISCONNECTING else { addProperty(returnObj, keyStatus, statusDisconnecting); connectionState = BluetoothProfile.STATE_DISCONNECTING; PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); callbackContext.sendPluginResult(pluginResult); //Call disconnect and change connection station connectCallbackContext = callbackContext; } bluetoothGatt.disconnect(); } private void closeAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotDisconnected(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); BluetoothDevice device = bluetoothGatt.getDevice(); addProperty(returnObj, keyStatus, statusClosed); addProperty(returnObj, keyAddress, device.getAddress()); addProperty(returnObj, keyName, device.getName()); bluetoothGatt.close(); bluetoothGatt = null; discoveredState = STATE_UNDISCOVERED; connectCallbackContext = null; operationCallbackContext = null; callbackContext.success(returnObj); } private void discoverAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject returnObj = new JSONObject(); //Already initiated discovery if (discoveredState == STATE_DISCOVERING) { addProperty(returnObj, keyError, errorDiscover); addProperty(returnObj, keyMessage, logAlreadyDiscovering); callbackContext.error(returnObj); return; } //Already discovered else if (discoveredState == STATE_DISCOVERED) { returnObj = getDiscovery(); callbackContext.success(returnObj); return; } //Else undiscovered, so start discovery discoveredState = STATE_DISCOVERING; operationCallbackContext = callbackContext; bluetoothGatt.discoverServices(); } private void readAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } operationCallbackContext = callbackContext; boolean result = bluetoothGatt.readCharacteristic(characteristic); if (!result) { JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); addProperty(returnObj, keyError, errorRead); addProperty(returnObj, keyMessage, logReadFail); callbackContext.error(returnObj); operationCallbackContext = null; return; } } private void subscribeAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } boolean isNotification = obj.optBoolean(keyIsNotification, true); BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); if (isNotDescriptor(descriptor, callbackContext)) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); //Subscribe to the characteristic boolean result = bluetoothGatt.setCharacteristicNotification(characteristic, true); if (!result) { addProperty(returnObj, keyError, errorSubscription); addProperty(returnObj, keyMessage, logSubscribeFail); callbackContext.error(returnObj); return; } //Set the descriptor for notification if (isNotification) { result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); } //Or for indication else { result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); } if (!result) { //Clean up bluetoothGatt.setCharacteristicNotification(characteristic, false); addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); callbackContext.error(returnObj); return; } operationCallbackContext = callbackContext; //Write the descriptor value result = bluetoothGatt.writeDescriptor(descriptor); if (!result) { //Clean up bluetoothGatt.setCharacteristicNotification(characteristic, false); addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorFail); callbackContext.error(returnObj); operationCallbackContext = null; } } private void unsubscribeAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); if (isNotDescriptor(descriptor, callbackContext)) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); //Unsubscribe to the characteristic boolean result = bluetoothGatt.setCharacteristicNotification(characteristic, false); if (!result) { addProperty(returnObj, keyError, errorSubscription); addProperty(returnObj, keyMessage, logUnsubscribeFail); callbackContext.error(returnObj); return; } //Set the descriptor for disabling notification/indication result = descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); if (!result) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); callbackContext.error(returnObj); return; } operationCallbackContext = callbackContext; //Write the actual descriptor value result = bluetoothGatt.writeDescriptor(descriptor); if (!result) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorFail); callbackContext.error(returnObj); operationCallbackContext = null; } } private void writeAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); byte[] value = getPropertyBytes(obj, keyValue); if (value == null) { addProperty(returnObj, keyError, errorWrite); addProperty(returnObj, keyMessage, logWriteValueNotFound); callbackContext.error(returnObj); return; } boolean result = characteristic.setValue(value); if (!result) { addProperty(returnObj, keyError, errorWrite); addProperty(returnObj, keyMessage, logWriteValueNotSet); callbackContext.error(returnObj); return; } operationCallbackContext = callbackContext; result = bluetoothGatt.writeCharacteristic(characteristic); if (!result) { addProperty(returnObj, keyError, errorWrite); addProperty(returnObj, keyMessage, logWriteFail); callbackContext.error(returnObj); operationCallbackContext = null; return; } } private void readDescriptorAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); if (isNotDescriptor(descriptor, callbackContext)) { return; } operationCallbackContext = callbackContext; boolean result = bluetoothGatt.readDescriptor(descriptor); if (!result) { JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); addProperty(returnObj, keyDescriptorUuid, formatUuid(descriptor.getUuid())); addProperty(returnObj, keyError, errorReadDescriptor); addProperty(returnObj, keyMessage, logReadDescriptorFail); callbackContext.error(returnObj); operationCallbackContext = null; return; } } private void writeDescriptorAction(JSONArray args, CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } JSONObject obj = getArgsObject(args); if (isNotArgsObject(obj, callbackContext)) { return; } BluetoothGattService service = getService(obj); if (isNotService(service, callbackContext)) { return; } BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); if (isNotCharacteristic(characteristic, callbackContext)) { return; } BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); if (isNotDescriptor(descriptor, callbackContext)) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(service.getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); addProperty(returnObj, keyDescriptorUuid, formatUuid(descriptor.getUuid())); //Let subscribe/unsubscribe take care of it if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorNotAllowed); callbackContext.error(returnObj); return; } byte[] value = getPropertyBytes(obj, keyValue); if (value == null) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorValueNotFound); callbackContext.error(returnObj); return; } boolean result = descriptor.setValue(value); if (!result) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); callbackContext.error(returnObj); return; } operationCallbackContext = callbackContext; result = bluetoothGatt.writeDescriptor(descriptor); if (!result) { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorFail); callbackContext.error(returnObj); operationCallbackContext = null; return; } } private void rssiAction(CallbackContext callbackContext) { if (isNotInitialized(callbackContext)) { return; } if (wasNeverConnected(callbackContext)) { return; } if (isNotConnected(callbackContext)) { return; } operationCallbackContext = callbackContext; boolean result = bluetoothGatt.readRemoteRssi(); if (!result) { JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorRssi); addProperty(returnObj, keyMessage, logRssiFail); callbackContext.error(returnObj); operationCallbackContext = null; return; } } private void isInitializedAction(CallbackContext callbackContext) { boolean result = (bluetoothAdapter != null && bluetoothAdapter.isEnabled()); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyIsInitialized, result); callbackContext.success(returnObj); } private void isScanningAction(CallbackContext callbackContext) { boolean result = (scanCallbackContext != null); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyIsScanning, result); callbackContext.success(returnObj); } private void isConnectedAction(CallbackContext callbackContext) { boolean result = (connectionState == BluetoothAdapter.STATE_CONNECTED); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyIsConnected, result); callbackContext.success(returnObj); } private void isDiscoveredAction(CallbackContext callbackContext) { boolean result = (discoveredState == STATE_DISCOVERED); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyIsDiscovered, result); callbackContext.success(returnObj); } //Enable Bluetooth Callback @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { //If this was a Bluetooth enablement request... if (requestCode == REQUEST_BT_ENABLE) { //If callback doesnt exist, no reason to proceed if (initCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); //If Bluetooth was enabled... if (resultCode == Activity.RESULT_OK) { //After requesting, check again whether it's enabled BluetoothManager bluetoothManager = (BluetoothManager) cordova.getActivity().getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); //Bluetooth wasn't enabled if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { addProperty(returnObj, keyError, errorInitialize); addProperty(returnObj, keyMessage, logNotEnabled); initCallbackContext.error(returnObj); } //Bluetooth was enabled else { addProperty(returnObj, keyStatus, statusInitialized); initCallbackContext.success(returnObj); } } //Else user didn't enable Bluetooth else { addProperty(returnObj, keyError, errorInitialize); addProperty(returnObj, keyMessage, logNotEnabledUser); initCallbackContext.error(returnObj); } initCallbackContext = null; } } //Scan Callback private LeScanCallback scanCallback = new LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { if (scanCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyName, device.getName()); addProperty(returnObj, keyAddress, device.getAddress()); addProperty(returnObj, keyRssi, rssi); addPropertyBytes(returnObj, keyAdvertisement, scanRecord); addProperty(returnObj, keyStatus, statusScanResult); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); scanCallbackContext.sendPluginResult(pluginResult); } }; //Bluetooth callback for connecting, discovering, reading and writing private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { //Get the connected device BluetoothDevice device = gatt.getDevice(); connectionState = newState; //Device was connected if (newState == BluetoothProfile.STATE_CONNECTED) { //This shouldn't happen if (connectCallbackContext == null) { return; } //Create json object with address, name and connection status JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyStatus, statusConnected); addProperty(returnObj, keyAddress, device.getAddress()); addProperty(returnObj, keyName, device.getName()); //Keep connection call back for disconnect PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); connectCallbackContext.sendPluginResult(pluginResult); } //Device was disconnected else if (newState == BluetoothProfile.STATE_DISCONNECTED) { operationCallbackContext = null; if (connectCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyStatus, statusDisconnected); addProperty(returnObj, keyAddress, device.getAddress()); addProperty(returnObj, keyName, device.getName()); connectCallbackContext.success(returnObj); connectCallbackContext = null; } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { discoveredState = STATE_DISCOVERED; } else { discoveredState = STATE_UNDISCOVERED; } //Shouldn't happen, but check for null callback if (operationCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); //If successfully discovered, return list of services, characteristics and descriptors if (status == BluetoothGatt.GATT_SUCCESS) { returnObj = getDiscovery(); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorDiscover); addProperty(returnObj, keyMessage, logDiscoveryFail); operationCallbackContext.error(returnObj); } //Clear the callback operationCallbackContext = null; } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //If no callback, just return if (operationCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(characteristic.getService().getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); //If successfully read, return value if (status == BluetoothGatt.GATT_SUCCESS) { addProperty(returnObj, keyStatus, statusRead); addPropertyBytes(returnObj, keyValue, characteristic.getValue()); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorRead); addProperty(returnObj, keyMessage, logReadFailReturn); operationCallbackContext.error(returnObj); } //Clear callback operationCallbackContext = null; } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { //If callback is null, just return if (operationCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(characteristic.getService().getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); addProperty(returnObj, keyStatus, statusSubscribedResult); addPropertyBytes(returnObj, keyValue, characteristic.getValue()); //Return the characteristic value PluginResult result = new PluginResult(PluginResult.Status.OK, returnObj); result.setKeepCallback(true); operationCallbackContext.sendPluginResult(result); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //If no callback, just return if (operationCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(characteristic.getService().getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); //If write was successful, return the written value if (status == BluetoothGatt.GATT_SUCCESS) { addProperty(returnObj, keyStatus, statusWritten); addPropertyBytes(returnObj, keyValue, characteristic.getValue()); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorWrite); addProperty(returnObj, keyMessage, logWriteFailReturn); operationCallbackContext.error(returnObj); } //Clear callback operationCallbackContext = null; } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { //If callback is null, just return if (operationCallbackContext == null) { return; } BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(characteristic.getService().getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); addProperty(returnObj, keyDescriptorUuid, formatUuid(descriptor.getUuid())); //If descriptor was successful, return the written value if (status == BluetoothGatt.GATT_SUCCESS) { addProperty(returnObj, keyStatus, statusReadDescriptor); addPropertyBytes(returnObj, keyValue, descriptor.getValue()); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorReadDescriptor); addProperty(returnObj, keyMessage, logReadDescriptorFailReturn); operationCallbackContext.error(returnObj); } //Clear callback operationCallbackContext = null; } @Override public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { //If callback is null, just return if (operationCallbackContext == null) { return; } BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyServiceUuid, formatUuid(characteristic.getService().getUuid())); addProperty(returnObj, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); //See if notification/indication is enabled or disabled and use subscribe/unsubscribe callback instead if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { if (descriptor.getValue() == BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) { addProperty(returnObj, keyStatus, statusUnsubscribed); operationCallbackContext.success(returnObj); } else { addProperty(returnObj, keyStatus, statusSubscribed); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); pluginResult.setKeepCallback(true); operationCallbackContext.sendPluginResult(pluginResult); } return; } addProperty(returnObj, keyDescriptorUuid, formatUuid(descriptor.getUuid())); //If descriptor was written, return written value if (status == BluetoothGatt.GATT_SUCCESS) { addProperty(returnObj, keyStatus, statusWrittenDescriptor); addPropertyBytes(returnObj, keyValue, descriptor.getValue()); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorWriteDescriptor); addProperty(returnObj, keyMessage, logWriteDescriptorFailReturn); operationCallbackContext.error(returnObj); } //Clear callback operationCallbackContext = null; } @Override public void onReadRemoteRssi (BluetoothGatt gatt, int rssi, int status) { //If no callback, just return if (operationCallbackContext == null) { return; } JSONObject returnObj = new JSONObject(); //If successfully read RSSI, return value if (status == BluetoothGatt.GATT_SUCCESS) { addProperty(returnObj, keyStatus, statusRssi); addProperty(returnObj, keyRssi, rssi); operationCallbackContext.success(returnObj); } //Else it failed else { addProperty(returnObj, keyError, errorRssi); addProperty(returnObj, keyMessage, logRssiFailReturn); operationCallbackContext.error(returnObj); } //Clear callback operationCallbackContext = null; } }; private String formatUuid(UUID uuid) { String uuidString = uuid.toString(); if (uuidString.startsWith(baseUuidStart) && uuidString.endsWith(baseUuidEnd)) { return uuidString.substring(4, 8); } return uuidString; } //Helpers for BluetoothGatt classes private BluetoothGattService getService(JSONObject obj) { String uuidServiceValue = obj.optString(keyServiceUuid, null); if (uuidServiceValue == null) { return null; } if (uuidServiceValue.length() == 4) { uuidServiceValue = baseUuidStart + uuidServiceValue + baseUuidEnd; } UUID uuidService = null; try { uuidService = UUID.fromString(uuidServiceValue); } catch (Exception ex) { return null; } BluetoothGattService service = bluetoothGatt.getService(uuidService); if (service == null) { return null; } return service; } private BluetoothGattCharacteristic getCharacteristic(JSONObject obj, BluetoothGattService service) { String uuidCharacteristicValue = obj.optString(keyCharacteristicUuid, null); if (uuidCharacteristicValue == null) { return null; } if (uuidCharacteristicValue.length() == 4) { uuidCharacteristicValue = baseUuidStart + uuidCharacteristicValue + baseUuidEnd; } UUID uuidCharacteristic = null; try { uuidCharacteristic = UUID.fromString(uuidCharacteristicValue); } catch (Exception ex) { return null; } BluetoothGattCharacteristic characteristic = service.getCharacteristic(uuidCharacteristic); if (characteristic == null) { return null; } return characteristic; } private BluetoothGattDescriptor getDescriptor(JSONObject obj, BluetoothGattCharacteristic characteristic) { String uuidDescriptorValue = obj.optString(keyDescriptorUuid, null); if (uuidDescriptorValue == null) { return null; } if (uuidDescriptorValue.length() == 4) { uuidDescriptorValue = baseUuidStart + uuidDescriptorValue + baseUuidEnd; } UUID uuidDescriptor = null; try { uuidDescriptor = UUID.fromString(uuidDescriptorValue); } catch (Exception ex) { return null; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuidDescriptor); if (descriptor == null) { return null; } return descriptor; } //Helpers to check conditions and send callbacks private boolean isNotInitialized(CallbackContext callbackContext) { if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorInitialize); addProperty(returnObj, keyMessage, logNotInit); callbackContext.error(returnObj); //Assumption that disabled Bluetooth adapter "kills" current scans, current connected devices, etc //Clean up callbacks initCallbackContext = null; scanCallbackContext = null; connectCallbackContext = null; operationCallbackContext = null; //Clean up states connectionState = BluetoothProfile.STATE_DISCONNECTED; discoveredState = STATE_UNDISCOVERED; //Clean up other variables bluetoothGatt = null; bluetoothAdapter = null; return true; } private boolean isNotArgsObject(JSONObject obj, CallbackContext callbackContext) { if (obj != null) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorArguments); addProperty(returnObj, keyMessage, logNoArgObj); callbackContext.error(returnObj); return true; } private boolean isNotService(BluetoothGattService service, CallbackContext callbackContext) { if (service != null) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorService); addProperty(returnObj, keyMessage, logNoService); callbackContext.error(returnObj); return true; } private boolean isNotCharacteristic(BluetoothGattCharacteristic characteristic, CallbackContext callbackContext) { if (characteristic != null) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorCharacteristic); addProperty(returnObj, keyMessage, logNoCharacteristic); callbackContext.error(returnObj); return true; } private boolean isNotDescriptor(BluetoothGattDescriptor descriptor, CallbackContext callbackContext) { if (descriptor != null) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorDescriptor); addProperty(returnObj, keyMessage, logNoDescriptor); callbackContext.error(returnObj); return true; } private boolean isNotDisconnected(CallbackContext callbackContext) { //Determine whether the device is currently connected including connecting and disconnecting //Certain actions like connect and reconnect can only be done while completely disconnected if (connectionState == BluetoothProfile.STATE_DISCONNECTED) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorIsNotDisconnected); addProperty(returnObj, keyMessage, logIsNotDisconnected); callbackContext.error(returnObj); return true; } private boolean isDisconnected(CallbackContext callbackContext) { //Determine whether the device is currently disconnected NOT including connecting and disconnecting //Certain actions like disconnect can be done while connected, connecting, disconnecting if (connectionState != BluetoothProfile.STATE_DISCONNECTED) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorIsDisconnected); addProperty(returnObj, keyMessage, logIsDisconnected); callbackContext.error(returnObj); return true; } private boolean isNotConnected(CallbackContext callbackContext) { //Determine whether the device is currently disconnected including connecting and disconnecting //Certain actions like read/write operations can only be done while completely connected if (connectionState == BluetoothProfile.STATE_CONNECTED) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorIsNotConnected); addProperty(returnObj, keyMessage, logIsNotConnected); callbackContext.error(returnObj); return true; } private boolean wasNeverConnected(CallbackContext callbackContext) { //Determine whether a connection was ever attempted on the device if (bluetoothGatt != null) { return false; } JSONObject returnObj = new JSONObject(); addProperty(returnObj, keyError, errorNeverConnected); addProperty(returnObj, keyMessage, logNeverConnected); callbackContext.error(returnObj); return true; } //General Helpers private void addProperty(JSONObject obj, String key, Object value) { //Believe exception only occurs when adding duplicate keys, so just ignore it try { obj.put(key, value); } catch (JSONException e) { } } private void addPropertyBytes(JSONObject obj, String key, byte[] bytes) { String string = Base64.encodeToString(bytes, Base64.NO_WRAP); addProperty(obj, key, string); } private JSONObject getArgsObject(JSONArray args) { if (args.length() == 1) { try { return args.getJSONObject(0); } catch (JSONException ex) { } } return null; } private byte[] getPropertyBytes(JSONObject obj, String key) { String string = obj.optString(key, null); if (string == null) { return null; } byte[] bytes = Base64.decode(string, Base64.NO_WRAP); if (bytes == null || bytes.length == 0) { return null; } return bytes; } private UUID[] getServiceUuids(JSONObject obj) { JSONArray array = obj.optJSONArray(keyServiceUuids); if (array == null) { return null; } //Create temporary array list for building array of UUIDs ArrayList<UUID> arrayList = new ArrayList<UUID>(); //Iterate through the UUID strings for (int i = 0; i < array.length(); i++) { String value = array.optString(i, null); if (value == null) { continue; } if (value.length() == 4) { value = baseUuidStart + value + baseUuidEnd; } //Try converting string to UUID and add to list try { UUID uuid = UUID.fromString(value); arrayList.add(uuid); } catch (Exception ex) { } } //If anything was actually added, convert list to array int size = arrayList.size(); if (size == 0) { return null; } UUID[] uuids = new UUID[size]; uuids = arrayList.toArray(uuids); return uuids; } private String getAddress(JSONObject obj) { //Get the address string from arguments String address = obj.optString(keyAddress, null); if (address == null) { return null; } //Validate address format if (!BluetoothAdapter.checkBluetoothAddress(address)) { return null; } return address; } private JSONObject getDiscovery() { JSONObject deviceObject = new JSONObject(); BluetoothDevice device = bluetoothGatt.getDevice(); addProperty(deviceObject, keyStatus, statusDiscovered); addProperty(deviceObject, keyAddress, device.getAddress()); addProperty(deviceObject, keyName, device.getName()); JSONArray servicesArray = new JSONArray(); List<BluetoothGattService> services = bluetoothGatt.getServices(); for (BluetoothGattService service : services) { JSONObject serviceObject = new JSONObject(); addProperty(serviceObject, keyServiceUuid, formatUuid(service.getUuid())); JSONArray characteristicsArray = new JSONArray(); List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics(); for (BluetoothGattCharacteristic characteristic : characteristics) { JSONObject characteristicObject = new JSONObject(); addProperty(characteristicObject, keyCharacteristicUuid, formatUuid(characteristic.getUuid())); JSONArray descriptorsArray = new JSONArray(); List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors(); for (BluetoothGattDescriptor descriptor : descriptors) { JSONObject descriptorObject = new JSONObject(); addProperty(descriptorObject, keyDescriptorUuid, formatUuid(descriptor.getUuid())); descriptorsArray.put(descriptorObject); } addProperty(characteristicObject, keyDescriptors, descriptorsArray); characteristicsArray.put(characteristicObject); } addProperty(serviceObject, keyCharacteristics, characteristicsArray); servicesArray.put(serviceObject); } addProperty(deviceObject, keyServices, servicesArray); return deviceObject; } }