Here's my problem
I have an electronic lock that uses BLE, it works fine on ios(Both LightBlue & the app I made)
But on Android, after bonding with the lock(I entered the pin code 000000), I can connect to the device, but one of the Service (UUID:7570) becomes empty, and I can't read or write to any characteristic.
Also the device always gets disconnected after about 20 seconds.
This is the ScreenShot from ios
This is the ScreenShot from android (with empty Service)
Here's the log I got from nRF Connect
nRF Connect, 2018-02-07
OHGA-ELock 60012 (68:C9:0B:15:99:0D)
V 17:57:52.234 Connecting to 68:C9:0B:15:99:0D...
D 17:57:52.234 gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE)
D 17:57:52.767 [Callback] Connection state changed with status: 0 and new state: CONNECTED (2)
I 17:57:52.767 Connected to 68:C9:0B:15:99:0D
D 17:57:52.769 wait(1600ms)
D 17:57:52.785 [Broadcast] Action received: android.bluetooth.device.action.ACL_CONNECTED
V 17:57:54.373 Discovering services...
D 17:57:54.373 gatt.discoverServices()
D 17:57:54.395 [Callback] Services discovered with status: 0
I 17:57:54.395 Services discovered
V 17:57:54.414 Generic Access (0x1800)
- Device Name [R] (0x2A00)
- Appearance [R] (0x2A01)
- Peripheral Preferred Connection Parameters [R] (0x2A04)
Generic Attribute (0x1801)
Unknown Service (00007570-0000-0000-0000-000000000000)
Device Information (0x180A)
- System ID [R] (0x2A23)
- Model Number String [R] (0x2A24)
- Serial Number String [R] (0x2A25)
- Firmware Revision String [R] (0x2A26)
- Hardware Revision String [R] (0x2A27)
- Software Revision String [R] (0x2A28)
- Manufacturer Name String [R] (0x2A29)
D 17:58:10.701 [Callback] Connection state changed with status: 8 and new state: DISCONNECTED (0)
E 17:58:10.701 Error 8 (0x8): GATT CONN TIMEOUT
I 17:58:10.701 Disconnected
D 17:58:10.794 [Broadcast] Action received: android.bluetooth.device.action.ACL_DISCONNECTED
I've tested it on different Android phones with different testing apps.
Is there a chance this is an issue of the BLE device?
Please let me know if more specific details are needed.
Any help or suggestion would be appreciated.
Thanks!
the device always gets disconnected after about 20 seconds
This is because of the supervision timeout. In Android it is hardcoded to 20 seconds during which a re-connect doesn’t work. For more information to know what this 20 seconds and supervision timeout take a look here- Android BLE connection timeouts and GATT internal errors
Related
My application is able to connect to the BLE peripheral(which is an OBDII/J1939 device) device successfully.
2018-01-24 14:58:38,413 INFO LogUtil - GATT Server Status = (0) : BLE_HCI_STATUS_CODE_SUCCESS(0x00)
2018-01-24 14:58:38,414 INFO LogUtil - GATT Server New State = (2) : STATE_CONNECTED
2018-01-24 14:58:38,414 INFO LogUtil - Connected to GATT server.
Application started communication with the device but after some time it received GATT server disconnection message in onConnectionStateChange in callback implementation of BluetoothGattCallback . Below are the logs from application:
2018-01-24 15:07:46,396 INFO LogUtil - GATT Server Status = (40) : BLE_HCI_INSTANT_PASSED(0x28)
2018-01-24 15:07:46,397 INFO LogUtil - GATT Server New State = (0) : STATE_DISCONNECTED
2018-01-24 15:07:46,398 INFO LogUtil - Disconnected from GATT server.
Not able to find any reason behind BLE_HCI_INSTANT_PASSED status code.
Any help on this will be helpful.
When data is being transmitted over BLE, data transfers can only start at sync points in time known as "connection events". At the BLE link layer there are couple special requests that can be made which are relative to these sync points. They are:
LL_CHANNEL_MAP_REQ - A request to change the BLE channels being transmitted on. Bluetooth chips will change the channel map based on the noise in the environment to try to limit packet drop.
LL_CONNECTION_UPDATE_REQ - A request to change the frequency of "connection events" (known as the "connection interval"). This is done to achieve better throughput/latency or save more power.
Each of these Link Layer requests when sent over the air contains an "Instant" to change. The "Instant" is the "connection event" in the future to apply the change.
At the Link Layer, BLE is reliable. This means each Link Layer packet must be ack'd by the other side. In a noisy RF environment, it's possible a link layer packet may require a couple retries to actually send. This means the packet could arrive many "connection events" after originally intended.
If one of the packets mentioned above is received after the "Instant" the changes were supposed to be applied, by definition the BLE chip must disconnect with reason 0x28 (Instant Passed)
For additional details on the topic, the Bluetooth Core Specification available from the Bluetooth SIG website is a good reference:
My BLE server permanently measures a sensor value and sends a notification with 20 byte user data after each measurement. The goal is to generate as much throughput as possible.
On the client side, the value sent by the server is received and processed.
rxBleConnection.setupNotification(setDescriptorEnableNotification(characteristic))
.flatMap(notificationObservable -> notificationObservable)
.observeOn(Schedulers.newThread())
.buffer(1)
.subscribe(bytes -> {
onNotificationReceived(bytes, buffer);
} , throwable -> {
// Handle an error here.
onNotificationSetupFailure(throwable);
}
);
If I set the Connection intervall to 11.25ms, I receive all values. However, if I set the connection interval to 30ms, I receive a few values and then the connection is closed.
In the Android Log i see the followed message:
BleGattException status=8 (0x8),
bleGattOperationType=BleGattOperation{description='CONNECTION_STATE'
Why is the connection interrupted and what is the trigger?
With the help of a BLE Sniffer this is not recognizable. The set connection parameters are accepted and the transfer begins. Suddenly the transmission ends and the error message appears.
Update:
BLE Sniffer screenshot has been added:
30ms, this is connection interval you set in server or android?
Btw, on android you can set speed mode
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
}
Error 8 means the connection timed out. There is nothing wrong on the Android side. The problem is with the communication between the two Bluetooth controllers. If you have a sniffer then you should be able to see who is the one that fails to send packets.
Here is an Image from BLE Sniffer.
BLE Sniffer
I have the similar problem. On Android 7.0 there were two ways to keep connection:
1) If devices are bonded and there is a charachteristic reading callback with constant thread of packets. If there are no packets by some time, then connection fails.
2) If devides are not bonded, but I do TXCharacheristic.read every few seconds. If don't do that some time, then connection fails.
But now in Android 7.1.2 this way doesn't work.
May be the first way will work for you.
On your Android device you should make bonding, on your kit you should handle this bonding.
On Nexus 6P and Samsung S7 it doesn't work anymore, but I didn't try it on the other devices.
I suppose, you have min connection interval 7.5 on your BLE kit, but now it is deprecated on Android.
Try to set min connection interval to 11.25 on your BLE kit and set connection priority
gatt.requestConnectionPriority(CONNECTION_PRIORITY_HIGH);
where CONNECTION_PRIORITY_HIGH = 1
in onConnectionStateChange.
It had worked for me when I had changed min connection interval in my nordic from 7.5 to 11.25.
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)
Device1: Nexus 4 Android 4.3 BLE Central (Using BluetoothLeGatt sample app in API 18 SDK as is)
Device2: Raspberry Pi with working BT4.0 USB Dongle, Raspbian Whezzy 20130726. Compiled Bluez 5.8 from the sources.(Since the packaged ver is 4.99 which not officially support BLE yet)
Working as peripheral, I started bluetoothd with "-p alert" (GATT Alert profile).
Test1: Using two raspi (device2 above) I can connect and browse alert gatt service/characteristics on other device using gattool.
Test2: Using iPhone/iPad (free BLE app: LightBlue) as Central I can also connect and browse alert gatt services and characteristics value on the Raspi.
Test3: When connecting using Nexus 4 sample app, (which is unstable), it asking for authorization on both device.Even after I can get rid off the authorization, the hcidump look like they are exchanging SDP info, the service list still not come up on Android sample app.
Test4: Nexus 4 sample app can connect and browse service on Fitbit One and Wahoo HRM.
Using hcidump (on the raspi), I can see different connection initiation:
Test1/2:
>HCI Event: LE Meta Event (0x3e) plen 19
LE Connection Complete
status 0x00 handle 44, role slave
bdaddr ... (Public)
Test3:
>HCI Event: Connect Request (0x04) plen 10
bdaddr ... class 0x5a020c type ACL
(Is it Bluetooth classic connection?)
Question:
Is there any way I can make Android BLE connection to create HCI Event: LE Meta Event on Raspi?