web-dev-qa-db-ja.com

Android 4.3:複数のBluetooth Low Energyデバイスに接続する方法

私の質問は:Android 4.3(クライアント)は複数のBLEデバイス(サーバー)とアクティブに接続できますか?もしそうなら、どうすればそれを達成できますか?

これまでにやったこと

BLEおよびAndroid 4.3 BLE APIを使用して達成できるスループットを評価しようとしています。さらに、同時に接続してアクティブにできるデバイスの数も調べてみます。 Nexus 7(2013)を使用し、Android 4.4をマスターとして、TI CC2540キーフォブをスレーブとして使用します。

スレーブ用の簡単なサーバーソフトウェアを作成し、BLE通知を介して10000個の20バイトパケットを送信しました。 Androidアプリは、Bluetooth SIGの Application Accelerator に基づいています。

1台のデバイスでうまく機能し、接続間隔7.5ミリ秒で約56 kBitsのペイロードスループットを達成できます。複数のスレーブに接続するために、 Nordic Developer Zone で書いた北欧の従業員のアドバイスに従いました。

はい、1つのアプリで複数のスレーブを処理できます。 1つのBluetoothGattインスタンスで各スレーブを処理する必要があります。接続するスレーブごとに特定のBluetoothGattCallbackも必要になります。

だから私はそれを試してみましたが、それは部分的に機能します。複数のスレーブに接続できます。複数のスレーブで通知を登録することもできます。問題は、テストを開始すると始まります。最初はすべてのスレーブから通知を受け取りますが、2つの接続間隔の後、1つのデバイスからの通知だけが送信されます。約10秒後、他のスレーブは接続タイムアウトに達したように見えるため、切断します。時々、テストの開始直後から1つのスレーブからの通知のみを受け取ります。

また、読み取り操作で属性にアクセスしようとしても同じ結果になりました。数回の読み取りの後、1台のデバイスからの答えだけが届きました。

このフォーラムには、似たような質問がいくつかあります。 Android 4.3は複数のBLEデバイス接続をサポートしますか?ネイティブAndroidがありますBLE GATT実装の同期性? または Ble複数接続 。しかし、この答えのどれも、それが可能かどうか、そしてそれをどのように行うかについて私に明確にしたものではありません。

アドバイスをいただければ幸いです。

35
Andreas Mueller

遅延を追加するすべての人が、BLEシステムが別のアクションを送信する前に要求したアクションを完了することを許可しているだけだと思います。 AndroidのBLEシステムには、キューイングの形式がありません。もしあなたがそうするなら

BluetoothGatt g;
g.writeDescriptor(a);
g.writeDescriptor(b);

その後、最初の書き込み操作はすぐに2番目の操作で上書きされます。はい、それは本当に愚かです、そして、ドキュメントはおそらく実際にこれについて言及するべきです。

待機を挿入すると、最初の操作が完了してから2番目の操作が実行されます。しかし、それは巨大ないハックです。より良い解決策は、独自のキューを実装することです(Googleが持つべきです)。幸いなことに、Nordicは私たちのためにそれをリリースしました。

https://github.com/NordicSemiconductor/Puck-central-Android/tree/master/PuckCentral/app/src/main/Java/no/nordicsemi/puckcentral/bluetooth/gatt

編集:ところで、これはBLE APIの普遍的な動作です。 WebBluetoothは同じように動作しますが(Javascriptを使用すると使いやすくなります)、iOSのBLE APIも同じように動作すると思います。

21
Timmmm

bluetooth-lowenergy 問題の再検討 Android :まだ遅延を使用しています。

概念: BluetoothGattCallback (concontion、service discovery、write、readなど)を引き起こす主要なアクションのたびに、ディールが必要です。追伸GoogleのBLEの例をご覧ください 接続性のAPIレベル19サンプル ブロードキャストの送信方法を理解し、一般的な理解などを得るために...

まず、 scan (または scan )BluetoothDevicesの場合、connectionQueueに目的のデバイスを入れて呼び出しますinitConnection()

次の例をご覧ください。

private Queue<BluetoothDevice> connectionQueue = new LinkedList<BluetoothDevice>();

public void initConnection(){
    if(connectionThread == null){
        connectionThread = new Thread(new Runnable() {
            @Override
            public void run() {
                connectionLoop();
                connectionThread.interrupt();
                connectionThread = null;
            }
        });

        connectionThread.start();
    }
}

private void connectionLoop(){
    while(!connectionQueue.isEmpty()){
        connectionQueue.poll().connectGatt(context, false, bleInterface.mGattCallback);
        try {
            Thread.sleep(250);
        } catch (InterruptedException e) {}
    }
}

これですべてが正常であれば、接続が完了し、 BluetoothGattCallback.onConnectionStateChange(BluetoothGatt gatt、int status、int newState) が呼び出されました。

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        switch(status){
            case BluetoothGatt.GATT_SUCCESS:
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    broadcastUpdate(BluetoothConstants.ACTION_GATT_CONNECTED, gatt);
                }else if(newState == BluetoothProfile.STATE_DISCONNECTED){
                    broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED, gatt);
                }
                break;
        }

    }
protected void broadcastUpdate(String action, BluetoothGatt gatt) {
    final Intent intent = new Intent(action);

    intent.putExtra(BluetoothConstants.EXTRA_MAC, gatt.getDevice().getAddress());

    sendBroadcast(intent);
}

追伸sendBroadcast(intent)は次のように実行する必要がある場合があります。

Context context = activity.getBaseContext();
context.sendBroadcast(intent);

次に、ブロードキャストは BroadcastReceiver.onReceive(...) によって受信されます

public BroadcastReceiver myUpdateReceiver = new BroadcastReceiver(){

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if(BluetoothConstants.ACTION_GATT_CONNECTED.equals(action)){
            //Connection made, here you can make a decision: do you want to initiate service discovery.
            // P.S. If you are working with multiple devices, 
            // make sure that you start the service discovery 
            // after all desired connections are made
        }
        ....
    }
}

放送受信機であなたがやりたいことをした後、ここに私が続ける方法があります:

private Queue<BluetoothGatt> serviceDiscoveryQueue = new LinkedList<BluetoothGatt>();

private void initServiceDiscovery(){
    if(serviceDiscoveryThread == null){
        serviceDiscoveryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                serviceDiscovery();

                serviceDiscoveryThread.interrupt();
                serviceDiscoveryThread = null;
            }
        });

        serviceDiscoveryThread.start();
    }
}

private void serviceDiscovery(){
    while(!serviceDiscoveryQueue.isEmpty()){
        serviceDiscoveryQueue.poll().discoverServices();
        try {
            Thread.sleep(250);
        } catch (InterruptedException e){}
    }
}

繰り返しますが、サービスの検出に成功すると、 BluetoothGattCallback.onServicesDiscovered(...) が呼び出されます。繰り返しますが、私はBroadcastReceiverにインテントを送信し(今回は異なるアクション文字列を使用)、通知/表示の読み取り、書き込み、有効化を開始できるようになりました...P.S。複数のデバイスを使用している場合は、すべてのデバイスがサービスの発見を報告した後に、読み取り、書き込みなどを開始してください。

private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();

private void startThread(){

    if(initialisationThread == null){
        initialisationThread = new Thread(new Runnable() {
            @Override
            public void run() {
                loopQueues();

                initialisationThread.interrupt();
                initialisationThread = null;
            }
        });

        initialisationThread.start();
    }

}

private void loopQueues() {

    while(!characteristicReadQueue.isEmpty()){
        readCharacteristic(characteristicReadQueue.poll());
        try {
            Thread.sleep(BluetoothConstants.DELAY);
        } catch (InterruptedException e) {}
    }
    // A loop for starting indications and all other stuff goes here!
}

BluetoothGattCallbackには、BLEセンサーからのすべての着信データが含まれます。データを含むブロードキャストをBroadcastReceiverに送信し、そこで処理することをお勧めします。

13
Rain

私は自分でBLE機能を備えたアプリを開発しています。複数のデバイスに接続して通知をオンにする方法は、遅延を実装することでした。

そこで、(UIスレッドをブロックしないように)新しいスレッドを作成し、新しいスレッドで接続して通知をオンにします。

たとえば、BluetoothDevice.connectGatt();の後Thread.sleep()を呼び出します。

また、読み取り/書き込み通知と有効化/無効化通知に同じ遅延を追加します。

[〜#〜] edit [〜#〜]

Android ANRを発生させないように、このようにwaitを使用します

public static boolean waitIdle() {
        int i = 300;
        i /= 10;
        while (--i > 0) {
            if (true)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        }

        return i > 0;
    }
7
Rain

彼の答えは雨です。AndroidでBLEを使用する場合、ほとんどすべてに遅延が必要です。それでいくつかのアプリを開発しましたが、それは本当に必要です。それらを使用することで、多くのクラッシュを回避できます。

私の場合、すべての読み取り/書き込みコマンドの後に遅延を使用しています。そうすることで、BLEデバイスからほとんど常に応答を受け取ることができます。私はこのようなことをします:(もちろん、メインスレッドでの多くの作業を避けるために、すべてが別のスレッドで行われます)

 readCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }
 myChar.getValue();

または:

 myChar.setValue(myByte);
 writeCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }

これは、複数の特性を連続して読み書きする場合に非常に便利です... As Androidは、コマンドをほぼ瞬時に実行するのに十分な速さです。エラーまたはインコヒーレントな値を取得...

それがあなたの質問に対する正確な答えではない場合でも、それが役立つことを願っています。

2
kodartcha

残念ながら、現在のAndroid BLEスタックの通知は少しバグがあります。ハードコードされた制限がいくつかあり、単一のデバイスでも安定性の問題がいくつか見つかりました。通知は4つしかありません...それがすべてのデバイスにわたるのか、デバイスごとにあるのかはわかりません。その情報のソースを今すぐ見つけようとしています。)

ポーリングループに切り替えて(たとえば、問題のアイテムを1秒でポーリングする)、安定性が向上するかどうかを確認します。また、別のスレーブデバイス(HRMまたはTI SensorTagなど)への切り替えを検討して、おそらくスレーブ側のコードに問題があるかどうかを確認します(iOSまたは別のプラットフォームに対してテストして、そうでないことを確認できない限り)問題の一部)。

Edit通知制限のリファレンス

0
Ben Von Handorf