Detectando el cambio de nombre del dispositivo BLE en Android

Dec 01 2020

Tenemos un dispositivo BLE en el que estamos trabajando que genera datos a través del nombre del dispositivo. El dispositivo funciona correctamente y se puede ver cambiando los nombres correctamente utilizando una aplicación como nRF Connect. Sin embargo, estamos teniendo dificultades para hacer lo mismo en nuestra propia aplicación de Android. Podemos detectar bien los dispositivos, pero casi nunca pasarán de los nombres originales que se les dieron.

El código con el que comencé tiene un bucle que se inicia en onResume () que escanea usando un BluetoothLeScanner y la función 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);
    }
}

Y se detecta en mLeScanCallback que filtra según los dispositivos. Básicamente, si no ha visto un dispositivo antes, lo agrega a una lista de Beacons. Pero, si lo ha visto antes, actualiza los valores con la función seen () del Beacon con el nombre del Beacon como se ve, ya que de ahí vendrá la información. En ambos casos, actualizará su adaptador para completar la nueva información.

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

Sin embargo, incluso después de actualizar el nombre, mLeScanCallback solo devolverá el nombre original.

Después de buscar un poco por aquí, lo que seguí encontrando fue usar la función fetchUuidsWithSdp () e intenciones como ACTION_FOUND, ACTION_UUID, ACTION_DISCOVERY_FINISHED y ACTION_NAME_CHANGED para ver el cambio de nombre correctamente. Entonces, agregué fetchUuidsWithSdp () a mLeScanCallback. Sin embargo, si bien esto activaría los Intents, el nombre aún no se actualizaba. Intenté llamar a fetchUuidsWithSdp () en las intenciones reales, pero eso tampoco ayudó. Por extraño que parezca, ACTION_NAME_CHANGED se disparaba ocasionalmente si apagaba la pantalla de mi teléfono o me alejaba lo suficiente del dispositivo BLE. Pero, todo lo que hace onPause () es llamar super.onPause()y BLEScan(false). Y, dado que estas eran cosas que ya estaba haciendo en mi ciclo, no estaba seguro de cómo incorporar esto a mi código mientras estaba despierto.

Después de buscar más, descubrí que, para usar la función fetchUuidsWithSdq (), debe usar la función startDiscovery () de su BluetoothAdapter. Entonces, cambié BLEScan para usarlo, eliminando mLeScanCallback por completo.

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

Sin embargo, mientras que los Intents disparaban antes, ahora no, a pesar de que estaban en mi filtro de intenciones. Después de buscar un poco más, encontré a algunas personas que decían que startDiscovery () no funciona con dispositivos Le. Entonces, busqué lo que se supone que debes usar en su lugar ... lo que me trajo todo el camino de regreso a startScan con BluetoothLeScanner. En ese momento, me di cuenta de que había ido en círculo y necesitaba ayuda.

Repasé todo mi proceso porque, en algún momento, me perdí algo. Simplemente no sé dónde estaba. ¿Debería usar startScan ()? ¿No estoy usando startDiscovery () correctamente? ¿O hay algo más que debería usar por completo? El hecho de que ACTION_NAME_CHANGED se haya disparado ocasionalmente me da ganas de volver a eso, pero ¿cómo puedo hacer que funcione en todo momento cuando el dispositivo está activo?

Respuestas

M.Kotzjan Dec 02 2020 at 21:16

Creo que simplemente podría tener un problema con el almacenamiento en caché en Android. Vea esta respuesta aquí para una posible solución:https://stackoverflow.com/a/50745997/7473793

Seth Dec 03 2020 at 23:48

Si bien no encontré lo que estaba haciendo mal en mi código original (aunque parece que Android podría estar roto a este respecto), encontré una solución. NFC Toolbox de nRF tiene su código fuente disponible y escanea en lotes con filtros agregados, así como su propio escáner Le de una biblioteca nórdica. Así es como se ve mi función de escaneo ahora:

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

Y mi devolución de llamada se ve así:

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

Eso pareció funcionar. Sin embargo, debe agregar lo siguiente a sus dependencias.

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

Después de eso, mi intención de nombre ACTION_NAME_CHANGE se activa correctamente y los datos se actualizan.

No estoy seguro de si es la forma diferente de recuperar el nombre del resultado o si se trata de un escaneo por lotes. Pero, si incluso Nordic no está usando la biblioteca BLE estándar de Android, supongo que esta es la mejor manera de hacerlo.