0
votes

I am scanning BLE devices which are created by iOS device. Then I connect to specific service and read specific characteristic. It works perfect when iOS app which has GATT service is in the foreground. But when hide iOS server app, Android client stops detect BLE GATT devices.

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 app successfully sees hidden GATT server

Updates Here is filters code part

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

}

Full scanner class code is below:

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();
}
}
1
What do you mean by when hide iOS server app?Dariusz Seweryn
That means - when iOS application whitch creates GATT server goes to backgroundAlex
Assuming that Android client stops detect BLE GATT devices means that it does not emit the device when scanning—are you sure that the BLE Scanner app does not simply cache the scan results from the time the iOS app was in foreground?Dariusz Seweryn
No, because when iOS backs from background to foreground rx scanner again detects itAlex
Could you give more info about all the ScanFilters you use and what is the exact flow which you subscribe to?Dariusz Seweryn

1 Answers

0
votes

There is no problem with your code nor there is an issue with the RxAndroidBle. The peripheral you are scanning for is discovered eventually.

What you are experiencing is the intended behaviour of iOS app in the background mode—search for The bluetooth-peripheral Background Execution Mode on the official reference site.

The reference states:

If all apps that are advertising are in the background, the frequency at which your peripheral device sends advertising packets may decrease.

If you intend to use the iOS application as the peripheral then there is not much you can do (check the documentation). Alternatively you can check if you can implement your peripheral on a different hardware (I do not know your exact use-case—but it is not the point of this question).

Best Regards