Our recent blog post about GPS integration mentions integration with the Dual XGPS150 bluetooth GPS receiver module. We found that we could connect directly to the bluetooth socket and read GPS coordinates as text data. The data is in NMEA format which is documented on a number of websites such as http://www.gpsinformation.org/dale/nmea.htm.

Full source code to connect and read results is below. Note that it runs on a separate thread and will attempt to reconnect at approx 1s intervals if bluetooth connection is lost. It finds the first available bluetooth device whose name begins with “XGPS150”, which should be the required module.

LocationHandlerDualAV.java

/** Copyright (C) Ogglebox Sensory Computing Ltd.
 *  This example Java code is free to use.
 *  All that we ask in return is that you write and tell us about your application
 *  - we'd be delighted to hear from you.
 *
 *  Disclaimer
 *  ----------
 *  No warranties are given whether express, implied, statutory, or other.
 *  To the extent lawfully possible we have no liability whatsoever arising from
 *  this material, irrespective of how it may be used.
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.ParcelUuid;
import android.util.Log;

import GeoLocationBasic;
import GeoLocationListener;

public class LocationHandlerDualAV implements LocationHandler {

	/** Set true to log additional connection information. */
	public static final boolean logConnectionDiagnostics = false;

	private static final String TAG = "LocationHandlerDualAV";
	private static final int TIMEOUT = 10000;
	private static final UUID DEFAULT_SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); 
	private Thread mainThread;
	private MainLoop mainLoop;
	private GeoLocationListener listener;

	/** Construct new class, initially unconnected to bluetooth. */
	public LocationHandlerDualAV() {
	}

	/** Start processing loop which will attempt to connect to bluetooth. */
	public void connect() {

		// ensure disconnected
		disconnect();

		// create and start
		synchronized (this) {
			mainLoop = new MainLoop();
			mainThread = new Thread(mainLoop);
			mainThread.start();
		}
	}

	/** End processing loop, which should also disconnect from bluetooth. */
	@Override
	public void disconnect() {
		sendRequestStopAndWait();
	}

	/** Set listener for results. */
	@Override
	public void setLocationListener(GeoLocationListener listener) {
		this.listener = listener;
	}

	/** Set flag to request that processing loop terminates.
	 * 
	 * @return handle to thread of processing loop.
	 */
	private synchronized Thread sendRequestStop() {
		Thread currentThread = mainThread;
		if (currentThread != null) {
			mainLoop.sendRequestStop();
		}
		mainLoop = null;
		mainThread = null;
		return currentThread;
	}

	/** Set flag to request that processing terminates, and wait until it has. */
	private void sendRequestStopAndWait() {

		// send request and get the thread which was current at time request sent
		Thread currentThread = sendRequestStop();

		// ensure thread has finished
		waitForThread(currentThread);
	}

	/** Helper to wait for a thread to finish 
	 * 
	 * @param t The thread to wait for.
	 */
	private static void waitForThread(Thread t) {
		if (t != null) {
			try {
				t.join();
			} 
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/** Member class to hold the main processing loop */
	private class MainLoop implements Runnable {

		/** Flag for request to end processing - should be accessed only by accessor methods */
		private boolean requestStop = false;

		/** Set flag to request the processing ends. */
		private synchronized void sendRequestStop() {
			requestStop = true;
		}
		
		/** Internally query flag to see if request to end processing was received. */
		private synchronized boolean hasRequestStop() {
			return requestStop;
		}

		@Override
		public void run() {

			BluetoothSocket socket = null;
			BufferedReader input = null;
			long lastTime = 0;

			while (!hasRequestStop()) {

				try {

					// attempt open (will throw exception on failure)
					if (socket == null) {
						BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
						Set<BluetoothDevice> devices = adapter.getBondedDevices();
						BluetoothDevice chosenDevice = null;
						if (devices.size() > 0) {
							if (logConnectionDiagnostics) {
								Log.i(TAG, "Bluetooth devices found: ");
							}
							for (BluetoothDevice device : devices) {								
								if (device.getName().startsWith("XGPS150")) {
									if (logConnectionDiagnostics) {
										Log.i(TAG, "-- " + device.getName() + " -- required device");
									}
									chosenDevice = device;
								}
								else {
									if (logConnectionDiagnostics) {
										Log.i(TAG, "-- " + device.getName());
									}
								}
							}
						}
						
						if (chosenDevice == null) {
							throw new RuntimeException("Required bluetooth device not found.");
						}
						else {
							if (logConnectionDiagnostics) {
								ParcelUuid[] uuids = chosenDevice.getUuids();
								Log.i(TAG, chosenDevice.getName() + " UUIDs: ");
								for (ParcelUuid uuid : uuids) {
									Log.i(TAG, "-- " + uuid.toString());
								}
							}
							
							socket = chosenDevice.createInsecureRfcommSocketToServiceRecord(DEFAULT_SPP_UUID);
							socket.connect();
							input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
						}
					}
					else {

						// attempt read (shouldn't block forever due to setSoTimeout above)
						String s = input.readLine();
						// Log.d(TAG, s);
	
						if (s == null) {
							// could mean timeout so close everything
							if ((System.currentTimeMillis() - lastTime) > TIMEOUT) {
								throw new Exception("Timeout");
							}
						}
						else if (s.startsWith("$GPGGA")) {
							String[] tokens = s.split(",");
							if (tokens.length >= 10 ) {
								// System.err.println("Lat: " + tokens[2] + " Long: " + tokens[4] + tokens[5] + " Alt: " + tokens[9]);
								double latitude = fromNMEA(Double.parseDouble(tokens[2]));
								double longitude = fromNMEA(Double.parseDouble(tokens[4]));
								if (tokens[5].compareTo("W") == 0)
									longitude = -longitude;
								double altitude = Double.parseDouble(tokens[9]);
								GeoLocationBasic loc = new GeoLocationBasic()
									.setLatitude(latitude)
									.setLongitude(longitude)
									.setAltitude(altitude);
								if (listener != null ) {
									listener.updateLocation(loc);
								}
								lastTime = System.currentTimeMillis();
							}
						}
					}
				}
				catch (Exception e) {

					String message = e.getMessage();
					if (message == null) {
						message = "<no message>";
					}
					
					// report error
					Log.d(TAG, "lost connection: " + message);

					// cleanup input stream
					if (input != null) {
						try {
							input.close();
						} 
						catch (IOException e1) {
						}
						input = null;				 
					}

					// cleanup socket
					if (socket != null) {
						try {
							socket.close();
						} 
						catch (IOException e2) {
						}

						socket = null;
					}
					
					// prevent over-fast polling
					try {
						Thread.sleep(1000);
					}
					catch (InterruptedException e3) {
					}
				}
			}
		}
	}
	
	/** Convert NMEA degrees-minutes value to degrees and fraction-of-degrees. */
	private static double fromNMEA(double nmea) {
		int degrees = (int)nmea / 100;
		double minutes = nmea - (100*degrees);
		return degrees + (minutes / 60.0);
	}
}

GeoLocation.java

/** Copyright (C) Ogglebox Sensory Computing Ltd.
 *  This example Java code is free to use.
 *  All that we ask in return is that you write and tell us about your application
 *  - we'd be delighted to hear from you.
 *
 *  Disclaimer
 *  ----------
 *  No warranties are given whether express, implied, statutory, or other.
 *  To the extent lawfully possible we have no liability whatsoever arising from
 *  this material, irrespective of how it may be used.
 */
 
 /** Used to wrap device-dependent geo-location classes
 *  so that we can do unit tests without the device present. 
 */
public interface GeoLocation {

	public double getLongitude();

	public double getLatitude();
	
	public double getAltitude();
	
}

GeoLocationBasic.java

/** Copyright (C) Ogglebox Sensory Computing Ltd.
 *  This example Java code is free to use.
 *  All that we ask in return is that you write and tell us about your application
 *  - we'd be delighted to hear from you.
 *
 *  Disclaimer
 *  ----------
 *  No warranties are given whether express, implied, statutory, or other.
 *  To the extent lawfully possible we have no liability whatsoever arising from
 *  this material, irrespective of how it may be used.
 */
 
 public class GeoLocationBasic implements GeoLocation {

	private double longitude;
	private double latitude;
	private double altitude;
	
	public GeoLocationBasic() {			
	}
	
	public GeoLocationBasic setLongitude(double x) {
		longitude = x;
		return this;
	}
	
	public GeoLocationBasic setLatitude(double x) {
		latitude = x;
		return this;
	}

	public GeoLocationBasic setAltitude(double x) {
		altitude = x;
		return this;
	}

	@Override
	public double getLongitude() {
		return longitude;
	}

	@Override
	public double getLatitude() {
		return latitude;
	}

	@Override
	public double getAltitude() {
		return altitude;
	}		
};

GeoLocationListener.java

/** Copyright (C) Ogglebox Sensory Computing Ltd.
 *  This example Java code is free to use.
 *  All that we ask in return is that you write and tell us about your application
 *  - we'd be delighted to hear from you.
 *
 *  Disclaimer
 *  ----------
 *  No warranties are given whether express, implied, statutory, or other.
 *  To the extent lawfully possible we have no liability whatsoever arising from
 *  this material, irrespective of how it may be used.
 */
 
 public interface GeoLocationListener {

	public void updateLocation(GeoLocation loc);

}

If you’re a developer and found this useful, please do write and tell us - we’d be delighted to hear about your work.