GATT callback fails to register
Asked Answered
S

4

51

I'm trying to write an application to send messages over Bluetooth Low Energy, which will then be passed on by UART in my peripheral. I've followed the steps here and the app scans for and finds the device successfully. However, connection using the BluetoothGatt = BluetoothDevice.connectGatt(context, autoconnect, callback) method fails, with logcat saying "Failed to register callback".

Call made from:

//device scan callback
private BluetoothAdapter.LeScanCallback btScanCallback = new BluetoothAdapter.LeScanCallback() 
{
    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord)
    {       
        some stuff
        currBtGatt = device.connectGatt(parentActivity, false, btGattCallback);
    }
};

And the Gatt callback:

//GATT callback
private BluetoothGattCallback btGattCallback = new BluetoothGattCallback()
{       
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
    {
        // if connected successfully
        if(newState == BluetoothProfile.STATE_CONNECTED)
        {
            //discover services
            updateStatus("Connected");
            gatt.discoverServices();
        }
        else if(newState == BluetoothProfile.STATE_DISCONNECTED)
        {
            updateStatus("Disconnected");
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status)
    {
        if(status == BluetoothGatt.GATT_SUCCESS)
        {
            //pick out the (app side) transmit channel
            currBtService = gatt.getService(uartUuids[0]);
            currBtCharacteristic = currBtService.getCharacteristic(uartUuids[1]);
        }
        else 
        {
            updateStatus("Service discovery failed");
        }
    }
};

Logcat says:

11-19 10:40:39.363: D/BluetoothAdapter(11717): stopLeScan()
11-19 10:40:39.373: D/BluetoothGatt(11717): connect() - device: DC:6D:75:0C:0F:F9, auto: false
11-19 10:40:39.373: D/BluetoothGatt(11717): registerApp()
11-19 10:40:39.373: D/BluetoothGatt(11717): registerApp() - UUID=3ba20989-5026-4715-add3-a5e31684009a
11-19 10:40:39.373: I/BluetoothGatt(11717): Client registered, waiting for callback
11-19 10:40:49.373: E/BluetoothGatt(11717): Failed to register callback
11-19 10:40:49.533: D/BluetoothGatt(11717): onClientRegistered() - status=0 clientIf=5
11-19 10:40:49.533: E/BluetoothGatt(11717): Bad connection state: 0
11-19 10:40:49.593: D/BluetoothGatt(11717): onClientConnectionState() - status=0 clientIf=5 device=DC:6D:75:0C:0F:F9
11-19 10:40:49.593: W/BluetoothGatt(11717): Unhandled exception: java.lang.NullPointerException

Interestingly, my peripheral moves to a "connected" state (I have indication LEDs) and I can connect to it from the same phone with a demonstration application or with a PC BLE dongle. Any ideas appreciated.

[EDIT] the connectGatt method returns null, which I guess is expected.

[EDIT] On inspection of API 18 source code, it appears that the "Failed to register callback" message is delivered because the method registerApp() returns false because the registerClient() method of the IBluetoothGatt "mService" throws a remote exception, probably at the line:

enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");

because the log message in the very next line is never seen. So it could be a permissions thing, except that the application has bluetooth and bluetooth_admin permissions.

Scant answered 19/11, 2013 at 10:52 Comment(0)
H
102

I finally figured this problem out. The device I am using is a Samsung Galaxy S4 and the actual problem (thanks Wibble for guidance in your answer, but you are slightly off in your conclusion) appears to be a threading issue.

In Wibble's answer, he stated that adding a button to connect fixed his issue. I started wondering why that matters, and I also can connect and disconnect fine during an entire session without a GUI button using background worker threads. As soon as I force close my application, restart it, and try to connect, I start getting the error "Failed to register callback." and nothing works any more. I almost pulled my hair out over this one :)

See my post in Samsung's forums for more detail on my exact issues.

Solution: To get around this issue, just make sure you run any BLE interaction code (device#connectGatt, connect, disconnect, etc) code in the UIThread (with a handler, local service, or Activity#runOnUiThread). Follow this rule of thumb and you will hopefully avoid this dreadful problem.

Deep in our library, I only had access to the application context. You can create a handler from a context that will post to the main thread by using new Handler(ctx.getMainLooper());

If you face other connection problems, deploy the sample app in samples\android-18\legacy\BluetoothLeGatt and see if that application works. That was kind of my baseline for realizing BLE does actually work with my peripheral, and gave me hope that if I dug enough in our library I would eventually find the answer.

EDIT: I did not see this 'Failed to register callback' issue on the Nexus 4, Nexus 5, or Nexus 7 2013 when using background threads to perform BLE operations. It may just be an issue in Samsungs 4.3 implementation.

Heliometer answered 10/12, 2013 at 23:20 Comment(18)
Thank you so much! I just couldn't understand why my app registers without any exception when I call connectGatt, but why no GattCallback was called. Calling connectGatt on main thread fixed it!Screw
@Linard Glad to assist. I think it's a bug and just needs addressed. Until then, thanks, Stack OverflowHeliometer
These are so annoying bugs... Google could just mention something like "has to be called from main thread" in the docsScrew
I'm having this issue running my scan in a separate process on an S4. I wish Samsung would fix this bug. My code works fine on Nexus devices as well.Turanian
I know this thread is old, but can anyone tell me exactly which methods need be run on the main thread? Is it absolutely anything having to do with BluetoothAdapter and BluetoothGatt? Using a handler on connect/disconnect/scan/stopScan fixes some of my problems, but more than that seems to cause more.Aisha
@Aisha At a glance, I make the following invocations on the main thread: gatt#connect, device#connectGatt, gatt#close, gatt#discoverServicesHeliometer
@Heliometer Thanks! It turns out most of my problems were from a hardware issue; I haven't witnessed switching out any of the other methods to help. It'll be good if we get a definitive statement on proper procedure someday though.Aisha
Thanks, this is a great catch (and terrible Android code). This is still a problem on 4.4.2 as well.Speculator
Unfortunately I have exactly the same error, but the code is already behind a button. All BLE code is executed in the main (UI) thread. Still, I can connect a single time, but the second time I get status 133.Malemute
@TonniTielens I haven't done BLE in a while now, but 133 is a generic error. Make sure you are finishing service discovery after the first connect, and that you close the gatt completely. Also, did you give the sample app a try (if it's still there).Heliometer
@Lo-Tan: Thanks.I finally managed to fix it. Apparently in Android 4.3, callbacks on LeScanCallback are done on different threads, while in 5.0 they seem to always be done on the main thread (or atleast the thread the scan was started on). This results in different behavior in 4.3 and 5.0, when immediately trying to connect to a device from the onLeScan callback. In 4.3 this results in an error. In 5.0 it doesn't.Malemute
@TonniTielens: thank you very much for this insight. I would really appreciate if these threading requirements would be specified in the documentation, especially if they are different in some OS versions.Screw
Great! Thanks for solving this mystery for me! BTW: You don't need a context to create a handler in the main thread. Instead of new Handler(ctx.getMainLooper()) you can also write new Handler(Looper.getMainLooper())Canard
Is this really true? I have an implementation where everything is done in a service. When the service is started by the application, the service starts a scanner, and if it detects a device that it can work with, it connects to it. The only user intervention comes up if pairing is needed. I have no handlers for any of the gatt operations (connectGatt, BluetoothGatt.connect(), BluetoothGatt.close(), etc. Is this a no no? Should I be invoking a handler for everyone of those Gatt calls?Malacology
I came across a device which did this with starting scans. After 29 scans, the error message appeared, This was with different applications but using the same BTLE code. The error only happens on this device (so far). I put the start and stop scans on the Looper via a Handler as suggested. But the result was the same. After 29 good scans, the error returned. I hate putting more stuff on the main UI thread as Android Studio already sends me warning messages that the app is doing too much work on its UI thread. Does it have to be the UI thread, or will any thread work? Take it off the callback?Malacology
If I start/stop the scans in a thread, it doesn't work at all. If I use a Handler(Looper.getMainLooper).post(new Runnable(...)) it works 29 times. If I call it straight (no threads of any kind) it works 29 times. I am confused! I can understand it needing a thread to offload the callback, but why the UI thread? These are NOT UI activities. Its transports!!Malacology
@BrianReinhold I understand that, but for Samsung, there was an issue where the OS handles for your app appear to get lost or disassociated on app close if you ran BLE operations off the main thread, and that's the only reason I recommended you running BLE code on the main thread. This may or not be an issue any more. I have not worked with BLE for a few years now. If you are talking to a device that your team is making, verify that there aren't any firmware issues. Twenty-nine successes before a failure - I suspect it's your peripheral.Heliometer
@Heliometer Its not the peripheral but something funny with the Android device. Whenever you start a scan, at the lower levels there is a clientIf count registering your callback. On all other devices (using the same software) this count stays fixed at some value. On this device it increments each start scan from 3 until it hits 32. All subsequent scan restarts after that fail and the count goes to 0. But here is something weirder. I put the start scan in a normal thread and it doesnt work at all! UI thread (ok). No additional thread at all (ok). That I do not understand!Malacology
T
4

So, My problem was running it from a recursive service. connectGatt worked fine with lollipop but older versions returned null. running on a main thread solved the problem. This is my solution:

public void connectToDevice( String deviceAddress) {
    mDeviceAddress = deviceAddress;
    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);

    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {


            if (device != null) {

                mGatt = device.connectGatt(getApplicationContext(), true, mGattCallback);
                scanLeDevice(false);// will stop after first device detection
            }
        }
    });
}
Thetisa answered 19/11, 2015 at 16:24 Comment(0)
O
1

I can also confirm that Lo-Tan is the answer to check first. I've tested a lot of devices, some of them are behaving well when you run from a secondary thread. Some may block after a while, the behaviour is unpredicted.

Here is the list of things to do:

  1. Maker sure you use new Handler(Looper.getMainLooper()).post(new Runnable) on any gatt operation (connect, disconnect, close) but also on the scanner operations (startScan, stopScan etc.).

  2. There is race condition for direct connection on Android 6 (or maybe 5) so try to connect gatt like this:

     new Handler(getContext().get().getMainLooper()).post(() -> {
         if (CommonHelper.isNOrAbove()) {
            connectedGatt = connectedBLEDevice.connectGatt(context.get(), true, gattCallback, BluetoothDevice.TRANSPORT_AUTO);
             Timber.tag("HED-BT").d("Connecting BLE after N");
         } else {   
            try {
                Method connectGattMethod = connectedBLEDevice.getClass().getMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
                 connectedGatt = (BluetoothGatt) connectGattMethod.invoke(connectedBLEDevice, context.get(), false, gattCallback, BluetoothDevice.TRANSPORT_AUTO);
                 Timber.tag("HED-BT").d("Connecting BLE before N");
             } catch (Exception e) {
                 failedConnectingBLE();
             }
         }
     });
    
  3. When disconnecting the gatt, call disconnect() first and close() after in the GattCallback routine.

Oona answered 26/1, 2017 at 15:36 Comment(0)
S
-3

In order to automatically connect to a Bluetooth device, ie without explicit user input as I was attempting to do, the permission BLUETOOTH_PRIVILEDGE is required. However, this is not available to third-party applications so my code failed. Adding a menu option to connect and using the same code works fine.

http://developer.android.com/reference/android/Manifest.permission.html#BLUETOOTH_PRIVILEGED

Scant answered 19/11, 2013 at 16:54 Comment(2)
I never have the same experience. My app scans and connects peripheral s without user input and work properly. The document said BLUETOOTH_PRIVILEGED is required to "pair bluetooth devices without user interaction", which is quite different to "connect". I think that the peripheral you use requires pairing.Schlueter
@Schlueter I disagree because a) the connectGatt() function doesn't attempt to pair (it looks like only already-connected devices can be paired) and b) as far as I'm aware there's nothing in the source code for the peripheral requested pairing/authentication. But thanks for pointing out that there's a difference, BLUETOOTH_PRIVILEDGED could well be irrelevant.Scant

© 2022 - 2024 — McMap. All rights reserved.