Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ble scanning callback only get called several times then stopped

I have 2 phones with Android 5.0.2, they both installed the latest Radius Beacon's App: Locate Beacon, meanwhile, I turned on 2 IBeacon sender, and can see the RSSI keep changing in both phone with the App.

But when I tried to write some sample code to simulate above situation, I found the ble scan callback always stop get called after called 2 or 3 times, I initially suspect the 'Locate Beacon' may use different way, so I tried with 2 kinds of API, one is for old 4.4, and another is the new way introduced in android 5, but both the same behavior(but all running on android 5).

the 4.4 one:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter;
private static final String LOG_TAG = "BleCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        calledTimes++;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                calledTimesTextView.setText(Integer.toString(calledTimes));
            }
        });
        Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    mBluetoothAdapter.startLeScan(mLeScanCallback);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

And the 5.0.2:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mLescanner;
private ScanCallback mLeScanCallback;
private static final String LOG_TAG = "BleFingerprintCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    this.mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    this.mLescanner = this.mBluetoothAdapter.getBluetoothLeScanner();

    ScanSettings bleScanSettings = new ScanSettings.Builder().setScanMode(
            ScanSettings.SCAN_MODE_LOW_LATENCY).build();

    this.mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            calledTimes++;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    calledTimesTextView.setText(Integer
                            .toString(calledTimes));
                }
            });
            Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
        }

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

        }

        @Override
        public void onScanFailed(int errorCode) {
        }
    };
    this.mLescanner.startScan(null, bleScanSettings, this.mLeScanCallback);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

They are very simple and just show a counter in UI, proved finally always stopped at 2 or 3.

I've played this ble advertising receiving before on a SamSung note 2 with android 4.4 device, it works perfectly, the callback get called every second. then anyone can help? why Radius' Locate Beacon works well here?

like image 789
Shawn Avatar asked Apr 19 '15 14:04

Shawn


1 Answers

Different Android devices behave differently when scanning for connectable BLE advertisements. On some devices (e.g. the Nexus 4), the scanning APIs only get one callback per scan for transmitters sending a connectable advertisement, whereas they get a scan callback for every advertisement for non-connectable advertisements. Other devices (e.g. the Nexus 5) provide a scan callback every single advertisement regardless of whether it is connectable.

The Locate app you mention uses the open source Android Beacon Library to detect beacons. It is built on top of the same scanning APIs you show in your question, but it gets around this problem by defining a scan period (1.1 seconds by default in the foreground) and stopping and restarting a scan at this interval. Stopping and restarting the scan causes Android to send a new callback.

A few other notes here:

  • This issue of getting multiple scan callbacks for connectable devices applies to both the 4.x and 5.x scanning APIs.

  • It is unclear whether the difference in delivering scan callbacks for connectable advertisements on different devices is due to Android firmware differences or bluetooth hardware chipset differences.

  • There doesn't seem to be a way to detect if a device requires a scan restart to get additional callbacks for connectable advertisements, so if you are targeting a wide variety of devices, you need to plan to stop and restart scanning.

  • Using Android's raw scanning APIs is a great way to understand how BLE beacons work. But there are lots of complexities with working with BLE beacons (this is just one example) which is why using a SDK like the Android Beacon Library is a good choice to keep you from pulling your hair out.

Full disclosure: I am the author of the Locate app in the lead developer on the Android Beacon Library open source project.

like image 82
davidgyoung Avatar answered Sep 18 '22 13:09

davidgyoung