Android에서 BLE 장치 이름 변경 감지

Dec 01 2020

우리는 장치의 이름을 통해 데이터를 출력하는 BLE 장치가 있습니다. 장치가 제대로 작동하고 nRF Connect와 같은 앱을 사용하여 이름이 제대로 변경되는 것을 볼 수 있습니다. 그러나 우리는 우리 자신의 안드로이드 앱에서 똑같은 일을하는 데 어려움을 겪고 있습니다. 우리는 장치를 잘 감지 할 수 있지만, 주어진 원래 이름을 지나치는 일은 거의 없습니다.

내가 시작한 코드에는 BluetoothLeScanner 및 startScan () 함수를 사용하여 스캔하는 onResume ()에서 시작되는 루프가 있습니다.

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의 이름으로 Beacon의 seen () 함수로 값을 업데이트합니다. 두 경우 모두 어댑터를 업데이트하여 새 정보를 채 웁니다.

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와 같은 인 텐트를 사용하여 이름이 제대로 변경되었는지 확인했습니다. 그래서 mLeScanCallback에 fetchUuidsWithSdp ()를 추가했습니다. 그러나 이것이 인 텐트를 트리거하는 동안 이름은 여전히 ​​업데이트되지 않았습니다. 실제 의도에서 fetchUuidsWithSdp () 호출을 시도했지만 도움이되지 않았습니다. 이상하게도 휴대 전화 화면을 끄거나 BLE 기기에서 충분히 멀리 떨어진 경우 ACTION_NAME_CHANGED가 가끔 실행됩니다. 그러나 onPause ()가하는 일은 모두 super.onPause()BLEScan(false). 그리고 이것들은 내가 이미 내 루프에서하고 있었기 때문에 깨어있는 동안 어떻게 이것을 내 코드로 가져 오는지 확신 할 수 없었습니다.

더 많은 검색 후 fetchUuidsWithSdq () 함수를 사용하려면 BluetoothAdapter의 startDiscovery () 함수를 사용해야합니다. 그래서 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 장치에서 작동하지 않는다고 말하는 사람들을 발견했습니다. 그래서 대신에 사용해야하는 것을 검색했습니다. 이로 인해 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가 고장난 것처럼 들리지만) 해결 방법을 찾았습니다. nRF의 NFC Toolbox는 소스 코드를 사용할 수 있으며 필터가 추가 된 일괄 스캔과 Nordic 라이브러리의 자체 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 라이브러리를 사용하지 않는다면 이것이 최선의 방법이라고 생각합니다.