简体   繁体   中英

Detecting BLE Device name change in Android

We have a BLE device we are working on that outputs data via the device's name. The device is functioning properly and can be seen changing names properly using an app like nRF Connect. However, we are having a difficult time doing the same in our own Android app. We can detect the devices fine, but they will almost never move past the original names they were given.

The code I started with has a loop that is started in onResume() that scans using a BluetoothLeScanner and the startScan() function.

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

And it is detected in mLeScanCallback that filters based on devices. Basically, if it hasn't seen a device before, it adds it to a list of Beacons. But, if it has seen it before, it updates the values with the Beacon's seen() function with the Beacon's name as seen as that's where the information will come from. In both cases, it will update its adapter to populate the new information.

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

However, even after updating the name, mLeScanCallback will only ever return the original name.

After some searching through here, the thing I kept finding was to use the function fetchUuidsWithSdp() and intents such as ACTION_FOUND, ACTION_UUID, ACTION_DISCOVERY_FINISHED, and ACTION_NAME_CHANGED to see the name change properly. So, I added fetchUuidsWithSdp() to mLeScanCallback. However, while this would trigger the Intents, the name still wasn't updating. I tried calling fetchUuidsWithSdp() in the actual intents, but that didn't help either. Strange enough, ACTION_NAME_CHANGED would occasionally fire if I turned off my phone's screen or walked far away enough from the BLE device. But, all onPause() does is call super.onPause() and BLEScan(false) . And, since these were things I was already doing in my loop, I wasn't sure how to bring this into my code while it was awake.

After more searching, I found that, to use the fetchUuidsWithSdq() function, you need to use the startDiscovery() function of your BluetoothAdapter. So, I changed BLEScan to use it, cutting out mLeScanCallback completely.

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

However, while the Intents were firing before, they weren't now, even though they were in my intent filter. After doing some more searching, I found some people saying that startDiscovery() doesn't work with Le devices. So, I searched for what you're supposed to use instead...which brought me all the way back to startScan with BluetoothLeScanner. At that point, I'd realized I'd gone in a circle and needed help.

I went over my entire process because, somewhere along the lines, I missed something. I just don't know where it was. Should I be using startScan()? Am I not using startDiscovery() properly? Or is there something else I should be using entirely? The fact that ACTION_NAME_CHANGED fired occasionally makes me want to go back to that, but how do I get it working at all times when the device is awake?

I think you might simply have a problem with caching on android. See this answer here for a possible solution: https://stackoverflow.com/a/50745997/7473793

While I didn't find what I was doing wrong in my original code (though it sounds like Android might just be broken in this respect), I did find a workaround. nRF's NFC Toolbox has it's source code available and scans in batches with added filters as well as it's own Le scanner from a Nordic library. Here's what my scan function looks like now:

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

And my callback looks like this:

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

That seemed to do the trick. You need to add the following to your dependencies, though.

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

After that, my name ACTION_NAME_CHANGE intent is triggering properly and data is updating.

I'm not sure if it's the different way of retrieving the name from the result or if it's batch scanning. But, if even Nordic isn't using the standard Android BLE library, I'm guessing this is the best way to go.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM