Communicating between iOS and Android with Bluetooth LE
Solution 1
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
Solution 2
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)
}
}
}
}
}
Solution 3
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.
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.
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.
iPhone 8 (BLE 5.0) as Central and iPhone 7 plus (BLE 4.2) as Peripheral : MTU = 512 : Throughput - 7.1 KiloBytes per sec.
iPhone 8 (BLE 5.0) as Central and Samsung S8 (BLE 5.0) as Peripheral : Samsung S8 failed to work as peripheral
iPhone 8 (BLE 5.0) as Central and iPhone 8 plus (BLE 5.0) as Peripheral : MTU = 512 : Throughput - 15.5 KiloBytes per sec.
Solution 4
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).
Related videos on Youtube
afrederick
Updated on January 14, 2021Comments
-
afrederick over 3 years
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?
-
www.jensolsson.se almost 11 yearsYou use the latest iOS, right?
-
afrederick almost 11 years@www.jensolsson.se Yes
-
allprog almost 11 yearsYou should buy a BLE dongle and use TI's packet sniffer solution: ti.com/tool/packet-sniffer What you mention sounds weird. Also, try this app: 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.
-
afrederick almost 11 years@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: code.google.com/p/android/issues/… It appears as if this could be an low level implementation bug on the Android side, but it has yet to be confirmed.
-
allprog almost 11 yearsThat'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. :)
-
afrederick almost 11 years@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!
-
edoardotognoni almost 11 yearsHi 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:code.google.com/p/android/issues/detail?id=58896 Keep an eye on it!
-
vee over 10 yearsI 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.
-
-
allprog almost 11 yearsThanks for the explanation. Your issue was found by the OP too. Let's hope this gets solved in the next release.
-
andresmafra about 10 yearsHi guys, any update on this? Android team is working on this issue? I see no updates on their issue tracking...
-
ThomasW about 9 yearsIt was marked obsolete by Google on Dec 7, 2014. However, people are reporting it is still an issue.
-
domenukk about 9 yearsThank 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.
-
Inder Kumar Rathore about 9 yearsTried 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. -
domenukk almost 9 yearsJust 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.
-
idiogo almost 9 yearsXcode 6.3, 17 errors on BLEMingle.swift. Are you sure that you pushed to github the latest version on the master branch?
-
domenukk almost 9 yearsDid 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 almost 9 yearsWhile compiling or running? I am currently not at home but will try later this week and report back. ;)
-
domenukk almost 9 yearsI 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.
-
omikes almost 9 yearsOK, 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!
-
domenukk almost 9 yearsAnyway we'll be running into massive problems with this solution as soon as there are more than 2 devices involved...
-
AlfuryDB over 8 yearsIve 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.
-
omikes over 7 yearsI've got it working again, updated to the latest versions of iOS and Android. Give it a try.
-
cutsoy about 6 yearsThis is really useful information. Do you have any idea why it might fail with the S8?
-
Sudhin Philip about 6 yearsI believe it might be due to Samsung Android customized OS version. But I cannot find any official documentation on this.