46

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

在过去的几天里,我一直在解决这个问题,但没有成功。问题是我无法确定哪个设备遇到错误并因此导致断开连接。在连接阶段或服务发现阶段没有来自 CBPeripheralManagerDelegate 的回调,所以我不知道在什么时候发生错误(如果错误发生在 iOS 端)。在 Android 端,调用了一个方法来启动服务发现,但是它们的回调“onServicesDiscovered”从未被调用,这令人困惑。有什么办法可以深入了解 iOS 端的 BTLE 通信,看看发生了什么并确定发生了什么错误?

4

6 回答 6

29

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

但是当 Android 与普通的外围 BLE 设备通信时,它工作得很好。事实上,BLE 示例就像一个魅力。例如,问题是何时与 iOS 设备通信:在建立连接后,它们开始协商其连接参数(此阶段不会发生在普通 BLE 外围设备上),这就是问题出现的时候。Android 向 iOS 发送错误命令,iOS 断开连接。这基本上就是它的工作原理

一些问题已经报告给谷歌,其中一个已经被接受,我希望他们能尽快开始处理。

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

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

于 2013-09-12T07:19:53.670 回答
19

我写了一个简单的工作示例,相对简单,并将其开源包含在 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("[ 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 端 - 发送到 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 端 - 从 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)
                }
            }
        }
    }
}
于 2015-06-21T00:28:46.563 回答
6

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

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

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

  1. iPhone 8 (BLE 5.0) 作为 Central 和 Linux 桌面(带有 BLE 加密狗 4.0 的 Ubuntu 16.04):MTU = 2048:吞吐量 - 每秒 2.5 千字节。

  2. iPhone 8 (BLE 5.0) 作为中央和 Android 操作系统,BLE 版本 4.2 作为外围设备(小米 Mi A1):MTU = 180:吞吐量 - 每秒 2.5 千字节。

  3. iPhone 8 (BLE 5.0) 作为中央处理器和 iPhone 7 plus (BLE 4.2) 作为外围设备:MTU = 512:吞吐量 - 每秒 7.1 千字节。

  4. iPhone 8 (BLE 5.0) 作为中央和三星 S8 (BLE 5.0) 作为外围设备:三星 S8 无法作为外围设备工作

  5. iPhone 8 (BLE 5.0) 作为中央处理器和 iPhone 8 plus (BLE 5.0) 作为外围设备:MTU = 512:吞吐量 - 15.5 千字节每秒。

于 2018-03-01T13:04:00.967 回答
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 设备在 Android 设备上进行正常的 BLE 扫描 (startLeScan),但使用广播接收器启动 BT Classic 扫描解决了问题 (startDiscovery)。

于 2015-06-25T12:49:40.383 回答
-1

我只是想分享我的知识,因为我前段时间处理过它,但我退出了,因为谷歌没有支持。上述代码,我非常感谢,但不起作用。您可以在合理的时间内编写 iOS 到 iOS 或 android 到 android 蓝牙 le 应用程序,但是当您尝试在 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 之间不起作用。

于 2015-11-24T11:30:59.683 回答
-2

我们已经对跨平台 BLE 连接(iOS<-> Android)进行了很多试验,并了解到仍然存在许多不兼容和连接问题。

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

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

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

于 2016-05-03T16:17:30.710 回答