Real-time Bluetooth SPP data transfer on Android only works 5 seconds

I have a bluetooth home device measuring ECG at a frequency of 500 Hz: every 2 ms the device sends 9 bytes of data (header, ECG measurement, footer). Thus, this is approximately 9 * 500 = 4.5 kB / s of data stream.

I have a C ++ Windows program capable of connecting a device and retrieving a data stream (displaying it with Qt / qwt). In this case, I use the Windows control panel to connect the device, and I connect it through the virtual COM port using the serial serial interface. This works fine, and I get a real-time data stream: I get a measuring point every 2 ms or so.

I ported the entire C ++ program to Android through QtCreator (Qt 5.3.2). I had problems in real time. The data stream was in real time for the first 5 seconds, and then the performance dropped sharply (see How to make good real-time data transfer using the Java Android SDK ).

Since I could solve this problem due to C ++ / Qt , I wrote a completely clean, clean Java / Android project using Eclipse . And he has the same problem !!!

Questions: Is something wrong with this code? Why do I get real-time data in just the first 5 seconds? What happens after 5 seconds of intensive use of BT on the Android platform and why does it slow down the reception of BT data?

Here is my Java program:

BluetoothHelper.java (with functions for connecting / disconnecting / reading and writing data:

package com.example.helloworld; import android.util.Log; import android.content.Context; import android.os.Bundle; import java.util.Locale; import java.util.concurrent.Semaphore; import java.lang.String; import java.lang.Thread; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; import java.text.SimpleDateFormat; import java.lang.InterruptedException; import android.app.Activity; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothManager; import android.util.SparseArray; import android.content.Intent; import android.content.IntentFilter; import android.content.BroadcastReceiver; import java.util.UUID; import java.util.Date; import java.util.Calendar; import java.util.Vector; import java.util.Set; import java.util.Arrays; public class BluetoothHelper { private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mDevice; private BluetoothSocket mSocket; private OutputStream mOutputStream; private InputStream mInputStream; private BroadcastReceiver mReceiver; private Activity myActivity; private Vector<BluetoothDevice> mDevices; private byte[] mHeader; private byte[] mFrame; public BluetoothHelper(Activity a) { myActivity = a; mHeader = new byte[3]; mFrame = new byte[256]; mDevices = new Vector(); } /* Check bluetooth is enabled, return "" if OK, else, return error string */ public String initializeBluetooth(){ String error = ""; System.out.println("Initializing bluetooth..."); mBluetoothManager = (BluetoothManager) myActivity.getSystemService(Context.BLUETOOTH_SERVICE); if ( mBluetoothManager == null ) { error = "Bluetooth manager is not found"; } else { mBluetoothAdapter = mBluetoothManager.getAdapter(); if( mBluetoothAdapter == null ) { error = "Bluetooth adapter is not found"; } else if( ! mBluetoothAdapter.isEnabled() ) { error = "Bluetooth adapter is off"; } else { System.out.println("Bluetooth successfully initialized"); return ""; } } return error; } private void addDevice( final BluetoothDevice device ) { mDevices.add(device); } public Vector<BluetoothDevice> getDevices() { return mDevices; } /* Clear previously detected device list */ public boolean clearDeviceList(){ // Clear old list mDevices.clear(); return true; } /* Fill local device list with paired devices */ public boolean addPairedDevices(){ //System.out.println("Entering addPairedDevices"); if( mBluetoothAdapter == null ) { System.out.println("No bluetooth adapter"); return false; } Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // If there are paired devices if (pairedDevices.size() > 0) { //System.out.println("Found paired devices"); // Loop through paired devices for (BluetoothDevice device : pairedDevices) { addDevice( device ); } } return true; } public String connectToDevice(final BluetoothDevice device) { if ( mDevice != null ) disconnectDevice(); if( mBluetoothAdapter == null || myActivity == null ) return "System not initialized or bluetooth not active"; if ( device.getBondState() != BluetoothDevice.BOND_BONDED ) { // TODO: find a way to do a synchronized bounding operation return "Device is not bonded"; } final boolean[] the_result = new boolean[1]; the_result[0] = false; final Semaphore mutex = new Semaphore(0); Runnable connectRunnable = new Runnable() { @Override public void run() { UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); try { mSocket = device.createInsecureRfcommSocketToServiceRecord( MY_UUID ); System.out.println("Created RFcomm socket"); mSocket.connect(); if ( mSocket.isConnected() ) { System.out.println("Connected RFcomm socket"); mOutputStream = mSocket.getOutputStream(); mInputStream = mSocket.getInputStream(); System.out.println("Retrieved output stream"); the_result[0] = true; } else { System.out.println("Failed to connect RFcomm socket"); } } catch (IOException e) { System.out.println("Failed to open RFcomm socket (createRfcommSocketToServiceRecord)"); System.out.println(e.toString()); } mutex.release(); } }; myActivity.runOnUiThread( connectRunnable ); // waiting for thread to be completed... try { mutex.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } if ( the_result[0] ) { System.out.println("Connection succeeded"); return ""; } else { System.out.println("Connection failed"); return "Failed to connect device"; } } /* Request to disconnect the device */ public boolean disconnectDevice(){ System.out.println("Disconnecting device..."); if ( mSocket != null ) { // block read/write mOutputStream = null; mInputStream = null; try { mSocket.close(); } catch( IOException e ) { e.printStackTrace(); return false; } mSocket = null; } mDevice = null; return true; } /* Send bytes to the connected device */ public boolean writeData( byte[] buffer ) { if( mOutputStream == null ) { System.out.println("No connection, can't send data"); } else { try { mOutputStream.write( buffer ); return true; } catch (IOException e) { System.out.println( "Failed to send data" ); e.printStackTrace(); } } return false; } public static String byteArrayToHex(byte[] a, int size) { StringBuilder sb = new StringBuilder(size * 5); for( int i = 0; i != size; ++i ) sb.append(String.format("0x%02x ", a[i] & 0xff)); return sb.toString(); } public int getBytesPending() { try { return mInputStream.available(); } catch (IOException e) { return 0; } } /* Non blocking read function. Read bytes from the connected device. * Return number of bytes read * return 0 if not enough bytes available * return -1 in case of error */ public int readData( byte[] buffer, int size, boolean blocking ) { if ( mInputStream == null ) { System.out.println("No connection, can't receive data"); } else { try { final boolean verbose = false; if ( blocking ) { if ( verbose ) System.out.println( "Blocking request of " + buffer.length + " byte(s)" ); int res = 0; int temp = 0; while ( true ) { temp = mInputStream.read( buffer, res, size - res ); res += temp; if ( res >= size ) { break; } else { if ( verbose ) System.out.println( "Received " + res + " byte(s) to far : " + byteArrayToHex(buffer,size) ); } try { Thread.sleep(10); } catch(InterruptedException ex) { } } if ( verbose ) System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) ); return res; } else { int available = mInputStream.available(); if ( verbose && available != 0 ) { Calendar c = Calendar.getInstance(); Date date = new Date(); c.setTime(date); c.get(Calendar.MILLISECOND); SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); String currentTime = sdf.format(date); System.out.println( currentTime + ":" + c.get(Calendar.MILLISECOND) + " - " + available + " bytes available, requested " + buffer.length ); } if ( available >= size ) { int res = mInputStream.read( buffer, 0, size ); // only call read if we know it not blocking if ( verbose ) System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) ); return res; } else { return 0; } } } catch (IOException e) { System.out.println( "Failed to read data...disconnected?" ); //e.printStackTrace(); } } return -1; } public byte[] readNextFrame( boolean blocking ) { if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length ) { int size = mHeader[2]; if ( size < 0 ) size = -size; if ( readData( mFrame, size, blocking ) == size ) { byte[] res = new byte[mHeader.length + size]; System.arraycopy(mHeader, 0, res, 0, mHeader.length); System.arraycopy(mFrame, 0, res, mHeader.length, size); return res; } } return null; } */ read frame but without allocating any memory, does not retur condumed bytes */ public boolean eatNextFrame( boolean blocking ) { if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length ) { int size = mHeader[2]; if ( size < 0 ) size = -size; if ( readData( mFrame, size, blocking ) == size ) { return true; } } return false; } public boolean startECG() { // some code sending instructions to configure my device } } 

The main Java file connecting and executing the receipt of 10 seconds:

  // Here is the code for Medoc: BluetoothHelper helper = new BluetoothHelper(this); String error = helper.initializeBluetooth(); if ( error.isEmpty() ) { if ( helper.addPairedDevices( ) ) { if ( !helper.getDevices().isEmpty() ) { if ( helper.getDevices().size() == 1 ) { BluetoothDevice device = helper.getDevices().firstElement(); error = helper.connectToDevice( device ); if ( error.isEmpty() ) { if ( helper.startECG() ) { // acquiere data for 10 seconds Date start = new Date(); Date end = new Date(); Date empty = null; int lastMinute = 0; int maxBufferSize = 0; boolean receivedData = false; while ( end.getTime() - start.getTime() < 10 * 1000 ) { int currentMinute = (int) (( end.getTime() - start.getTime() ) / 1000); if ( currentMinute != lastMinute ) { if ( receivedData ) System.out.println( "During second #" + lastMinute + " max buffer size was : " + maxBufferSize ); else System.out.println( "During second #" + lastMinute + " no data was received!" ); maxBufferSize = 0; receivedData = false; lastMinute = currentMinute; } if ( helper.eatNextFrame(false) ) { receivedData = true; } if ( helper.getBytesPending() == 0 ) { if ( empty == null ) { empty = new Date(); } } else { if ( empty != null ) { Date now = new Date(); int elapsed = (int) ( now.getTime() - empty.getTime() ); if ( elapsed > 100 ) System.out.println( "No pending data, during " + elapsed + "ms" ); empty = null; } } maxBufferSize = Math.max( helper.getBytesPending(), maxBufferSize ); end = new Date(); } AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage( "Done" ); dlgAlert.setPositiveButton("Ok",null); dlgAlert.create().show(); } else { error = "Failed to start ECG"; } helper.disconnectDevice(); } } else { error = "Too many devices found"; } } else { error = "No device found"; } } else { error = "Failed to scan for devices"; } } if ( !error.isEmpty() ) { AlertDialog.Builder dlgAlert2 = new AlertDialog.Builder(this); dlgAlert2.setMessage( error ); dlgAlert2.setPositiveButton("Ok",null); dlgAlert2.create().show(); } 

And here is the output of the program:

 12-01 14:12:51.755: I/System.out(15940): During second #0 max buffer size was : 63 12-01 14:12:52.755: I/System.out(15940): During second #1 max buffer size was : 133 12-01 14:12:53.755: I/System.out(15940): During second #2 max buffer size was : 66 12-01 14:12:54.755: I/System.out(15940): During second #3 max buffer size was : 61 12-01 14:12:55.755: I/System.out(15940): During second #4 max buffer size was : 129 12-01 14:12:56.705: I/System.out(15940): No pending data, during 501ms 12-01 14:12:56.755: I/System.out(15940): During second #5 max buffer size was : 939 12-01 14:12:57.755: I/System.out(15940): During second #6 max buffer size was : 980 12-01 14:12:58.755: I/System.out(15940): During second #7 max buffer size was : 1008 12-01 14:12:59.195: I/System.out(15940): No pending data, during 488ms 12-01 14:12:59.695: I/System.out(15940): No pending data, during 489ms 12-01 14:12:59.755: I/System.out(15940): During second #8 max buffer size was : 990 12-01 14:13:00.185: I/System.out(15940): No pending data, during 490ms 12-01 14:13:01.205: I/System.out(15940): Disconnecting device... 

As you can see, during the first 5 seconds the read buffer remains small, and there is no moment when the buffer is empty for more than 100 ms (see the output of the code "No data pending"). Then from the fifth second we:

  • start with long periods (~ 500 ms), where the read buffer remains empty (InputStream :: available () returns 0), even if my device constantly sends data to Android.
  • can see that the buffer size max increases significantly.

After the first 5 seconds of collecting data, it is as if the data is being buffered somewhere and becomes readable in blocks of ~ 500 ms by InputStream .....

Sometimes it can be even worse, no data received at all after 5 seconds:

 12-01 14:35:54.595: I/System.out(16386): During second #0 max buffer size was : 22 12-01 14:35:55.595: I/System.out(16386): During second #1 max buffer size was : 93 12-01 14:35:56.595: I/System.out(16386): During second #2 max buffer size was : 108 12-01 14:35:57.595: I/System.out(16386): During second #3 max buffer size was : 61 12-01 14:35:58.595: I/System.out(16386): During second #4 max buffer size was : 64 12-01 14:35:59.595: I/System.out(16386): During second #5 max buffer size was : 63 12-01 14:36:00.595: I/System.out(16386): During second #6 no data was received! 12-01 14:36:01.595: I/System.out(16386): During second #7 no data was received! 12-01 14:36:02.595: I/System.out(16386): During second #8 no data was received! 

Note. I tried to sleep a few seconds before creating a BluetoothHelper and before calling startECG() . The same behavior (deceleration slows down or stops after 5 seconds).

Edit: I am experiencing the following:

  • Phone Nexus 5, Android 4.4.2
  • Tablet Nexus 7, Android 4.4.2
  • Galaxy S4 with Android 4.4.2

But not on the S3 galaxy with the custom CyanogenMod 11 Android 4.4.2: streaming data seems perfect, without freezing after 5 seconds, and the data arrives in real time ...

Edit December 15th:

As suggested, I moved to a separate thread: Did BluetoothHelper implement Runnable and added these methods / attributes to the class:

 private int mFramesReceived; private long mLongestPause; public void clearReceived() { mFramesReceived = 0; mLongestPause = 0; } public int received() { return mFramesReceived; } public long longestPause() { return mLongestPause; } @Override public void run() { System.out.println( "Started thread" ); int lastSeconde = 0; long currentTimeMillis = System.currentTimeMillis(); long started = System.currentTimeMillis(); // Keep listening to the InputStream until an exception occurs while (true) { if ( eatNextFrame( true ) ) { //System.out.println( "Got some data" ); mLongestPause = Math.max( mLongestPause, System.currentTimeMillis() - currentTimeMillis ); currentTimeMillis = System.currentTimeMillis(); mFramesReceived++; int currentSeconde = (int) (( System.currentTimeMillis() - started ) / 1000); if ( currentSeconde != lastSeconde ) { if ( mFramesReceived != 0 ) System.out.println( "During second #" + lastSeconde + " max pause was : " + mLongestPause ); else System.out.println( "During second #" + lastSeconde + " no data was received!" ); clearReceived(); lastSeconde = currentSeconde; } } else { System.out.println( "Failed to get some data, connection closed?" ); break; } } } 

Then changed the caller to:

 if ( helper.startECG() ) { new Thread(helper).start(); try { Thread.sleep(10000); // wait 10 seconds } catch(InterruptedException ex) { Thread.currentThread().interrupt(); } AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage( "Done" ); dlgAlert.setPositiveButton("Ok",null); dlgAlert.create().show(); } else { error = "Failed to start ECG"; } helper.disconnectDevice(); 

And this does not fix the problem, here is the conclusion:

 During second #0 max pause was : 48 During second #1 max pause was : 45 During second #2 max pause was : 33 During second #3 max pause was : 35 During second #4 max pause was : 58 During second #5 max pause was : 498 During second #6 max pause was : 477 During second #7 max pause was : 480 During second #8 max pause was : 986 During second #9 max pause was : 497 
+5
source share
3 answers

This issue seems to be similar to the one reported here .

After 5 seconds, I had either a lost connection, or real-time streaming slows sharply.

As stated here, Android> 4.3 clearly does not like one-way communication exceeding 5 seconds. Therefore, I now send a dummy command to the device every 1 second (like "keep-alive"), and now Android is happy because it is not a one-way communication anymore ... and therefore streaming data is also good after the fifth second than before !

+4
source

Use the concept of streams to read and write bytes on peripheral devices at the same time. To solve the problem, use the data transfer example for Android Android. You are using a regular java class to send and receive data on another invalid device. You must use the Threading concept to send and receive data via Bluetooth.

see the link below for reading and writing data via Bluetooth.

http://developer.android.com/guide/topics/connectivity/bluetooth.html

+3
source

You should not rely on InputStream.available() to indicate how many bytes are available in the stream (see https://developer.android.com/reference/java/io/InputStream.html#available () for details). Since you know the exact size of your data packet (9 bytes), read 9 bytes to the buffer each time: mInputStream.read(buffer, 0, 9) .

With Bluetooth, it is difficult to guarantee binary delivery in real time, as there can be many reasons for delays (for example, increased distance between devices, obstacles, etc.). Thus, it is usually best to constantly call read and forward the received parts of the data to process the components. For example, in one of my projects, I implemented the Android Service , waiting for data packets from Bluetooth and notifying the user interface with the received data. You can implement Service or AsyncTask .

Another recommendation: avoid unnecessary memory allocations in the methods you often use (for example, readData ). You can measure elapsed time using System.currentTimeMillis() . Garbage collection can be one of the reasons for performance degradation.

+1
source

Source: https://habr.com/ru/post/1208041/


All Articles