41

我有一个使用CoreBluetooth在iPad(中央)和iPhone(外设)之间进行通信的应用程序。我有一项服务有两个特点。我有一个运行最新Android 4.3并支持BTLE的Nexus 7。 Android在BTLE潮流方面稍微晚了一点,但似乎他们正在接近它,类似于iOS的做法,最初他们只支持作为中央处理器的外设模式进入后续版本。我可以加载示例Android BTLE应用程序并浏览附近的外围设备。通过我的iPhone广告作为外设,我可以在Android端的附近外围设备列表中看到来自CBAdvertisementDataLocalNameKey的值。我可以连接到iPhone,并且在建立连接时蓝牙符号从浅灰色变为黑色。连接总是持续10秒,然后断开连接。在Android方面,我应该看到连接后立即出现可用服务和特征列表。我已经证明Android代码安装正确,因为我可以将其连接到我拥有的TI CC2541DK-SENSOR硬件,并且在连接到它时列出了所有服务和特性。iOS和Android之间通过蓝牙进行通信LE

我已经花了几天的时间解决这个问题没有成功。问题是我无法确定哪个设备出现错误,从而导致断开连接。在连接阶段或服务发现阶段没有CBPeripheralManagerDelegate的回调,所以我不知道发生了什么错误(如果错误在iOS端)。在Android端调用一个方法来启动服务发现,但是他们的回调“onServicesDiscovered”永远不会被调用,这是令人困惑的。有没有什么办法可以挖掘iOS侧BTLE通信的内心,看看发生了什么,并确定发生了什么错误?

+0

您使用的是最新的iOS版,对吗? –

+0

@ www.jensolsson.se是 – afrederick

+1

您应该购买BLE加密狗并使用TI的数据包嗅探器解决方案:http://www.ti.com/tool/packet-sniffer您提到的内容听起来很奇怪。另外,试试这个应用程序:https://itunes.apple.com/tr/app/ble-utility/id606210918?mt=8它可以很容易地模拟i *设备上的服务。如果成功,那么问题很可能在您的代码中。如果失败,则需要进行更深入的检查。 – allprog

回答

24

我已经经历了至少一个星期这个同样的问题。我已经在这里问过一个问题,我已经自己回答了。主要问题是Android BUG问题。它在固定的L2CAP信道上发送不允许的命令。

但是,当Android与正常的外设BLE设备进行通信时,它工作得很好。事实上,BLE样本的工作就像一个魅力。问题是什么时候与iOS设备通信:在连接建立后,他们开始协商他们的连接参数(这个阶段对于普通的BLE外设不会发生),这就是问题出现的时间。 Android向iOS发送错误的命令,iOS会丢弃连接。这基本上是如何工作的

已经向Google报告了一些问题,其中一个已经被接受,我希望他们很快就能开始工作。

不幸的是,你可以做的是等到下一个Android版本。无论如何,我强烈建议你看看我的问题报告和我的所有测试文件,如果你想对这个问题做一些解释。

这里的链接:https://code.google.com/p/android/issues/detail?id=58725

+1

感谢您的解释。您的问题也是由OP找到的。希望在下一个版本中解决这个问题。 – allprog

+4

嗨,大家好,对此有何更新? Android团队正在处理这个问题?我在他们的问题跟踪中看不到更新... – andresmafra

+1

2014年12月7日,谷歌已标记为过时。但是,人们报告它仍然是一个问题。 – ThomasW

14

我写了一个简单的工作示例,也比较简单,包括在Github上它的开源:https://github.com/GitGarage。到目前为止,它只用Android Nexus 9和iPhone 5s进行过测试,但我认为它也适用于Nexus 6和各种iPhone类型。到目前为止,它被明确设置为在一个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("[ [email protected]#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", 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("[ [email protected]#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", 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) 
       } 
      } 
     } 
    } 
} 
+0

感谢您的分享。很高兴知道它在理论上有效,并且有一些示例代码。但是,当我试图结帐你的git例如BLEMingle.swift和BLECentralDelegate.swift丢失。 – domenukk

+0

试图编译你的ios代码,使用'xcode 6.3.2'编译错误后,它不能编译。我运行代码但无法发送文本。将尽力解决问题。 –

+0

试图编译它,但BLEMingle.swift抛出17错误...你能检查你的git状态吗?非常感谢您的努力。即使不起作用,这也是一个巨大的帮助。 – domenukk

1

我正在做一些类似的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设备做一个正常BLE的Android设备(startLeScan)上进行扫描,但启动BT经典扫描具有广播接收器解决了这个问题( startDiscovery)。

-1

我只是想分享我的知识对我前一段时间处理它,我不干,因为是由谷歌的支持。上述代码,我非常感谢,不起作用。您可以在合理的时间内编写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)我合作,但谷歌没有发音,似乎他们关闭了这个问题,并没有任何改变在Android M,因为我一直在看代码,并没有看到没有进一步的差异。当Android尝试连接并且特别是在“if else”句子中时,问题就出现了;此代码基本上拒绝传输并切断通信,因此它不起作用。目前,没有解决方案。你可以做一个WiFi直接解决方案,但这是一个限制,并有进一步的问题。如果你想通过外部硬件(树莓,传感器等)实现BLE,但这个问题不存在,但它在iOS和Android之间不起作用。这两种平台的技术是完全一样的,但在Android中不能很好地实现,或者是因为谷歌的目的而陷入困境,以致于无法打开两个平台之间的幽灵交流。

-2

也许有点延迟,但也许你的疼痛可稍微缓解;)

我们一直在尝试了很多具有跨平台的BLE连接(iOS版< - > Android)和了解到,仍然有很多的不兼容和连接问题。

如果您的用例是功能驱动的,而您只需要基本的数据交换,那么我建议您查看可为您实现跨平台通信的框架和库,而无需从头构建它。

例如:http://p2pkit.io或者google附近

声明:我Uepaa工作,开发Android和iOS p2pkit.io。

0

我想向这个主题添加一些信息,作为我们在跨平台之间关于BLE主题的RnD的一部分。

Xiomi Mi A1(操作系统版本Oreo,Android 8.0)的外设模式没有任何问题。

下面是我们在iPhone 8和Xiomi Mi A1的RnD中发现的吞吐量的一些观察结果,但它仍需要与最新三星S8中使用的其他定制Android操作系统成熟。以下数据基于write_with_response。

  1. iPhone 8(BLE 5.0)作为中部和Linux桌面(Ubuntu的16.04与BLE加密狗4.0):MTU = 2048:吞吐量 - 每秒2.5千字节。作为外设(Xiomi Mi A1),BLE版本4.2的iPhone 8(BLE 5.0):MTU = 180:吞吐量 - 每秒2.5 KiloBytes。作为中央的iPhone 8(BLE 5.0)和作为外围设备的iPhone 7 plus(BLE 4.2):MTU = 512:吞吐量 - 每秒7.1千字节。

  2. iPhone 8(BLE 5.0)作为中央和三星S8(BLE 5.0)作为外围设备:三星S8失败,因为外围

  3. iPhone 8(BLE 5.0)作为中央和iPhone 8再加上工作(BLE 5.0 )作为外设:MTU = 512:吞吐量 - 每秒15.5千字节。