用蓝牙LE在iOS和Android之间进行通信
我有一个使用CoreBluetooth在iPad(中央)和iPhone(外设)之间进行通信的应用程序。 我有一个有两个特点的服务。 我有一个运行最新的Android 4.3与BTLE支持的Nexus 7。 Android在BTLE的风潮上稍微晚了一点,但似乎他们已经接近iOS的样子了,最初他们只支持作为中央处理器的外设模式进入后期版本。 我可以加载示例Android BTLE应用程序并浏览附近的外围设备。 通过我的iPhone广告作为外设,我可以在Android端的附近外围设备列表中看到来自CBAdvertisementDataLocalNameKey的值。 我可以连接到iPhone,连接时蓝牙符号从浅灰色变成黑色。 连接总是持续10秒,然后断开连接。 在Android方面,我应该看到一个可用的服务和特性列表在连接时立即出现。 我已经certificateAndroid代码安装正确,因为我可以将它连接到TI的CC2541DK-SENSOR硬件,并且在连接到它时列出所有服务和特性。
我花了几天的时间解决这个问题,但没有成功。 问题是我无法确定哪个设备出现错误,从而导致断开连接。 CBPeripheralManagerDelegate在连接阶段或服务发现阶段没有callback,所以我不知道在哪一点发生错误(如果错误是在iOS端)。 在Android端调用一个方法来启动服务发现,但是他们的callback“onServicesDiscovered”永远不会被调用,这是令人困惑的。 有没有什么办法可以深入到iOS端的BTLE通信的内部,看看发生了什么事情,并确定发生了什么错误?
这个问题我已经经历了至less一个星期。 我已经在这里问了一个问题,我已经自己回答了。 主要的问题是Android的BUG问题。 它在固定的L2CAP信道上发送一个不允许的命令。
但是,当Android正在与普通的外围BLE设备进行通信时,它工作得非常好。 事实上,BLE样品就像一个魅力。 问题是什么时候与iOS设备通信:在连接build立之后,他们开始协商连接参数(这个阶段在正常的BLE外设中不会发生),这就是问题出现的时间。 Android向iOS发送一个错误的命令,iOS将断开连接。 这基本上是如何工作的
有些问题已经向Google报告,其中一个已经被接受了,我希望他们能尽快开始工作。
不幸的是,你可以做的是等到下一个Android版本。 无论如何,我强烈build议你看看我的问题报告和我所有的testing文件,如果你想对这个问题有所了解。
以下是链接: https : //code.google.com/p/android/issues/detail?id = 58725
我已经写了一个简单的工作示例,相当简单,并将其包含在Github上的开源代码: https : //github.com/GitGarage 。 到目前为止,它只被testing了一个Android Nexus 9和一个iPhone 5s,但我认为它也可以与Nexus 6和各种iPhonetypes一起使用。 到目前为止,它明确地build立了一个Android和一个iPhone之间的通信,但我认为做更多的是可调的。
这里是关键的方法…
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 – 从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 – 发送到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 – 从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) } } } } }
我正在做一个类似的Android中央和iOS外设。 我发现如果没有订阅任何外设的服务,它们就会断开连接。
不要忘记在订阅时更新描述符,否则它实际上不会做任何事情(即在iOS端调用委托方法)。
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); }
也可能注意到,我甚至无法看到iOS设备在Android设备(startLeScan)上进行正常的BLE扫描,但是使用广播接收机启动BT Classic扫描解决了问题(startDiscovery)。
我只是想分享我的知识,因为我前一段时间处理它,我放弃了,因为没有谷歌的支持。 前面提到的代码,我非常感谢,不起作用。 你可以在合理的时间内编写一个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 )我合作,但谷歌没有发言的一切,似乎他们closures了这个问题,并没有任何改变在Android M,因为我一直在调查的代码,并没有看到没有进一步的差异。 当Android尝试连接,特别是在“if else”句子时,问题就出现了。 这个代码基本上拒绝传输和切断通信,所以它不工作。 目前没有解决scheme。 你可以做一个WiFi的直接解决scheme,但这是一个限制,还有进一步的问题。 如果你想用外部硬件(树莓,传感器等)来实现BLE,那么这个问题就不存在了,但是在iOS和Android之间并不适用。 这两种平台的技术是完全一样的,但是在Android中并没有很好的实现,或者是被谷歌用来打开两个平台之间的幽灵交stream的陷阱。
也许有点延迟,但也许你的痛苦可以稍微缓解;)
我们一直在用跨平台BLE连接(iOS < – > Android)进行大量的实验,并且发现还有很多不兼容和连接问题。
如果你的用例是function驱动的,而你只需要基本的数据交换,那么我build议你看一下可以实现跨平台通信的框架和库,而不需要从头构build它。
例如: http : //p2pkit.io或谷歌附近
免责声明:我为Uepaa工作,为Android和iOS开发p2pkit.io。