GPS Programming Tips for Windows Mobile - Part 2

 

- Useful samples on the web

- Bluetooth Shared Source possible enhancements

As I wrote in a previous post, in my case I wanted to play (once in my life!) with NMEA sentences and therefore didn't want to use GPS Intermediate Driver, at least for the first "release" of the (for-my-own-fun-) application: but now that I've done it (and the application is working indeed!), I'm sure that the next version will be based on it. Only when you don't have something you can really appreciate it when finally getting it... Smile

Now, let's talk about useful samples on the web, not related to the GPS Intermediate Driver. A very good place to look for info for GPS-programming is GeoFrameworks . Their Mastering GPS Programming is a very good guide if you want to start programming location-aware applications. They even sell a set of controls, but I can't express on them since I didn't test them. Jon Person also made it available here.

Also, as usual when looking for NETCF sample code, OpenNETCF is the first place. Microsoft MVPs have done a really extraordinary job on providing NETCF developers with what was and is missing respect to the Desktop Framework through their Smart Device Framework, and some other components as well. For GPS they created the Shared Source Serial and GPS Library

In any case, if you don't want to use GPSID, then you must provide a sort of "physical" layer responsible for retrieving data from the GPS antenna. In this case Microsoft comes to help, through the Windows Embedded Source Tools for Bluetooth Technology (my GPS Antenna was a paired Bluetooth one). I slightly modified it in order to better accomplish my goals, and probably it's worth mentioning here.

For example, in my Configuration Form I wanted to have a ComboBox showing the currently paired devices. In order to easily use the .DataSource property of the combo (or any other ListControl-based control) you may add a read-only "PairedDevicesArrayList" property:

 /// <summary> 
/// ArrayList needed to easily fill a ListControl-based control (such as a Combo) through its .DataSource property 
/// </summary> 
public ArrayList PairedDevicesArrayList 
{ 
    get 
    { 
        ArrayList temp = new ArrayList(); 
        foreach (BluetoothDevice dev in btRadio.PairedDevices) 
        { 
            temp.Add(new BluetoothDevice(dev.Name, dev.Address)); 
        } 
        return temp; 
    } 
}

so that the UI could use something like:

 cmbPairedDevices.DataSource = BtLayer.PairedDevicesArrayList; 
cmbPairedDevices.DisplayMember = "Name"; 
if (cmbPairedDevices.Items.Count != 0) 
    cmbPairedDevices.SelectedIndex = 0; 

The previous property is based on BluetoothDeviceCollection.PairedDevices, and I noticed that under the registry key containing paired devices info, there were some so-called "devices" I wasn't interested about, such as "activesync", "headset", "modem", etc. So I slightly modified the sample code here as well:

 /// <summary> 
/// A collection representing Bluetooth devices which have been previously paired with this device. 
/// </summary> 
public BluetoothDeviceCollection PairedDevices 
{ 
    get 
    { 
        BluetoothDeviceCollection pairedDevices = new BluetoothDeviceCollection(); 
        const string BT_DEVICE_KEY_NAME = "Software\\Microsoft\\Bluetooth\\Device"; 
        IntPtr btDeviceKey = Registry.OpenKey(Registry.GetRootKey(Registry.HKey.LOCAL_MACHINE), BT_DEVICE_KEY_NAME); 
        ArrayList subKeyNames = Registry.GetSubKeyNames(btDeviceKey); 
        foreach (string deviceAddr in subKeyNames) 
        { 
            string deviceName = ""; 
            byte[] deviceAddress = new byte[8]; 
            IntPtr currentDeviceKey = Registry.OpenKey(btDeviceKey, deviceAddr); 
            deviceName = (string)Registry.GetValue(currentDeviceKey, "name"); 
            //RAFFAEL 
            //2 possible checks: 
            //1. the key name has some numbers 
            //2. the key has a subkey "Services" <-- choose this, good enough for now (but this can be done better) 
            ArrayList subKeyCheckList = Registry.GetSubKeyNames(currentDeviceKey); 
            if (subKeyCheckList.Count != 0) //at least one subkey 
            { 
                foreach(object subkeyName in subKeyCheckList) 
                { 
                    if ((string)subkeyName != "Services") //Services is not there 
                        break; //exit foreach 
                    else //Service is there 
                    { 
                        long longDeviceAddress = long.Parse(deviceAddr, System.Globalization.NumberStyles.HexNumber); 
                        BitConverter.GetBytes(longDeviceAddress).CopyTo(deviceAddress, 0); 
                        BluetoothDevice currentDevice = new BluetoothDevice(deviceName, deviceAddress); 
                        pairedDevices.Add(currentDevice); 
                    } 
                } 
            } 
            Registry.CloseKey(currentDeviceKey); 
            //END RAFFAEL 
        } 
        Registry.CloseKey(btDeviceKey); 
        return pairedDevices; 
    } 
}

Then, it's a matter of connecting to the selected paired device, through BluetoothDevice.Connect() method:

 /// <summary> 
/// Connect to the Serial Port service of the paired device whose index within the Paired Devices collection is 'selectedIndex' 
/// </summary> 
/// <param name="selectedIndex">index of the paired devices collection</param> 
/// <returns></returns> 
public bool ConnectToSerialPortOfSelectedPairedDevice(int selectedIndex) 
{ 
    bool res = true; 

    //check if paired device is correctly opened 
    if (null == (gpsDevice = (BluetoothDevice)BtRadio.PairedDevices[selectedIndex])) 
    { 
        //MessageBox.Show("Can't find any paired device."); 
        //or throw an exception that is caught by upper layers
        return false; 
    } 

    //try to connect to the Serial Port service for MAX_RETRIES times 
    int count = 0; 
    while ((null == (gpsStream = gpsDevice.Connect(StandardServices.SerialPortServiceGuid))) && (count < MAX_RETRIES)) 
    { 
        ++count; 
        if (count >= MAX_RETRIES) 
        { 
            res = false; 
            break; 
        } 
    } 
    return res; 
}

And finally you can use the data to fill a buffer, for example, through the NetworkStream.Read() method. Note that if you then pass the buffer to a NMEA Interpreter, you must remember to "clean" its contents in case your algorithm doesn't guarantee that the buffer is always filled the same way. And remember that NMEA sentences don't have a fixed length, also because this depends on how antennas-manufacturers chose the "output pattern" and at which point in time the buffer was started being filled. If you forget to "reset" the buffer (as I was doing...) you may see that the application will start thinking that you're going back and forward from a location you were in a previous point in time... Confused

Here you should also implement an algorithm to handle the loss of Bluetooth signal (which is something different from the "quality of the GPS signal").

 //gpsStream is a NetworkStream object, representing the stream which reading data from. 
//BluetoothRadio and BluetoothDevice use WINSOCK to connect to the antenna 
...
bCanRead = this.gpsStream.CanRead; 
if (bCanRead) 
{ 
    //clean the buffer
    for (int i = 0; i<buffer.Length; ++i) 
        buffer[i] = 0; 

    //now read
    try 
    { 
        bRead0Bytes = (0 == this.gpsStream.Read(buffer, 0, BUFFERSIZE - 1)); 
    } 
    catch (System.IO.IOException ioex) 
    { 
        //If you receive an IOException, check the InnerException property to determine if it was caused by a SocketException. 
        //If so, use the ErrorCode property to obtain the specific error code, and refer to the Windows Sockets version 2 API 
        //error code documentation in MSDN for a detailed description of the error. 
        if (ioex.InnerException.GetType() == typeof(SocketException)) 
        { 
            int socketerr = ((SocketException)(ioex.InnerException)).ErrorCode; 
            throw new Exception(string.Format("SocketException - error code: {0}", socketerr)); 
        } 
        throw new Exception("IOException while trying to read from GPS"); 
    } 
    catch (Exception ex)
    {
        //throw exception to upper layers
    }
}

In the next post I'll talk a bit about the intrinsic limitations of the GPS Signal Quality and possible way to reduce the errors. Among other concepts I'll show how to possibly exhibit location data through Drawing objects, in order to receive graphs like for example the following ones:

320x240_WM6Standard_path 320x240_WM6Standard_speed

Cheers,

~raffaele