Проблема при использовании RxBleClient для сканирования устройств BLE, находящихся в фоновом режиме

Я сканирую устройства BLE, созданные устройством iOS. Затем я подключаюсь к конкретному сервису и читаю конкретную характеристику. Он отлично работает, когда на переднем плане находится приложение iOS, в котором есть служба GATT. Но при скрытии серверного приложения iOS клиент Android перестает обнаруживать устройства BLE GATT.

public static ScanFilter[] getFilters(UUID serviceUuid) {
   ...
    filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuid)).build());
    return filters.toArray(new ScanFilter[filters.size()]);
}
public static ScanSettings getScanSettings() {
    return new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
            .build();
}

Приложение BLE Scanner успешно обнаруживает скрытый сервер GATT

Обновления Вот часть кода фильтров.

public final class ScannerUtil {
public static final List<UUID> BEACON_UUUIDs = Arrays.asList(
...........

        UUID.fromString("a8427a96-70bd-4a7e-9008-6e5c3d445a2b"));


public static ScanSettings getScanSettings() {
    return new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_BALANCED) // change if needed
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
            .build();
}

public static ScanFilter[] getFilters(UUID serviceUuid) {
    List<ScanFilter> filters = Stream.of(BEACON_UUUIDs)
            .map(iBeaconScanFilter::setScanFilter)
            .collect(toList());
    filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuid)).build());
    return filters.toArray(new ScanFilter[filters.size()]);
}

}

Полный код класса сканера приведен ниже:

public class BLEGlobalScanner {
    private final ScannerConfiguration configuration;
    private final Context context;
    private final RxBleClient rxBleClient;
    private final Map<String, String> devicesMap = new HashMap<>();
    private final Map<String, DeviceApoloBeacon> beaconsMap = new HashMap<>();
    private final ScanFilter[] scanFilter;
public BLEGlobalScanner(ScannerConfiguration configuration, Context context) {
    this.configuration = configuration;
    this.context = context;
    this.rxBleClient = RxBleClient.create(context);
    this.scanFilter = getFilters(configuration.beacons(), configuration.gattServer().server());
}

public Observable<BluetoothDeviceApolo> start() {
    return bluetoothEnableObservable(context).switchMap(aBoolean -> startScanner())
            .filter(Optional::isPresent)
            .map(Optional::get);
}

private Observable<Optional<BluetoothDeviceApolo>> startScanner() {
    return rxBleClient.scanBleDevices(getScanSettings(), scanFilter)
            .buffer(2, TimeUnit.SECONDS)
            .flatMap(rxBleDevices -> Observable.from(rxBleDevices)
                    .distinct(scanResult -> scanResult.getBleDevice().getMacAddress())
                    .concatMap(this::handleDevices)
                    .map(Optional::of))
            .observeOn(mainThread())
            .onErrorResumeNext(throwable -> {
                Timber.e(throwable, "startScanner");
                return Observable.just(Optional.empty());
            })
            .onExceptionResumeNext(Observable.just(Optional.empty()))
            .retry();
}

private Observable<BluetoothDeviceApolo> handleDevices(ScanResult scanResult) {
    if (beaconsMap.containsKey(scanResult.getBleDevice().getMacAddress())) {
        return Observable.fromCallable(() -> beaconsMap.get(scanResult.getBleDevice().getMacAddress()))
                .map(beacon -> beacon.toBuilder()
                        .lastSeen(System.currentTimeMillis())
                        .rssi(scanResult.getRssi())
                        .build());
    } else {
        return handleBeacon(scanResult)
                .map(device -> (BluetoothDeviceApolo) device)
                .switchIfEmpty(
                        handleDevice(scanResult).map(deviceApolo -> (BluetoothDeviceApolo) deviceApolo)
                );
    }
}

private Observable<DeviceApoloBeacon> handleBeacon(ScanResult scanResult) {
    return Observable.fromCallable(() -> scanResult.getScanRecord().getManufacturerSpecificData(COMPANY_ID_APPLE))
            .filter(bytes -> bytes != null)
            .filter(bytes -> DeviceApoloBeacon.requiredManufactureSize == bytes.length)
            .map(bytes -> DeviceApoloBeacon.builder()
                    .manufacturedData(bytes)
                    .lastSeen(System.currentTimeMillis())
                    .rssi(scanResult.getRssi())
                    .build())
            .filter(beacon -> configuration.beacons().contains(beacon.uuuid()))
            .doOnNext(beacon -> beaconsMap.put(scanResult.getBleDevice().getMacAddress(), beacon));
}


private Observable<DeviceApolo> handleDevice(ScanResult scanResult) {
    final RxBleDevice rxBleDevice = scanResult.getBleDevice();
    if (devicesMap.containsKey(rxBleDevice.getMacAddress())) {
        return Observable.fromCallable(() -> devicesMap.get(rxBleDevice.getMacAddress()))
                .timestamp()
                .map(deviceStr -> DeviceApolo.create(deviceStr.getValue(), deviceStr.getTimestampMillis(), scanResult.getRssi()));
    } else {
        return readCharacteristic(rxBleDevice, scanResult.getRssi());
    }
}

private Observable<DeviceApolo> readCharacteristic(RxBleDevice rxBleDevice, final int rssi) {
    return rxBleDevice.establishConnection(false)
            .compose(new ConnectionSharingAdapter())
            .switchMap(rxBleConnection -> rxBleConnection.readCharacteristic(configuration.gattServer().characteristic()))
            .map(String::new)
            .doOnNext(s -> devicesMap.put(rxBleDevice.getMacAddress(), s))
            .timestamp()
            .map(deviceStr -> DeviceApolo.create(deviceStr.getValue(), deviceStr.getTimestampMillis(), rssi))
            .retry();
}
}

person Alex    schedule 02.11.2017    source источник
comment
Что вы имеете в виду под when hide iOS server app?   -  person Dariusz Seweryn    schedule 02.11.2017
comment
Это означает - когда приложение iOS, которое создает GATT-сервер, переходит в фоновый режим   -  person Alex    schedule 03.11.2017
comment
Предполагая, что Android client stops detect BLE GATT devices означает, что он не излучает устройство при сканировании - уверены ли вы, что BLE Scanner app не просто кэширует результаты сканирования с того времени, когда приложение iOS было на переднем плане?   -  person Dariusz Seweryn    schedule 03.11.2017
comment
Нет, потому что, когда iOS возвращается с фона на передний план, rx-сканер снова обнаруживает это   -  person Alex    schedule 03.11.2017
comment
Не могли бы вы дать больше информации обо всех ScanFilter, которые вы используете, и на какой именно поток вы подписаны?   -  person Dariusz Seweryn    schedule 03.11.2017
comment
По-прежнему отсутствует информация о том, что происходит. Не могли бы вы показать полную цепочку Observable, которой вы .subscribe()? Журналы тоже могут помочь.   -  person Dariusz Seweryn    schedule 13.11.2017
comment
@DariuszSeweryn Я добавил полный код класса сканера   -  person Alex    schedule 18.11.2017
comment
У меня есть идея - прежде чем я отправлю ответ - помогает ли выключение / включение адаптера BT решить проблему?   -  person Dariusz Seweryn    schedule 19.11.2017
comment
К сожалению, это не помогает   -  person Alex    schedule 19.11.2017
comment
Не могли бы вы установить RxBleLog.setLogLevel(RxBleLog.VERBOSE) и опубликовать вывод logcat?   -  person Dariusz Seweryn    schedule 20.11.2017
comment
Какие теги вы хотите видеть?   -  person Alex    schedule 21.11.2017
comment
В основном, какие устройства сканируются и какие операции выполняет библиотека.   -  person Dariusz Seweryn    schedule 21.11.2017


Ответы (1)


Нет проблем ни с вашим кодом, ни с RxAndroidBle. Периферийное устройство, которое вы сканируете на предмет со временем обнаружен.

Вы столкнулись с предполагаемым поведением приложения iOS в фоновом режиме - ищите The bluetooth-peripheral Background Execution Mode на официальный справочный сайт.

В справке говорится:

Если все рекламные приложения работают в фоновом режиме, частота, с которой ваше периферийное устройство отправляет рекламные пакеты, может уменьшиться.

Если вы собираетесь использовать приложение iOS в качестве периферийного устройства, вы мало что можете сделать (проверьте документацию). В качестве альтернативы вы можете проверить, можете ли вы реализовать свое периферийное устройство на другом оборудовании (я не знаю вашего точного варианта использования, но вопрос не в этом).

С уважением

person Dariusz Seweryn    schedule 04.01.2018