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

Android dropping BLE advertising packets

Hi there,

I'm new to Android BLE development and simply trying to read out a BLE temperature sensor that encodes the temp. in the service data from the advertising packets.
This works, however the update rate is very slow (~1min), while the two sensors send out an advertising packet every second. So it seems like a great deal of packets get dropped/scanner misbehaves.

When I look at the sensors with NRF connect, the update rate is perfect, so I must be doing something terribly wrong here?

I tried many different types of filtering (in the callback, before the callback,...) and scanning modes. Very little change..

Thanks a lot in advance, I'm learning a lot!

package com.portacapena.ble.grinn_ble;

import android.Manifest;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.ParcelUuid;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;

import static java.util.Locale.US;

public class MainActivity extends AppCompatActivity {

    BluetoothManager btManager;
    BluetoothAdapter btAdapter;
    BluetoothLeScanner btScanner;
    Button startScanningButton;
    Button stopScanningButton;
    TextView peripheralTextView;
    private final static int REQUEST_ENABLE_BT = 1;
    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        peripheralTextView = (TextView) findViewById(R.id.PeripheralTextView);
        peripheralTextView.setMovementMethod(new ScrollingMovementMethod());

        startScanningButton = (Button) findViewById(R.id.StartScanButton);
        startScanningButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                startScanning();
            }
        });

        stopScanningButton = (Button) findViewById(R.id.StopScanButton);
        stopScanningButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                stopScanning();
            }
        });
        stopScanningButton.setVisibility(View.INVISIBLE);

        btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
        btAdapter = btManager.getAdapter();
        btScanner = btAdapter.getBluetoothLeScanner();


        if (btAdapter != null && !btAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent,REQUEST_ENABLE_BT);
        }

        // Make sure we have access coarse location enabled, if not, prompt the user to enable it
        if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("This app needs location access");
            builder.setMessage("Please grant location access so this app can detect peripherals.");
            builder.setPositiveButton(android.R.string.ok, null);
            builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
                }
            });
            builder.show();
        }
        startScanning();
    }

    // Device scan callback.
    private ScanCallback leScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            //peripheralTextView.append("Device Name: " + result.getDevice().getName() + " rssi: " + result.getRssi() + "\n");
            ScanRecord scanRecord =  result.getScanRecord();
            String mac = result.getDevice().getAddress();
            //String deviceName = scanRecord.getDeviceName();

            byte[] serviceData = scanRecord.getServiceData(ParcelUuid.fromString("00001809-0000-1000-8000-00805f9b34fb"));


            Log.d("serviceData", serviceData.toString());

            StringBuilder hexOut = new StringBuilder();

            Log.d("Hex", hexOut.toString());

            long exponent = unsignedToSigned(serviceData[3],8);
            Log.d("exponent",Long.toString(exponent));
            long[] mant = new long[3];
            mant[0] = (serviceData[2] << 16) & 0xFFFFFF;
            mant[1] = (serviceData[1] << 8) & 0xFFFF;
            mant[2] = (serviceData[0]) & 0xFF;

            long mantissa =  unsignedToSigned(mant[0] + mant[1] + mant[2],24);

            Log.d("mantissa", Long.toString(mantissa));
            double temp  = mantissa * Math.pow(10,exponent);
            Log.d("Temperatuur",Double.toString(temp));
            peripheralTextView.append("Adres: " + mac + "   Temperatuur: " + String.format(Locale.US,"%.2f",temp) + "\n");

            // auto scroll for text view
            final int scrollAmount = peripheralTextView.getLayout().getLineTop(peripheralTextView.getLineCount()) - peripheralTextView.getHeight();
            // if there is no need to scroll, scrollAmount will be <=0
            if (scrollAmount > 0)
                peripheralTextView.scrollTo(0, scrollAmount);
        }

        public long unsignedToSigned(long unsigned, int size){

            long signed = 0;
            if ((unsigned & (1 << (size-1))) != 0)
            {
                signed = (-1) * (1 << (size - 1))  + unsigned ^ (1 << (size - 1)) ;
            }
            else {
                signed = unsigned;
            }

            return signed;
        }

    };

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_COARSE_LOCATION: {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    System.out.println("coarse location permission granted");
                } else {
                    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setTitle("Functionality limited");
                    builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
                    builder.setPositiveButton(android.R.string.ok, null);
                    builder.setOnDismissListener(new DialogInterface.OnDismissListener() {

                        @Override
                        public void onDismiss(DialogInterface dialog) {
                        }

                    });
                    builder.show();
                }
                return;
            }
        }
    }

    public void startScanning() {
        System.out.println("start scanning");
        //peripheralTextView.setText("");
        startScanningButton.setVisibility(View.INVISIBLE);
        stopScanningButton.setVisibility(View.VISIBLE);

        byte[] emptyData = new byte[0];
        byte[] mask = new byte[] {
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
        };

        final Vector<ScanFilter> filter = new Vector<ScanFilter>();
        ScanFilter.Builder builder = new ScanFilter.Builder().setServiceData(ParcelUuid.fromString("00001809-0000-1000-8000-00805f9b34fb"),emptyData,mask);//.setDeviceName("BTTEMP(MCP)");


        //builder.setServiceData(ParcelUuid.fromString("00001809-0000-1000-8000-00805f9b34fb"),emptyData);

        filter.add(builder.build());

        final ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
        scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
        scanSettingsBuilder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE);
        scanSettingsBuilder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
        scanSettingsBuilder.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT);


       AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                btScanner.startScan(filter, scanSettingsBuilder.build(), leScanCallback);
                Log.d("scan","Scan gestart");
            }
        });

    }

    public void stopScanning() {
        System.out.println("stopping scanning");
        peripheralTextView.append("Stopped Scanning");
        startScanningButton.setVisibility(View.VISIBLE);
        stopScanningButton.setVisibility(View.INVISIBLE);
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                btScanner.stopScan(leScanCallback);
            }
        });

    }



}

  • Hi,

    Here are my advices:

    (Related to your problem)
    1. Why do you start scanning in AsyncTask? You should avoid AsyncTasks in Activities as they may leak the Context. Just start scanning in the startScanning() method. The same with stopping scanning.
    2. How about setting just the SCAN_MODE_LOW_LATENCY and remove the 3 other settings. Use default values.
    3. Try scanning without filter and discard non-matching records. Just check if you get updates more often.
    (General)
    4. You startScanning() also if the Bluetooth is disabled or absent, or you don't have permission.
    5. After the permission has been granted you don't start scan.
    6. Keep the ParcelUuid as a constant, don't create a new instance each time you find a device.
    7. Use ScrollView with TextView inside instead of scrolling TextView.
    8. We recommend to use Android Scanner Compat Library. This brings same scanning API to older platforms.
    9. Use DialogFragment instead with AlertDialog, instead of just AlertDialog. Then it will not be dismissed on rotation.

Related