Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Where am I? What am I doing?

GPS satellite Do have a GPS receiver laying around?  Do you travel?  Do you blog?  If you answered yes to these questions, you might be interested in this Windows Live Writer plugin.  People who travel and like to blog about it can just click a button to insert their current coordinates along with a Virtual Earth Map.
Arian's Blog

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: $50 and up (depending on hardware choice)
Software: Visual Basic or Visual C# Express Editions, Windows Live Writer
Hardware: A GPS receiver that supports the NMEA protocol (they all should...)
Download:

Introduction

I've had a yellow Garmin etrex GPS unit in the home for about two years now.  My family enjoys geocaching (though we don't go nearly often enough...), and it was a good price.  The unit has a small screen, but it lets you enter coordinates, then track your way to them.  Really, just having your current coordinates is a pretty cool thing!

I've always enjoyed playing with GPS software, and I've liked the idea of creating my own.  Unfortunately, I didn't know how to interface with the hardware, so I was out of luck.  After buying a serial/USB cable, I was half-way there, but what to do with the data stream?

In this article, learn how to read the stream of data from (hopefully) any GPS unit, then create a plugin for Windows Live Writer so you can add your current location to any blog entry.   Scott Hanselman created a GPS interface in Where the Heck am I?  Connecting .NET 2.0 to a GPS back in October of 2006.  As always with Scott, it was a great article, but I didn't come across it until I'd made it through a lot of roadblocks already!  My result at this point works pretty well so I decided to stick with it, plus it integrates with Windows Live Writer.

"Where am I?" GPS plugin for Windows Live Writer

Figure 1: The plugin in action

If you want, download the source code from the links above, or just follow along with the article.  If you haven't already, download the appropriate version of Visual Studio 2005 Express Edition.  The plugin will run on either XP or Vista. 

Choosing your Hardware

Though my main GPS receiver is the Garmin etrex, I also recently bought an Ambicom unit with no screen -- just a Bluetooth link.  It's the perfect device just for obtaining information for a PDA, laptop, or other Bluetooth-connected device if you don't need display on the device.  It uses the SPP profile for data exchange, so it's just another COM port as well.

My other unit, the Garmin, has four metal dots hidden under a rubber flap.  A number of sites provide information on the pin-outs, or you can buy a cable (serial).  I bought a serial-USB interface to avoid the serial port on the back of the machine.  My assumption is that all GPS units end up with a serial connection via virtual or physical COM port.  If I'm wrong, you may need to do some extra work to get the data.

Speaking NMEA 

Speaking about data... once you have a connection to the unit (4800 bps, 8-N-1), you will have a continuously updated stream of information flowing in.  The format of this data is defined by the National Marine Electronics Association (NMEA), and is an older format than most people would expect.  Interpreting the information is pretty easy once you understand the format (of course, that's often the case!).

Sample NMEA Data

$GPRMB,V,,,,,,,,,,,,A,N*13
$GPGGA,,,,,,0,00,,,M,,M,,*66
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,3,1,10,02,60,
$GPRMC,,V,,,,,,,210807,0.2,E,N*36
241,00,04,78,042,00,05,24,314,00,09,20,252,00*7F
$GPGSV,3,2,10,10,07,185,00,12,44,307,00,17,36,089,00,20,08,035,00*7A
$GPGSV,3,3,10,28,05,147,00,30,08,319,00*75
$GPGLL,,,,,,V,N*64
$GPBOD,,T,,M,,*47
$PGRME,,M,,M,,M*00

There are three things to notice at first glance (four if you're really on the ball!).  First, every line starts with a dollar sign.  Second, that dollar sign is always followed by five upper-case letters (not all begin with "G" though).  Third, the data following the capital letters is comma-delimited.  If you looked really close, you might have noticed that every line ends with an asterisk, then two more characters that appear to be in hexadecimal format.

The capital letters after the dollar sign represent the message ID.  All standard NMEA messages start with "G."  In this example, the line beginning with "P" is a Garmin-specific message.  It's proprietary, but it's also documented and freely available from their web site.

With each line being comma-delimited, processing is pretty easy at a high-level.  From the System.IO.Ports.SerialPort class, you can call ReadLine() to get a line of data as a string.  Each line of data is called a "sentence" in the standard.  From there, the Split() method can be used to return an array for the "words."

That hexadecimal number is the checksum for the sentence.  It's a simple formula too.  Take, in order, every character between (not including) the dollar sign and asterisk, and XOR them together.  The first character seeds the checksum (in other words, a one-character sentence's checksum would be equal to that one character).  The LocationInterface class handles all of the serial I/O and NMEA protocol implementation (the little there is!).

Visual Basic

Public Shared Function GetChecksum(ByVal sentence As String) As String
    ' A sentence can't be shorter than 9 characters ($XXXXX*0x)
    If sentence.Length < 9 Then
        Return ""
    End If

    ' Seed with the second character (remember, skip the $)
    Dim checksum As Integer = Convert.ToByte(sentence(1))
    Dim loc As Integer = 0, [end] As Integer = sentence.Length - 3

    ' Loop through the characters
    For Each c As Char In sentence
        ' XOR every character between the dollar sign and the asterisk
        If loc > 1 AndAlso loc < [end] Then
            checksum = checksum Xor Convert.ToByte(c)
        End If
        loc += 1
    Next

    ' "X2" is the format string for hexadecimal
    Return checksum.ToString("X2")
End Function

Visual C#

// Calculates the checksum for a sentence
public static string GetChecksum(string sentence)
{
    // A sentence can't be shorter than 9 characters ($XXXXX*0x)
    if( sentence.Length < 9 ) return "";

    // Seed with the second character (remember, skip the $)
    int checksum = Convert.ToByte(sentence[1]); // Second character
    int loc = 0, end = sentence.Length - 3;

    // Loop through the characters
    foreach (char c in sentence)
    {
        // XOR every character between the dollar sign and the asterisk
        if (loc > 1 && loc < end)
        {
            c2 = c2 ^ Convert.ToByte(c);
        }
        loc++;
    }
    // "X2" is the format string for hexadecimal
    return checksum.ToString("X2");
}

With this handy method, you can validate every line of data that you have before bothering to process it.  If a line's bad, don't make a guess.  The nature of NMEA is to repeat data frequently.  If you missed it this time, you'll have it a second or so later probably.  In my code, when I want to get the next data sentence, I call my ReadNextGoodLine() method.  This calls ReadLine() on the SerialPort, and validates the checksum.  It will try up to ten times before returning a NULL.

Visual Basic

Private Function ReadNextGoodLine(ByVal port As SerialPort) As String
    ' Must read the next line.  This may not be a complete NMEA line
    ' (or even not an NMEA line at all!)
    Dim line As String = Nothing
    For i As Integer = 0 To 9

        ' Up to ten tries...
        line = port.ReadLine()

        If Not IsSentenceValid(line) Then
            line = Nothing
        Else
            Exit For

        End If
    Next

    Return line
End Function

Visual C#

private string ReadNextGoodLine(SerialPort port)
{
    // Must read the next line.  This may not be a complete NMEA line
    // (or even not an NMEA line at all!)
    string line = null;

    // Up to ten tries...
    for (int i = 0; i < 10; i++)
    {
        line = port.ReadLine();

        if (!IsSentenceValid(line)) line = null;
        else break;
    }

    return line;
}

Now that I can read sentences and validate them, I can also create a basic auto-scan GPS search.   From the SerialPort class, I can call GetPortNames().  This returns a string array of port names.  I can instantiate each port by name, call open, then call ReadNextGoodLine().  If it returns NULL (couldn't find valid NMEA after ten lines), assume it isn't a GPS unit and keep moving to the next port.  The ComPortName property saves the name, and can also be set manually to avoid the search.

Of Latitudes and Longitudes...

One thing I learned from this project, is just how much math is involved with GPS!  You take it for granted when the device shows coordinates, but there is so much more going on in there.

Recall that GPS is all about geosynchronous satellites sending out time signals.  Based on the time delay from satellites with known geostationary locations, you can locate your position.  As any experienced GPS user knows, however, when you first turn on the unit, or if you are in a house, underground, or often even wooded areas, the unit will be searching for a signal and won't display anything.  The $GPRMC sentence continues to arrive, but the coordinates may be blank, or indicate the lack of a "fix."

$GPRMC,153538,A,4140.5555,N,09100.0000,W,000.0,246.0,310807,,,A*66

The GPRMC consists of a lot of information, including latitude, longitude, speed, heading, and UTC date/time.  If you break up the sentence based on commas, the data words that I concentrate in the sample code are 3 (latitude), 4 (N/S), 5 (longitude), 6 (E/W), and 2 (A means fix, V or blank is no fix).

Visual Basic

Public Function ParseGPRMC(ByVal sentence As String) As CoordinatePair
    Dim loc As CoordinatePair = Nothing
    Dim fix As Boolean = False

    ' Split the sentence into words
    Dim words As String() = sentence.Split(","c)

    ' Not all sentences have data populated
    If words(3) <> "" AndAlso words(4) <> "" AndAlso words(5) <> "" AndAlso words(6) <> "" Then
        ' NMEA coordinates are in DM (Degrees-Minutes) format 
        Dim lat As New Coordinate(Integer.Parse(words(3).Substring(0, 2)), _
Decimal.Parse(words(3).Substring(2)), _
DirectCast([Enum].Parse(GetType(HemisphereOptions), words(4)), HemisphereOptions)) Dim lng As New Coordinate(Integer.Parse(words(5).Substring(0, 3)), _
Decimal.Parse(words(5).Substring(3)), _
DirectCast([Enum].Parse(GetType(HemisphereOptions), words(6)), HemisphereOptions)) loc = New CoordinatePair(lat, lng) ' Does the device currently have a satellite fix? If words(2) = "A" Then ' Fix has been obtained loc.Fixed = True ElseIf words(2) = "V" Then ' Fix has been lost loc.Fixed = False End If End If Return loc End Function

Visual C#

public CoordinatePair ParseGPRMC(string sentence)
{
    CoordinatePair loc = null;

    // Split the sentence into words
    string[] words = sentence.Split(',');

    // Not all sentences have data populated
    if (words[3] != "" && words[4] != "" && words[5] != "" && words[6] != "")
    {
        // NMEA coordinates are in DM (Degrees-Minutes) format 
        Coordinate lat = new Coordinate(int.Parse(words[3].Substring(0, 2)), 
decimal.Parse(words[3].Substring(2)),
(HemisphereOptions)Enum.Parse(typeof(HemisphereOptions), words[4])); Coordinate lng = new Coordinate(int.Parse(words[5].Substring(0, 3)),
decimal.Parse(words[5].Substring(3)),
(HemisphereOptions)Enum.Parse(typeof(HemisphereOptions), words[6])); loc = new CoordinatePair(lat, lng); // Does the device currently have a satellite fix? if (words[2] == "A") loc.Fixed = true; // Fix has been obtained else if (words[2] == "V") loc.Fixed = false; // Fix has been lost } return loc; }

The latitude and longitude values in NMEA data are expressed as DM format.  DM is short for Degrees-Minutes, and actually the minutes are expressed in decimal form.  In the above example, "4140.5555" is meant to be parsed as 41 degrees and 40.5555 minutes.  Longitude uses three digits for degrees, so "09100.0000" is 091 degrees and 00.0000 minutes.  Degrees-Minutes is just one of three common ways to express coordinates though.  Degrees-Minutes-Seconds splits the minutes into minutes and seconds.  If you think of it like time, it's easier.  If the decimal minutes is .50000, then that's half of 60, or 30 minutes.  A number like .55555 would spill into the seconds place as well.  You can also use Degrees-Decimal which simply uses one decimal number to express the degrees along with any fractional "extra."  Just remember that the source format is Degrees-Minutes, and given any format, you can convert to any other.  My Coordinate class provides ToString() methods to return in all three representations, and internally it stores it as int degrees and minutes and decimal seconds.  For more information on these numbers, Wikipedia has a great article.

Plugging In

Functioning as a  Live Writer plugin requires that you do several things.  First of all, you will need to download and install the Windows Live Writer SDK.  This gives you access to an assembly to add a reference to in your project.  Your entry point class should import the WindowsLive.Writer.Api namespace, and inherit ContentSource.  You'll need to add the WriterPlugin attribute with values for a unique GUID (use the Tools | Create GUID menu item in Visual Studio to help with that), and set a name at least.

The EditOptions method must be overridden if you provide an Options dialog (in this sample, it's just a message box.  The CreateContent method is the meat of the plugin.  This can do anything, then assign a string to the newContent ref parameter.  It returns a DialogResult to indicate if it was OK'd or Cancel'd.

The InsertableContentSource attribute lets you set the SideBar text property, and the WriterPlugin attribute has a property for ImagePath to create an image link:

Plugin Insert links 

Figure 2: Ready to use

When CreateContent is called, the sample plugin shows the LocationPreviewForm dialog.  This in turn triggers the GPSInit method in LocationInterface to find and connect to the GPS unit.  If it's found, the COM port and coordinates are shown.  All GPS activity is performed in a BackgroundWorker to maintain a responsive user interface.  The Title and Description fields are used for a pushpin in a Virtual Earth embedded AJAX map.  This is a full-blown instance with the scrolling and zooming.

 

Location Preview dialog

Figure 3: The plugin dialog

Actually, I would have liked to use a static bitmap, but I'm not sure how to obtain good free mapping images in a small amount of code, other than services like MapPoint which aren't free.  Changing this aspect of the code would be very easy in the GetOutput method, and you could remove the image/map altogether and simply show the coordinates or other information.  It's not exactly extensible, but it's easy to modify.

Finally, in order to simplify debugging, I created a post-build event to copy the plugin DLL to the Windows Live Writer Plugins directory:

XCOPY /D /Y /R "$(TargetPath)" "C:\Program Files\Windows Live Writer\Plugins\"

I also went into the Debug settings for the project for the project and set the Start Action to Start external program, setting it to the path to the EXE:

C:\Program Files\Windows Live Writer\WindowsLiveWriter.exe

Now, when you press F5, it copies the DLL and attaches the debugger to a new instance of Windows Live Writer for Edit-and-Continue and other debugging.  It works well!

One thing to remember, is that Live Writer locks the DLL's when it's running, so you will get an error if you even build while it's running, since the DLL can't be copied.

Next Steps

At this point, I'd like to create more flexibility on what the plugin does with the coordinates.  It could locate images through various sources, include altitude, speed, and heading when available, or just pretty up the embedded information.  I'd like to work on the GPS classes a bit to make them more robust, handling some of the other messages, and support raising events to be used in other applications.  Developing GPS applications is conceptually pretty easy once you have the data.  It's just a matter of exposing the data in a good way.

Conclusion

Working with GPS data was much easier than I expected.  I had some Bluetooth/USB issues at first, but working with the data was fun and gave me lots of ideas!  See what you can do with location information to enhance some existing application, or think up something entirely new.  Have fun with the code and as usual, contact me through my blog for comments, complaints, or questions.


Avatar80 Arian Kulp is an independent software developer and writer working in the Midwest.  He has been coding since the fifth grade on various platforms, and also enjoys photography, nature, and spending time with his family.  Arian can be reached through his web site at http://www.ariankulp.com.

Tags:

Follow the Discussion

  • Arian Kulp&#39;s BlogArian Kulp's Blog

    New Coding4Fun : Where am I? What am I doing?

  • Grayson PeddieGrayson Peddie

    Hey, that's pretty cool! I'm thinking about getting AT&T Tilt, as I like to have a built-in GPS in a smartphone. I like to do computer programming using a GPS receiver for Windows Mobile 6.0! However, I do have my Alltel PPC6700 (I purchased it and pay my bill to my mom as I'm a college student).

  • Clint RutkasClint I'm a "developer"

    @Bill, you can try, if it mounts as a serial port, this example should handle it.

  • BillBill

    I have an older (1996) USB GPS receiver that came with MS Streets & Trips.  Do you think this would work?

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.