web-dev-qa-db-fra.com

Android 4.3: Comment se connecter à plusieurs appareils Bluetooth Low Energy

Ma question est la suivante: Android 4.3 (client) peut-il avoir des connexions actives avec plusieurs périphériques BLE (serveurs)? Si oui, comment puis-je y arriver?

Ce que j'ai fait jusqu'à présent

J'essaie d'évaluer le débit que vous pouvez atteindre avec les API BLE et Android 4.3 BLE. En outre, j'essaie également de savoir combien d'appareils peuvent être connectés et actifs en même temps. J'utilise un Nexus 7 (2013), Android 4.4 en tant que maître et une télécommande TI CC2540 en tant qu'esclaves.

J'ai écrit un logiciel de serveur simple pour les esclaves, qui transmet 10 000 paquets de 20 octets via des notifications BLE. J'ai basé mon application Android sur le Application Accelerator du Bluetooth SIG.

Cela fonctionne bien pour un appareil et je peux atteindre un débit d'environ 56 kBits à un intervalle de connexion de 7,5 ms. Pour me connecter à plusieurs esclaves, j'ai suivi les conseils d'un employé nordique qui avait écrit dans la Nordic Developer Zone :

Oui, il est possible de gérer plusieurs esclaves avec une seule application. Vous devez gérer chaque esclave avec une instance BluetoothGatt. Vous aurez également besoin de BluetoothGattCallback spécifique pour chaque esclave auquel vous vous connectez.

Alors j'ai essayé ça et ça marche en partie. Je peux me connecter à plusieurs esclaves. Je peux aussi m'inscrire pour recevoir des notifications sur plusieurs esclaves. Le problème commence lorsque je commence le test. Je reçois d’abord des notifications de tous les esclaves, mais après quelques intervalles de connexion, seules les notifications d’un seul appareil arrivent. Après environ 10 secondes, les autres esclaves se déconnectent, car ils semblent avoir atteint le délai de connexion. Parfois, dès le début du test, je ne reçois que les notifications d'un esclave.

J'ai également essayé d'accéder à l'attribut lors d'une opération de lecture avec le même résultat. Après quelques lectures, seules les réponses d’un appareil sont apparues.

Je suis conscient que quelques questions similaires se posent sur ce forum: Android 4.3 prend-il en charge plusieurs connexions d’appareils BLE? , L’application native Android BLE GATT est-elle synchrone? Ou Ble connexion multiple . Mais aucune de ces réponses ne m'a clairement montré si c'était possible et comment le faire.

Je serais très reconnaissant pour les conseils.

34
Andreas Mueller

Je suppose que tout le monde qui ajoute des retards ne fait que permettre au système BLE de terminer l'action que vous avez demandée avant de soumettre une autre action. Le système BLE d'Android n'a aucune forme de file d'attente. Si tu fais 

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

alors la première opération d'écriture sera immédiatement remplacée par la seconde. Oui c'est vraiment stupide et la documentation devrait probablement en faire mention.

Si vous insérez une attente, la première opération est terminée avant la seconde. C'est un énorme bidouillage moche cependant. Une meilleure solution consiste à implémenter votre propre file d'attente (comme Google devrait l'avoir). Heureusement, les pays nordiques en ont publié un pour nous.

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

Edit: À propos, il s’agit du comportement universel des API BLE. WebBluetooth se comporte de la même manière (mais Javascript facilite son utilisation) et je pense que l'API BLE d'iOS se comporte également de la même manière.

21
Timmmm

Voir le bluetooth-lowenergy problème sur  Android : J'utilise toujours les retards.

Le concept: après chaque action majeure qui provoque le BluetoothGattCallback (par exemple, une détection, une découverte de service, une écriture, une lecture) est nécessaire. P.S. Consultez l'exemple de Google sur BLE exemple de niveau 19 de l'API pour la connectivité pour comprendre comment les diffusions doivent être envoyées et obtenir une compréhension générale, etc.

Premièrement, scan (ou scan ) pour BluetoothDevices, renseignez la connectionQueue avec les périphériques souhaités et appelez initConnection ().

Regardez l'exemple suivant.

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) {}
    }
}

Maintenant, si tout va bien, vous avez établi les connexions et BluetoothGattCallback.onConnectionStateChange (BluetoothGatt gatt, int status, int newState) a été appelé.

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);
}

P.S. sendBroadcast (intention) pourrait devoir être fait comme ceci:

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

Puis l’émission est reçue par 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
        }
        ....
    }
}

Après avoir fait ce que vous voulez dans le récepteur de radiodiffusion, voici comment je continue:

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){}
    }
}

De nouveau, après une découverte de service réussie, BluetoothGattCallback.onServicesDiscovered (...) est appelé. De nouveau, j’envoie une intention à BroadcastReceiver (cette fois-ci avec une chaîne d’action différente) et c’est maintenant que vous pouvez commencer à lire, écrire et activer des notifications/indications ...P.S. Si vous travaillez avec plusieurs périphériques, assurez-vous de commencer la lecture, l'écriture, etc., après que tous les périphériques ont signalé que leurs services ont été découverts.

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 aura toutes vos données entrantes du capteur BLE. Une bonne pratique consiste à envoyer une diffusion avec les données à votre BroadcastReceiver et à la gérer là-bas.

13
Rain

Je développe moi-même une application avec des fonctionnalités BLE. J'ai réussi à me connecter à plusieurs appareils et à activer les notifications en mettant en place des délais.

Donc, je fais un nouveau thread (afin de ne pas bloquer le thread d'interface utilisateur) et dans le nouveau thread se connecter et activer les notifications.

Par exemple, après BluetoothDevice.connectGatt (); appelez Thread.sleep ();

Et ajoutez le même délai pour les notifications lecture/écriture et activation/désactivation.

MODIFIER

Utilisez wait comme ça pour qu'Android ne réponde pas à l'ANR

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

La pluie est juste dans sa réponse, vous avez besoin de retards pour à peu près tout lorsque vous travaillez avec BLE sous Android. J'ai développé plusieurs applications avec et c'est vraiment nécessaire. En les utilisant, vous évitez beaucoup de crash.

Dans mon cas, j'utilise des délais après chaque commande de lecture/écriture. Ce faisant, vous vous assurez que vous recevez presque toujours la réponse du périphérique BLE. Je fais quelque chose comme ceci: (bien sûr, tout est fait dans un thread séparé pour éviter de trop travailler sur le thread principal)

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

ou:

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

C'est vraiment utile lorsque vous lisez/écrivez plusieurs caractéristiques à la suite ... Comme Android est assez rapide pour exécuter les commandes presque instantanément, si vous n'utilisez pas de délai entre elles, vous risquez d'obtenir des erreurs ou des valeurs incohérentes ...

J'espère que cela vous aidera, même si ce n'est pas exactement la réponse à votre question.

2
margabro

Malheureusement, les notifications dans la pile Android BLE actuelle sont un peu buggées. Il existe certaines limites codées en dur et j'ai constaté certains problèmes de stabilité, même avec un seul périphérique. (J'ai lu à un moment donné que vous ne pouviez avoir que 4 notifications ... pas sûr que ce soit pour tous les appareils ou par appareil. Essayer de trouver la source de cette information maintenant.)

Je voudrais essayer de passer à une boucle d'interrogation (par exemple, interroger les éléments en question 1/sec) et voir si vous trouvez votre stabilité augmente. J'envisagerais également de passer à un autre périphérique esclave (par exemple un MRH ou le SensorTag de TI) pour voir s'il existe peut-être un problème avec le code côté esclave (à moins que vous puissiez le tester sur iOS ou une autre plate-forme et confirmer que ce n'est pas partie du problème).

Edit: Référence de la limitation de notification

0
Ben Von Handorf