Android BLE Gatt connection change statuses - android

I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.
Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.
Posting a sample BluetoothGattCallback
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
/*i need to know the possible values for this status variable*/
if(newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered service discovered");
}
};
Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method

Here is the list of codes i have
Programmatically disconnected - 0
Device went out of range - 8
Disconnected by device - 19
Issue with bond - 22
Device not found - 133(some phone it gives 62)
I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.

Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.
public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;
protected BluetoothLowEnergy bluetoothLowEnergy;
public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
super();
this.bluetoothLowEnergy = bluetoothLowEnergy;
IntentFilter intentFilterStateChange = new IntentFilter(action);
intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
}
public void onDestroy(){
this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
}
}
public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {
private BluetoothLowEnergy bluetoothLowEnergy;
private boolean deviceBonded;
public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
this.bluetoothLowEnergy = bluetoothLowEnergy;
this.deviceBonded = false;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
switch (state) {
case BluetoothDevice.BOND_NONE:
Log.d(LOG_TAG, " NOT BONDED - dev " + bluetoothDevice.getAddress());
this.deviceBonded = false;
break;
case BluetoothDevice.BOND_BONDING:
Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
break;
case BluetoothDevice.BOND_BONDED:
Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
deviceBonded = true;
bluetoothLowEnergy.onBluetoothBonded();
break;
default:
break;
}
}
}
public void resetDeviceBonded(){
this.deviceBonded = false;
}
public boolean isDeviceBonded() {
return deviceBonded;
}
}
public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {
private String devicePIN;
public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
this.devicePIN = "";
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
!getDevicePIN().isEmpty()) {
if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
bluetoothDevice.setPin(getDevicePIN().getBytes());
Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
bluetoothDevice.createBond();
Log.d(LOG_TAG," pin entered and request sent...");
abortBroadcast();
}
}
}
public void setDevicePIN(String pin){
this.devicePIN = pin;
}
public String getDevicePIN(){
return this.devicePIN ;
}
}
public class BluetoothLowEnergy extends BluetoothGattCallback {
// listener that has the methods that the application (activity)
// will use to send / receive data, or to reflect the system state
// in the UI
public interface BluetoothListener {
/**
* Triggered when the scanning has started successfully
*/
void onBluetoothStartScan();
/**
* Triggered when the scanning stops
* #param scanResults results of the scanning
*/
void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);
/**
* Triggered when the device is ready to send/receive data
*/
void onBluetoothConnectionReady();
/**
* Triggered when a bluetooth msg is received
* #param msg message received
*/
void onBluetoothReceiveMsg(String msg);
/**
* Triggered whenever data is send
* #param success true means data was sent fine to the remote device, false otherwise
*/
void onBluetoothSend(String data, boolean success);
/**
* Triggered if no bluetooth is connected, and we need a connection
* to send / receive / discover services
*/
void onBluetoothNotConnected();
}
// custom exceptions
public class BluetoothNotEnabledException extends Exception { }
public class BluetoothLowEnergyNotSupported extends Exception { }
public class BluetoothDeviceNotFound extends Exception { }
// service and characteristic uuids that are going to be used to
// send / receive data between central and peripheral GATTs
private static final String SERVICE_UUID = "FFE0-";
private static final String CHARACTERISTIC_UUID = "FFE1-";
// timeout for bluetooth scan (in ms)
public static final int SCAN_TIMEOUT = 5000;
// BLE LOG TAG
public static final String LOG_TAG = "BLUETOOTH_BLE";
// model
private boolean bluetoothScanning;
private boolean bluetoothConnected;
private Map<String, BluetoothDevice> bluetoothScanResults;
// gui
private Activity activity;
// bluetooth
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private ScanCallback bluetoothScanCallback;
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic characteristic;
public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
this.activity = activity;
this.bluetoothListener = bluetoothListener;
// this keeps track of the scanning and connection states
this.bluetoothScanning = this.bluetoothConnected = false;
// keeps track of the scanning results
this.bluetoothScanResults = new HashMap<>();
// set bluetooth pairing request and bonded callback
// these broadcasters will be responsible to detect and validate
// the bonded state of your device
this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);
// set the scan callback methods that will add results to
// this.bluetoothScanResults map
this.bluetoothScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result: results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
bluetoothScanResults.put(deviceAddress, device);
Log.d(LOG_TAG, "Found device " + deviceAddress);
}
};
// Use this to determine whether BLE is supported on the device.
if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
throw new BluetoothLowEnergyNotSupported();
}
}
/**
* This method should be called when the activity is destroyed
*/
public void onDestroy(){
this.bondedBroadcaster.onDestroy();
this.pairingRequestBroadcaster.onDestroy();
this.disconnect();
}
/**
* This method is called when we finish pairing/bonding to the device
*/
public void onBluetoothBonded(){
// if we have the services already discovered, then we can
// send/receive data, to do so we call the bluetooth listener below
if (servicesDiscovered){
this.bluetoothListener.onBluetoothConnectionReady();
// if we know we have a connection established, then we can
// discover services
} else if (bluetoothConnected){
bluetoothGatt.discoverServices();
}
}
/**
* This method is called whenever a connection is established or a disconnection happens
*/
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
// if these conditions == true, then we have a disconnect
if ( status == BluetoothGatt.GATT_FAILURE ||
status != BluetoothGatt.GATT_SUCCESS ||
newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Disconnected from %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
this.disconnect();
// if these conditions == true, then we have a successful connection
} else if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothConnected = true;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Connected to %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
// this sleep is here to avoid TONS of problems in BLE, that occur whenever we start
// service discovery immediately after the connection is established
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// BEGIN - find the service and characteristic that we want (defined as a static attribute
// of the BluetoothLowEnergy class)
Log.d(LOG_TAG, "Discovering services ...");
BluetoothGattService service = null;
for (BluetoothGattService serv: gatt.getServices()){
Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
service = serv;
Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
break;
}
}
if (service == null){
return;
}
for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
this.characteristic = charac;
Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
break;
}
}
if (this.characteristic == null){
return;
}
Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
bluetoothAdapter.cancelDiscovery();
// END - find the service and characteristic
// set that we want to write to the selected characteristic and be notified if
// it changes (the remote GATT peripheral sends data to the Android's GATT Center)
this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
gatt.setCharacteristicNotification(this.characteristic, true);
// we finished service discovery
this.servicesDiscovered = true;
// if we have paired/bonded then we are ready to send/receive data
if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
this.bluetoothListener.onBluetoothConnectionReady();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicRead(gatt, charac, status);
restartDisconnectTimeout();
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
String characValue = new String(charac.getValue(), CHARSET)
.replaceAll(DATA_FILTER_REGEX,"");
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Read - %s",
characValue
));
if (charac.getUuid().equals(this.characteristic.getUuid())) {
this.bluetoothListener.onBluetoothReceiveMsg(characValue);
}
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicWrite(gatt, charac, status);
restartDisconnectTimeout();
try {
String characValue = new String(charac.getValue(), CHARSET);
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Write - SUCCESS - %s",
characValue
));
bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
super.onCharacteristicChanged(gatt, charac);
Log.d(LOG_TAG,"Characteristic Changed");
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
}
/**
* Remove pairing/bonding of the device
* #param device Device to remove bonding
*/
public static void removeBond(BluetoothDevice device){
try {
if (device == null){
throw new Exception();
}
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
Log.d(LOG_TAG, "removeBond() called");
Thread.sleep(600);
Log.d(LOG_TAG, "removeBond() - finished method");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Clears the GATT services cache, so that new services can be discovered
* #param bluetoothGatt GATT Client to clear service's discovery cache
*/
public static void refresh(BluetoothGatt bluetoothGatt){
try {
Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
method.invoke(bluetoothGatt, (Object[]) null);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Connect to the GATT Peripheral device
* #param uuid GATT Peripheral address / mac / uuid to connect to
* #param pin PIN to authenticate and pair to the device
*/
public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
checkBluetooth();
// do not connect twice
if (this.isConnected()){
return;
}
// get device
BluetoothDevice device = this.bluetoothScanResults.get(uuid);
if (device == null){
throw new BluetoothDeviceNotFound();
}
this.deviceUUID = uuid;
pairingRequestBroadcaster.setDevicePIN(pin);
removeBond(device);
// create connection to the bluetooth device
bluetoothGatt = device.connectGatt(activity, false, this);
refresh(bluetoothGatt);
}
/**
* Disconnect from BLE device. This method should be called whenever we want to
* close the APP, or the BLE connection.
*/
public void disconnect() {
Log.d(LOG_TAG, "disconnect() - executed");
if (bluetoothGatt != null) {
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(characteristic, false);
}
//remove device authorization/ bond/ pairing
removeBond(bluetoothGatt);
// disconnect now
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
}
bluetoothGatt = null;
characteristic = null;
bluetoothConnected = false;
servicesDiscovered = false;
// set device as not bonded anymore
bondedBroadcaster.resetDeviceBonded();
}
/**
* bluetooth nearby devices scan is on
* #return true if scanning is on, false otherwise
*/
public boolean isScanning(){
return (this.bluetoothScanning);
}
/**
* Check bluetooth system state (on or off)
* #return true if system is on, false otherwise
*/
public boolean isEnabled(){
try {
checkBluetooth();
return bluetoothAdapter.isEnabled();
} catch (BluetoothNotEnabledException e) {
return false;
}
}
/**
* Check bluetooth connection
* #return true if connected, false otherwise
*/
public boolean isConnected(){
return (this.bluetoothConnected);
}
/**
* Start bluetooth scan for nearby devices
* #param filters Scan filters that define what devices to scan for
*/
public void startScan(List<ScanFilter> filters)
throws BluetoothNotEnabledException{
checkBluetooth();
// dont run two scans simultaneously
if (isScanning()) {
return;
}
// disconnect previously connected devices
if (isConnected()) {
this.disconnect();
return;
}
// setup bluetooth scanning settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
// start scanning
this.bluetoothScanning = true;
this.bluetoothScanResults.clear();
this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Stops scanning after a pre-defined scan period.
Handler bluetoothHandler = new Handler();
bluetoothHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, SCAN_TIMEOUT);
// start scan with default scan callback
this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
// we have started successfully the BLE scanning
bluetoothListener.onBluetoothStartScan();
}
/**
* Stop bluetooth scan for nearby devices
*/
public void stopScan(){
if (!bluetoothScanning) {
return;
}
// set app scan state to false
bluetoothScanning = false;
if (bluetoothLeScanner != null) {
bluetoothLeScanner.stopScan(bluetoothScanCallback);
bluetoothLeScanner = null;
}
// we have stopped BLE scanning, call the user's callback
bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
}
/**
* Send a message via bluetooth
* #param msg message to send
*/
public void send(String msg) {
if (!bluetoothConnected || characteristic == null){
bluetoothListener.onBluetoothNotConnected();
return;
}
try {
msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Sending message: %s",
msg));
characteristic.setValue(msg.getBytes(CHARSET));
bluetoothGatt.writeCharacteristic(characteristic);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG,
"BluetoothLowEnergy.send: Failed to convert message string to byte array");
}
}
public String getDeviceUUID(){
return deviceUUID;
}
public Activity getActivity(){
return activity;
}
/**
* Check if bluetooth is enabled and working properly
*/
private void checkBluetooth() throws BluetoothNotEnabledException{
if (bluetoothAdapter == null) {
final BluetoothManager bluetoothManager =
(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null){
throw new BluetoothNotEnabledException();
}
bluetoothAdapter = bluetoothManager.getAdapter();
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
throw new BluetoothNotEnabledException();
}
}
}
The key methods and functions to avoid problems used above are:
Thread.sleep(600)
removeBond(device)
refresh(gatt)
gatt.disconnect()
gatt.close()

In my case I got this response from bluetooth stack because the device was already bonded with my phone. I removed it from my settings and the error 22 vanished.

in aosp (android source code). you can find any error in bluetooth source code, and know the meaning of status code.
the file path is system/bt/stack/include/gatt_api.h
Here's the link: https://android.googlesource.com/platform/system/bt/+/ea7ab70a711e642653dd5922b83aa04a53af9e0e/stack/include/gatt_api.h but it all display by hex.
for example:
hex
Decimal
reason
0x08
8
connection timeout
0x13
19
connection terminate by peer user
0x16
22
connectionterminated by local host
0x22
34
connection fail for LMP response tout
0x85
133
gatt_error

Related

How to discover android ble services after successful pairing with pin entry?

Inside the BluetoothGattCallback method onConnectionStateChange I am checking for the successful connection with the BLE device and calling the discoverServices method afterwards. The BLE device needs a pin entry (prompt by Android) for an successful pairing. I want to discover all available services immediately after connecting to the device because when switching to the main activity of the application there should be data from the available characteristics already displayed.
I tried to analyze the functionality of the onConnectionStateChange method and the behavior of the BLE device with pin. Unfortunately the method is called once you initialize the connection and again after successfully entering the pin. There isn't a difference within the receiving state codes. Status and newState are exactly the same when initializing and after successful pin entry and connection. Therefore i added this workaround with the Thread.sleep method to wait for the user entry of the pin and call afterwards the discoverServices method. But this workaround is not very practical because the user need to enter the pin within these 10 seconds. If not the connection is successful but the services won't be discovered.
How can i check or differ between these two states? Initializing the connection and successful enter of the pin?
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.d("KOPPLUNG", "In onConnectionStateChange with status: " + status + " and newState: " + newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
broadcastUpdate(ACTION_GATT_CONNECTED, mCallbackBleAddress);
Log.i(TAG, "Connected to GATT server." + mCallbackBleAddress);
// Attempts to discover services after successful connection.
try {
Thread.sleep(10000);
} catch (Exception e) {
}
for(int i = 0; i<5; i++){
if(mBoltDeviceHandler.getBoltDevice(mCallbackBleAddress).getBluetoothGatt().discoverServices()){
Log.i(TAG, "Attempting to start service discovery:" + true);
break;
}
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
switch (status){
case 0: Log.i(TAG, "Sucessfully Disconnected from GATT server.");
break;
case 133: // Handle internal Android Bug
Log.i(TAG, "Connection aborted, Android Error 133");
broadcastUpdate(ACTION_GATT_CONNECTION_NOT_SUCCESSFUL, mCallbackBleAddress);
break;
default: Log.i(TAG, "Unexpected Disconnection from GATT server. Errorcode: "+status);
broadcastUpdate(ACTION_GATT_DISCONNECTED, mCallbackBleAddress);
autoconnect(gatt.getDevice());
}
Addition
The code for building up the connection is divided up in three different classes. The application starts with an scan activity where available devices are being searched and listed. By clicking on one list item (device) the connection process will be started.
onListItemClick
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
if (device == null) return;
Context mContext = getApplicationContext();
BoltDevice boltDevice = new BoltDevice(device,mContext);
int i = mBoltDeviceHandler.addBoltDevice(boltDevice);
Intent resultIntent = new Intent();
resultIntent.putExtra("IN_CONNECTION", boltDevice.getBleAddress());
resultIntent.putExtra("POSITION",i);
if(i != -1){
setResult(Activity.RESULT_OK, resultIntent);
}
else {
setResult(Activity.RESULT_CANCELED, resultIntent);
}
finish();
}
By running the above code an object from type BoltDevice is being created and added to the BoltDeviceHandler.
Relevant code from BoltDevice class
public BoltDevice(BluetoothDevice device, Context context) {
mContext = context;
this.mBluetoothDevice = device;
this.mBleAddress = device.getAddress();
this.mDeviceName = device.getName();
mBatteryLevel = 0;
mSpecialBolt = false;
//Workarround for 133 error taken from: https://github.com/googlesamples/android-BluetoothLeGatt/issues/44
mStartGattHandler.postDelayed(mStartGattRunnable, START_GATT_DELAY);
}
public void closeGatt() {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
}
public boolean connect() {
// Previously connected device. Try to reconnect.
if (mBluetoothGatt != null) {
Log.d(TAG, "Trying to connect with existing bluetoothGATT to: " + mBleAddress);
if (mBluetoothGatt.connect()) {
return true;
} else {
Log.d(TAG, "Could not connect to existing bluetoothGATT: " + mBleAddress);
return false;
}
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothLeService.connect(mBluetoothDevice);
Log.d(TAG, "Trying to create a new connection to: " + mBluetoothDevice.getAddress());
return true;
}
public boolean disconnect() {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
return true;
} else {
return false;
}
}
public void bindService(){
Intent gattServiceIntent = new Intent(mContext, BluetoothLeService.class);
}
Relevant code from the MainActivity (active after the ScanActivity is finished)
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case (5) : {
if (resultCode == Activity.RESULT_OK) {
int i = data.getIntExtra("POSITION",-1);
mReconnect[i] = false;
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!isFinishing()) {
updateText(i,true);
ToastCompat.makeText(MainActivity.this, "In Connection!", ToastCompat.LENGTH_SHORT).show();
}
}
});
}
if (resultCode == Activity.RESULT_CANCELED) {
if(data!=null) {
int i = mBoltDeviceHandler.getBoltDevicePositionInList(data.getStringExtra("IN_CONNECTION"));
mReconnect[i] = true;
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!isFinishing()) {
ToastCompat.makeText(MainActivity.this, "In Connection!", ToastCompat.LENGTH_SHORT).show();
}
}
});
}
else{
ToastCompat.makeText(this,"No device selected!", ToastCompat.LENGTH_SHORT).show();
}
}
break;
}
}
}
(...)
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final String address = intent.getStringExtra(BluetoothLeService.EXTRA_ADDRESS);
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
ToastCompat.makeText(MainActivity.this, mBoltDeviceHandler.getBoltDevice(address).getDeviceName() + " " + getResources().getString(R.string.BLE_Connected), ToastCompat.LENGTH_SHORT).show();
updateText(mBoltDeviceHandler.getBoltDevicePositionInList(address), false);
ToastCompat.makeText(MainActivity.this, mBoltDeviceHandler.getBoltDevice(address).getDeviceName()+ ": "+ getResources().getString(R.string.BLE_ServicesDiscovered), ToastCompat.LENGTH_SHORT).show();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
(...)
First of all, I would recommend using the Nordic library. That saved me a lot of headhacke when working with BLE on android and allow to keep a clean architecture.
You should have something of the state machine like :
connect to device
request GATT services / characteristic
request for the user code on Android
send user code
retrieve data over BLE
Also, after receiving a onConnection status change notifcation, you should xwait a few ms before starting discovering GATT
The onConnectionStateChange event is triggered just after the Android connects to a device.Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU), the indication is received few hundred milliseconds later, depending on the connection interval. When received, Android will start performing a service discovery operation on its own, internally, and will NOT notify the app that services has changed.
public final void onConnectionStateChange(#NonNull final BluetoothGatt gatt,
final int status, final int newState) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
// Sometimes, when a notification/indication is received after the device got
// disconnected, the Android calls onConnectionStateChanged again, with state
// STATE_CONNECTED.
// See: https://github.com/NordicSemiconductor/Android-BLE-Library/issues/43
if (bluetoothDevice == null) {
Log.e(TAG, "Device received notification after disconnection.");
log(Log.DEBUG, "gatt.close()");
try {
gatt.close();
} catch (final Throwable t) {
// ignore
}
return;
}
// Notify the parent activity/service
Log("Connected to " + gatt.Device.Address);
isConnected = true;
connectionState = State.Connected;
OnDeviceConnected(gatt.Device);
/*
* TODO: Please calculate the proper delay that will work in your solution.
* If your device does not use Service Change indication (for example does not have DFU) the delay may be 0.
*/
var delay = 1600; // around 1600 ms is required when connection interval is ~45ms.
postDelayed(() -> gatt.DiscoverServices(), delay);
} else {
if (newState == ProfileState.Disconnected)
{
var wasConnected = IsConnected;
if (gatt != null)
{
NotifyDeviceDisconnected(gatt.Device); // This sets the mConnected flag to false
if (_initialConnection) ConnectDevice(gatt.Device);
if (wasConnected || status == GattStatus.Success)
return;
}
}
/*
* Connection attempt did fail! Retry possible ?
*/
onError(gatt.Device, Error.LinkLost, status);
}
}

Android BLE never receiving notifications from characteristics

I've been struggling for quite a while now, trying to get my BLE device to communicate with my android app.
First off, here is my full code for BLE handling :
BleCentral.java
import java.util.HashMap;
import java.util.function.Supplier;
import android.util.Log;
import android.content.Context;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;
public class BleCentral
{
private HashMap<String, BluetoothGatt> m_connectedDevices;
public BleCentral()
{
m_connectedDevices = new HashMap<>();
}
private BluetoothAdapter m_GetAdapter(Context ctx)
{
final BluetoothManager bleMgr = (BluetoothManager)(ctx.getSystemService(Context.BLUETOOTH_SERVICE));
BluetoothAdapter adapter = bleMgr.getAdapter();
if (adapter == null || !adapter.isEnabled())
{
Log.e("BLE Central", "BLE either not available or not enabled. Please do something about it.");
return null;
}
return adapter;
}
public BluetoothDevice GetDevice(Context ctx, String address)
{
return m_GetAdapter(ctx).getRemoteDevice(address);
}
public <T extends BlePeripheral>
T Connect(Context ctx, String address, Supplier<T> supplier)
{
BluetoothDevice device = GetDevice(ctx, address);
T result = supplier.get();
m_connectedDevices.put(address, device.connectGatt(ctx, false, result, BluetoothDevice.TRANSPORT_LE));
return result;
}
}
BlePeripheral.java
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattService;
public abstract class BlePeripheral
extends BluetoothGattCallback
{
private final String kCCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";
private Handler m_handler;
private boolean m_deviceReady;
private BluetoothGatt m_bluetoothGatt;
private Queue<CmdQueueItem> m_cmdQueue;
private boolean m_cmdQueueProcessing;
// ------------------------------------------------------------------------
// -- Own methods
protected BlePeripheral()
{
m_handler = new Handler();
m_deviceReady = false;
m_bluetoothGatt = null;
m_cmdQueue = new LinkedList<>();
m_cmdQueueProcessing = false;
}
public boolean IsDeviceReady()
{ return m_deviceReady; }
public void EnableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), true); }
public void DisableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), false); }
protected void WriteCharacteristic(UUID service, UUID characteristic, byte[] value, boolean requestResponse)
{ EnqueueWriteCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), value, requestResponse); }
protected void ReadCharacteristic(UUID service, UUID characteristic)
{ EnqueueReadCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic)); }
// ------------------------------------------------------------------------
// -- BluetoothGattCallback overrides
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
{
super.onConnectionStateChange(gatt, status, newState);
final BluetoothDevice device = gatt.getDevice();
switch (status)
{
case 133: /* GATT_ERROR */
Log.e("BLE", "GATT_ERROR");
gatt.close();
try { Thread.sleep(150); }
catch (InterruptedException e) { e.printStackTrace(); }
break;
case 0: /* GATT_SUCCESS */
switch (newState)
{
case BluetoothGatt.STATE_CONNECTED:
Log.i("BLE", "Connected to " + device.getAddress() + " (" + device.getName() + ")");
m_bluetoothGatt = gatt;
int delayWhenBonded = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) ? 2000 : 0;
switch (device.getBondState())
{
case BluetoothDevice.BOND_NONE:
delayWhenBonded = 0;
case BluetoothDevice.BOND_BONDED:
m_handler.postDelayed(new Runnable() {
#Override
public void run() {
boolean result = gatt.discoverServices();
if (!result)
Log.e("BLE", "discoverServices() failed to start");
}
}, delayWhenBonded);
break;
case BluetoothDevice.BOND_BONDING:
Log.i("BLE", "Waiting for bonding to complete");
break;
}
break;
case BluetoothGatt.STATE_DISCONNECTED:
gatt.close();
break;
}
break;
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
super.onServicesDiscovered(gatt, status);
if (status == 129 /* GATT_INTERNAL_ERROR */)
{
Log.e("BLE", "Service discovery failed");
gatt.disconnect();
return;
}
final List<BluetoothGattService> services = gatt.getServices();
Log.i("BLE", "Discovered " + services.size() + " services for " + gatt.getDevice().getAddress());
m_deviceReady = SetupDevice(gatt);
if (!m_deviceReady)
Log.e("BLE", "Peripheral does not comply to this device's requirements");
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic)
{
super.onCharacteristicChanged(gatt, characteristic);
Log.i("BLE", "onCharacteristicChanged: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicRead(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicRead: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicWrite(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicWrite: " + characteristic.getUuid());
}
#Override
public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status)
{
super.onDescriptorWrite(gatt, descriptor, status);
ProcessCmdQueue();
final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
if(status != 0 /* GATT_SUCCESS */)
{
Log.e("BLE", "WriteDescriptor failed for characteristic " + parentCharacteristic.getUuid());
return;
}
if(descriptor.getUuid().equals(UUID.fromString(kCCC_DESCRIPTOR_UUID)))
{
if(status == 0 /* GATT_SUCCESS */)
{
byte[] value = descriptor.getValue();
if (value != null)
{
if (value[0] != 0)
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now notifying");
else
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now NOT notifying");
}
}
}
}
// ------------------------------------------------------------------------
// -- Command Queue implementation
/* An enqueueable write operation - notification subscription or characteristic write */
private class CmdQueueItem
{
BluetoothGattCharacteristic characteristic;
byte[] dataToWrite; // Only used for characteristic write
boolean writeWoRsp; // Only used for characteristic write
boolean enabled; // Only used for characteristic notification subscription
public m_queueItemType type;
}
private enum m_queueItemType
{
SubscribeCharacteristic,
ReadCharacteristic,
WriteCharacteristic
}
/* queues enables/disables notification for characteristic */
public void EnqueueSetNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.enabled = enabled;
m_queueItem.type = m_queueItemType.SubscribeCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* queues enables/disables notification for characteristic */
public void EnqueueWriteCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean requestResponse)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.dataToWrite = dataToWrite;
m_queueItem.writeWoRsp = !requestResponse;
m_queueItem.type = m_queueItemType.WriteCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* request to fetch newest value stored on the remote device for particular characteristic */
public void EnqueueReadCharacteristic(BluetoothGattCharacteristic ch)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.type = m_queueItemType.ReadCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/**
* Add a transaction item to transaction queue
* #param m_queueItem
*/
private void EnqueueBleCommand(CmdQueueItem m_queueItem)
{
m_cmdQueue.add(m_queueItem);
// If there is no other transmission processing, go do this one!
if (!m_cmdQueueProcessing)
ProcessCmdQueue();
}
/**
* Call when a transaction has been completed.
* Will process next transaction if queued
*/
private void ProcessCmdQueue()
{
if (m_cmdQueue.size() <= 0)
{
m_cmdQueueProcessing = false;
return;
}
m_cmdQueueProcessing = true;
CmdQueueItem m_queueItem = m_cmdQueue.remove();
switch (m_queueItem.type)
{
case WriteCharacteristic:
writeDataToCharacteristic(m_queueItem.characteristic, m_queueItem.dataToWrite, m_queueItem.writeWoRsp);
break;
case SubscribeCharacteristic:
setNotificationForCharacteristic(m_queueItem.characteristic, m_queueItem.enabled);
break;
case ReadCharacteristic:
requestCharacteristicValue(m_queueItem.characteristic);
break;
}
}
public void requestCharacteristicValue(BluetoothGattCharacteristic ch)
{
if (m_bluetoothGatt == null)
return;
m_bluetoothGatt.readCharacteristic(ch);
}
private void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean writeWoRsp)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setValue(dataToWrite);
ch.setWriteType(writeWoRsp ? BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE : BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
m_bluetoothGatt.writeCharacteristic(ch);
}
/* enables/disables notification for characteristic */
private void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
boolean success = m_bluetoothGatt.setCharacteristicNotification(ch, enabled);
if(success)
{
// This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
// see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor != null)
{
if (enabled)
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
else
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
if (m_bluetoothGatt.writeDescriptor(descriptor))
{
Log.i("BLE Peripheral", "SetNotification (Set + CCC) succeeded!");
}
}
else
Log.i("BLE Peripheral", "SetNotification (Set only) succeeded!");
}
else
Log.e("BLE Peripheral", "SetNotification failed!");
}
// ------------------------------------------------------------------------
// -- Abstract methods
protected abstract boolean SetupDevice(BluetoothGatt gatt);
protected abstract void OnUpdate(UUID service, UUID characteristic, final byte[] value);
}
Code to create and connect to a device
BleCentral central = new BleCentral();
m_customDevice = central.Connect(this, deviceMacAddress, () -> new CustomDevice());
CustomDevice is just inheriting the BlePeripheral class, implementing SetupDevice (that checks that all services and characteristics are there) and OnUpdate (that receives new data and handles it).
Now, two things bother me:
When connecting to the device, sometimes it works right away, and sometimes not. If not, I have to go connect to the device through another app such as Bluefruit Connect, then start my app again, and then it will connect ;
When it connects, it goes through service discovery and all, and in the setNotificationForCharacteristic function everything gets called correctly (I get onDescriptorWrite called and all) but I never receive any notification.
Since I'm the one behind the code running on my BLE peripheral, I can guarantee that the characteristic I'm trying to get data from is of the NOTIFY type (and not e.g. INDICATE).
If it can help in any way, the only NOTIFY characteristic there is sends a 56-byte array filled with 14 floats as often as it can. Early prototypes with Web Bluetooth or NativeScript (with the nativescript-bluetooth plugin) showed me that this actually works and in these cases I get results roughly about every 90 milliseconds.
I think I've rewritten this code about 3 times already and I'm getting a bit desperate, so any help going in the right direction is appreciated. :D
Thanks a lot!
Edit: Just for science, I tried to switch the characteristic to a READ one on the device, then spawning a thread reading it every second, instead of waiting for notifications. Well, onCharacteristicRead is called, but the byte array passed to it always has a length of zero...
I found the issue - it was not related to the Java code at all but caused by a hardware fault in my BLE device that caused all value updates to be discarded.

Android BLE connect and disconnect quickly to read 1 characteristic. Some BLE devices stop broadcasting after quick connection the disconnect

I am trying to write and android application that scans for BLE devices and when it finds the certain devices with a naming scheme it connects to it and reads a characteristic (A user defined name for the device) then disconnects right after. It would then display the device in a list with any other devices found and read the user defined name. The user can then chose a device to connect to (Or multiple devices) and connect to it and stream data from it.
The problem that keeps happening is after it gets the user defined name and is disconnected the BLE devices stop broadcasting and I can no longer find it when I scan or if I try to connect to it after I read the user defined name and disconnected to it.
Is this an issue with the Android BLE stack or do I need to add more delays (I have 100 millisecond delays throughout the bluetoothservice I use)
Here is part of the code I use in my service
public boolean initialize() {
Log.i(TAG, "Initializing");
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
mReadyToWrite = true;
mReadyToRead = true;
mReady = true;
mCharacteristicWriteQueue = new ArrayDeque<BluetoothGattCharacteristic>();
mCharacteristicReadQueue = new ArrayDeque<BluetoothGattCharacteristic>();
mDescriptorWriteQueue = new ArrayDeque<BluetoothGattDescriptor>();
mDescriptorReadQueue = new ArrayDeque<BluetoothGattDescriptor>();
//mBluetoothGattMap = new HashMap<String, BluetoothGatt>();
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address The device address of the device.
*
* #return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
if(mBluetoothGattMap.containsKey(address)) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGattMap.get(address).connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
mBluetoothGattMap.put(address, device.connectGatt(this, false, mGattCallback));
Log.d(TAG, "Trying to create a new connection to address " + address);
//mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect(String address) {
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
Log.i(TAG, "Disconnecting from gatt");
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
mBluetoothGattMap.get(address).disconnect();
}
public void close(String address) {
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
mBluetoothGattMap.get(address).close();
mBluetoothGattMap.remove(address);
Log.w(TAG, "Succeeed removing it");
}
public int getConnectionState(String address) {
Log.i(TAG, "getting connection state for " + address);
BluetoothGatt gatt = mBluetoothGattMap.get(address);
return mBluetoothManager.getConnectionState(gatt.getDevice(), BluetoothProfile.GATT);
}
/**
* Request a read on a given {#code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {#code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* #param characteristic The characteristic to read from.
*/
public void readCharacteristic(String address, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "reading characteristic");
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
if(mReadyToRead && mReady) {
boolean result = mBluetoothGattMap.get(address).readCharacteristic(characteristic);
mReadyToRead = false;
mReady = false;
if(!result) {
Log.i(TAG, "read failed");
}
}else {
mCharacteristicReadQueue.push(characteristic);
}
}
public void writeCharacteristic(String address, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "writeCharacteristic - readyToWrite = " + mReadyToWrite + " queue size = " + mCharacteristicWriteQueue.size());
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
if(mReadyToWrite && mReady) {
boolean result = mBluetoothGattMap.get(address).writeCharacteristic(characteristic);
mReadyToWrite = false;
mReady = false;
if(!result) {
Log.i(TAG, "characteristic write failed");
}
}else {
mCharacteristicWriteQueue.push(characteristic);
}
}
public void readDescriptor(String address, BluetoothGattDescriptor descriptor) {
Log.i(TAG, "reading descriptor");
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
if(mReadyToRead && mReady) {
boolean result = mBluetoothGattMap.get(address).readDescriptor(descriptor);
mReadyToRead = false;
mReady = false;
if(!result) {
Log.i(TAG, "descriptor read failed");
}
}else {
mDescriptorReadQueue.push(descriptor);
}
}
public void writeDescriptor(String address, BluetoothGattDescriptor descriptor) {
Log.i(TAG, "writing descriptor for characteristic " + descriptor.getCharacteristic().getUuid().toString());
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
if(mReadyToWrite && mReady) {
boolean result = mBluetoothGattMap.get(address).writeDescriptor(descriptor);
mReadyToWrite = false;
mReady = false;
if(!result) {
Log.i(TAG, "descriptor write failed");
}
}else {
mDescriptorWriteQueue.push(descriptor);
}
}
public BluetoothGattCharacteristic getCharacteristic(String address, UUID uuid) {
if(!mBluetoothGattMap.containsKey(address)) {
Log.i(TAG, "Device address " + address + " not found");
return null;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
for(BluetoothGattService service : mBluetoothGattMap.get(address).getServices()) {
Log.i(TAG, "Service: " + service.getUuid().toString());
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
Log.i(TAG, "Characteristic: " + characteristic.getUuid().toString());
if(characteristic.getUuid().equals(uuid)) {
return characteristic;
}
}
}
Log.i(TAG, "Characteristic not found");
return null;
}
public Set<String> getConnectedDevices(){
return this.mBluetoothGattMap.keySet();
}
/**
* Enables or disables notification on a give characteristic.
*
* #param characteristic Characteristic to act on.
* #param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(String address, BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
try {
synchronized (Thread.currentThread()) {
Thread.currentThread().wait(100);
}
}catch(InterruptedException e){
//ignore
}
mBluetoothGattMap.get(address).setCharacteristicNotification(characteristic, enabled);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHAR_CONFIG));
if(descriptor != null) {
boolean status = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
Log.i(TAG, "descriptor " + descriptor.getUuid().toString() + " setValue() status: " + status);
Log.i(TAG, "descriptor value: " + descriptor.getValue());
writeDescriptor(address, descriptor);
}
}
public void setPhoneEvents(byte priorities) {
for(String address : mBluetoothGattMap.keySet()) {
BluetoothGattCharacteristic characteristic = getCharacteristic(address, UUID.fromString(GattAttributes.ALERT_ATTRIBUTE));
if (characteristic != null) {
byte prioritiesBuf[] = new byte[1];
prioritiesBuf[0] = priorities;
characteristic.setValue(prioritiesBuf);
writeCharacteristic(address, characteristic);
Log.i(TAG, String.format("Forwarded phone alert priorities: 0x%X", priorities));
} else {
Log.e(TAG, "Failed to get the Alert ID characteristic from Gatt Server for device address " + address);
}
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {#code BluetoothGatt#discoverServices()} completes successfully.
*
* #return A {#code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices(String address) {
if (mBluetoothGattMap.get(address) == null) return null;
return mBluetoothGattMap.get(address).getServices();
}
The problem that keeps happening is after it gets the user defined
name and is disconnected the BLE devices stop broadcasting and I can
no longer find it when I scan or if I try to connect to it after I
read the user defined name and disconnected to it.
Shouldn't be your BLE device need start advertising soon after it's connection dropped?
I would also suggest that you can let your BLE device advertise your customized service other than your app to connect then read the characteristic; you just use "ScanFilter" to filter out what your favorite devices.
You just let low level code do to this.
The observed behaviour is really a feature of the BLE Peripheral device and not Android, or more generally, the Central device program.
I've recently been working with a Laird BL600 - and it has an 'Over the Air', OTA, programming mode - and when that mode is enabled the module advertises the OTA service for up to 10 seconds after being powered on: and that's it. The Peripheral application in this case has been desinged not to drop back to advertising after any connection is broken. To re-enter OTA mode a power cycle of the device is intended.
As suggested by Guo, if there is control over the Peripheral program, an easier scheme would be for the BLE Peripheral to include a readable identifier (device name) and the GUID of its main service in its advertising packet - which would make filtering which devices to present to the user easier.
When the user wishes to connect to the Peripheral, then that will only succeed using device.connectGatt(this, false, mGattCallback)
if the device is still advertising and in range. I think the intention of the second parameter in this call - 'autoconnect' - is to mark the connection, within Android, as pending - and that the connection should happen automatically when the device is back in range and advertising - but I've not found this to be very reliable.

Android BluetoothGatt becomes null on activity change

I am developing an android application which requires me to send data to a Bluetooth Low Energy device.After the connection event is successful and after I receive a call back message I want to change the activity and display new GUI where on switch click I want to send data to connected device. The problem is after the activity has changed my BluetoothGatt becomes null and BluetoothGattCharacteristic also becomes null and i am not able to send the data. how can I solve this issue? Below is my code main class which onResume calls the connection activity and connects to first available device and after connection is successful it receives callback message and changes the activity.
public class MainActivity extends Activity implements BluetoothLeUart.Callback {
public TextView messages;
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
messages = (TextView) findViewById(R.id.messages);
// Initialize UART.
uart = new BluetoothLeUart(getApplicationContext());
messages.setMovementMethod(new ScrollingMovementMethod());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
protected void onResume() {
super.onResume();
writeLine("Scanning for devices ...");
uart.registerCallback(this);
uart.connectFirstAvailable();
}
#Override
protected void onStop() {
super.onStop();
uart.unregisterCallback(this);
uart.disconnect();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
// UART Callback event handlers.
// UART Callback event handlers.
#Override
public void onConnected(BluetoothLeUart uart) {
// Called when UART device is connected and ready to send/receive data.
//messages.append("connected2");
writeLine("Connected!");
Intent intent = new Intent(MainActivity.this,SwitchClass.class);
startActivity(intent);
}
}
This is my codes BluetoothLeUart class which does connection activity and gives callback message.
public class BluetoothLeUart extends BluetoothGattCallback implements BluetoothAdapter.LeScanCallback {
// UUIDs for UART service and associated characteristics.
public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
// UUID for the UART BTLE client characteristic which is necessary for notifications.
public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
// UUIDs for the Device Information service and associated characeristics.
public static UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MANUF_UUID = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MODEL_UUID = UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb");
public static UUID DIS_HWREV_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
public static UUID DIS_SWREV_UUID = UUID.fromString("00002a28-0000-1000-8000-00805f9b34fb");
// Internal UART state.
private Context context;
private WeakHashMap<Callback, Object> callbacks;
private BluetoothAdapter adapter;
private BluetoothGatt gatt;
private BluetoothGattCharacteristic tx;
private BluetoothGattCharacteristic rx;
private boolean connectFirst;
private boolean writeInProgress; // Flag to indicate a write is currently in progress
// Queues for characteristic read (synchronous)
private Queue<BluetoothGattCharacteristic> readQueue;
// Interface for a BluetoothLeUart client to be notified of UART actions.
public interface Callback {
public void onConnected(BluetoothLeUart uart);
public void onConnectFailed(BluetoothLeUart uart);
public void onDisconnected(BluetoothLeUart uart);
public void onReceive(BluetoothLeUart uart, BluetoothGattCharacteristic rx);
public void onDeviceFound(BluetoothDevice device);
public void onDeviceInfoAvailable();
}
public BluetoothLeUart(Context context) {
super();
this.context = context;
this.callbacks = new WeakHashMap<Callback, Object>();
this.adapter = BluetoothAdapter.getDefaultAdapter();
this.gatt = null;
this.tx = null;
this.rx = null;
this.disManuf = null;
this.disModel = null;
this.disHWRev = null;
this.disSWRev = null;
this.disAvailable = false;
this.connectFirst = false;
this.writeInProgress = false;
this.readQueue = new ConcurrentLinkedQueue<BluetoothGattCharacteristic>();
}
// Send data to connected UART device.
public void sendbyte(byte[] data) {
if (tx == null || data == null || data.length == 0) {
// Do nothing if there is no connection or message to send.
return;
}
// Update TX characteristic value. Note the setValue overload that takes a byte array must be used.
tx.setValue(data);
writeInProgress = true; // Set the write in progress flag
gatt.writeCharacteristic(tx);
// ToDo: Update to include a timeout in case this goes into the weeds
while (writeInProgress); // Wait for the flag to clear in onCharacteristicWrite
gatt.readCharacteristic(rx);
}
// Send data to connected UART device.
public void send(String data) {
if (data != null && !data.isEmpty()) {
sendbyte(data.getBytes(Charset.forName("UTF-8")));
}
}
// Register the specified callback to receive UART callbacks.
public void registerCallback(Callback callback) {
callbacks.put(callback, null);
}
// Unregister the specified callback.
public void unregisterCallback(Callback callback) {
callbacks.remove(callback);
}
// Disconnect to a device if currently connected.
public void disconnect() {
if (gatt != null) {
gatt.disconnect();
}
gatt = null;
tx = null;
rx = null;
}
// Stop any in progress UART device scan.
public void stopScan() {
if (adapter != null) {
adapter.stopLeScan(this);
}
}
// Start scanning for BLE UART devices. Registered callback's onDeviceFound method will be called
// when devices are found during scanning.
public void startScan() {
if (adapter != null) {
adapter.startLeScan(this);
}
}
// Connect to the first available UART device.
public void connectFirstAvailable() {
// Disconnect to any connected device.
disconnect();
// Stop any in progress device scan.
stopScan();
// Start scan and connect to first available device.
connectFirst = true;
startScan();
}
// Handlers for BluetoothGatt and LeScan events.
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Connected to device, start discovering services.
if (!gatt.discoverServices()) {
// Error starting service discovery.
connectFailure();
}
else {
notifyOnConnected(this);
}
}
else {
// Error connecting to device.
connectFailure();
}
}
else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// Disconnected, notify callbacks of disconnection.
rx = null;
tx = null;
notifyOnDisconnected(this);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// Notify connection failure if service discovery failed.
if (status == BluetoothGatt.GATT_FAILURE) {
connectFailure();
return;
}
// Save reference to each UART characteristic.
tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID);
rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID);
// Save reference to each DIS characteristic.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
// Add device information characteristics to the read queue
// These need to be queued because we have to wait for the response to the first
// read request before a second one can be processed (which makes you wonder why they
// implemented this with async logic to begin with???)
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
// Request a dummy read to get the device information queue going
// gatt.readCharacteristic(disManuf);
// Setup notifications on RX characteristic changes (i.e. data received).
// First call setCharacteristicNotification to enable notification.
if (!gatt.setCharacteristicNotification(rx, true)) {
// Stop if the characteristic notification setup failed.
connectFailure();
return;
}
// Next update the RX characteristic's client descriptor to enable notifications.
BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID);
if (desc == null) {
// Stop if the RX characteristic has no client descriptor.
connectFailure();
return;
}
desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
if (!gatt.writeDescriptor(desc)) {
// Stop if the client descriptor could not be written.
connectFailure();
return;
}
// Notify of connection completion.
notifyOnConnected(this);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
notifyOnReceive(this, characteristic);
}
#Override
public void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//Log.w("DIS", characteristic.getStringValue(0));
// Check if there is anything left in the queue
BluetoothGattCharacteristic nextRequest = readQueue.poll();
if(nextRequest != null){
// Send a read request for the next item in the queue
// gatt.readCharacteristic(nextRequest);
}
else {
// We've reached the end of the queue
disAvailable = true;
// notifyOnDeviceInfoAvailable();
}
}
else {
//Log.w("DIS", "Failed reading characteristic " + characteristic.getUuid().toString());
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
// Log.d(TAG,"Characteristic write successful");
}
writeInProgress = false;
}
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// Stop if the device doesn't have the UART service.
if (!parseUUIDs(scanRecord).contains(UART_UUID)) {
//main.writeLine("Parse UUID failed...");
//main.messages.append("Parse UUID failed...");
return;
}
// Connect to first found device if required.
if (connectFirst) {
// Stop scanning for devices.
stopScan();
// Prevent connections to future found devices.
connectFirst = false;
// Connect to device.
gatt = device.connectGatt(context, true, this);
}
}
// Private functions to simplify the notification of all callbacks of a certain event.
private void notifyOnConnected(BluetoothLeUart uart) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onConnected(uart);
}
}
}
private List<UUID> parseUUIDs(final byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
// main.writeLine(advertisedData.toString());
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
//main.writeLine("case 02,03...");
// main.messages.append("case 02,03...");
while (len > 1) {
int uuid16 = advertisedData[offset++];
uuid16 += (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
//main.writeLine("case 06,07...");
// main.messages.append("case 06,07...");
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
//Log.e(LOG_TAG, e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
default:
//main.writeLine("case default...");
// main.messages.append("case default");
offset += (len - 1);
break;
}
}
return uuids;
}
}
Here is my class from where I send the data
public class SwitchClass extends Activity {
public TextView messages;
public Switch Switch1;
public byte[] switchData = {'U','1','1','1','0','0','2','Z'};
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
//messages.setText("anirudh");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.switchlayout);
Switch1 = (Switch) findViewById(R.id.switch1);
uart = new BluetoothLeUart(getApplicationContext());
Switch1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
switchString = new String(switchData);
writeLine(switchString);
// send this array 8 bytes to BLE
sendData(switchString);
}
}
});
public void sendData(String sendVal) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(sendVal.toCharArray(), 0, 8);
uart.send(stringBuilder.toString());
}
You are creating a service from this activity and you are destroying it onStop
so your service is now been disconnected , so automatically you cant get bluetoothgatt and adapter for it
I suggest you to have child fragment inside you activity so your service will stay alive and you can have as many fragment you want to display the thing !!
Hopes this will help you !!
you can visit https://github.com/captain-miao/bleYan, It's a simple BLE library and example.
I also got exactly the same error like yours. In my case I comment out or deleted the following line under onServiceDiscover method. It works suddenly.It may not be the answer, Hope it will give you some clue to solve.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
You must set to synchronized your BluetoothGatt Object like
public void setBluetoothGatt(BluetoothGatt gatt) {
synchronized (this) {
this.bluetoothGatt = gatt;
}
}
because BluetoothGatt throw DeadObject Exception when you change the Activity

Bluetooth GATT onConnectionState Change does not work on Lollipop

I currently have a method which writes to the BLE devices to beep it. My Bluetooth Callback goes as follows :
public class ReadWriteCharacteristic extends BluetoothGattCallback {
public ReadWriteCharacteristic(Context context, String macAddress, UUID service, UUID characteristic, Object tag, Activity activity) {
mMacAddress = macAddress;
mService = service;
mCharacteristic = characteristic;
mTag = tag;
mContext = context;
this.activity =activity;
final BluetoothManager bluetoothManager =
(BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
}
final private static String TAG = "ReadCharacteristic";
private Object mTag;
private String mMacAddress;
private UUID mService;
private BluetoothManager mBluetoothManager = null;
private BluetoothAdapter mBtAdapter = null;
private BluetoothGatt mBluetoothGatt = null;
private String mBluetoothDeviceAddress ="";
private UUID mCharacteristic;
BluetoothDevice device;
private Activity activity;
private BluetoothAdapter mBluetoothAdapter;
private Context mContext;
ReadWriteCharacteristic rc;
private int retry = 5;
public String getMacAddress() {
return mMacAddress;
}
public UUID getService() {
return mService;
}
public UUID getCharacteristic() {
return mCharacteristic;
}
public byte[] getValue() { return mValue; }
public void onError() {
Log.w(TAG, "onError");
}
public void readCharacteristic(){
if (retry == 0)
{
onError();
return;
}
retry--;
device = mBluetoothAdapter.getRemoteDevice(getMacAddress());
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
int connectionState = mBluetoothManager.getConnectionState(device,
BluetoothProfile.GATT);
if (device != null) {
if (connectionState == BluetoothProfile.STATE_DISCONNECTED)
{
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null
&& getMacAddress().equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.w(TAG, "Re-use GATT connection");
if (mBluetoothGatt.connect()) {
Log.w(TAG, "Already connected, discovering services");
mBluetoothGatt.discoverServices();
//return ;
} else {
Log.w(TAG, "GATT re-connect failed.");
return;
}
}
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return;
}
Log.w(TAG, "Create a new GATT connection.");
rc= ReadWriteCharacteristic.this;
Log.w(TAG, "Starting Read [" + getService() + "|" + getCharacteristic() + "]");
mBluetoothGatt = device.connectGatt(mContext, false, rc);
refreshDeviceCache(mBluetoothGatt);
mBluetoothDeviceAddress = getMacAddress();
} else {
Log.w(TAG, "Attempt to connect in state: " + connectionState);
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
readCharacteristic();
}
}
}
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.w(TAG,"onConnectionStateChange [" + status + "|" + newState + "]");
if ((newState == 2)&&(status ==0)) {
gatt.discoverServices();
}
else if(status == 133 )
{
//gatt.disconnect();
gatt.close();
mBluetoothGatt = null;
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else{
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
// gatt.close();
Log.w(TAG, "[" + status + "]");
//gatt.disconnect();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
mBluetoothGatt = null;
readCharacteristic();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.w(TAG,"onServicesDiscovered [" + status + "]");
BluetoothGattService bgs = gatt.getService(getService());
if (bgs != null) {
BluetoothGattCharacteristic bgc = bgs.getCharacteristic(getCharacteristic());
gatt.readCharacteristic(bgc);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.w(TAG,"onCharacteristicWrite [" + status + "]");
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG,"onCharacteristicWrite [" + getDataHex(characteristic.getValue()) + "]");
// gatt.disconnect();
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
// gatt.close();
// mBluetoothGatt=null;
}
else if(status ==133)
{
gatt.close();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else{
//gatt.disconnect();
gatt.close();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.w(TAG,"onCharacteristicRead [" + status + "]");
if (status == BluetoothGatt.GATT_SUCCESS) {
mValue = characteristic.getValue();
// Perform write operations
gatt.writeCharacteristic(bgc);
}
else if(status ==133)
{
gatt.close();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else {
// gatt.disconnect();
gatt.close();
}
}
}
This code works perfectly on device running Kitkat and below. But on Devices running Lollipop, this code works fine for the first instance. But from the next instance, irrespective of whether I disconnect or close the connection and try again, it just does not work. It keeps giving me a status code of 257 in onConnectionStateChange method. As far as I know, The Bluetooth GATT methods are the same for both kitkat and Lollipop devices.
What surprises me is that this code works fine on Lollipop devices when i use the old BLE API i.e startLeScan ( For eg - mBluetoothAdapter.startLeScan(mLeScanCallback);). This problem only arises when I use the new BLE API i.e BluetoothLeScanner ( scanner.startScan(filters, settings, new scancallback());). The scanning rate is really slow for Lollipop devices using the old BLE API, hence I cannot use it. I just don't understand how to solve this problem.Has anyone faced the same problem and found a solution? Any help would be deeply appreciated.
Quite a few things I would change here. Create a class variable for the data you want to read from the characteristic, such as private string heartRate;
1) You don't need the readCharacteristic() method. Instead, in the onConnectionStateChange once the device has connected properly, call mBluetoothGatt.discoverServices(). Then in the onServicesDiscovered() method I would call gatt.getServices(). Then use a foreach loop and loop through the returned services and compare the UUID of the service until you find the one you care about. Then if heartRate == null, call service.getCharacteristic(HeartRateUUID) and then read the characteristic. In onCharacteristicRead() check if the UUID is equal to the heart rate characteristic. If it is, assign the value of the characteristic to the heartRate variable. If you are interested, I can type out the methods or provide pseudocode.
2) I wouldn't call gatt.connect() followed by gatt.discoverServices(). gatt.connect() will reconnect to the current device as soon as it sees an advertisement packet from the device. I would call gatt.connect() and then call gatt.discoverServices() in the onConnectedStateChange() method.
3) In the onConnectedStateChange method don't use the gatt variable. Use mBluetoothGatt instead. mBluetoothGatt.disconnect() disconnects from the currently connected device. mBluetoothGatt.close() terminates the gatt instance. You cannot call mBluetoothGatt.connect() after calling mBluetoothGatt.Close(). This might not be needed, but if the device is connected I call mBluetoothGatt.disconnect() followed by mBluetoothGatt.close().
4) You can also chain characteristic readings together. In onCharacteristicRead(), after you get the value of the heartRate, you can immediately call characteristic.getService().getCharacteristic(UUIDTemperature) and then read that characteristic. It will call the OnCharacteristicRead method again.
Let me know if you want me to clarify anything; I'm typing on the crappy Surface Pro 3 keyboard. :)

Categories

Resources