web-dev-qa-db-ja.com

iOSとAndroid Bluetooth LEを使用した通信

CoreBluetoothを使用してiPad(中央)とiPhone(周辺機器)の間で通信する機能するアプリがあります。 2つの特性を持つ1つのサービスがあります。 BTLEをサポートする最新のAndroid 4.3を実行しているNexus 7があります。 AndroidはBTLEの時流に乗るのに少し遅れていますが、iOSがやったように近づいているようです。サンプルAndroid BTLEアプリを読み込んで、近くの周辺機器を参照できます。周辺機器としてのiPhoneの広告では、Android側の近くの周辺機器のリストでCBAdvertisementDataLocalNameKeyの値を見ることができます。 iPhoneに接続できますが、接続が確立されると、Bluetoothシンボルがライトグレーから黒に変わります。接続は常に正確に10秒続き、その後切断されます。 Android側では、接続するとすぐに利用可能なサービスと特性のリストが表示されるはずです。私が持っているTI CC2541DK-SENSORハードウェアに接続できるため、Androidコードが正しくセットアップされていることを証明し、すべてのサービスと特性が接続時にリストされます。

過去数日間、問題をトラブルシューティングしましたが成功しませんでした。問題は、どのデバイスでエラーが発生しているのかを特定できず、切断の原因となっていることです。接続フェーズまたはサービスディスカバリフェーズ中にCBPeripheralManagerDelegateからのコールバックがないため、エラーが発生するポイントがわかりません(エラーがiOS側にある場合)。 Android側では、サービス検出を開始するためのメソッドが呼び出されますが、それらのコールバック「onServicesDiscovered」は呼び出されず、困惑します。 iOS側のBTLE通信の根底を掘り下げて、何が起こっているのかを確認し、どのエラーが発生しているかを判断する方法はありますか?

44
afrederick

これと同じ問題を抱えて、少なくとも1週間は既にこれを経験しています。私はすでにここで質問をしました、そして、私はすでに自分で答えました。主な問題はAndroid BUGの問題です。固定L2CAPチャンネルで許可されていないコマンドを送信しています。

しかし、Androidが通常の周辺BLEデバイスと通信している場合、それはかなりうまく機能します。実際、BLEサンプルは魅力のように機能します。問題は、たとえばiOSデバイスと通信するときです。接続が行われた後、接続パラメーターのネゴシエーションを開始します(このフェーズは通常のBLE周辺機器では発生しません)、これが問題が発生したときです。AndroidはiOSに不正なコマンドを送信します、iOSは接続をドロップします。

いくつかの問題は既にGoogleに報告されており、そのうちの1つは既に受け入れられており、すぐに作業を開始できることを願っています。

残念ながら、あなたができることは、次のAndroidリリースまで待つことです。とにかく、あなたがいくらかの光を作りたいなら、私のすべてのテスト文書で私の問題報告を見てもらうことを強くお勧めしますこの問題について。

リンクは次のとおりです。 https://code.google.com/p/Android/issues/detail?id=58725

28
edoardotognoni

比較的簡単な、簡単な作業例を作成し、Githubのオープンソースに含めました: https://github.com/GitGarage 。これまでのところ、Android Nexus 9およびiPhone 5sでのみテストされていますが、Nexus 6およびさまざまなiPhoneタイプでも動作するものと思われます。 1つのAndroidと1つの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)
                }
            }
        }
    }
}
15
omikes

クロスプラットフォーム間のRnD on BLEトピックの一部として、このスレッドにいくつかの情報を追加したいと思います。

Xiomi Mi A1(OSバージョンOreo、Android 8.0)では、周辺モードは問題なく動作しています。

IPhone 8とXiomi Mi A1でのRnDで見つかったスループットの観察結果はほとんどありませんが、最新のSamsung S8で使用されている他のカスタムAndroid OSで成熟する必要があります。以下のデータは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:Throughput-2.5 KiloBytes per sec。

  3. centralとしてのiPhone 8(BLE 5.0)およびPeripheralとしての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. centralとしてのiPhone 8(BLE 5.0)およびPeripheralとしてのiPhone 8 plus(BLE 5.0):MTU = 512:スループット-15.5キロバイト/秒.

5
Sudhin Philip

私はAndroid Centralと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クラシックスキャンを開始すると解決しました問題(startDiscovery)。