Détection du changement de nom de l'appareil BLE sous Android

Dec 01 2020

Nous travaillons sur un appareil BLE qui génère des données via le nom de l'appareil. L'appareil fonctionne correctement et peut être vu changer de nom correctement à l'aide d'une application telle que nRF Connect. Cependant, nous avons du mal à faire de même dans notre propre application Android. Nous pouvons bien détecter les appareils, mais ils ne dépasseront presque jamais les noms d'origine qui leur ont été donnés.

Le code avec lequel j'ai commencé a une boucle qui est lancée dans onResume () qui scanne à l'aide d'un BluetoothLeScanner et de la fonction startScan ().

public void BLEScan(final boolean enable){

    final int SCAN_PERIOD = 12000;
    final BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

    if (enable) {
        Log.d(TAG, "Starting Scan");
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(false);
                invalidateOptionsMenu();
            }
        }, SCAN_PERIOD);
        
        bluetoothLeScanner.flushPendingScanResults(mLeScanCallback);
        bluetoothLeScanner.startScan(mLeScanCallback);
    } else {
        Log.d(TAG, "Stopping Scan");
        bluetoothLeScanner.flushPendingScanResults(mLeScanCallback);
        bluetoothLeScanner.stopScan(mLeScanCallback);
        
        mHandler.removeCallbacksAndMessages(null);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(true);
            }
        }, SCAN_PERIOD);
    }
}

Et il est détecté dans mLeScanCallback qui filtre en fonction des périphériques. Fondamentalement, s'il n'a jamais vu d'appareil auparavant, il l'ajoute à une liste de balises. Mais, s'il l'a déjà vu, il met à jour les valeurs avec la fonction saw () du Beacon avec le nom du Beacon vu que c'est de là que les informations viendront. Dans les deux cas, il mettra à jour son adaptateur pour renseigner les nouvelles informations.

private ScanCallback mLeScanCallback =
    new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            final ScanResult res2 = result;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    BluetoothDevice device = res2.getDevice();
                    String address = device.getAddress();
                    
                    if (device.getName() != null){
                        if (device.getName().contains("ABT:")){
                            if (!mLeBeacons.containsKey(address)){
                                BleBeacon beacon = new BleBeacon(device.getName(), address);
                                bleList.add(beacon);
                                adapter.notifyDataSetChanged();

                                mLeBeacons.put(device.getAddress(), beacon);
                            } else {
                                for (int x = 0; x < bleList.size(); x++){
                                    if (device.getAddress().equals(bleList.get(x).getMAC())){
                                        bleList.get(x).seen(device.getName());
                                        adapter.notifyDataSetChanged();
                                    }
                                }
                            }
                        }
                    }
                }
            });
        }
    };

Cependant, même après la mise à jour du nom, mLeScanCallback ne retournera jamais que le nom d'origine.

Après quelques recherches ici, la chose que j'ai continué à trouver était d'utiliser la fonction fetchUuidsWithSdp () et des intentions telles que ACTION_FOUND, ACTION_UUID, ACTION_DISCOVERY_FINISHED et ACTION_NAME_CHANGED pour voir le nom changer correctement. J'ai donc ajouté fetchUuidsWithSdp () à mLeScanCallback. Cependant, bien que cela déclenche les intentions, le nom n'était toujours pas mis à jour. J'ai essayé d'appeler fetchUuidsWithSdp () dans les intentions réelles, mais cela n'a pas aidé non plus. Curieusement, ACTION_NAME_CHANGED se déclencherait parfois si j'éteignais l'écran de mon téléphone ou si je m'éloignais suffisamment de l'appareil BLE. Mais, tout ce que fait onPause () est d'appeler super.onPause()et BLEScan(false). Et, puisque c'était des choses que je faisais déjà dans ma boucle, je ne savais pas comment intégrer cela dans mon code pendant qu'il était éveillé.

Après plus de recherche, j'ai trouvé que, pour utiliser la fonction fetchUuidsWithSdq (), vous devez utiliser la fonction startDiscovery () de votre BluetoothAdapter. J'ai donc changé BLEScan pour l'utiliser, supprimant complètement mLeScanCallback.

public void BLEScan(final boolean enable){
    final int SCAN_PERIOD = 12000;
    if (enable) {
        Log.d(TAG, "Starting Scan");
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(false);
                invalidateOptionsMenu();
            }
        }, SCAN_PERIOD);

        if (mBluetoothAdapter.isDiscovering()){
            mBluetoothAdapter.cancelDiscovery();
        }
        mBluetoothAdapter.startDiscovery();
    } else {
        Log.d(TAG, "Stopping Scan");
        mBluetoothAdapter.cancelDiscovery();

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(true);
            }
        }, SCAN_PERIOD);
    }
}

Cependant, alors que les intentions se déclenchaient auparavant, elles ne l'étaient pas maintenant, même si elles étaient dans mon filtre d'intention. Après avoir fait quelques recherches supplémentaires, j'ai trouvé des personnes disant que startDiscovery () ne fonctionne pas avec les appareils Le. J'ai donc cherché ce que vous êtes censé utiliser à la place ... ce qui m'a ramené à startScan avec BluetoothLeScanner. À ce moment-là, j'avais réalisé que j'étais allé en cercle et que j'avais besoin d'aide.

J'ai passé en revue tout mon processus parce que, quelque part le long des lignes, j'ai raté quelque chose. Je ne sais juste pas o where c'était. Dois-je utiliser startScan ()? Est-ce que je n'utilise pas correctement startDiscovery ()? Ou y a-t-il autre chose que je devrais utiliser entièrement? Le fait qu'ACTION_NAME_CHANGED ait été déclenché occasionnellement me donne envie d'y revenir, mais comment le faire fonctionner à tout moment lorsque l'appareil est réveillé?

Réponses

M.Kotzjan Dec 02 2020 at 21:16

Je pense que vous pourriez simplement avoir un problème avec la mise en cache sur Android. Voir cette réponse ici pour une solution possible:https://stackoverflow.com/a/50745997/7473793

Seth Dec 03 2020 at 23:48

Bien que je n'ai pas trouvé ce que je faisais mal dans mon code d'origine (bien qu'il semble qu'Android puisse être cassé à cet égard), j'ai trouvé une solution de contournement. La boîte à outils NFC de nRF a son code source disponible et scanne par lots avec des filtres ajoutés ainsi que son propre scanner Le d'une bibliothèque nordique. Voici à quoi ressemble ma fonction de scan maintenant:

    public void BLEScan(final boolean enable){

        final BluetoothLeScannerCompat bluetoothLeScanner = BluetoothLeScannerCompat.getScanner();
        final ScanSettings settings = new ScanSettings.Builder()
                .setLegacy(false)
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(1000).setUseHardwareBatchingIfSupported(false).build();
        final List<ScanFilter> filters = new ArrayList<>();
        ParcelUuid Uuid = new ParcelUuid(UUID.fromString(uuidString));
        filters.add(new ScanFilter.Builder().setServiceUuid(Uuid).build());

        if (enable) {
            Log.d(TAG, "Starting Scan");
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    BLEScan(false);
                    invalidateOptionsMenu();
                }
            }, SCAN_PERIOD);

            bluetoothLeScanner.startScan(filters, settings, mLeScanCallback);
        } else {
            BLEService.refreshGatt();
            Log.d(TAG, "Stopping Scan");
            bluetoothLeScanner.stopScan(mLeScanCallback);

            mHandler.removeCallbacksAndMessages(null);

            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    BLEScan(true);
                }
            }, SCAN_PERIOD);
        }
    }

Et mon rappel ressemble à ceci:

    private no.nordicsemi.android.support.v18.scanner.ScanCallback mLeScanCallback =
            new no.nordicsemi.android.support.v18.scanner.ScanCallback() {
                @Override
                public void onBatchScanResults(@NonNull final List<no.nordicsemi.android.support.v18.scanner.ScanResult> results) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            for (final no.nordicsemi.android.support.v18.scanner.ScanResult result : results){
                                BluetoothDevice device = result.getDevice();

                                if (device != null){
                                    String address = device.getAddress();
                                    String name = result.getScanRecord() != null ? result.getScanRecord().getDeviceName() : null;

                                    if (!mLeBeacons.containsKey(address)) {
                                        BleBeacon beacon = new BleBeacon(device.getName(), address);
                                        bleList.add(beacon);
                                        adapter.notifyDataSetChanged();

                                        mLeBeacons.put(device.getAddress(), beacon);
                                    } else {
                                        for (int x = 0; x < bleList.size(); x++){
                                            if (device.getAddress().equals(bleList.get(x).getMAC())){
                                                bleList.get(x).seen(device.getName());
                                                adapter.notifyDataSetChanged();
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    });
                }

Cela semblait faire l'affaire. Vous devez cependant ajouter ce qui suit à vos dépendances.

implementation 'no.nordicsemi.android.support.v18:scanner:1.4.2'

Après cela, mon intention de nom ACTION_NAME_CHANGE se déclenche correctement et les données sont mises à jour.

Je ne sais pas si c'est la manière différente de récupérer le nom du résultat ou s'il s'agit d'une analyse par lots. Mais, si même Nordic n'utilise pas la bibliothèque Android BLE standard, je suppose que c'est la meilleure façon de procéder.