This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Long running Android scanning in background.

I read the discussion in the following link, but my application is different so I want to ask for advice on how to proceed.

https://devzone.nordicsemi.com/f/nordic-q-a/50642/background-operation-of-ble-library-with-android-8---request-for-example

I am making a wearable device that is encapsulated in silicone, there is no way to charge or replace the battery. It only needs to update the android (and ios) phone once every 2 minutes but the phone needs to hear all the updates even when the screen is off. A cloud service will update other phones that are out of reach. 

I've got a prototype on an nrf52840 dongle. I also have an android app which was modified from the blinky example to only scan and do android notifications by reading the values advertised by the device. I calculated that I would get 3+ years from a single CR2032 assuming I use an insight ISP1807 chip and advertise with +8 TX for a full second, only every 2 minutes. Meaning 10 adverts repeated with a 100ms duration to give a 1 second advertising burst. The device then sleeps for 119 seconds and the process repeats.

My problem is with the behaviour of android since it seems to reduce scanning on background tasks. This seems to make either beacon devices and connection-less devices, or android, a poor choice since I believe they will stop communicating effectively.

To get background scanning to work at all, I created an IntentService that looks like this. I am not an android expert but I have a broad programming background (20+ years)

public class MyScanningIntentService extends IntentService
{
//    public static final String EXTRA_PARAM2 = "no.nordicsemi.android.blinky.extra.PARAM2";
    Context mContext;

    public MyScanningIntentService()
    {
        super("MyScanningIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent)
    {
        mContext = this;
        if (intent != null)
        {
            final ScanSettings settings = new ScanSettings.Builder()
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)// SCAN_MODE_BALANCED
                    .setReportDelay(500)
                    .setUseHardwareBatchingIfSupported(false)
                    .build();

            final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
            final List<ScanFilter> scanFilters = new ArrayList<>();
            // My manufacturer id is 0xFFFF during dev. 
            // This seems necessary or the background scan doesn't work at all on my samsung s5
            ScanFilter scanFilter = new ScanFilter.Builder()
                    .setManufacturerData(0xFFFF, null)      
                    .build();
            scanFilters.add(scanFilter);
            scanner.startScan(scanFilters , settings, scanCallback);
        }
    }

    private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(final int callbackType, @NonNull final ScanResult result) {
            CheckScanDevice(result);
        }

        @Override
        public void onBatchScanResults(@NonNull final List<ScanResult> results) {

            for (int zzz = 0; zzz < results.size(); zzz++)
            {
                ScanResult result = results.get(zzz);
                CheckScanDevice(result);
            }
        }

        @Override
        public void onScanFailed(final int errorCode) {
            // TODO This should be handled
        }
    }        
    
    private void CheckScanDevice(ScanResult scanResult)
    {
    //Read advert data using scanRecord.getManufacturerSpecificData(0xFFFF) 
    // Raise notification when necessary
    }

    };

In order to reduce battery usage on the android (and iOS) I was going to use a timer to start and stop scanning because I know that each device will advertise every 2 minutes. So I only need to scan again for each device during its advertising period which is every 2 minutes. I wanted to support many devices on each phone so I planned to do a continuous foreground scan for 2 minutes to learn the timing of each of the devices and then to only scan to coincide with each devices advert time. (I hope you understand my clumsy wording!)

I do not have an android 8 device to test on. 

  1. I am not sure what SDK version to target. I want to support as many android phones as possible.
  2. Will I have fewer problems if I establish a connection to the phones? It seems like scannign is the problem but I am not 100% confident about this.
    1. I think this will increase battery usage on the device.
    2. It might reduce the number of phones I support
  3. Does my scanning code look appropriate? I think I already see reduced scanning behaviour on my android Samsung S5. 
  4. Should I make my app replace the wallpaper or something more drastic to ensure the phone keeps scanning?

Any advice is appreciated

Parents
  • Hi Jason, 

    I think keeping the device and the phone in connection could be a better idea. Scanning continuously (or with timing like what you described) consume the phone's power. And on some phones with combo chip the antenna is shared between Wifi and Bluetooth making it harder to utilize two of them at the same time. 

    Regarding your questions:

    1. Target the latest one, and set minimum to the lowest possible (depends on the features you use) 

    2. Yes. Using a connection would be more stable and you can guarantee that the phone won't kill the scanning service.

    For example you can configure a connection with 0.5 second interval and 1 minutes timeout. You can configure the slave latency so that you only need to wakeup and response to the central every 30 seconds. I think it would consume much less compare to your plan of sending 10 advertising packet every 2 minutes (each advertising event would require 3 packets sent) 

    Keeping a connection to a phone is quite basic and I don't think you would have problem with most phones. 

    3. I have forwarded your code to our app team, here is their feedback:

    Scanning is battery hungry on the phones as well. New Android versions don't allow for long lasting background scan without a filter.

    Connection is much better approach for all use cases event determining location (beacons).

    Regarding his code:

    1. Use Service, not IntentService
    2. No need to keep the context, service IS a context
    3. With useHardwareBatchingIfSupported(false) they are not using offloaded batching, the library emulates it using cpu, which drains battery
    4. For bg scanning they should use low power mode, not low patency
    5. They should use scanning with an intent, not a callback.

    4. No. Better with scanning with an intent or at least a filter and low power mode. Newer phones may kill scanning after a few minutes. 

Reply
  • Hi Jason, 

    I think keeping the device and the phone in connection could be a better idea. Scanning continuously (or with timing like what you described) consume the phone's power. And on some phones with combo chip the antenna is shared between Wifi and Bluetooth making it harder to utilize two of them at the same time. 

    Regarding your questions:

    1. Target the latest one, and set minimum to the lowest possible (depends on the features you use) 

    2. Yes. Using a connection would be more stable and you can guarantee that the phone won't kill the scanning service.

    For example you can configure a connection with 0.5 second interval and 1 minutes timeout. You can configure the slave latency so that you only need to wakeup and response to the central every 30 seconds. I think it would consume much less compare to your plan of sending 10 advertising packet every 2 minutes (each advertising event would require 3 packets sent) 

    Keeping a connection to a phone is quite basic and I don't think you would have problem with most phones. 

    3. I have forwarded your code to our app team, here is their feedback:

    Scanning is battery hungry on the phones as well. New Android versions don't allow for long lasting background scan without a filter.

    Connection is much better approach for all use cases event determining location (beacons).

    Regarding his code:

    1. Use Service, not IntentService
    2. No need to keep the context, service IS a context
    3. With useHardwareBatchingIfSupported(false) they are not using offloaded batching, the library emulates it using cpu, which drains battery
    4. For bg scanning they should use low power mode, not low patency
    5. They should use scanning with an intent, not a callback.

    4. No. Better with scanning with an intent or at least a filter and low power mode. Newer phones may kill scanning after a few minutes. 

Children
  • Thank you for the excellent response. I will begin making the necessary changes. 

  • Hi Hung,

    I've been busy creating a new Android+iOS app that uses google Flutter and google Firestore cloud backend. The cloud database overcomes the range problems I was having. My device is in direct body contact so it sometimes gets very low signal range due to the signal being absorbed by the body.

    I also made a new device prototype with the example provided in examples\ble_peripheral\ble_app_hrs.  I then made another version based on examples\ble_peripheral\experimental\ble_app_multiperipheral. I thought it would be nice to allow the device to maintain connections with several phones simultaneously, (it's not crucial to the product.)

    I notice the 2 examples are quite different. ble_app_hrs uses the advertising module (ble_advertising_init + peer manager etc) while the ble_app_multiperipheral example calls the soft device functions directly (sd_ble_gap_adv_start etc) . I'm not sure which is better to proceed with.

    The reason I am writing to you is because in your reply you said :-

    For example you can configure a connection with 0.5 second interval and 1 minutes timeout. You can configure the slave latency so that you only need to wakeup and response to the central every 30 seconds. 

    Can you tell me what parameters I should use to get connections like this? Either for the ble_app_hrs example or for the bel_app_multiperipheral example (or another example!)

    I've been reading the design guidelines from Apple and the parameters seem crucial to achieving long battery life. 

    I assume I just need to set the right values for these #defines (from the ble_app_multiperipheral example)

    #define APP_ADV_INTERVAL                64                                      /**< The advertising interval (in units of 0.625 ms; this value corresponds to 40 ms). */
    #define APP_ADV_DURATION                BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED   /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
    
    #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(100, UNIT_1_25_MS)        /**< Minimum acceptable connection interval (0.5 seconds). */
    #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(200, UNIT_1_25_MS)        /**< Maximum acceptable connection interval (1 second). */
    #define SLAVE_LATENCY                   0                                       /**< Slave latency. */
    #define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)         /**< Connection supervisory time-out (4 seconds). */
    
    #define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(20000)                  /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (15 seconds). */
    #define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(5000)                   /**< Time between each call to sd_ble_gap_conn_param_update after the first call (5 seconds). */
    #define MAX_CONN_PARAMS_UPDATE_COUNT    3                                       /**< Number of attempts before giving up the connection parameter negotiation. */
    
    
    
    
    
    

    I've already added some test code to create a timer to call advertising_start() intermittently. Assuming that this would be better than constant advertising. I was going to set the timer to advertise once every 2 minutes just to avoid wasting energy but I'm not sure what is best. Any advice is greatly appreciated. 

    Thanks for your help

    -Jason

Related