簡體   English   中英

使用藍牙 LE 在 iOS 和 Android 之間進行通信

[英]Communicating between iOS and Android with Bluetooth LE

我有一個使用 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 通信的內部結構,看看發生了什么並確定發生了什么錯誤?

我已經經歷了至少一周的時間,遇到同樣的問題。 我已經在這里問了一個問題,我已經自己回答了。 主要問題是Android BUG問題。 它在固定的 L2CAP 通道上發送不允許的命令。

但是當 Android 與普通的外圍 BLE 設備通信時,它工作得很好。 事實上,BLE 示例的作用就像一個魅力。 問題是當與 iOS 設備通信時,例如:在建立連接后,他們開始協商他們的連接參數(這個階段不會發生在正常的 BLE 外圍設備上),這就是問題出現的時候。 Android 向 iOS 發送錯誤命令,iOS 斷開連接。 這基本上是它的工作原理

一些問題已經報告給谷歌,其中一個已經被接受,我希望他們能盡快開始處理。

不幸的是,你能做的就是等到下一個 Android 版本。 無論如何,如果您想了解這個問題,我強烈建議您查看我的問題報告以及我所有的測試文檔。

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

我寫了一個簡單的工作示例,相對簡單,並將其開源在 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)
                }
            }
        }
    }
}

我想在這個線程中添加一些信息,作為我們關於跨平台之間 BLE 主題的 RnD 的一部分。

外圍模式在小米 A1(操作系統版本 Oreo,Android 8.0)上正常工作,沒有任何問題。

以下是我們在 iPhone 8 和小米米 A1 上的 RnD 期間發現的關於吞吐量的一些觀察結果,但它仍然需要與最新三星 S8 中使用的其他自定義 Android 操作系統成熟。 以下數據基於 write_with_response。

  1. iPhone 8 (BLE 5.0) 作為 Central 和 Linux 桌面(Ubuntu 16.04 with BLE dongle 4.0):MTU = 2048:吞吐量 - 每秒 2.5 千字節。

  2. iPhone 8 (BLE 5.0) 作為 Central 和 Android OS,BLE 版本 4.2 作為 Peripheral(Xiomi 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) 作為 Central 和 Samsung S8 (BLE 5.0) 作為 Peripheral:Samsung S8 無法作為外圍設備工作

  5. iPhone 8 (BLE 5.0) 作為中央設備,iPhone 8 plus (BLE 5.0) 作為外圍設備:MTU = 512:吞吐量 - 15.5 KB/秒。

我正在用 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)。

我只是想分享我在這方面的知識,因為我前段時間處理過它,但由於沒有谷歌的支持,我退出了。 上述代碼,我非常感謝,不起作用。 您可以在合理的時間內對 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 之間不起作用。 該技術在兩個平台上完全相同,但在 Android 中沒有很好地實現,或者是谷歌故意插入的陷阱,不打開幽靈在兩個平台之間進行通信。

我們已經對跨平台 BLE 連接(iOS<-> Android)進行了大量試驗,並了解到仍然存在許多不兼容和連接問題。

如果您的用例是功能驅動的並且您只需要基本的數據交換,我建議您查看可以為您實現跨平台通信的框架和庫,而無需您從頭開始構建它。

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

免責聲明:我為 Uepaa 工作,為 Android 和 iOS 開發 p2pkit.io。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM