47

I've got a working app using CoreBluetooth to communicate between an iPad (central) and iPhone (peripheral). I have one service that has two characteristics. I have a Nexus 7 running the latest Android 4.3 with BTLE support. Android is a bit late to jump on the BTLE bandwagon but it appears they are approaching it similarly to how iOS did, where initially they only support acting as a central with the peripheral mode coming in a later version. I can load the sample Android BTLE app and browse for nearby peripherals. With my iPhone advertising as a peripheral I can see the value from CBAdvertisementDataLocalNameKey in the list of nearby peripherals on the Android side. I can connect to the iPhone and the Bluetooth symbol turns from light gray to black when the connection is made. The connection always lasts exactly 10 seconds and then disconnects. On the Android side I'm supposed to see a list of available services and characteristics appear immediately upon connection. I've proved the Android code is setup correctly because I can connnect it to the TI CC2541DK-SENSOR hardware that I have and all services and characteristics are listed upon connecting to it.

I've spent the last few days troubleshooting the issue with no success. The problem is I can't determine which device is experiencing an error and thus causing the disconnection. There are no callbacks from CBPeripheralManagerDelegate during the connection phase or service discovery phase so I have no idea at what point an error occurs (if the error is on the iOS side). On the Android side a method is called to initiate service discovery however their callback "onServicesDiscovered" is never called which is perplexing. Is there any way I can dig into the guts of the BTLE communication on the iOS side to see what's going on and determine what error is taking place?

afrederick
  • 1,388
  • 1
  • 10
  • 24
  • You use the latest iOS, right? – www.jensolsson.se Aug 23 '13 at 19:53
  • @www.jensolsson.se Yes – afrederick Aug 23 '13 at 19:56
  • 1
    You should buy a BLE dongle and use TI's packet sniffer solution: http://www.ti.com/tool/packet-sniffer What you mention sounds weird. Also, try this app: https://itunes.apple.com/tr/app/ble-utility/id606210918?mt=8 It makes it easy to simulate services on the i* device. If that succeeds, then the issue is most probably in your code. If that fails, then deeper inspection is required. – allprog Aug 23 '13 at 19:57
  • 1
    @allprog I downloaded the BLE Utility app as well as LightBlue, both apps experienced the same results as my custom app. I found another thread that links here: https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=58725 It appears as if this could be an low level implementation bug on the Android side, but it has yet to be confirmed. – afrederick Aug 23 '13 at 20:02
  • That's very probable. They rolled it out just now and started from nothing. There surely are issues. Sadly Google seems to more or less ignore BLE. They didn't even spend much time Java-izing the API. Keep this post updated. I think you'll submit the answer soon. :) – allprog Aug 23 '13 at 20:08
  • @allprog Will do! It will help if people go and "star" that issue on the code.google.com site. Hopefully this gets resolved in the next revision of Android 4.3, I sure would love to get iOS and Android talking back and forth! – afrederick Aug 23 '13 at 20:10
  • Hi everyone. I'm the developer which reported the isse that @afrederick linked. I think that allprog said the truth. Anyway I give you guys an update. There is another issue report at AOSP page which has been owned by an Android guy. The issue is the same of mine one. So let's hope they will get their hands-on! This is the issue link:https://code.google.com/p/android/issues/detail?id=58896 Keep an eye on it! – edoardotognoni Sep 12 '13 at 07:24
  • 2
    I encountered the same issue, Nexus 4 on 4.3 connecting to iPhone 5 acting as peripheral using LightBlue. Could connect but device discovery never finished and would disconnect. Updated the Nexus 4 to 4.4 (Build number KRT16S) and connection and discovering services works. Was able to read and write characteristics as well. – vee Dec 19 '13 at 18:49

6 Answers6

30

I've already gone through this for at least one week having this same issue. I've already asked a question here and I've already answered on my own. The main problem is an Android BUG issue. It's sending a non permitted command on a fixed L2CAP channnel.

But when Android is communicating with normal peripheral BLE devices, it works pretty well. In fact, the BLE sample works like a charm. The problem is when is comunicating with an iOS device for example: Just after the connection is made, they start negotiating their connection parameters (this phase doesn't happen with normal BLE peripheral), and this is when the problem comes up. Android sends a bad command to iOS, iOS drops the connection. That's basically how it works

Some issues have been already reported to Google, and one of them have been already accepted and I hope they will start working on it soon.

Unfortunately, what you can do, is to wait until next Android release. Anyway, I highly suggest you to have a look at my issue report with all my test documents if you want to make some light on this problem.

Here's the link: https://code.google.com/p/android/issues/detail?id=58725

ThomasW
  • 16,981
  • 4
  • 79
  • 106
edoardotognoni
  • 2,752
  • 3
  • 22
  • 31
  • 1
    Thanks for the explanation. Your issue was found by the OP too. Let's hope this gets solved in the next release. – allprog Sep 12 '13 at 08:06
  • 4
    Hi guys, any update on this? Android team is working on this issue? I see no updates on their issue tracking... – andresmafra Jun 16 '14 at 11:25
  • 1
    It was marked obsolete by Google on Dec 7, 2014. However, people are reporting it is still an issue. – ThomasW Apr 24 '15 at 03:07
20

I've written a simple working example, well relatively simple, and included it open-source on Github: https://github.com/GitGarage. So far it has only been tested with an Android Nexus 9 and an iPhone 5s, but I presume it would also work with a Nexus 6 and various iPhone types. So far it is set up explicitly to communicate between one Android and one iPhone, but I presume it is tweakable to do much more.

Here are the key methods...

DROID SIDE - Sending to iOS:

private void sendMessage() {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            if (mBTAdapter == null) {
                return;
            }
            if (mBTAdvertiser == null) {
                mBTAdvertiser = mBTAdapter.getBluetoothLeAdvertiser();
            }
               // get the full message from the UI
            String textMessage = mEditText.getText().toString(); 
            if (textMessage.length() > 0)
            {
                   // add 'Android' as the user name
                String message = "Android: " + textMessage; 

                while (message.length() > 0) {
                    String subMessage;
                    if(message.length() > 8)
                    {    // add dash to unfinished messages
                        subMessage = message.substring(0,8) + "-"; 
                        message = message.substring(8);
                        for (int i = 0; i < 20; i++) // twenty times (better safe than sorry) send this part of the message. duplicate parts will be ignored
                        {
                            AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
                            mBTAdvertiser.startAdvertising(BleUtil.createAdvSettings(true, 100), ad, mAdvCallback);
                            mBTAdvertiser.stopAdvertising(mAdvCallback);
                        }
                    }
                    else
                    {  // otherwise, send the last part
                        subMessage = message;
                        message = "";
                        for (int i = 0; i < 5; i++)
                        {
                            AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
                            mBTAdvertiser.startAdvertising(
                                    BleUtil.createAdvSettings(true, 40), ad,
                                    mAdvCallback);
                            mBTAdvertiser.stopAdvertising(mAdvCallback);
                        }
                    }
                }
                threadHandler.post(updateRunnable);
            }
        }
    });
    thread.start();
}

DROID SIDE - Receiving from iOS:

@Override
public void onLeScan(final BluetoothDevice newDevice, final int newRssi,
                     final byte[] newScanRecord) {

    int startByte = 0;
    String hex = asHex(newScanRecord).substring(0,29);
       // check five times, startByte was used for something else before
    while (startByte <= 5) {
       // check if this is a repeat message
        if (!Arrays.asList(used).contains(hex)) {
            used[ui] = hex;

            String message = new String(newScanRecord);
            String firstChar = message.substring(5, 6);
            Pattern pattern = Pattern.compile("[ a-zA-Z0-9~!@#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", Pattern.DOTALL);
               // if the message is comprised of standard characters...
            Matcher matcher = pattern.matcher(firstChar);
            if (firstChar.equals("L"))
            {
                firstChar = message.substring(6, 7);
                pattern = Pattern.compile("[ a-zA-Z0-9~!@#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", Pattern.DOTALL);
                matcher = pattern.matcher(firstChar);
            }

            if(matcher.matches())
            {
                TextView textViewToChange = (TextView) findViewById(R.id.textView);
                String oldText = textViewToChange.getText().toString();
                int len = 0;
                String subMessage = "";
                   // add this portion to our final message
                while (matcher.matches())  
                {
                    subMessage = message.substring(5, 6+len);
                    matcher = pattern.matcher(message.substring(5+len, 6+len));
                    len++;
                }
                subMessage = subMessage.substring(0,subMessage.length()-1);

                Log.e("Address",newDevice.getAddress());
                Log.e("Data",asHex(newScanRecord));
                boolean enter = subMessage.length() == 16;
                enter = enter && !subMessage.substring(15).equals("-");
                enter = enter || subMessage.length() < 16;
                textViewToChange.setText(oldText + subMessage.substring(0, subMessage.length() - 1) + (enter ? "\n" : ""));
                ui = ui == 2 ? -1 : ui;
                ui++;

                Log.e("String", subMessage);
            }
            break;
        }
        startByte++;
    }
}

iOS SIDE - Sending to Android:

func startAdvertisingToPeripheral() {
    var allTime:UInt64 = 0;
    if (dataToSend != nil)
    {
        datastring = NSString(data:dataToSend, encoding:NSUTF8StringEncoding) as String
        datastring = "iPhone: " + datastring
        if (datastring.length > 15)
        {
            for (var i:Double = 0; i < Double(datastring.length)/15.000; i++)
            {
                let delay = i/10.000 * Double(NSEC_PER_SEC)
                let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
                allTime = time
                dispatch_after(time, dispatch_get_main_queue(), { () -> Void in self.sendPart() });
            }
        }
        else
        {
            var messageUUID = StringToUUID(datastring)
            if !peripheralManager.isAdvertising {
                peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: messageUUID)]])
            }
        }
    }
}

iOS SIDE - Receiving from Android:

func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {

    delegate?.didDiscoverPeripheral(peripheral)
    var splitUp = split("\(advertisementData)") {$0 == "\n"}
    if (splitUp.count > 1)
    {
        var chop = splitUp[1]
        chop = chop[0...chop.length-2]
        var chopSplit = split("\(chop)") {$0 == "\""}

        if !(chopSplit.count > 1 && chopSplit[1] == "Device Information")
        {
            var hexString = chop[4...7] + chop[12...19] + chop[21...26]
            var datas = hexString.dataFromHexadecimalString()
            var string = NSString(data: datas!, encoding: NSUTF8StringEncoding) as String
            if (!contains(usedList,string))
            {
                usedList.append(string)
                if (string.length == 9 && string[string.length-1...string.length-1] == "-")
                {
                    finalString = finalString + string[0...string.length-2]
                }
                else
                {
                    lastString = finalString + string + "\n"
                    println(lastString)
                    finalString = ""
                    usedList = newList
                    usedList.append(string)
                }
            }
        }
    }
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
omikes
  • 8,064
  • 8
  • 37
  • 50
  • Thank you for sharing. It's good to know it works in theory and good to have some sample code. However when I tried to checkout your git example BLEMingle.swift and BLECentralDelegate.swift were missing. – domenukk Jun 22 '15 at 17:55
  • Tried to compile your your ios code using `xcode 6.3.2` it doesn't compile, after solving compiler errors. I run the code but was not able to send the text. Will try to solve the problem. – Inder Kumar Rathore Jun 24 '15 at 11:39
  • Just tried to compile it, but BLEMingle.swift throws 17 errors... Could you check your git status? Thanks a lot for your effort. Even non-functioning it's a huge help. – domenukk Jun 29 '15 at 16:04
  • Xcode 6.3, 17 errors on BLEMingle.swift. Are you sure that you pushed to github the latest version on the master branch? – idiogo Jun 30 '15 at 19:22
  • Did you try to clean and rebuild everything? Or check it out on a different account? Maybe it didn't even bother to recompile this file since you didn't change anything? – domenukk Jul 10 '15 at 13:28
  • While compiling or running? I am currently not at home but will try later this week and report back. ;) – domenukk Jul 13 '15 at 21:49
  • I checked it out on another computer and it compiles just fine. I just removed the references to the jar that were still in the build. Sending files to iOS works flawlessly! Great work! However to the Nexus 6 it sometimes only receives parts of the messages. I'm debugging a bit now. – domenukk Jul 16 '15 at 17:12
  • 1
    OK, awesome. I think the hard part is done, for sending messages to Android I was thinking of sending the same message part multiple times, then on the receiving end it would just keep a log of twenty or so recent message parts and cancel out any duplicates that come across. Progress! – omikes Jul 16 '15 at 18:58
  • Anyway we'll be running into massive problems with this solution as soon as there are more than 2 devices involved... – domenukk Jul 17 '15 at 09:47
  • Ive tried this code and it doesn´t work because basically there is an android bug, internally speaking. About compiling, using XCode 7 doesnt work and doesnt compile, it compiles in Android though. Ive written the iOS code in objective-c to make it compilable but it doesnt work either. – AlfuryDB Oct 15 '15 at 16:36
  • 3
    I've got it working again, updated to the latest versions of iOS and Android. Give it a try. – omikes Oct 04 '16 at 18:52
6

I would like to add few information to this thread as a part of our RnD on BLE topic between cross platform.

Peripheral mode is working without any issues with Xiomi Mi A1 (OS version Oreo, Android 8.0).

Here are few observation on throughput that we found during our RnD on iPhone 8 and Xiomi Mi A1 but it still has to get matured with other custom Android OS used in latest Samsung S8. The below data is based on write_with_response.

  1. iPhone 8 (BLE 5.0) as Central and Linux desktop (Ubuntu 16.04 with BLE dongle 4.0): MTU = 2048 : Throughput - 2.5 KiloBytes per sec.

  2. iPhone 8 (BLE 5.0) as Central and Android OS with BLE version 4.2 as Peripheral(Xiomi Mi A1): MTU = 180 : Throughput - 2.5 KiloBytes per sec.

  3. iPhone 8 (BLE 5.0) as Central and iPhone 7 plus (BLE 4.2) as Peripheral : MTU = 512 : Throughput - 7.1 KiloBytes per sec.

  4. iPhone 8 (BLE 5.0) as Central and Samsung S8 (BLE 5.0) as Peripheral : Samsung S8 failed to work as peripheral

  5. iPhone 8 (BLE 5.0) as Central and iPhone 8 plus (BLE 5.0) as Peripheral : MTU = 512 : Throughput - 15.5 KiloBytes per sec.

Sudhin Philip
  • 644
  • 8
  • 15
1

I am doing something similar with an Android central and an iOS peripheral. I found that they would disconnect if nothing subscribed to any of the peripheral's services.

Don't forget to update the descriptor when subscribing else it doesn't actually do anything (i.e. call the delegate method on the iOS side).

public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.v(TAG, "BluetoothAdapter not initialized");
        return;
    }

    UUID uuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");    // UUID for client config desc
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid);
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);

    mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}

It might also be of note that I couldn't even see the iOS device doing an normal BLE scan on the Android device (startLeScan), but starting a BT Classic scan with a broadcast receiver solved the problem (startDiscovery).

-1

I just wanted to share my knowledge on that as I dealt with it some time ago and I quit as there is no support by google. The aforementioned code, what I thank a lot, does not work. You can code in a reasonable time an iOS to iOS or android to android bluetooth le application but the problem comes when you try to communicate between iOS and android. There is a well documented google issue (https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=58725) I collaborated but google didnt pronounce at all and it seems they closed the issue and nothing has changed in android M as Ive been looking into the code and can´t see no further differences. The problem comes when Android tries to connect and specifically in a "if else" sentence; this code basically rejects the transmission and cuts the communication so it doesn´t work. At the moment, there is no solution for that. You can do a WiFi direct solution, but it´s a limitation and there are further problems doing that. The problem doesn´t exist if you want to implement BLE with external hardware (raspberry, sensors, etc.,) but it doesn´t work between iOS and android. The technology is quite the same in both platforms but it´s not well implemented in Android or is purpose inserted pitfall by google to not open the spectre to communicate between both platforms.

AlfuryDB
  • 289
  • 1
  • 4
-2

We have been experimenting a lot with cross-platform BLE connections (iOS<-> Android) and learned that there are still many incompatibilities and connection issues.

If your use case is feature-driven and you only need basic data exchange I would suggest to look at Frameworks and Libraries that can achieve cross-platform communication for you, without you needing to build it up from scratch.

For example http://p2pkit.io or google nearby

Disclaimer: I work for Uepaa, developing p2pkit.io for Android and iOS.

p2pkit
  • 1,159
  • 8
  • 11