bluetooth-le icon indicating copy to clipboard operation
bluetooth-le copied to clipboard

(Possible) Android 11 issue - Error: Connection timeout 9 out of 10 times

Open RichyFennell opened this issue 2 years ago • 13 comments

Describe the bug Experiencing failed connection attempts 9 out of 10 times on certain Ble devices when I build on Android 11 but not on Android 10 or any iOS devices.

Connecting to the same BLE device, very short range, data light flashes on the front of the device but connection times out with Android 11. Fine on all other devices. Difficult to reproduce as it only happens on Android 11 devices on a particular Ble device. Publishing here just in case other people experience it.

To Reproduce Steps to reproduce the behavior:

try { await BleClient.initialize();

        //needed for iOS, not for Android.
        await BleClient.getDevices([deviceId]);

        // connect to device, the onDisconnect callback is optional
        await BleClient.connect(deviceId, (deviceIdDisco) => this.onDisconnect(deviceIdDisco));

        const servicesAvail = await BleClient.getServices(deviceId);
        console.log('services Avail:', JSON.stringify(servicesAvail));

         await BleClient.write(deviceId, UUID_SERVICE, UUID_CHARACTERISTIC, data)
      }catch (error) {
        console.error(error);
    }

Fails on BleClient.connect with error 'Error: Connection timeout.' 9 out of ten times on Android 11 but works perfect on iOS and Android 10/9. Takes around 4 seconds to timeout with lights flashing on the device im connecting to.

Expected behavior Connects and allows me to write data to the device

Plugin version:

  • @capacitor-community/bluetooth-le: 1.8.0

  • iOS working

  • Android 11 Fail

  • Android 10 & 9 working

Hardware is Techlast 8 inch tablets with Android 9/10/11 Connecting device is a Mobile Printer with no real branding

Additional context Could totally be a hardware / OS clash just making people aware just in case others have Android 11 issues.

Thanks

RichyFennell avatar Jul 11 '22 18:07 RichyFennell

Having a similar issue with Android 11. Created issue 359 which details what I'm experiencing. BleClient.disconnect disconnects from BLE peripheral, but there's some setting that isn't fully implemented which prevents the first BleClient.connect call thereafter from connecting to the peripheral. Calling BleClient.connect a second time on the same peripheral then allows app to connect to peripheral. I don't think this is a hardware issue, but sounds like an Android 11/BLE library incompatibility issue, but I'm no expert.

karunt avatar Jul 11 '22 19:07 karunt

Having a similar issue with Android 11. Created issue 359 which details what I'm experiencing. BleClient.disconnect disconnects from BLE peripheral, but allows app to remain bound to peripheral. Calling BleClient.connect on the same peripheral the first time after disconnecting causes app and peripheral to unbind. Calling BleClient.connect a second time on the same peripheral then allows app to connect to peripheral. Would be nice if there were an API that allowed us to manually unbind app from peripheral as a work around. I don't think this is a hardware issue, but sounds like an Android 11/BLE library incompatibility issue, but I'm no expert.

Thats really helpful, thanks Karunt, I'll put a second connect/disconnect at the end of my process and update if that fixes fixes the issue.

RichyFennell avatar Jul 12 '22 11:07 RichyFennell

If it's helpful, here's how I structured my code (in Angular/Ionic) for connecting to a peripheral. If there's an error (in this case, there is a connection timeout error), then I show an alert with the error. Dismissing the alert causes the app to re-scan available devices (using the BleClient.requestDevice api),, followed by connection to the selected device.

  async addBleDevice(){
    try{
      await this.bleService.connectToDevice();
    }
    catch(error){
      if(error.message){
        this.headerText = 'Alert';
        this.subHeaderText = 'Connection Unsuccessful';
        this.messageText = error.message;
        this.buttonArray = [{text: 'OK', role: 'cancel', handler: () => this.addBleDevice()}];
        this.showAlert();
      }
    }
  }
bleService.connectToDevice(){
    try{
      this.connectedDevice = await BleClient.requestDevice();
      await BleClient.connect(this.connectedDevice.deviceId);
      return this.connectedDevice;
    } catch(error){
         //Insert your error related code
    }
}

karunt avatar Jul 12 '22 13:07 karunt

Have you tried manually setting the timeout? The default is 10 sec for connect. https://github.com/capacitor-community/bluetooth-le#connect

pwespi avatar Jul 13 '22 19:07 pwespi

I don't think timeout is the issue. The exact same code worked 2 months ago prior to Android 11 upgrade, and I don't believe the default timeout has changed since.. Even now, BleClient.connect successfully transmits a message to the BLE peripheral, except that the type of message it sends appears to be identical to what BleClient.disconnect sends. A second implementation of BleClient.connect resolves the matter. It's almost as though BleClient.disconnect doesn't complete its job of disconnecting the app from the peripheral, and the first implementation of BleClient.connect thereafter is needed to complete the disconnection. I structured my work around such that BleClient.connect is now called within a while loop which allows for 5 connection attempts (and resulting timeout errors are basically disregarded until after the 5th attempt). Invariably, the app connects to the peripheral on the 2nd try, which is consistent with the problem described in issue 359. Furthermore, when the peripheral is reset to factory standard (ie, no prior disconnect attempt), the very first implementation of BleClient.connect successfully connects the app to the peripheral (with the default timeout), further showing that the default timeout doesn't appear to the be the issue. Here's a really rudimentary code with promises in nested try/catch blocks to show the work around (I am working on accomplishing the same with observables), which is an alternative to the code I posted above (basically avoids user having to select a new peripheral device each time a connection times out, with up to 5 tries to connect):

async connectToDevice(){
    let connectionStatus = false;
    let n = 0;
    try{
      this.connectedDevice = await BleClient.requestDevice();
      while (connectionStatus === false && n < 5){
        try{
          n ++;
          await BleClient.connect(this.connectedDevice.deviceId);
          connectionStatus = true;
          return this.connectedDevice;
        } catch(error){
          if(n === 5){
            const device = this.connectedDevice.name ? this.connectedDevice.name : this.connectedDevice.deviceId;
            throw new Error('Unable to connect to ' + device + '. Select another Bluetooth device.');
          } else if (n < 5){
            console.log('Do nothing with error');
          } else {
            throw new Error('Some other error');
        }}
      }
    }
    catch(err){
      if (err.message === 'requestDevice cancelled.'){
         console.log('Do nothing with requestDevice cancellation request');
      }
    }
}

karunt avatar Jul 13 '22 20:07 karunt

It seems that I have the same problems with some devices (in my case Samsung A41 and google pixel3 but he work on pixel5 and 6). I think other devices will soon have the same problem. My app has been downloaded over 100 times, so it's very device specific. (This bug is only on Android 11 too) I will try your code but I don't have these problematic phone in my possession.

ohlive avatar Aug 05 '22 12:08 ohlive

Turns out I’m having similar issue with the bleClient write function too. Anyone else experienced timeout error with write?

karunt avatar Aug 05 '22 18:08 karunt

Hi , i relaunch this subject, I try the code with multiple connect but its not the solution to my side. I use the method requestLEScan().

With over 50 differents android phone is ok but i have 3 phones that not working. I have tell to my client to download BleScanner (on the problematic phone) to see if my product are visible. And with this application is OK, we found my product. Its not working in android 12 equally.

With apple, no problem.

Someone see this problem?

ohlive avatar Aug 30 '22 11:08 ohlive

Here's a leaner, observables based, code to connect that is now working for me consistently:

To connect to peripheral with BLE:

  connectToDevice(){
    const connectToDevice$: Observable<void> =
      from(BleClient.requestDevice()).pipe(
        mergeMap(resp => from(BleClient.connect(resp.deviceId))),
        retry(5),
        catchError(error => {
          if(error.message !== 'requestDevice cancelled.'){
            throw new Error('Unable to connect');
          } else{
            throw new Error(error.message);
          }
        })
        );
    return connectToDevice$;
  }

To write to peripheral device using BLE:

  write(connectedDeviceId: string, endpoint: string, payload: any){
    const characteristicUUID = this.mapCharacteristic(endpoint);
    const write$ = of(this.mapCharacteristic(endpoint)).pipe(
      concatMap(resp => BleClient.write(connectedDeviceId, this.baseUUID, resp, payload)),
      retry(5),
      map(resp => 'success')
    );
    return write$;
}

You can then subscribe to the return values from these functions (they both return observables) wherever they're being called. Each of these functions tries to connect (or write) up to 5 times, and if it still fails, then throws an error. Let us know if you're able to resolve your issue with this construct.

karunt avatar Aug 30 '22 11:08 karunt

Karunt, i done a code similar to you with retry who increase my reliabilty to the connection, so is a good point.

In my case, the problems comes to the geolocalisation. In fact, even i ask to activate this last one, if directly, in the phone, the geolocalisation are deactivate, the scan dont working (with requestLEScan in my case). So when customers activate natively in our phone the geolocalisation, is OK for me and no problems to connect

Thanks for the code anyway.

ohlive avatar Sep 01 '22 13:09 ohlive

Hi all, I'm still facing the above issue on Android 13 (API 33, OnePlus 10T 5G). I'm coding on Ionic-React and building the app with Capacitor to test an android version of that. After successfully scan with BleClient.requestDevice() I get stuck on BleClient.connect() getting a Connection Timeout error. I cannot figure out why in other devices (a Samsung A510F with Android 10, API 29) the connect method perfectly works. Can anyone please help meee? :)

AlesIanni avatar Mar 10 '23 11:03 AlesIanni

Currently I don't see how this could be fixed in the plugin, as it's an issue with specific Android devices. Therefore I'm closing this issue for now. If anyone has an idea how this could be fixed, let me know and I'm happy to take another look.

pwespi avatar Apr 02 '23 15:04 pwespi

You could build in a fail-safe code/check into the plugin that re-attempts a connection in case a device specific issue causes the connection to not proceed the first time around. This way individual programmers don't have to build in their own fail safe code, especially since you know that there are several devices that are experiencing this issue. On Sunday, April 2, 2023 at 11:42:15 AM EDT, Patrick Wespi @.***> wrote:

Currently I don't see how this could be fixed in the plugin, as it's an issue with specific Android devices. Therefore I'm closing this issue for now. If anyone has an idea how this could be fixed, let me know and I'm happy to take another look.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

karunt avatar Apr 02 '23 15:04 karunt