Wykrywanie zmiany nazwy urządzenia BLE w systemie Android
Mamy urządzenie BLE, nad którym pracujemy, które wysyła dane za pośrednictwem nazwy urządzenia. Urządzenie działa poprawnie i można zobaczyć, jak zmienia się poprawnie nazwy za pomocą aplikacji takiej jak nRF Connect. Jednak mamy trudności z robieniem tego samego w naszej własnej aplikacji na Androida. Możemy wykryć urządzenia dobrze, ale prawie nigdy nie wyjdą one poza oryginalne nazwy, które otrzymały.
Kod, od którego zacząłem, ma pętlę uruchamianą w onResume (), która skanuje za pomocą BluetoothLeScanner i funkcji 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);
}
}
I jest wykrywany w mLeScanCallback, który filtruje na podstawie urządzeń. Zasadniczo, jeśli wcześniej nie widział urządzenia, dodaje je do listy sygnałów nawigacyjnych. Ale jeśli widział to wcześniej, aktualizuje wartości za pomocą funkcji seen () Beacona z nazwą Beacon, która jest widoczna, ponieważ stamtąd będą pochodzić informacje. W obu przypadkach zaktualizuje swój adapter, aby wypełnić nowe informacje.
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();
}
}
}
}
}
}
});
}
};
Jednak nawet po zaktualizowaniu nazwy mLeScanCallback zwróci zawsze tylko oryginalną nazwę.
Po pewnym przeszukaniu tutaj, ciągle znajdowałem użycie funkcji fetchUuidsWithSdp () i intencji, takich jak ACTION_FOUND, ACTION_UUID, ACTION_DISCOVERY_FINISHED i ACTION_NAME_CHANGED, aby zobaczyć poprawną zmianę nazwy. Więc dodałem fetchUuidsWithSdp () do mLeScanCallback. Jednak chociaż spowodowałoby to uruchomienie intencji, nazwa nadal nie była aktualizowana. Próbowałem wywołać funkcję fetchUuidsWithSdp () w rzeczywistych zamiarach, ale to też nie pomogło. Co dziwne, ACTION_NAME_CHANGED od czasu do czasu uruchamiał się, gdy wyłączałem ekran telefonu lub oddalałem się wystarczająco od urządzenia BLE. Ale wszystko, co robi onPause (), to wywołanie super.onPause()
i BLEScan(false)
. A ponieważ to były rzeczy, które już robiłem w mojej pętli, nie byłem pewien, jak wprowadzić to do mojego kodu, gdy był aktywny.
Po dalszych poszukiwaniach stwierdziłem, że aby użyć funkcji fetchUuidsWithSdq (), musisz użyć funkcji startDiscovery () swojego adaptera Bluetooth. Więc zmieniłem BLEScan, aby go używać, całkowicie wyłączając 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);
}
}
Jednak podczas gdy intencje działały wcześniej, teraz nie były, mimo że znajdowały się w moim filtrze intencji. Po przeprowadzeniu dalszych poszukiwań znalazłem ludzi, którzy twierdzili, że startDiscovery () nie działa z urządzeniami Le. Więc szukałem tego, czego powinieneś zamiast tego użyć ... co sprowadziło mnie z powrotem do rozpoczęcia skanowania za pomocą BluetoothLeScanner. W tym momencie zdałem sobie sprawę, że zatoczyłem krąg i potrzebowałem pomocy.
Przeszedłem przez cały proces, ponieważ gdzieś po drodze coś przeoczyłem. Po prostu nie wiem, gdzie to było. Czy powinienem używać startScan ()? Czy nie używam poprawnie startDiscovery ()? A może jest jeszcze coś, czego powinienem używać w całości? Fakt, że ACTION_NAME_CHANGED jest uruchamiany od czasu do czasu, sprawia, że chcę do tego wrócić, ale jak sprawić, by działało przez cały czas, gdy urządzenie jest wybudzone?
Odpowiedzi
Myślę, że możesz po prostu mieć problem z buforowaniem na Androidzie. Zobacz tę odpowiedź tutaj, aby uzyskać możliwe rozwiązanie:https://stackoverflow.com/a/50745997/7473793
Chociaż nie znalazłem tego, co robiłem źle w moim oryginalnym kodzie (choć brzmi to tak, jakby Android mógł po prostu być zepsuty pod tym względem), znalazłem obejście tego problemu. NFC Toolbox NFC ma dostępny kod źródłowy i skanuje partiami z dodanymi filtrami, a także własny skaner Le z biblioteki skandynawskiej. Oto jak wygląda teraz moja funkcja skanowania:
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);
}
}
A moje wywołanie zwrotne wygląda następująco:
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();
}
}
}
}
}
}
});
}
Wydawało się, że to działa. Musisz jednak dodać następujące elementy do swoich zależności.
implementation 'no.nordicsemi.android.support.v18:scanner:1.4.2'
Następnie moja nazwa ACTION_NAME_CHANGE intencja jest uruchamiana prawidłowo, a dane są aktualizowane.
Nie jestem pewien, czy jest to inny sposób pobierania nazwy z wyniku, czy jest to skanowanie wsadowe. Ale jeśli nawet Nordic nie używa standardowej biblioteki BLE Androida, myślę, że jest to najlepsza droga.