Обнаружение изменения имени устройства BLE в Android

Dec 01 2020

У нас есть устройство BLE, над которым мы работаем, которое выводит данные через имя устройства. Устройство работает правильно, и можно увидеть, как правильно меняют имена, используя такое приложение, как nRF Connect. Однако нам сложно сделать то же самое в нашем собственном приложении для Android. Мы можем нормально обнаруживать устройства, но они почти никогда не изменят исходные имена, которые им были даны.

В коде, с которого я начал, есть цикл, который запускается в onResume (), который сканирует с помощью BluetoothLeScanner и функции 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);
    }
}

И это обнаруживается в mLeScanCallback, который фильтрует по устройствам. По сути, если он раньше не видел устройство, он добавляет его в список маяков. Но, если он видел это раньше, он обновляет значения с помощью функции Beacon visible () с именем Beacon, как видно, откуда будет поступать информация. В обоих случаях он обновит свой адаптер, чтобы заполнить новую информацию.

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

Однако даже после обновления имени mLeScanCallback вернет только исходное имя.

После некоторого поиска здесь я обнаружил, что я использовал функцию fetchUuidsWithSdp () и такие намерения, как ACTION_FOUND, ACTION_UUID, ACTION_DISCOVERY_FINISHED и ACTION_NAME_CHANGED, чтобы правильно увидеть изменение имени. Итак, я добавил fetchUuidsWithSdp () в mLeScanCallback. Однако, хотя это вызвало бы намерения, имя все еще не обновлялось. Я пробовал вызвать fetchUuidsWithSdp () в реальных намерениях, но это тоже не помогло. Как ни странно, ACTION_NAME_CHANGED иногда срабатывает, если я выключаю экран телефона или отхожу достаточно далеко от устройства BLE. Но все, что делает onPause (), - это вызов super.onPause()и BLEScan(false). И поскольку это были вещи, которые я уже делал в своем цикле, я не был уверен, как внести это в свой код, пока он не спал.

После дополнительных поисков я обнаружил, что для использования функции fetchUuidsWithSdq () вам необходимо использовать функцию startDiscovery () вашего BluetoothAdapter. Итак, я изменил BLEScan, чтобы использовать его, полностью исключив 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);
    }
}

Однако если раньше намерения срабатывали, то теперь их не было, хотя они были в моем фильтре намерений. После еще нескольких поисков я обнаружил, что некоторые люди говорят, что startDiscovery () не работает с устройствами Le. Итак, я искал то, что вы должны использовать вместо этого ... что вернуло меня к startScan с BluetoothLeScanner. В этот момент я понял, что пошел по кругу и нуждался в помощи.

Я просмотрел весь свой процесс, потому что где-то по ходу дела я что-то упустил. Я просто не знаю, где это было. Стоит ли использовать startScan ()? Я неправильно использую startDiscovery ()? Или есть что-то еще, что я должен полностью использовать? Тот факт, что ACTION_NAME_CHANGED срабатывает, иногда заставляет меня вернуться к этому, но как мне заставить его работать все время, когда устройство не спит?

Ответы

M.Kotzjan Dec 02 2020 at 21:16

Думаю у вас может просто проблема с кешированием на андроиде. См. Этот ответ здесь для возможного решения:https://stackoverflow.com/a/50745997/7473793

Seth Dec 03 2020 at 23:48

Хотя я не обнаружил, что я делал неправильно в исходном коде (хотя похоже, что Android может просто сломаться в этом отношении), я нашел обходной путь. NFC Toolbox от nRF имеет доступный исходный код и сканирует партиями с добавленными фильтрами, а также имеет собственный сканер Le из скандинавской библиотеки. Вот как теперь выглядит моя функция сканирования:

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

И мой обратный вызов выглядит так:

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

Похоже, это помогло. Однако вам необходимо добавить в свои зависимости следующее.

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

После этого мое имя ACTION_NAME_CHANGE срабатывает правильно, и данные обновляются.

Я не уверен, что это другой способ получения имени из результата или это пакетное сканирование. Но, если даже Nordic не использует стандартную библиотеку Android BLE, я думаю, это лучший способ.