Communication between iOS and Android with Bluetooth LE

I have a working application using CoreBluetooth for communication between iPad (central) and iPhone (peripheral). I have one service that has two characteristics. I have a Nexus 7 on which the latest version of Android 4.3 with BTLE support is installed. Android is a little late to jump on BTW, but it looks like they are approaching it similarly to how iOS works, where initially they only support a central role when peripheral mode comes out in a later version. I can download a sample Android BTLE application and view neighboring peripherals. With my iPhone ad as peripherals, I can see the value from CBAdvertiseDataLocalNameKey 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 complete. The connection always lasts exactly 10 seconds and then disconnects. On the Android side, I should see a list of available services and features immediately after connecting. I proved that the Android code is configured correctly, because I can connect it to the TI CC2541DK-SENSOR equipment that I have, and all the services and specifications are indicated when connected to it.

I spent the last few days to fix the problem without success. The problem is that I can’t determine which device is experiencing the error and thus causes a shutdown. There are no callbacks from CBPeripheralManagerDelegate during the connect phase or service discovery phase, so I don’t know at what point the error occurred (if the error is on the iOS side). On the Android side, a method is called to initiate a service discovery, but their "onServicesDiscovered" callback is never called, which is perplexing. Is there a way I can delve into the courage of BTLE communication on the iOS side to find out what is happening and determine what error is occurring?

+44
ios android-4.3-jelly-bean android-bluetooth bluetooth-lowenergy core-bluetooth
Aug 23 '13 at 19:03
source share
6 answers

I have already gone through this for at least one week, having the same problem. I already asked a question here, and I already answered myself. The main issue is the Android BUG issue. It sends an unauthorized command to the fixed L2CAP channel.

But when Android communicates with regular BLE peripherals, it works very well. In fact, the BLE pattern works like a charm. The problem is that when you work with an iOS device, for example: immediately after establishing a connection, they begin to coordinate their connection parameters (this phase does not happen with a normal BLE peripheral device), and that is when the problem arises. Android sends a bad iOS command; iOS disconnects the connection. This is basically how it works.

Some problems have already been reported by Google, and one of them has already been accepted, and I hope that they will begin to work on this in the near future.

Unfortunately, what you can do is wait until the next release of Android. In any case, I highly recommend that you look at my problem report with all of my test documents if you want to highlight this problem.

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

+29
Sep 12 '13 at 7:19
source share

I wrote a simple working example, quite simple, and included it in open source Gitub: https://github.com/GitGarage . So far it has been tested only with Android Nexus 9 and iPhone 5, but I believe that it will also work with Nexus 6 and various types of iPhone. So far, it is explicitly configured for communication between one Android and one iPhone, but I believe that it is capable of doing much more.

Here are the basic 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 - receive 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 - receive 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) } } } } } 
+16
Jun 21 '15 at 0:28
source share

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

The peripheral mode works without problems with the Xiomi Mi A1 (OS version Oreo, Android 8.0).

Here are some bandwidth observations we found during our RnD on the iPhone 8 and Xiomi Mi A1, but it still needs to mature with other Android user OSs used in the latest version of the Samsung S8. The data below is based on write_with_response.

  1. iPhone 8 (BLE 5.0) as the central desktop and Linux (Ubuntu 16.04 with a BLE 4.0 key): MTU = 2048: bandwidth - 2.5 kilobytes per second.

  2. iPhone 8 (BLE 5.0) as the central device and Android OS with BLE version 4.2 as the peripheral device (Xiomi Mi A1): MTU = 180: bandwidth - 2.5 kilobytes per second.

  3. iPhone 8 (BLE 5.0) as the central device and iPhone 7 plus (BLE 4.2) as the peripheral device: MTU = 512: bandwidth - 7.1 kilobytes per second.

  4. iPhone 8 (BLE 5.0) as a central device and Samsung S8 (BLE 5.0) as a peripheral device: Samsung S8 did not work as a peripheral device

  5. iPhone 8 (BLE 5.0) as the center and iPhone 8 plus (BLE 5.0) as the peripheral device: MTU = 512: bandwidth - 15.5 kilobytes per second.

+5
Mar 01 '18 at 13:04 on
source share

I am doing something similar with the central part of Android and the peripherals of iOS. I found that they will disconnect if nothing subscribes to any of the peripheral services.

Do not forget to update the descriptor when subscribing, otherwise it actually does nothing (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); } 

You can also notice that I didn’t even see the iOS device perform a normal BLE scan on the Android device (startLeScan), but starting the BT Classic scan with the broadcast receiver solved the problem (startDiscovery).

+1
Jun 25 '15 at 12:49
source share

I just wanted to share my knowledge about this when I was doing this a while ago, and I left because Google does not support it. The above code, which I really thank, does not work. You can encode iOS for iOS or Android for the bluetooth le Android app in a reasonable amount of time, but the problem arises when you try to establish a connection 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 that they closed the problem and nothing changed in android M, since I studied the code and I see no further differences. The problem occurs when Android tries to connect and specifically in the "if else" clause; this code basically rejects the transmission and shortens the connection, so it doesn't work. There is currently no solution for this. You can use the direct WiFi solution, but this is a limitation, and there are additional problems. The problem does not exist if you want to implement BLE with external equipment (raspberries, sensors, etc.), but it does not work between iOS and Android. The technology is exactly the same on both platforms, but it is not very well implemented in Android or designed for Google’s built-in trap, so as not to open the ghost for communication between both platforms.

-one
Nov 24 '15 at 11:30
source share

Maybe a little delay, but maybe your pain can be a little relieved;)

We experimented a lot with cross-platform BLE connections (iOS ↔ Android) and found out that there are still a lot of incompatibilities and communication problems.

If your use case is related to a function and you need only basic data exchange, I would suggest looking at Frameworks and libraries that can provide cross-platform communication for you, without having to create it from scratch.

For example: http://p2pkit.io or next to Google

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

-2
May 3 '16 at 16:17
source share



All Articles