I have a problem while working with multiple devices connected via BT LE (Bluetooth low energy).
Work flow is as below:
Step of processing:
1. Scan all bluetooth devices to get address.
BluetoothAdapter btAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
BluetoothLeScanner bluetoothLeScanner = mBtAdapter.getBluetoothLeScanner();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
if (btAdapter .isEnabled()) {
bluetoothLeScanner.startScan(filters, settings, mScanCallback);
}
2.Loop all devices get by step 1. For each device:
BluetoothDevice btDevice = bluetoothAdapter.getRemoteDevice(deviceAddress);
BluetoothGatt bluetoothGatt = mBluetoothDevice.connectGatt(context, false, btCallback);
on onConnectionStateChange() of btCallBack I call bluetoothGatt .discoverServices(); when connection state is STATE_CONNECTED.
After doing data exchange with device. I call
bluetoothGatt.disconnect();
Thread.sleep(500);
bluetoothGatt.close();
Process next device
The problem is when I called bluetoothGatt.disconnect() and bluetoothGatt.close() but the first Bluetooth Dongle connection always hangs and the blue led of bluetooth dongle never blink even I kill the app.
Only the last Bluetooth dongle can release connection when work is done.
My question is why I call disconnect() and close() but it does not work ?
build.gradle: minSdkVersion 21, targetSdkVersion 28
Otherwise, this problem only occurs with some kind of Android devices not all devices. For example it works well on Samsung device (Android 7), but not working on other devices.
Any help would be appreciated.
After trying a lot. I fixed this problem by adding idle time between ICUs connection.
Step as below.
1. Connect to device 1
2. Transfer data
3. Disconnect device 1
4. Thread.sleep for some seconds
5. Connect to device 2
...
Hope this helps someone has problem as me.
Related
In my application i can scan for peripheral device just fine, but can't connect to it. Here is the code for connecting to the master device:
bleGatt = masterDevice.connectGatt(currentContext,false,gattCallback);
if(bleGatt == null){
Log.w(TAG,"Unable to create GATT client");
}
if i change the autoconnect flag to true, like:
bleGatt = masterDevice.connectGatt(currentContext,true,gattCallback);
then device does connects but, my custom service and characteristics are not discovered onServicesDiscovered call back. I think its some problem with this code running on device with API greater than 21
Any help would be appreciated, Thank you!
Background:
I have a BLE peripheral with two modes: "Application" and "Bootloader". In both modes, the device advertises with the same MAC address.
To switch from one mode to the other, the BLE peripheral must reboot itself. In doing so, it has to disconnect any active BLE connection.
The BLE peripheral only stays in Bootloader mode for about 5 seconds. If nobody connects to it within that window, it switches to Application mode.
The Problem:
Android takes a very long time to reconnect to the BLE device, long enough that I'm missing the 5 second window. The raw code has a few layers down to the BluetoothGATT and BluetoothAdapter layers, but the sequence of calls boils down to:
BluetoothGattCharacteristic c = mCharacteristics.get(POWER_STATE_UUID);
c.setValue(SHUTDOWN_VALUE);
mBluetoothGatt.writeCharacteristic(c);
// Signalled by BluetoothGattCallback.onCharacteristicWrite
bleWriteCondition.await();
mBluetoothGatt.disconnect();
// Wait for the underlying layer to confirm we're disconnected
while( mConnectionState != BluetoothProfile.STATE_DISCONNECTED ) {
// Signalled by BluetoothGattCallback.onConnectionStateChange
bleStateCondition.await();
}
mBluetoothGatt.connect();
while (mConnectionState != BluetoothProfile.STATE_CONNECTED) {
// Signalled by BluetoothGattCallback.onConnectionStateChange
bleStateCondition.await();
if (bleStateCondition.stat != 0) {
break;
}
}
Am I going about this entirely the wrong way? I've tried calling close() on the BluetoothGatt instance, then generating a new one with BluetoothDevice.connectGatt, but I get the same extremely slow behavior.
I'm testing on a Samsung Galaxy S4, API level 21.
The problem here is that the gatt connect call issues a background connection request. It can take quite a long time for this call to result in a connection. A description of the two types of connection request is here : Direct vs Background connections
The absolute fastest way to get a connection is to do a scan and upon finding your device issue a direct connection request to it. As the scan has just found it, you know it is there and the connection will complete quickly. This is more complicated than your example code, but will be most effective given your small window. A scan is the most aggressive way to find a device. However, if you already have the device object, you could just call a direct connection request on the device.
Scans are issued using code like this :
scanner = bluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
filters = new ArrayList<ScanFilter>();
ScanFilter uuidFilter = new ScanFilter.Builder()
.setServiceUuid(YOUR_SERVICE_UUID).build();
filters.add(uuidFilter);
scanner.startScan(filters, settings, myScanCallback);
Upon finding your device (using the scan callback), issue a direct connection request via this method call :
myGatt = myDevice.connectGatt(this, false, myGattCallback);
The key part being the parameter of false. The connection request will time out in around 30s if the device is not found.
I'm using two Android 5.0 devices to communicate through Bluetooth Low Energy and I wan't :
Device 1 to act as Central and Server.
Device 2 to act as Peripheral and Client.
This is the behavior I'd like to achieve :
1) Device 2 starts advertising (peripheral role).
2) Device 1 starts scanning (central role), and gets the advertising device (BluetoothDevice object) through the ScanCallback's onScanResult method.
3) I now want the advertising device (Device 2) to be notified that it has been scanned and be able to get the BluetoothDevice associated with Device 1.
4) Device 1 has an instance of BluetoothGattServer. Device 2 would now call connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) on Device 1 to get an instance of BluetoothGatt.
5) In the end, Device 1 is Server and Device 2 is Client.
So far I've found that in step 2, once Device 1 holds the BluetoothDevice for Device 2, it can only connect as client like in step 4 using connectGatt.
I might be able to use the BluetoothGattServer defined in Device 1, and call : gattServer.connect(BluetoothDevice device, boolean autoConnect) with device being Device 2.
But how will Device 2 be notified it's been connected to ?
And how will I get an instance of BluetoothGatt in Device 2 if I can't call connectGatt(Context, boolean, BluetoothGattCallback) on a BluetoothDevice?
Thank you in advance for your help !
Some documentation :
BluetoothGattServer
BluetoothDevice
1) Device 2 starts advertising (peripheral role).
Peripheral role will advertise, make sure to add CONNECTABLE
AdvertiseSettings.Builder settingBuilder = new AdvertiseSettings.Builder();
settingBuilder.setConnectable(true);
And start advertisement accordingly.
2) Device 1 starts scanning (central role), and gets the advertising device (BluetoothDevice object) through the ScanCallback's onScanResult method.
Perfect, now call connectGatt on this device(peripheral), make sure you stops the advertisement after you gets required device, otherwise you will end up sending multiple connect commands.
3) I now want the advertising device (Device 2) to be notified that it has been scanned and be able to get the BluetoothDevice associated with Device 1.
When you calls connectGatt from Central/client role, your peripheral will get a notification in its BluetoothGattServerCallback'onConnectionStateChange.
there you will know that connection has been made. though you have to register gatt Service with characteristics at peripheral side.
4) Device 1 has an instance of BluetoothGattServer. Device 2 would now call connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) on Device 1 to get an instance of BluetoothGatt.
Wrong, Device 1 will initiate connection as I have stated in point 3. both device's onConnectionStateChange will be called to know that connection has been made.
5) In the end, Device 1 is Server and Device 2 is Client.
Wrong, Device 2 is peripheral(Server), Device 1 is Monitor(Client)
You must turn it around a bit.
The scanner is the one connecting to the advertiser.
Dev1 scans dev2 adv and scan response. then dev1 should connect. Dev2 will get callback on connect.
There is no callback when someone hear your adv or request scan response on android.
Check instead 0x14 «List of 16-bit Service Solicitation UUIDs» from btsig if You want to advertise request for servers with a certain service to connect to You. It is a bit unusual ti see this used.
We are doing below process to do pair with BLE Device.
Connect() + discoverServices() + Pairing(Bonding) .
Sometimes Android OS unpaired our BT device in a weird way, that is:
without sending broadcast notification that bonding state has changed
even system Bluetooth settings app thinks that device is still paired
only bt restart (turning off and on via settings app) refreshes state and shows that device is not paired any longer
When Device is Successfully Paired the ACTION_BOND_STATE is change as below.
[6:19:28 PM] Himen Patel: 04-09 18:18:27.325: D/BluetoothGatt(8380): onCharacteristicWrite() - Device=C2:69:E9:57:93:A4 UUID=860b2c07-e3c5-11e2-a28f-0800200c9a66 Status=5
04-09 18:18:27.365: E/millisUntilFinished(8380): millisUntilFinished = 15
04-09 18:18:28.105: E/BelwithDeviceActor(8380): Bond state changed for: C2:69:E9:57:93:A4 new state: 11 previous: 10
04-09 18:18:28.105: E/millisUntilFinished(8380): millisUntilFinished = 20
04-09 18:18:29.135: E/millisUntilFinished(8380): millisUntilFinished = 18
04-09 18:18:30.135: E/millisUntilFinished(8380): millisUntilFinished = 17
04-09 18:18:31.145: E/millisUntilFinished(8380): millisUntilFinished = 16
04-09 18:18:32.145: E/millisUntilFinished(8380): millisUntilFinished = 15
04-09 18:18:33.105: D/BluetoothGatt(8380): onCharacteristicWrite() - Device=C2:69:E9:57:93:A4 UUID=032a0000-0000-0000-0000-000000000000 Status=137
04-09 18:18:33.115: E/BelwithDeviceActor(8380): Bond state changed for: C2:69:E9:57:93:A4 new state: 12 previous: 11
04-09 18:18:33.115: I/System.out(8380): unregisterReceiver true
Now when Pairing is removed by OS in weird way the ACTION_BOND_STATE is change as below.
.
.
.
.
Bond state changed for: C2:69:E9:57:93:A4 new state: 10.
we also get immediate event of act=android.bluetooth.device.action.ACL_DISCONNECTED flg=0x4000010 in our APP.
what's important here, at this point we just lost pairing with the device and protected characteristics don't work for us any longer.
if we restart bt using system settings app or BluetoothAdapter::disable() and enable() we can see that we are not paired with the device.
what's funny, without the bt restart, system settings app still thinks and shows that we are paired with the device.
tested with nexus 4 running 4.4.2, nexus 5 running 4.4.2 and even Samsung galaxy s4 running 4.3.
our expectation is that:
in case of unpairing there should be system broadcast
system preferences app should show current paring status even without bt restart
We have also Observed and get the sniffed data in which we found that our encryption is set to 0x000000 when our bonding is removed by OS in weird way.
I have no idea whether you still need help or whether you eventually solved your own problem (you know, since you did post this question back in April), but I wanted to go ahead and post the workaround I came up with because I know other people are having this problem.
Using a Nexus 7, I ran basically the same tests you did and came to the same conclusion:
If the Android tablet and the remote device were already bonded, there was a high chance that calling BluetoothGatt.discoverServices() would both disconnect and unbond the tablet from the remote device. But, certain parts of the Android OS seemed completely oblivious to the unbonding; although the Broadcast Receiver you registered to listen for bonding changes was notified that the bond between the two devices had been broken, the rest of the Android OS considered the bond to still be intact. Since the OS considered the tablet and the remote device to be bonded, the tablet could not write to any of the encrypted descriptors on the remote device, giving a write status of 15 (GATT_INSUFFICIENT_ENCRYPTION) whenever a descriptor write was attempted.
The Solution
The key is to unpair the Nexus and the remote device before they have the chance to unpair themselves in that weird way. What I do is check to see if the tablet and the remote device are bonded right before I start a Bluetooth Low Energy scan. If they are paired, I remove the bond using the function below and then start the scan. Unpairing the two devices programmatically ensures that the Android OS is aware that they are no longer bonded and, therefore, will go through the usual bonding process.
Below is the code is use to check to see if the remote device is paired with the tablet and unpair it if it is:
// Get the paired devices and put them in a Set
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// Loop through the Set of paired devices, checking to see
// if one of the devices is the device you need to unpair
// from. I use the device name, but I'm sure you can find
// another way to determine whether or not its your device
// -- if you need to. :)
for (BluetoothDevice bt : pairedDevices) {
if (bt.getName().contains("String you know has to be in device name")) {
unpairDevice(bt);
}
}
// Function to unpair from passed in device
private void unpairDevice(BluetoothDevice device) {
try {
Method m = device.getClass().getMethod("removeBond", (Class[]) null);
m.invoke(device, (Object[]) null);
} catch (Exception e) { Log.e(TAG, e.getMessage()); }
}
Why waiting for the error and then solving it by restarting the Bluetooth is a bad idea...
As you have already pointed out in your question, restarting the Bluetooth after the tablet and the remote device mysteriously unbond from each other forces the Android OS to realize that it is no longer bonded to the remote deivce. This was the original workaround I used, but it soon became clear that there were two major problems that came with this "solution":
Turning the Bluetooth on and off will disconnect all of the devices that were connected to the tablet.
Turning the Bluetooth on and off wastes a lot of time.
I would only restart the Bluetooth as a last resort. For example, if the unbonding error still miraculously occurred, your only choice would be to restart the Bluetooth.
We had the same issue and we've figured out that "connectGatt" has new "transport" argument (which defines transport protocol of connection), starting from SDK v 23.
So if you want to pair your devices you should use "TRANSPORT_BREDR", if you want to only connect to peripheral without bonding - use "TRANSPORT_LE"
Documentation: https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int)
While running on Nexus 7 with Android 4.4, the onScanResult throws a NullPointerExceptionas as seen in the log below:
03-18 17:59:34.170: D/BluetoothAdapter(5092): onScanResult() - Device=78:4B:08:02:7C:91 RSSI=-77
03-18 17:59:34.170: W/BluetoothAdapter(5092): Unhandled exception: java.lang.NullPointerException
However, other devices have no such problems.
I've found this on googlesource
public void onScanResult(String address, int rssi, byte[] advData) {
if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
// Check null in case the scan has been stopped
synchronized(this) {
if (mLeHandle <= 0) return;
}
try {
BluetoothAdapter adapter = mBluetoothAdapter.get();
if (adapter == null) {
Log.d(TAG, "onScanResult, BluetoothAdapter null");
return;
}
mLeScanCb.onLeScan(adapter.getRemoteDevice(address), rssi, advData);
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception: " + ex);
}
}
Which has a number of potential culprits, but of course I cannot set values for these variables.
Any why does this fail for Nexus 7 but not for other devices? Any ideas for workarounds?
Android 4.3, 4.4: BLE filtering in startLeScan(UUIDs, callback) doesn't work for 128-bit UUIDs
It works on Samsung s5, tested with Android 4.4.2 but for some reason, it fails on Nexus. Waiting for this fix from Google stack.
In case you want to search for specific address only, you could use this solution. Basically, you would have to use scanRec[], take an extra effort to parse it and then add device with matching address into a list adapter.
[I know, .... wish that simple API would work ! :P ]
Turning WIFI OFF:
I can confirm too, that turning WIFI OFF makes Bluetooth 4.0 more stable especially on Google Nexus (I have a Nexus 7).
The problem
is that the application I am developing needs both WIFI and continous Bluetooth LE scanning. So turning WIFI OFF was no option for me.
Moreover I have realised is that continous Bluetooth LE scanning can actually kill WIFI connection and make the WIFI adapter unable to re-connect to any WIFI network until BLE scan is ON. (I'm not sure about mobile networks and mobile internet).
This definitely happened on the following devices:
Nexus 7
Motorola Moto G
However BLE scanning with WIFI on seemed pretty stable on:
Samsung S4
HTC One
My workaround
I scan BLE for a short period of time 3-4 seconds then I turn scan OFF for 3-4 seconds. Then ON again.
Obviously I always turn BLE scan OFF when I'm connecting to a BLE device.
When I disconnect from a device I restart BLE (turn adapter OFF and then ON) to reset the stack before starting scan again.
I also reset BLE when discovering services or characteristics fails.
When I get advertisement data from a device that the app should connect to (lets say 500 times without being able to connect - thats about 5-10 seconds of advertising) I reset BLE again.