Why some android devices receive the data from a 10 Hz Bluetooth GPS modul (NMEA) not every ~100ms but more like 200-300ms?

291 Views Asked by At

I'm developing an GPS Bluetooth App (NMEA parsing from a 10 Hz GPS modul) and I have the following problem:

Some devices/Android versions receive the bluetooth NMEA data not like every ~100ms (in a 10Hz GPS modul case) but more like ~200-300ms and I get the data for many timestamps at once. This is ok for postprocessing but for a real time view where I want to update the activity with the data this is not great.

Bad Example: Jerky Bluetooth Data Receive

Here is an example from a console output of the NMEA data over the readLine() function, where it's not working properly (just compare the output console time to the UTC time of the GPS):

Console Output Time     GPS Time
13:35:11.555            NMEA UTC  123618.277
13:35:11.555            NMEA UTC  123618.377
13:35:11.805            NMEA UTC  123618.477 <--- in a TextView this would be not visible
13:35:11.805            NMEA UTC  123618.577 <--- in a TextView this would be visible for only 5ms
13:35:11.810            NMEA UTC  123618.677 <--- this value would be visible in a TextView because there is a long pause (245ms)
13:35:12.055            NMEA UTC  123618.777 <--- not visible
13:35:12.055            NMEA UTC  123618.877 <--- visible for 250 ms
13:35:12.305            NMEA UTC  123618.977

bad

So you see the console time i.e. "13:35:11.805" twice and a very short time later "13:35:11.810". This mean if I use this data for a TextView I will just see the last line "123618.677" in it and then there is a long pause and I would see "123618.877". Basically the readLine() function will be called like 2-3 times very fast. Then there is a pause of 200-300ms and again the same thing happen. No data in between is visible. It is a jerky updating on a TextView.

Good Example: Uniform Bluetooth Data Receive

This is a good example:

Console Output Time     GPS Time
13:42:37.229            NMEA UTC  124239.073
13:42:37.335            NMEA UTC  124239.173
13:42:37.417            NMEA UTC  124239.273 <---
13:42:37.522            NMEA UTC  124239.373 <---
13:42:37.632            NMEA UTC  124239.473 <--- All NMEA sentences were received about equally in ~100ms distances (uniform textView updating)
13:42:37.719            NMEA UTC  124239.573 <--- 
13:42:37.826            NMEA UTC  124239.673 <---
13:42:37.932            NMEA UTC  124239.773
13:42:38.013            NMEA UTC  124239.873
13:42:38.118            NMEA UTC  124239.973

good

In this case the data will be received about every 100ms and updating a TextView with this is great. It looks uniform.

The good example work every time on Galaxy S Plus(Android 4.3.1). On the Galaxy S3(Android 4.3) and Huawai P40 Pro (Android 10) it's like in the bad example. But the very interesting thing is when I connect, disconnect and connect again fast in my App it's often switches to the fast and steady transfer on the Huawai P40 Pro but not so often on the Galaxy S3. Or if I decouple the bluetooth device from the phone and reconnect it over my App (with pin input) it's also work sometimes good. After reconnecting again the most time it's bad again. I tested some other Bluetooth GPS Apps and they behave the same => unsteady transmission frequency. Has anyone experienced the same behavior? How can I solve this?

Code Example

This code leads to the bad console output. You have to change the MAC address for your GPS modul and add an "activity_logging_extern_gps_layout_test.xml" file with the "txtTime" TextView:

package your.packages.activities;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import your.packages.R;

public class LiveViewBluetoothGPSActivityTEST extends Activity
{
    public static final int MESSAGE_READ = 2;
    private TextView txtTime;
    private BluetoothAdapter mBluetoothAdapter = null;
    private BluetoothConnectionService mBluetConServ = null;

    private final Handler mHandler = new Handler(Looper.getMainLooper())
    {
        @Override
        public void handleMessage(Message msg)
        {
            switch(msg.what)
            {
                case MESSAGE_READ:
                    String readMessage = (String) msg.obj;

                    if(readMessage.startsWith("$GPGGA"))
                    {
                        Log.d("NMEA UTC", readMessage);
                        String timestamp = readMessage.split(",")[1];
                        txtTime.setText("Time: " + timestamp);
                    }
                    break;
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_logging_extern_gps_layout_test);
        txtTime = (TextView) findViewById(R.id.txtTime);
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    @Override
    public void onStart()
    {
        super.onStart();

        mBluetConServ = new BluetoothConnectionService(this, mHandler);
    }

    @Override
    public synchronized void onResume()
    {
        super.onResume();

        mBluetConServ.start();
        String deviceAddress = "00:11:22:33:44:55"; // put the mac address of your GPS modul here
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress);
        mBluetConServ.connect(device);
    }
}


class BluetoothConnectionService
{
    private static final String TAG = "BluetoothConnectionServ";
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;

    public BluetoothConnectionService(Context context, Handler handler)
    {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = handler;
    }

    public synchronized void start()
    {
        if(mConnectThread != null)
        {
            mConnectThread.cancel();
            mConnectThread = null;
        }

        if(mConnectedThread != null)
        {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

    }

    public synchronized void connect(BluetoothDevice device)
    {
        if(mConnectedThread != null)
        {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
    }

    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device)
    {
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();
    }

    private class ConnectThread extends Thread
    {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device)
        {
            mmDevice = device;
            BluetoothSocket tmp = null;

            try
            {
                Method m = device.getClass().getMethod("createRfcommSocket", int.class);
                tmp = (BluetoothSocket) m.invoke(device, 1);

            } catch(InvocationTargetException e)
            {
                throw new RuntimeException(e);
            } catch(NoSuchMethodException e)
            {
                throw new RuntimeException(e);
            } catch(IllegalAccessException e)
            {
                throw new RuntimeException(e);
            }
            mmSocket = tmp;
        }

        public void run()
        {
            setName("ConnectThread");

            mAdapter.cancelDiscovery();

            try
            {
                mmSocket.connect();
            } catch(IOException e)
            {
                try
                {
                    mmSocket.close();
                } catch(IOException e2)
                {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                BluetoothConnectionService.this.start();
                return;
            }

            synchronized(BluetoothConnectionService.this)
            {
                mConnectThread = null;
            }

            connected(mmSocket, mmDevice);
        }

        public void cancel()
        {
            try
            {
                mmSocket.close();
            } catch(IOException e)
            {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

    private class ConnectedThread extends Thread
    {
        private final BluetoothSocket mmSocket;
        private final BufferedReader mmBuffReader;

        public ConnectedThread(BluetoothSocket socket)
        {
            mmSocket = socket;
            InputStream tmpIn = null;

            try
            {
                tmpIn = socket.getInputStream();
            } catch(IOException e)
            {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmBuffReader = new BufferedReader(new InputStreamReader(tmpIn));
        }

        public void run()
        {
            String line;

            while(true)
            {
                try
                {
                    if((line = mmBuffReader.readLine()) != null)
                        mHandler.obtainMessage(LiveViewBluetoothGPSActivityTEST.MESSAGE_READ, line).sendToTarget();

                } catch(IOException e)
                {
                    break;
                }
            }
        }

        public void cancel()
        {
            try
            {
                mmSocket.close();
            } catch(IOException e)
            {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
}
1

There are 1 best solutions below

0
Mitch On

It's not possible to give a definitive answer here without more diagnostic info but try the following:

Put a breakpoint on the following line:

if((line = mmBuffReader.readLine()) != null)

Keep hitting the 'continue' button on your debugger and look at the formatting of your incoming $GPGGA lines - are there parts missing or do eg 2 lines joint together or do they all look perfect.

Is there a checksum at the end of the NMEA sentence on your input stream, and if so (and you can find out how it is created from the manual of your GPS receiver) then consider checking against it in your code & displaying a count of any failed values.

Corrupted incoming data (at the readLine() level) will indicate either a hardware issue or a lower level OS issue and will be unlikely to be in your own code structure.

A hardware issue (and I've had this problem) might be an incompatibility between the GPS receiver and your variant of device which could cause interruptions in the comms between GPS and tablet, and if the GPS has an output buffer this will start to fill - the result will be a delay on the tablet screen followed by a 'rush' of data when the data finally gets through.

This would explain variations between devices in your case.

In my case the issue was resolved by the GPS manufacturer making a hardware change to their receiver.

Having said that, I'm now having a similar issue after an update from Android 11 to 13 using hardware that previously was working & which I haven't resolved yet.

If all your NMEA data is coming in OK then try changing your display method and instead of using a handler try just adding each incoming NMEA sentence to eg an arrayList and wrapping that up within a separate thread to display on your screen - or at least something to test whether the handler is the sticking point.