using the android 4.4 BLE APIs on my Nexus7, i'm able to successfully interact with a peripheral BLE device -- connect, disconnect, read, write....
if however an active connection breaks for whatever reason (in this case, the peripheral is reset), i observe the following behavior....
my peripheral (by design) begins advertising after any active connection is terminated (for whatever reason); i can see this via my bluetooth packet sniffer....
i receive the onConnectionStateChanged callback as expected in my android app, at which point i invoke close() on my active BluetoothGatt instance; this is the same procedure i follow during a "normal" disconnect initiated from the client...
shortly after this, the android BLE stack tries to re-connect to the same peripheral; through the packet sniffer i can see the BLE connection request going out over the air...
my app, however, did not initiate this re-connection; indeed, i see no information from any bluetooth log suggesting this even happened!!!!
is there some "mode" in the BLE stack where it attempts to re-establish busted connections automatically???
thanks....
This happens on various Android phones whether the autoConnect flag is set to false or true.
Couldn't yet find a complete solution, it seems as the android BLE stack is spontaneously re-initiating the connection once it is getting the advertising signal again, just ignoring that it was the app that disconnected on purpose...
A partial solution may involve not using the BluetoothGatt.connect() method as explained here:
https://stackoverflow.com/a/23749770/4144487
So, a sample connect method can look like:
void connect(Context context) {
if (mGatt != null) {
mGatt.close();
}
mGatt = mDevice.connectGatt(context, false, callback);
}
To explain the importance of this issue, when it happens the peripheral thinks it is connected and my "real" app can't find it any more. At some phones like Galaxy S3 and Redmi note 3 I found that closing the bluetooth switch from the notification bar is "releasing" the peripheral and allowing me to discover the device. At others like Nexus 5x only a phone reboot will do the trick.
I've observed this happening if you use autoConnect=true when calling BluetoothGatt#connectGatt(). Generally I've found that it is best to use autoConnect=false, but with some devices you simply cannot connect unless you use true, so I usually do both. I try false first and if that fails then use true and then the behavior you're describing is something you simply have to work around.
Related
I am having a Arduino with BLE which has to send some data to any/all android phones over Bluetooth in it's range. My android phone should have a app which i intend to make will notify about data received.
How can i make such android app which auto-connects to any nearby BLE , if found without pairing even for first time and exchange data. I mean how in any application i can implement auto-connect without key pairing.I found that setting autoconnect=true will do this task , but i am not sure.
Any help, even some resource i will refer and clear my doubts.
The pre-requisites and steps are (code snippets in Java):
HC-XX module or similar BLE-device on the Arduino-side set to security mode 1 and security level 1 (no security AND no pairing)
Android 4.3 (API level 18) with built-in platform support for Bluetooth Low Energy (BLE)
Check on the device (mobile) that BLE is enabled
// 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()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
Find the BLE device(s). You use the startLeScan() method. This method takes a BluetoothAdapter.LeScanCallback as a parameter. You must implement this callback, because that is how scan results are returned. Because scanning is battery-intensive, you should observe the following guidelines:
As soon as you find the desired device, stop scanning.
Never scan on a loop, and set a time limit on your scan. A device that was previously available may have moved out of range, and continuing to scan drains the battery.
If you want to scan for only specific types of peripherals, you can instead call startLeScan(UUID[], BluetoothAdapter.LeScanCallback), providing an array of UUID objects that specify the GATT services your app supports.
The first step in interacting with a BLE device is connecting to it— more specifically, connecting to the GATT server on the device. To connect to a GATT server on a BLE device, you use the connectGatt() method. This method takes three parameters: a Context object, autoConnect (boolean indicating whether to automatically connect to the BLE device as soon as it becomes available), and a reference to a BluetoothGattCallback.
// Here we set autoconnect to true
bluetoothGatt = device.connectGatt(this, true, gattCallback);
To sum up auto connect alone will not do the job as you want no pairing. So security mode 1 and security level 1 (no security at all) has to be set. So make sure by using software sided encryption/auto sign-in that no unauthorized persons use your device
Read more about BLE in Android in detail here
Read more about BLE security in detail here
I have a ble device that I need to regularly extract data from securely and constantly. So on startup I want to make sure to bond the device if it is not already. I have the Mac address of the device in this case.
public void startApp(){
remoteDevice = bluetoothManager.getAdapter().getRemoteDevice(MAC_AD);
if(remoteDevice.getBondState()!=12){
remoteDevice.createBond();
}
}
What ends up happening is that the bond state will alternate between unbonded and currently_bonding but not fully bond.
The strange thing is that sometimes it will work, but usually not, particularly on my google pixel. Bonding through the settings is also very inconsistent.
Is there any way to retry this or some kind of Bluetooth reset that should be done so that I can bond consistently?
I've tried calling createBond() in intervals;
I've often found that calling createBond() directly can have hit-and-miss results depending on the platform (both ends). Logically it should use the same mechanism internally, but I've tended to get better results by calling read on a simple characteristic which has bonded requirements.
It either successes - meaning your connection is bonded - and you can continue. Or it fails, which internally triggers the bonding, and then you can try again after a short delay, at which point it should now be bonded.
So I'm able to connect to a BLE device just fine under normal circumstances. What I want to do is handle abnormal circumstances, like when the connection to a device fails or an established connection is lost (maybe it got thrown off a cliff or hit by a bus)
I'm using a CyPress BLE module to test this, and one of the tests I'm doing is to remove power from the module. However, onConnectionStateChange never gets called! All I ever see it respond to is successful connections. It'll spend hours trying to connect and never give up evidently. I would do a delayed cancellation of the connection attempt, but there is no way to cancel the connection attempt on a Bluetooth device (that I know of)! As far as I can tell it'll keep trying until the battery runs down.
Here's what my onConnectionStateChange looks like right now, inside the Gatt Callback. Note that I'm trying to catch and log ANY kind of callback involving ANY kind of connection state change... which never gets called unless connection success. Note that this is code is not on the activity itself. It's in an object held by a singleton. (I want to maintain connection across multiple activities)
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
mGatt = gatt;
Logy.CallPrint(LOGY_ENABLE, CLASSNAME, "Status: "+status+ " Newstate: "+newState);
switch(status)
{
case BluetoothGatt.GATT_SUCCESS:
mReconnectAttempts = MAX_ATTEMPTS;
if(newState == BluetoothGatt.STATE_CONNECTED)
{
DispatchEvent(Event.Type.BT_ON_CONNECT);
bIsConnected = true;
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED)
{
DispatchEvent(Event.Type.BT_ON_DISCONNECT);
bIsConnected = false;
}
break;
default:
if(newState == BluetoothGatt.STATE_DISCONNECTED)
{
bIsConnected = false;
if(mReconnectAttempts > 0)
{ // if we have attempts left, auto attempt to reconnect
DispatchEvent(Event.Type.BT_RECONNECTING);
mReconnectAttempts--;
gatt.connect();
bIsConnected = false;
}
else
{
mReconnectAttempts = MAX_ATTEMPTS;
DispatchEvent(Event.Type.BT_ON_CONNECT_FAIL);
bIsConnected = false;
}
} else {
Logy.CallPrint(LOGY_ENABLE, CLASSNAME, "onConnectionStateChange: Failed?");
}
}
super.onConnectionStateChange(gatt, status, newState);
}
Not being able to detect disconnects is an issue elsewhere in my code, like where I show a Progress Dialog indicating the app is connecting to a BLE device. Well, that dialog never goes away because the event "On Connect Fail" never gets thrown.
I think what you are looking for is Bluetooth Supervision timeout which is according to Bluetooth LE specifications :
a parameter that defines the maximum time between two received Data Packet PDUs before the connection is considered lost
Default Supervision timeoout is set to 20 seconds on Android (depending on Android version & device). For instance, here is the value of Supervision Timeout on Android 5.1.
There is no API to set this parameter, so you will have to wait 20 seconds (depending on your Android version & device) to get onConnectionStateChange callback with status BluetoothGatt.STATE_DISCONNECTED after you power off your BLE module
This answer aligns with the answer from Emil.
I coded up a test App with a single Activity running on Moto G4 Play Android 6.0.1 (Marshmellow: API23) and a Peripheral based on a Laird BL600
I was going to post some logs - but they don't format so well - so I'll just describe the results.
As usual, the Peripheral advertised and the Central scanned, and obtained a device instance.
The question does not state exactly how the first connection is made, but let's assume it is with 'autoconnect' false, of the form
mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
(this is the only style of connection used in the test App)
This gives an instance of BluetoothGatt and, as noted in the question, events are then reported asynchronously through the call back,
but there is also an instance of BluetoothGatt on which to call disconnect() even before any connection events have occurred.
The API documentation for disconnect() states - especially the part after the ','
BluetoothGatt.disconnect()
Disconnects an established connection, or cancels a connection attempt currently in progress.
To free up GATT resources, after disconnecting, the test App also then always calls
mBluetoothGatt.close();
Here are some different scenarios
device.connectGatt() -- Peripheral advertising - connection made -- followed by mBluetoothGatt.disconnect() and close().
Result: this is a normal successful connection, followed by Central device closing the connection in code. Call back events are received as expected. When the Central disconnects in the code, the underlying Bluetooth Service disconnects and the Peripheral gets a Disconnection event.
.
device.connectGatt() -- Peripheral advertising - connection made - followed by Peripheral switched off.
Result: this is a normal successful connection, call back events as expected, followed by connection broken by Peripheral. The Peripheral indicates preferred connection supervision timeout in BleGapSvcInit and this is confirmed by Central in a Connection Parameters Update. After Peripheral drops the connection a Disconnection event occurs in Central just after the connection supervision timeout. (Repeated with 8 seconds and 4 seconds supervision timeouts).
.
device.connectGatt() -- but Peripheral has stopped advertising, connection attempt allowed to time out.
Result: (This is a specific scenario of the question) After 30 seconds, probably the connection timeout of the Bluetooth Service on the phone,
there is a an onConnectionStateChange event indicating the new connection state is Disconnected - with (error) status 133.
Must still remember to call mBluetoothGatt.close() else an interface leak occurs and subsequent connection is made on the next client interface.
.
device.connectGatt() -- Peripheral still advertising but connection cancelled after 200ms using mBluetoothGatt.disconnect() and close().
Result: (I found this case the most interesting, if also the most unlikely to happen in a real application) Sometimes, (about 1 in 10)
the underlying Bluetooth Service actually did connect to the Peripheral; which saw a connect, followed by a disconnect; even though the App doesn't see
these events in the call back. Sometimes, even though, as far as the App is concerned, the App is disconnected, the underlying Bluetooth phone service
connected to the Peripheral - and remained connected in my test for a few minutes until I timed out! - and turned the BT service, or the Peripheral, off.
First, if an established connection is dropped you should get a disconnected state change event when the supervision timeout has passed. Otherwise there is some bug in Android.
Now about connection attempts.
Once you create a BluetoothGatt object with connectGatt and specify the auto connect parameter to true OR execute the connect method on an existing BluetoothGatt object, the phone will be in a state where it always and indefinitely tries to connect to the device and reconnect to the device if it disconnects for any reason until you either call disconnect or close on the gatt object.
So if you want to abort the connection after a while, just set up any kind of timer which calls disconnect on the gatt object (or close if you don't need it anymore) when it is triggered.
Also note that the status parameter of the onConnectionStateChange when newState is disconnected is not well-defined. In older Android versions it contains usually 0 or 133 and in newer versions often the Bluetooth standard's error code of the disconnect reason.
Also, if you get a disconnect state change event without previously have got a connected state change event, it usually indicates something has gone wrong in the internal Bluetooth stack (unless you use non-auto connect for which you always get a disconnect state change event after some timeout). Then I'd recommend that you close the gatt object and try again later.
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.
Alright here's the deal. I got two Galaxy Nexus phones both with bluetooth enabled.
I've written a bluetooth connection management app that I use for device discovery and connectivity. It also outputs all the available UUIDs the devices can support.
Looking from http://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm the following standard UUIDs are exposed from Galaxy Nexus devices.
0x1116 - NAP
0x112f - PBAP (Phonebook Access Profile)
0x111f - HFP (Hands free)
0x1105 - OPP (Object Push Profile)
0x1112 - HSP (Headset Profile)
0x110c - AVRCP
0x110a - A2DP
I am trying to connect via the OPP profile (UUID 00001105-0000-1000-8000-00805F9B34FB) and push objects (files) between the devices. I've gone though the entire Android API documentation on how to discover, pair/bond (threading etc.) and manage all bluetooth connections. I've managed to successfully connect and talk to a legacy board device via the SPP (0x1101) profile.
However, when I try to use socket.connect() between the two galaxy nexus phones, the pairing dialog shows up and I click Pair button on both devices. After that, I immediately get a Connection Refused IOException. Note that after pairing has occurred once I never get asked again which makes sense since the secure link is cached.
If I can't connect to these standard profiles using these standard UUIDs why are they exposed? How can I connect from my app to any of these profiles and interact with them? Is it because my app is not somehow trusted? What's weird is that even the Share functionality on Android does not work at all either. Is this something completely broken on Android?
Please avoid giving me hints to use the "well known UUID SPP one 0x1101" like the docs say. This is not what I want. I have a fairly good understanding of how this stuff works and I am looking for a real solution or explanation of the problem.
I've seen the typical "reflection" solution but I do not understand why is this still a problem on Android? Why do people use reflection to make this work? Can we file a bug on Android to fix this?
If those UUIDs are standard any app should be able to connect and interact with them. Why is this an issue and why do I get this exception?
Thanks in advance.
UPDATE
So for some reason the object push in the Android system started working. I actually attempted to connect via my app and it was not working. Then, I went to the Contacts app and tried to share a contact which magically worked. Then, I went back to my app and it now it works...wow. That is very weird and there must be an explanation to this.
I ran into this same issue and managed to find a solution that worked for me.
In my case I using three different test devices (Nexus 5, Galaxy S4, Note 2) and for some reason, the Note 2 wouldn't connect to my Bluetooth module yet the other two would.
The reasoning I've found is that Bluetooth drivers vary, and slightly different connection methods are needed to create a connection between different devices.
The three methods I use are called 'Secure', 'Insecure' and 'Reflection method'/'hax'.
switch(connType)
{
case Secure:
tmpSocket = device.createRfcommSocketToServiceRecord(_uuid);
break;
case Insecure:
tmpSocket = device.createInsecureRfcommSocketToServiceRecord(_uuid);
break;
case Hax:
Method createSocket = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
tmpSocket = (BluetoothSocket)createSocket.invoke(device, Integer.valueOf(1));
break;
}
In my case, the Secure mode worked for both the Nexus 5 and Galaxy S4 however it didn't work for the Note 2.
After some testing I discovered the Note 2 only works using 'Insecure' mode, so to cater to this, I basically attempt a connection and cycle through the different modes if necessary. When attempting a different connection mode I simply prompt 'retrying connection'. So, if the connection fails using secure, then I will attempt using Insecure and then using the reflection method.
I haven't run into the case where one of these three methods haven't worked.
Have you tried using a nonstandard profile? i.e. a custom UUID just for your app. This will also help you know your are (most likely) only connecting to your own app rather than some other app that is registered with the same profile.
From my experience, Bluetooth pairing is very buggy for the first pair attempt. However, using a custom UUID helps this somewhat.
The reflection method (I think) was originally an attempt to fix a bug with a specific device, however I think some people found success in using it elsewhere as well. The device was called the Spica or something similar.
As one of the comments also posted, I would also try connecting again after failing.
Basically write code that plans to fail the first attempt, but then the code tries to connect again in 5 seconds if there was a failure.
These are imperfect solutions but Bluetooth implementation on Android is also imperfect (IMHO). Hope that helps
EDIT
Based on the question update and comments:
I agree something is definitely buggy. Part of the problem I think is the BT drivers vary and each has a different BT stack with different quirks. I also found a question that makes use of both the reflection method AND custom UUID, AND other standard methods. This seems extreme to me but it does cover the most ground. Unfortunately as app developers we have no control over the low level stack/code/drivers.
I have found with my two Bluetooth sharing apps that the first pairing is always tricky.
I'm glad to know it's not just me.