使用蓝牙 LE 在 iOS 和 Android 之间进行通信
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18410081/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Communicating between iOS and Android with Bluetooth LE
提问by afrederick
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.
我有一个使用 CoreBluetooth 在 iPad(中央)和 iPhone(外围)之间进行通信的工作应用程序。我有一项具有两个特征的服务。我有一台 Nexus 7 运行最新的 Android 4.3,支持 BTLE。Android 加入 BTLE 的潮流有点晚了,但看起来他们正在接近它,类似于 iOS 的方式,最初他们只支持充当中心,而外围模式将在以后的版本中出现。我可以加载示例 Android BTLE 应用程序并浏览附近的外围设备。使用我的 iPhone 广告作为外围设备,我可以在 Android 端的附近外围设备列表中看到来自 CBAdvertisementDataLocalNameKey 的值。我可以连接到 iPhone,连接成功后蓝牙符号从浅灰色变为黑色。连接始终持续 10 秒,然后断开连接。在 Android 方面,我应该会在连接后立即看到可用服务和特征的列表。我已经证明 Android 代码设置正确,因为我可以将它连接到我拥有的 TI CC2541DK-SENSOR 硬件,并且在连接到它时会列出所有服务和特性。
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?
最近几天我一直在解决这个问题,但没有成功。问题是我无法确定哪个设备遇到错误并因此导致断开连接。在连接阶段或服务发现阶段没有来自 CBPeripheralManagerDelegate 的回调,所以我不知道在什么时候发生错误(如果错误发生在 iOS 端)。在 Android 端,调用了一个方法来启动服务发现,但是它们的回调“onServicesDiscovered”从未被调用,这令人困惑。有什么方法可以深入研究 iOS 端 BTLE 通信的内容,看看发生了什么并确定发生了什么错误?
采纳答案by edoardotognoni
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.
我已经经历了至少一周的时间,遇到同样的问题。我已经在这里问了一个问题,我已经自己回答了。主要问题是Android BUG问题。它在固定的 L2CAP 通道上发送不允许的命令。
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
但是当 Android 与普通的外围 BLE 设备通信时,它工作得很好。事实上,BLE 示例的作用就像一个魅力。问题是当与 iOS 设备通信时,例如:在建立连接后,他们开始协商他们的连接参数(这个阶段不会发生在正常的 BLE 外围设备上),这就是问题出现的时候。Android 向 iOS 发送错误命令,iOS 断开连接。这基本上是它的工作原理
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.
不幸的是,你能做的就是等到下一个 Android 版本。无论如何,如果您想了解这个问题,我强烈建议您查看我的问题报告以及我所有的测试文档。
Here's the link: https://code.google.com/p/android/issues/detail?id=58725
这是链接:https: //code.google.com/p/android/issues/detail?id=58725
回答by omikes
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.
我写了一个简单的工作示例,相对简单,并将其开源在 Github 上:https: //github.com/GitGarage。到目前为止,它仅在 Android Nexus 9 和 iPhone 5s 上进行了测试,但我认为它也适用于 Nexus 6 和各种 iPhone 类型。到目前为止,它被明确设置为在一台 Android 和一台 iPhone 之间进行通信,但我认为它可以做更多的调整。
Here are the key methods...
以下是主要方法...
DROID SIDE - Sending to iOS:
DROID SIDE - 发送到 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:
DROID SIDE - 从 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:
iOS 端 - 发送到 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:
iOS 端 - 从 Android 接收:
func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {
delegate?.didDiscoverPeripheral(peripheral)
var splitUp = split("\(advertisementData)") {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);
}
== "\n"}
if (splitUp.count > 1)
{
var chop = splitUp[1]
chop = chop[0...chop.length-2]
var chopSplit = split("\(chop)") {##代码## == "\""}
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)
}
}
}
}
}
回答by Sudhin Philip
I would like to add few information to this thread as a part of our RnD on BLE topic between cross platform.
我想在这个线程中添加一些信息,作为我们关于跨平台之间 BLE 主题的 RnD 的一部分。
Peripheral mode is working without any issues with Xiomi Mi A1 (OS version Oreo, Android 8.0).
外围模式在小米 A1(操作系统版本 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 和小米米 A1 上的 RnD 期间发现的关于吞吐量的一些观察结果,但它仍然需要与最新三星 S8 中使用的其他自定义 Android 操作系统成熟。以下数据基于 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.
iPhone 8 (BLE 5.0) 作为 Central 和 Linux 桌面(Ubuntu 16.04 with BLE dongle 4.0):MTU = 2048:吞吐量 - 2.5 KB/秒。
iPhone 8 (BLE 5.0) 作为 Central 和 Android OS,BLE 版本 4.2 作为 Peripheral(Xiomi Mi A1):MTU = 180:吞吐量 - 每秒 2.5 千字节。
iPhone 8 (BLE 5.0) 作为中央设备,iPhone 7 plus (BLE 4.2) 作为外围设备:MTU = 512:吞吐量 - 每秒 7.1 千字节。
iPhone 8 (BLE 5.0) 作为 Central 和 Samsung S8 (BLE 5.0) 作为 Peripheral:Samsung S8 无法作为外围设备工作
iPhone 8 (BLE 5.0) 作为中央设备,iPhone 8 plus (BLE 5.0) 作为外围设备:MTU = 512:吞吐量 - 15.5 KB/秒。
回答by Charlie Scott-Skinner
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.
我正在用 Android 中心和 iOS 外围设备做类似的事情。我发现如果没有订阅任何外围设备的服务,它们就会断开连接。
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).
订阅时不要忘记更新描述符,否则它实际上不会做任何事情(即调用 iOS 端的委托方法)。
##代码##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).
可能还需要注意的是,我什至看不到 iOS 设备在 Android 设备上执行正常的 BLE 扫描 (startLeScan),但是使用广播接收器启动 BT Classic 扫描解决了该问题 (startDiscovery)。
回答by AlfuryDB
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.
我只是想分享我在这方面的知识,因为我前段时间处理过它,但由于没有谷歌的支持,我退出了。上述代码,我非常感谢,不起作用。您可以在合理的时间内对 iOS 到 iOS 或 android 到 android 蓝牙文件应用程序进行编码,但是当您尝试在 iOS 和 android 之间进行通信时就会出现问题。有一个有据可查的谷歌问题(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) 我合作过,但 google 根本没有发音,似乎他们解决了这个问题,而且 android M 没有任何变化,因为我一直在研究代码,看不到进一步的差异。当 Android 尝试连接时,问题就出现了,特别是在“if else”语句中;此代码基本上拒绝传输并切断通信,因此它不起作用。目前,没有解决方案。你可以做一个 WiFi 直接解决方案,但这是一个限制,这样做还有更多的问题。如果您想使用外部硬件(树莓、传感器等)实现 BLE,则不存在此问题,但它在 iOS 和 android 之间不起作用。
回答by p2pkit
Maybe a bit delayed, but perhaps your pain can be relieved slightly ;)
也许有点延迟,但也许你的痛苦可以稍微缓解;)
We have been experimenting a lot with cross platform BLE connections (iOS<-> Android) and learned that there are still many incompatibilities and connection issues.
我们已经对跨平台 BLE 连接(iOS<-> Android)进行了大量试验,并了解到仍然存在许多不兼容和连接问题。
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.ioor google nearby
例如:http: //p2pkit.io或 google 附近
Disclaimer: I work for Uepaa, developing p2pkit.io for Android and iOS.
免责声明:我为 Uepaa 工作,为 Android 和 iOS 开发 p2pkit.io。