weliem / blessed-android

BLESSED, a Bluetooth Low Energy (BLE) library for Android
MIT License
557 stars 119 forks source link

Issues with iOS Device Connecting to an Android Peripheral #164

Open sean7512 opened 2 years ago

sean7512 commented 2 years ago

First of all, absolutely amazing library. I was able to more or less take my existing iOS code and port it straight over with incredible results. I am having 2 issues, both which revolve around using the Android device as a Peripheral.

  1. The majority of the time, the iOS device cannot connect to the Android phone. The iOS device sees the advertisement and when calling centralManager.connect(peripheral, options: nil) on the iOS side, it almost never connects. I never get a failedToConnect or successfulConnect on the iOS side, it appears the connection attempt just hangs.
  2. Sometimes, I can get the iOS device to connect, but once it connects, I have not been able to successfully connect a 2nd iOS device to the Android phone.

FWIW, I am using the Android phone as both a peripheral and client. I have no issue with the Android phone connecting to multiple iOS devices peripherals. Any insight would be greatly appreciated.

weliem commented 2 years ago

Hi, I tried to reproduce this using nRFConnect on iOS and using the peripheral example on Android: https://github.com/weliem/bluetooth-server-example

...but connecting seems to work every time. Can't see any issues. So most likely there is something you are doing differently...

sean7512 commented 2 years ago

Hello, thanks. I will take a look at your example. Here are some other observations I have made (I am using a Pixel 6a, iPhone 12 Pro, and iPad Air 4)...

  1. Everything seems to work perfectly fine after a 24 hour period or device restart (I assume thats after Android clears its bluetooth cache). It even works with multiple iOS devices, which means my original post was not accurate.
  2. If the Pixel's screen turns off while sitting idle, that is when things start to go wrong. When I re-unlock the device, that is when things start to work intermittently. Force stopping the app, toggling bluetooth on/off doesn't help either. Only a restart of the Pixel seems to fix things.

I will take a look at your example and see if I can reproduce with that as well.

weliem commented 2 years ago

I don't know how you built your app exactly, but Android may ofcourse kill your app at some point....

sean7512 commented 2 years ago

I am still debugging this to see where the issue is. The iOS app is an app that has been around for over 2 years and was iOS exclusive. The iOS side never has issues connecting to the other iOS devices (single or multiple at the same time). It is only having trouble connecting to Android devices.

I am unsure where to go from here, but I am still gathering additional details. I have it working about 75% of the time at this point, so I may be getting somewhere.

sean7512 commented 2 years ago

I am wondering if the issue is related to https://github.com/weliem/blessed-android/issues/156 as that would explain some of the behavior I am seeing as well.

sean7512 commented 2 years ago

I have done some more debugging and I do think #156 is the issue. Here is my current setup.

iPhone 12 Pro - iOS 16.1 iPaid Air - iOS 16.1 Pixel 6a - Android 13

Both the iPhone and iPad are using the App Store version of my app in question. On the Android app, I am using the same code as what is in Play Store, with 1 code change. I have commented out the Android scanning for peripherals (in my app, every device is a central AND a peripheral). So in this test, the iOS devices are both centrals and peripherals and the Android device is only a peripheral.

In this setup, the iOS devices have absolutely no issue connecting to the Android device and it works 100% of the time, under all circumstances (like the phone going to sleep, etc). The moment I uncomment the scan call, things get flaky and the iOS devices can only intermittently. connect to the Android device. So it does appear that the issue has to do when the Android phone is a central and a peripheral and another drive attempts to connect as a central when the Android phone is already a central to that device. I will continue debugging, but I am assuming it has to do with the extra connection that the system thinks exists (like in #156).

sean7512 commented 2 years ago

I am sorry for all of the spam, I am using this as a record of new findings to try to collate everything together in 1 place. Unfortunately the workaround in #156 does not help since my app in question is always in both peripheral and central mode the entire time the app is open and active ons screen, so there is no time to do the cleanup.

It looks like the additional connection being made is what is causing the remote iOS centrals from connecting successfully.

I did a test where in the Android app, I added a check on the scan discovery callback to not call connect on a peripheral until that address has connected to us as a central. This ends up fixing the issue 100% in my scenario with 2 iOS devices and 1 Android device. Essentially ensuring that the other device connects to us first before we connect to them makes everything work as intended, 100% of the time. Obviously this is not a suitable workaround because it would not work if there were multiple Android phones running the app since each Android phone would be waiting for the other Android phone to connect first. It only works in my certain scenario where the other devices are iOS (which do not have that check before connecting).

So what I think this means is that once an Android device connects to another peripheral, it stops that same device from successfully being a central, but if the other device connects first, the device can still be a central to them. I have been really into the code and I don't understand why this order is important and am unsure of what it means.... I think the issues likely stems from Android itself and not the library. I am continuing to think of a workaround, but nothing yet.

weliem commented 2 years ago

Great findings! Keep them coming. I haven't had the time to investigate this thoroughly myself. It looks like it is related to Android rather than this library. However, there might be a workaround possible....

In my own use of this library I always use it either as Central or Peripheral, but never both at the same time....so I never encountered this issue myself.

weliem commented 1 year ago

Just released version 2.4.1. that contains a possible fix for your issue.

You now need to tell the PeripheralManager who the Central is and then I check every connected event to see if it is a peripheral or central. This may solve your issues.

So do peripheralManager.setCentralManager(central) in your code when you create them.

sean7512 commented 1 year ago

That is awesome...I will be testing this today. Thank you so much1

sean7512 commented 1 year ago

This change seems to have made it worse. I can no longer connect to the Android peripheral no matter what (from the iPhone or iPad). I can try to do some debugging to see if I can see why and will let you know.

weliem commented 1 year ago

It is very unlikely that this particular change would prevent any connections to take place. It must be caused by something else.

Please share a log so I can see what is going on

sean7512 commented 1 year ago

Sure I can get you logs. If I comment out the peripheralManager.setCentralManager(central) then the centrals can connect just fine, so it absolutely appears to be from that change, but I am unsure why.

weliem commented 1 year ago

What I changed is that I have added a heuristic to PeripheralManager. If it gets a 'connected' event while there is also connection attempted by the CentralManager for the same address, it assumes it is for the CentralManager and it ignores it. The problem is that Android does not have a notion of a Central or Peripheral, and so I need to use heuristics to make the distinction.

So if that causes you trouble then you must be having outstanding connection attempts to the same address.

sean7512 commented 1 year ago

Let me know if you need other logs as well, this is just between the Pixel 6a and the iPhone. Both the iPhone and Pixel are acting as both a central and a peripheral. The Pixel has no issue seeing and connecting to the Pixel, but the iPhone is not able to connect to the Pixel when using the setCentralManager()


D/BluetoothGattServer: registerCallback() - UUID=96ce0d4b-5eb2-4924-8961-397953d34e1b
20D/BluetoothGattServer: onServerRegistered() - status=0 serverIf=6
20D/BluetoothGattServer: clearServices()
20D/BluetoothAdapter: isLeEnabled(): ON
20D/BluetoothAdapter: isLeEnabled(): ON
20D/BluetoothAdapter: isLeEnabled(): ON
20D/BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=9 mScannerId=0
20D/BluetoothGattServer: addService() - service: 809d72b1-94e5-4ba3-8951-868e29e4c75d
20D/BluetoothGattServer: onServiceAdded() - handle=134 uuid=809d72b1-94e5-4ba3-8951-868e29e4c75d status=0
20D/com.kvl.services.BluetoothService: discovered a connectable device
20D/BluetoothGatt: connect() - device: 48:8A:73:08:76:D9, auto: false
20D/BluetoothGatt: registerApp()
20D/BluetoothGatt: registerApp() - UUID=13ca4194-775e-49b1-8019-34b5dd1c2d21
20D/BluetoothGatt: onClientRegistered() - status=0 clientIf=7
20D/BluetoothGattServer: onServerConnectionState() - status=0 serverIf=6 device=48:8A:73:08:76:D9
20D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=7 device=48:8A:73:08:76:D9
20D/BluetoothGatt: discoverServices() - device: 48:8A:73:08:76:D9
20D/BluetoothGatt: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=6 latency=0 timeout=500 status=0
20D/BluetoothGattServer: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=6 latency=0 timeout=500 status=0
20D/BluetoothGatt: onSearchComplete() = Device=48:8A:73:08:76:D9 Status=0
20D/BluetoothGatt: configureMTU() - device: 48:8A:73:08:76:D9 mtu: 517
20D/BluetoothGatt: onConfigureMTU() - Device=48:8A:73:08:76:D9 mtu=517 status=0
20D/BluetoothGattServer: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=36 latency=0 timeout=500 status=0
20D/BluetoothGatt: requestConnectionPriority() - params: 1
20D/BluetoothGatt: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=36 latency=0 timeout=500 status=0
20D/BluetoothGatt: setCharacteristicNotification() - uuid: 0c33665e-d7be-4790-a673-b1e1b4aff7b4 enable: true
20D/BluetoothGattServer: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=12 latency=0 timeout=500 status=0
20D/BluetoothGatt: onConnectionUpdated() - Device=48:8A:73:08:76:D9 interval=12 latency=0 timeout=500 status=0
20D/com.kvl.services.BluetoothService: SUCCESS: Notify set to 'true' for 0c33665e-d7be-4790-a673-b1e1b4aff7b4
20D/BluetoothGatt: setCharacteristicNotification() - uuid: 21b80222-b2bd-4c05-97a7-14ddd2b7cae8 enable: true
20D/com.kvl.services.BluetoothService: SUCCESS: Notify set to 'true' for 21b80222-b2bd-4c05-97a7-14ddd2b7cae8
20D/BluetoothGatt: setCharacteristicNotification() - uuid: f9056650-9d55-479a-8cf4-b799ce9f88be enable: true
20D/com.kvl.services.BluetoothService: SUCCESS: Notify set to 'true' for f9056650-9d55-479a-8cf4-b799ce9f88be
20D/BluetoothGatt: setCharacteristicNotification() - uuid: 739d53f0-d276-40cb-ac4a-5131f6e50498 enable: true
20D/com.kvl.services.BluetoothService: SUCCESS: Notify set to 'true' for 739d53f0-d276-40cb-ac4a-5131f6e50498
20D/BluetoothGatt: setCharacteristicNotification() - uuid: dffee319-e146-4be3-9dc0-432751a14141 enable: true
20D/com.kvl.services.BluetoothService: SUCCESS: Notify set to 'true' for dffee319-e146-4be3-9dc0-432751a14141```
weliem commented 1 year ago

Ok this looks as expected. Your app sees 48:8A:73:08:76:D9 and connects to it. All normal and you don't get any incorrect OnCentralConnected event....which you would previously get before my fix.

But I don't see any connection attempt to your 'app as a peripheral'....try connecting using nrfConnect to see if that works

sean7512 commented 1 year ago

Yes, I can connect fine when using nrfConenct or LightBlue, but that is no surprise as if I use LightBlue on the iPhone, the iPhone is no longer a central (since my app stops being a central when its put in the background), so the connection to Android works fine. As mentioned earlier, if I stop the central manager on the Android device, connection to the Pixel works perfectly fine. I think this goes deeper than the library and is a bug within Android itself.

I am thinking o creating a simplified demo app so that I can share some code with you. Do you have the ability to build/install to a real iOS device by chance?

weliem commented 1 year ago

Sure, I can build and run on my iphone....

sean7512 commented 1 year ago

Ok great...Give me a day or 2 and. I can create some sample apps that may help...

FWIW, the app in question is Chicken Tender (https://www.chkntender.com). I am not sure where you are located but I currently only have it available in US, Canada, and Australia app stores.

weliem commented 1 year ago

I can see a million chicken related apps in the app store but I don't see yours....probably logical since I live in the Netherlands...

sean7512 commented 1 year ago

Yea that makes sense...

I have 2 sample apps (iOS/Mac and Android). I will get them into a repo tomorrow and give you access and I'll type my notes here on what I am seeing with it and how to reproduce. The sample apps are very rudimentary, so apologizes in advance.

sean7512 commented 1 year ago

Ok, I invited you to the repo, you should have the invite. It contains a Mac/iOS app and an android app. It is very rudimentary but it does exhibit the issue....

As I said earlier, everything works fine if the OTHER device (iPhone/iPad) connect to us first, then it all works fine and this is true in this demo app. However the issue is when the Android device connects to the iOS device first. In the sample app if you use the android device to connect to the iOS device first and then go to the iOS device and try connecting to the Android device. The connection will spin/stall for about 30 seconds to a. minute. Once it finally connects, notice that on the Android side, the connection was lost that yo made earlier and you need to reconnect. Once you reconnect then all is fine.

In this sample app, connections are done manually, but in Chicken Tender it is done automatically once discovered. I suspect this causes some additional issues since it appears for the iOS device to connect, Android cancels its connection first, but in my app it then reconnects automatically. That is likely why it never ends up working.

I can add the auto connect to the sample app, but I think what I have showcases the issue enough.

Please let me know if you need any help or have any questions with the sample apps (I really rushed through it, so they are not great).

weliem commented 1 year ago

Thx for taking the effort to make the apps. I will take a look somewhere this week. Let's see if there is a solution to the issue...

sean7512 commented 1 year ago

Absolutely, no worries. While I make no money from my free apps, I am very passionate about them working as flawless as possible and am extremely happy to help out in any way that I can.

Your library is absolutely amazing, I ported the bluetooth stack of the app from iOS to Android in under 1 day, your library has saved me probably 1-2 weeks of work, if not more when accounting for all the Android hacks/shortcomings that is needed.

weliem commented 1 year ago

I am able to reproduce the issue with your apps. However, I have no idea what is causing this issue. It seems Android is rejecting an incoming connection request when already connected to the same device....

Will do some more digging....

sean7512 commented 1 year ago

I am glad you are at least able to reproduce the issue and I am not being crazy. I am unable to come up with any suitable workaround. It's weird that when the other device connects first, then it all works fine.

Maybe this is worth me reporting the bug to Google?

sean7512 commented 1 year ago

Do you by chance have multiple Android devices? I only have one Android device and multiple iOS devices. I am wondering if you see the same issue if both sides are Android devices.

sean7512 commented 1 year ago

@weliem Just checking in to see if you ever had any thoughts on this by chance? I haven't found a fix or workaround to it yet.

sean7512 commented 5 months ago

@weliem Digging this back up. I have tried using the new Kotlin version, however, I am getting the same results as the issues with the Java library. I know you were able to reproduce the issue with my project, but I have still been unable to find a workaround, is there anything else you can think of? Everything works fine using 2.3.4 of the Java library, so that is where things broke for sure. 2.3.5 and above are broken.