Android BLE notifies you of packet loss

I am working on a hardware device that sends a continuous stream of data through BLE to an Android app. The android application receives this data in the form of GATT notifications, then processes this data and stores it in the database.

The project configuration details are as follows:

  • Phone - Moto E 1st Generation
  • Android version - Android 5.1 - Lollipop
  • iOS - iPhone 4 and 5 tested on iOS 7 and 8
  • Equipment - CC2541
  • Connection_Interval: 40 ms (installed in the firmware of the hardware).
  • Packets sent for the interval between connections: 4 (installed in the firmware of the hardware).

PROBLEM

When data is transferred from a hardware device to a BLE data capture application running on an Android phone, all data packets are not received. It receives only about 35-45 packets, while the expected number of packets is 50.

What is more surprising is that when we used the BLE packet sniffer, there was a perfect match between the data sniffed and displayed on the Android phone (which is incomplete / incorrect data). This makes me think that the hardware behaves differently when connected to an Android phone and does not send all the data.

When we use the same equipment with the iOS BLE data capture application, the data is received correctly.

I am puzzled and don't know about this behavior of BLE data in Android. How can an application on an iOS device correctly record all data, while an application on an Android phone cannot correctly record data?

Has anyone encountered such a packet loss / invalid data issue when using the BLE application on Android? Any inputs are welcome. Thank you so much for your help in advance.

The Android application uses the standard BLE code to connect to the device via BLE. Below is the Android code I am using:

import android.annotation.TargetApi; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.nfc.Tag; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.widget.Toast; import java.util.ArrayList; import java.util.List; @TargetApi(21) public class BLETestActivity extends Activity { private BluetoothAdapter mBluetoothAdapter; private int REQUEST_ENABLE_BT = 1; private Handler mHandler; private static final long SCAN_PERIOD = 10000; private BluetoothLeScanner mLEScanner; private ScanSettings settings; private List<ScanFilter> filters; private BluetoothGatt mGatt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bletest); mHandler = new Handler(); if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, "BLE Not Supported", Toast.LENGTH_SHORT).show(); finish(); } final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); } @Override protected void onResume() { super.onResume(); if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } else { if (Build.VERSION.SDK_INT >= 21) { Log.i("innnnn","spinnnn - " + Build.VERSION.SDK_INT); mLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); settings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build(); filters = new ArrayList<ScanFilter>(); } scanLeDevice(true); } } @Override protected void onPause() { super.onPause(); if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { scanLeDevice(false); } } @Override protected void onDestroy() { if (mGatt == null) { return; } mGatt.close(); mGatt = null; super.onDestroy(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_CANCELED) { //Bluetooth not enabled. finish(); return; } } super.onActivityResult(requestCode, resultCode, data); } private void scanLeDevice(final boolean enable) { if (enable) { mHandler.postDelayed(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mLEScanner.stopScan(mScanCallback); } } }, SCAN_PERIOD); if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mLEScanner.startScan(filters, settings, mScanCallback); } } else { if (Build.VERSION.SDK_INT < 21) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mLEScanner.stopScan(mScanCallback); } } } private ScanCallback mScanCallback = null;/* new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { Log.i("callbackType", String.valueOf(callbackType)); Log.i("result", result.toString()); BluetoothDevice btDevice = result.getDevice(); connectToDevice(btDevice); } @Override public void onBatchScanResults(List<ScanResult> results) { for (ScanResult sr : results) { Log.i("ScanResult - Results", sr.toString()); } } @Override public void onScanFailed(int errorCode) { Log.e("Scan Failed", "Error Code: " + errorCode); } };*/ private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { Log.i("onLeScan", device.toString()); connectToDevice(device); } }); } }; public void connectToDevice(BluetoothDevice device) { if (mGatt == null) { mGatt = device.connectGatt(this, false, gattCallback); scanLeDevice(false);// will stop after first device detection } } private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.i("onConnectionStateChange", "Status: " + status); switch (newState) { case BluetoothProfile.STATE_CONNECTED: Log.i("gattCallback", "STATE_CONNECTED"); gatt.discoverServices(); break; case BluetoothProfile.STATE_DISCONNECTED: Log.e("gattCallback", "STATE_DISCONNECTED"); break; default: Log.e("gattCallback", "STATE_OTHER"); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { List<BluetoothGattService> services = gatt.getServices(); // Log.i("onServicesDiscovered", services.toString()); for(int i=0;i<services.size();i++) { List<BluetoothGattCharacteristic> charList = services.get(i).getCharacteristics(); for (int j = 0; j < charList.size();j++) { BluetoothGattCharacteristic characteristic = charList.get(j); if (characteristic.getUuid().compareTo(Constants.HEART_DATA_UUID) == 0) { Log.i(BLETestActivity.class.getSimpleName(), "Characteristic UUID is " + characteristic.getUuid()); mGatt.setCharacteristicNotification(characteristic, true); BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Constants.HEART_DATA_DESCRIPTOR_UUID); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mGatt.writeDescriptor(descriptor); } } } } public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // Retrieve the byte values // Convert them to hex values byte[] data = characteristic.getValue(); // The following array contains data in the follwing form: // 0-1 - running counter counts // 2-4, 5-7, 8-10 - Sample 1 data for channel 1, channel2 and channel 3 // 11-13, 14-16, 17-19 - Sample 2 data for channel 1, channel 2 and channel 3 // Log.i(TAG," ------ " + Thread.currentThread().getId()); String heartBeatDataInHex = bytesToHex(data); // An error packet is received after every 17 samples. // Checking to make sure that this is not an error packet if (!(heartBeatDataInHex.substring(4, 10).equalsIgnoreCase("FFFFFF") && heartBeatDataInHex.substring(26, 40).equalsIgnoreCase("FFFFFFFFFFFFFF"))) { Log.i("testdata", heartBeatDataInHex + " ---- " + Thread.currentThread().getId()); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.i("onCharacteristicRead", characteristic.toString()); gatt.disconnect(); } }; private String bytesToHex(byte[] bytes) { final char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } } 
+6
source share
1 answer

I had a similar problem! I did some testing and I determined that the minimum connection interval is 48 ms with the android level api 18. ( connection interval for BLE on the Galaxy S3 Android 4.3 ).

Positive decision:

  • Make the connection interval slower (less than 48 ms).

  • From api level 21 to the top level, you can change the CONNECTION_PRIORITY Android developer to change the interval connection, but this implies a larger amount of energy consumption.

+3
source

All Articles