Main Site Documentation

Modified GPS Class


#1

I’ve been playing with the GPS class a bit as I’m looking to try to use the Fez Cobra for some Amateur Radio uses… I made a few tweaks to suit my needs as well as added a few extra functions I figured I’d share for others in case they needed it:


//-------------------------------------------------------------------------------------
//              GHI Electronics, LLC
//               Copyright (c) 2010
//               All rights reserved
//-------------------------------------------------------------------------------------
/*
 * You can use this file if you agree to the following:
 *
 * 1. This header can't be changed under any condition.
 *    
 * 2. This is a free software and therefore is provided with NO warranty.
 * 
 * 3. Feel free to modify the code but we ask you to provide us with
 *	  any bugs reports so we can keep the code up to date.
 *
 * 4. This code may ONLY be used with GHI Electronics, LLC products.
 *
 * THIS SOFTWARE IS PROVIDED BY GHI ELECTRONICS, LLC ``AS IS'' AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL 
 * GHI ELECTRONICS, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR ORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
 *
 *	Specs are subject to change without any notice
 *
 -------------------------------------------------------------------------------------
 *
 * HISTORY: 
 * 
 * 15/8/2010 - MJL
 *     - Removed getValue2 and getValue3 and replaced with String.substring
 *     - Changed Latitude and Longitude variables to doubles from integers
 *     - Changed initialize function to take com port and baud rate as paramters
 *     - Changed resolution of the Minutes value from MM to MM.MMMM
 *     - Added convertLocation function to convert from DD MM.MMMM to Decimal Degrees format
 *     - Added getDistance to find the distance between two specified locations
 *     - Added getMaidenhead to get the Maidenhead grid square for a specified location
 *
 * 
 */

using System;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;


namespace GHIElectronics.NETMF.FEZ
{
    public static partial class FEZ_Extensions
    {
        static public class GPS
        {
            static char[] split_char = new char[] { ',' };
            static string[] sentence_fields;
            static char[] time_utc = new char[8];
            static SerialPort _port;
            static byte[] buffer = new byte[200];
            static bool _data_is_valid;
            
            private static GPSStateMachine gps_decoder_state;

            static char[] pree_buffer = new char[10];
            static char[] pree = { 'G', 'P', 'R', 'M', 'C' };
            static int pree_index = 0;
            static char[] sentence = new char[200];
            static char[] GPRMC_sentence_array = new char[200];
            static int GPRMC_sentence_array_length = 0;
            static int sentence_index = 0;

            private enum GPSStateMachine
            {
                FindPre,
                CopyingSentence,
            }

            // inport - serial port such as COM1 or COM2
            // inbaud - baud rate, i.e. 19200 
            // For the GHI GPS you want to use 19200
            static public void Initialize(string inport, int inbaud)
            {
                _port = new SerialPort(inport, inbaud);
                
                _port.ReadTimeout = 0;
                _port.ErrorReceived += new SerialErrorReceivedEventHandler(_port_ErrorReceived);

                _port.Open();// If you open the port after you set the event you will endup with problems
                _port.DataReceived += new SerialDataReceivedEventHandler(_port_DataReceived);

            }

            static void _port_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {

                int buffer_index = 0;
                int data_received;

                //Thread.Sleep(10); 
                data_received = _port.Read(buffer, 0, buffer.Length);
                //_port.Close();

                while (buffer_index < data_received)
                {
                    switch (gps_decoder_state)
                    {
                        case GPSStateMachine.FindPre:
                            if (buffer[buffer_index] == pree[pree_index])
                            {
                                pree_index++;
                                if (pree_index >= pree.Length)
                                {
                                    gps_decoder_state = GPSStateMachine.CopyingSentence;
                                    pree_index = 0;
                                }
                            }

                            else
                                pree_index = 0;
                            break;

                        case GPSStateMachine.CopyingSentence:
                            sentence[sentence_index] = (char)buffer[buffer_index];
                            if (sentence[sentence_index] == '*' ||
                                sentence[sentence_index] == '\r' ||
                                sentence[sentence_index] == '\n')
                            {
                                //we have a full line
                                if (sentence_index > 10)
                                {
                                    if (sentence[12] == 'A')
                                    {

                                        lock (GPRMC_sentence_array)
                                        {
                                            Array.Copy(sentence, GPRMC_sentence_array, sentence_index);
                                            GPRMC_sentence_array_length = sentence_index;
                                            _data_is_valid = true;
                                        }

                                        //string line = new string(sentence, 0, sentence_index);
                                        //DecodeGPSSentence(line);
                                        //Debug.Print(line);
                                    }

                                    else
                                    {

                                        //invalid data
                                        _data_is_valid = false;
                                        Debug.Print("Searching for satellites...");
                                    }
                                }
                                sentence_index = 0;
                                gps_decoder_state = GPSStateMachine.FindPre;
                            }
                            else
                            {
                                sentence_index++;

                                if (sentence_index >= sentence.Length)
                                {

                                    Debug.Print("Sentence is too long!");
                                    sentence_index = 0;
                                    gps_decoder_state = GPSStateMachine.FindPre;
                                }
                            }

                            break;
                    }

                    buffer_index++;
                }

                //_port.Open();
            }



            static void _port_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
            {
                Debug.Print("COM Error: " + e.EventType.ToString());
            }


            public static DateTime GetUTCDateTime()
            {
                int year, month, day, hour, minutes, seconds;

                lock (GPRMC_sentence_array)
                {
                    if (_data_is_valid == false)

                    {
                        return DateTime.MinValue;
                    }

                    string sentence_string = new string(GPRMC_sentence_array, 0, GPRMC_sentence_array_length);
                    sentence_fields = sentence_string.Split(split_char);

                    if (sentence_fields.Length < 9)
                    {
                        return DateTime.MinValue;
                    }

                    // compute values from ASCII
                    hour = Convert.ToInt16(sentence_fields[1].Substring(0, 2));
                    minutes = Convert.ToInt16(sentence_fields[1].Substring(2, 2));
                    seconds = Convert.ToInt16(sentence_fields[1].Substring(4, 2));

                    day = Convert.ToInt16(sentence_fields[9].Substring(0, 2));
                    month = Convert.ToInt16(sentence_fields[9].Substring(2, 2));
                    year = Convert.ToInt16(sentence_fields[9].Substring(4, 2)) + 2000;
                    
                    _data_is_valid = false;

                    return new DateTime(year, month, day, hour, minutes, seconds);
                }

                

            }

            public static bool GetPosition(out double LatHour, out double LatMinute, out bool North, out double LongHour, out double LongMinute, out bool East)
            {

                lock (GPRMC_sentence_array)
                {

                    if (_data_is_valid == false)
                    {
                        // nothing to return
                        LatHour = LatMinute = LongHour = LongMinute = 0;
                        North = East = false;

                        return false;
                    }

                    string sentence_string = new string(GPRMC_sentence_array, 0, GPRMC_sentence_array_length);
                    sentence_fields = sentence_string.Split(split_char);

                    if (sentence_fields.Length < 9)
                    {
                        // nothing to return
                        LatHour = LatMinute = LongHour = LongMinute = 0;
                        North = East = false;

                        return false;
                    }

                    // compute values from ASCII
                    LatHour = Convert.ToDouble(sentence_fields[3].Substring(0, 2));
                    LatMinute = Convert.ToDouble(sentence_fields[3].Substring(2, 7));
                    
                    
                    if (sentence_fields[4][0] == 'S')
                        North = false;
                    else
                        North = true;

                    LongHour = Convert.ToDouble(sentence_fields[5].Substring(0, 3));
                    LongMinute = Convert.ToDouble(sentence_fields[5].Substring(3, 7));

                    
                    if (sentence_fields[6][0] == 'W')
                        East = false;
                    else
                        East = true;

                    _data_is_valid = false;

                    return true;
                }

            }

            // Converts from the format DDD MM.MMMM to the signed Decimal Degrees location format xxx.xxxxxxx
            public static double convertLocation(double inHour, double inMinute, bool inHemisphere)
            {
                try
                {
                    double temploc = (inHour) + ((inMinute) / 60.0);
                    if (inHemisphere == false)
                        temploc = +-temploc;

                    return temploc;
                }
                catch (Exception se)
                {
                    Debug.Print(se.ToString());
                    return 0;
                }

            }


            // Gets the distance between two coordinates on earth using position in decimal format
            // The variable units takes the following values:
            //      M - Miles (default)
            //      N - Nautical Miles
            //      K - Kilometers
            public static double getDistance(double inlat1, double inlong1, double inlat2, double inlong2, string units)
            {
                try
                {

                    Trigonometry trig = new Trigonometry();
                    double tempdistance = 0;
                    double theta = inlong1 - inlong2;

                    tempdistance = System.MathEx.Sin(trig.DegreeToRadian(inlat1)) *
                                   System.MathEx.Sin(trig.DegreeToRadian(inlat2)) + System.MathEx.Cos(trig.DegreeToRadian(inlat1)) *
                                   System.MathEx.Cos(trig.DegreeToRadian(inlat2)) * System.MathEx.Cos(trig.DegreeToRadian(theta));

                    tempdistance = System.MathEx.Acos(tempdistance);
                    tempdistance = trig.RadianToDegree(tempdistance);
                    
                    // Defaults to Miles
                    tempdistance = tempdistance * 60 * 1.1515;
                    switch (units.ToUpper())
                    {
                        case "N":
                            tempdistance = tempdistance * 0.8684;
                            break;
                        case "K":
                            tempdistance = tempdistance * 1.609344;
                            break;
                        default:
                            break;
                    }

                    return tempdistance;
                }
                catch (Exception se)
                {
                    Debug.Print(se.ToString());
                    return 0;
                }

            }

            // Finds maidenhead grid square for a location given a decimal longitude/latitude 
            // Adapted from Delphi code written by VK4ADC - http://www.vk4adc.com/gridlocw.php    
            public static string getMaidenhead(double inlat, double inlong)
            {
                try
                {
                    string temploc = "";
                    string alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                    string nums = "0123456789";
                    
                    double ge, te;
                    string g1zz, g2zz, g3zz, g4zz, g5zz, g6zz;
                    int g1, g2, g3, g4, g5, g6;

                    ge = (inlong + 180) / 20;
                    te = (inlat + 90) / 10;
                    g1 = (int)ge;
                    g2 = (int)te;
                    g1zz = alphas.Substring(g1, 1);
                    g2zz = alphas.Substring(g2, 1); ;
                    
                    ge = (ge - g1) * 10;
                    te = (te - g2) * 10;
                    g3 = (int)ge;
                    g4 = (int)te;
                    g3zz = nums.Substring(g3, 1); ;
                    g4zz = nums.Substring(g4, 1); ;
                    
                    g5 = (int)((ge-g3)*24);
                    g6 = (int)((te-g4)*24);
                    g5zz = alphas.Substring(g5, 1); 
                    g6zz = alphas.Substring(g6, 1);

                    temploc += g1zz + g2zz + g3zz + g4zz;
                    if (((int)inlat == inlat) && ((int)inlong == inlong))
                        return temploc;
                    else
                        temploc += g5zz.ToLower() + g6zz.ToLower();

                    return temploc;
                }
                catch (Exception se)
                {
                    Debug.Print(se.ToString());
                    return "";
                }
            }


        }

    }

    
    public class Trigonometry
    {

        public double DegreeToRadian(double angle)
        {
            return System.MathEx.PI * angle / 180.0;
        }

        public double RadianToDegree(double angle)
        {
            return angle * (180.0 / System.MathEx.PI);
        }

    }

}


#2

Very nice. I’ll probably be using a version of this driver for my RC car project.


#3

Thanks for the response Chris. I hope to clean it up a bit more, and add a few more functions. I wish I could bring over my APRS dll I’ve been working on for the last couple of weeks. I had written an APRS parser about three years ago in VB.net for communicating to my home automation software. Slowly I’ve been bringing it over to C# but it currently uses a few functions that don’t exist in the MicroFramework so a bit more work to port it. My end goal would be to have a portable APRS station that could communicate directly with a TNC (or Internet) for mapping stations, and maybe even messaging. The 7" TFT would be great for that ;D

I’ve done development with Arduinos (have several from mini thru mega), and Mikroe boards for doing development with PICs, but I find the Fez boards a blast. The ability to use C# and it’s IDE for debugging is incredible. I haven’t really touched the other boards since I’ve been playing with this. I’ve been eying the Fez Mini for the last couple days trying to think of a good use for it… it’s really tempting as it’s a great size but after buying the Fez Domino, Cobra and a bunch of accessories, need to wait a bit before my wife kills me :wink:

-Mike


#4

[quote]I’ve done development with Arduinos (have several from mini thru mega), and Mikroe boards for doing development with PICs, but I find the Fez boards a blast. The ability to use C# and it’s IDE for debugging is incredible. I haven’t really touched the other boards since I’ve been playing with this. I’ve been eying the Fez Mini for the last couple days trying to think of a good use for it… it’s really tempting as it’s a great size but after buying the Fez Domino, Cobra and a bunch of accessories, need to wait a bit before my wife kills me
[/quote]
Mike, Can we quote you on your comment on FEZ?

I am glad you like it :slight_smile:


#5

Very nice! I will try it soon!


#6

Gus,

No problem at all, I’m only speaking the truth :slight_smile:

-Mike


#7

Will use it and get you 200 exp. points :slight_smile:


#8

Could you create a sample project and upload it?
Thanks!

Edit:
Gus, there is a error in your sample code for the GPS extension.

Where it sais:


else
Debug.Print("Waiitng for valid GPS data");
Thread.Sleep(1000);

You forgot the brackets, so it should become:


else
{
Debug.Print("Waiting for valid GPS data");
}
Thread.Sleep(1000);


#9

Robert:

In this case, braces are not necessary. If braces are not present after an else statement then only the line after the else is executed.

But, in my opinion, it is good programming style to always include the braces.


#10

Not right Mike. If you do not use the brackets, the

Debug.Print("Waiting for valid GPS data");

gets displayed right after the GPS coördinates.

With a LCD shield, the causes the “Wa” to be displayed right after the data.

If you add a

FEZ_Shields.KeypadLCD.CursorHome();

then the

Debug.Print("Waiting for valid GPS data");

replaces the GPS data.

So brackets should absolutely be used in this case.
I agree with you to always use them. It can never go wrong that way.


#11

I have to disagree with your disagreement. :stuck_out_tongue:

As far as the syntax of C# is concerned the braces are not necessary.

If putting in the braces causes something to happen in your program, then you have a timing or race condition situation.

How does a Debug.Print interact with an LCD? Have you redirected debugging to the LCD?


#12

Yes sorry, I should not have placed Debug.Print(); for an lcd example… ::slight_smile:

Anyway. There is no failure in the code, the code is the example code. It works fine, but without the brackets the Wa of the word “Waiting” are placed behind the GPS data when you do not use the brackets.


#13

Does anyone know how to get meters?
It might be working with the code, but I do not quite understand it. Could someone create an example? :slight_smile:


#14

Hi Robert,

I could whip up something simple for you - what are you looking for? As for meters, it’s pretty easy… I can help you with that too. Google to the rescue :slight_smile:

Miles* 1.609344 = Kilometers / 1000 = meters

or

Feet * .3047999 = meters

-Mike


#15

Thank you, I would be looking for a simple demo solution which shows all the features of your code.

Just like the code from the brochure. A simple program which polls time and position of a satellite.
The returned position should then be displayed in debug or so, and possibly in meters.

I would be really happy if you could help me with that!!
If you like, we could discuss via email:

foekie01 ‘at’ hotmail ‘dot’ com

Thanks!


#16

A little note: I meant like getting the whole GPS data from the satellite.

Currently we get this coordinates:
NXX,XX - EXX,XX

Which are rounded numbers. (example: N50,50 - E50,50) but modern hand held devices output like this:

N50.321,50.123 where the 312 and 123 are meters.


#17

I’ll see what I can do… been crazy with work the last few days so not getting as much Fez play time this week as I thought :frowning: The output I currently do is is more along the lines of -70.123456 and 44.45789, dropping the E/W and N/S and increasing the resolution a bit . However it can easily be reformatted.

-Mike


#18

Actually I forgot I was working on this on Sunday night… it’s based on the Example code in the GPS brochure. I haven’t finished debugging it yet for 2 reasons… 1) It causes my Fez Domino to reboot randomly (but not the Cobra) and 2) for some odd reason one of the variables is getting stepped on in the GPS driver. In the GPS state machine, it correctly sets _data_is_valid = true (good data when I am debugging), it then in the next step while debugging when I do a getutcdatatime, _date_is_valid instantly becomes false. I’m wondering if some other packet is coming in before it has a chance to process the valid one for date/time… I hope to debug it a bit more this weekend, but to at least get you going:


using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.FEZ;

namespace FEZ_Cobra_Console_Application1
{
    public class Program
    {
        public static void Main()
        {
            
            FEZ_Extensions.GPS.Initialize("COM1", 19200);
            double LatHour, LatMinute, LongHour, LongMinute;
            bool North, East;
            
            double Latitude = 0;
            double Longitude = 0;
            
            Debug.Print("Waiting for valid GPS data...");
            while (true)
            {
                DateTime gpstime = FEZ_Extensions.GPS.GetUTCDateTime();
                if (gpstime != DateTime.MinValue)
                {
                    Debug.Print(gpstime.ToString());
                    break;
                }
                Thread.Sleep(1000);
            }
            Thread.Sleep(1000);
            while (true)
            {
                if (FEZ_Extensions.GPS.GetPosition(out LatHour, out LatMinute, out North, out LongHour, out LongMinute, out East))
                {
                    Latitude = FEZ_Extensions.GPS.convertLocation(LatHour, LatMinute, North);
                    Longitude = FEZ_Extensions.GPS.convertLocation(LongHour, LongMinute, East);
                    Debug.Print(FEZ_Extensions.GPS.GetUTCDateTime() + " Latitude: " + Latitude + " Longitude: " + Longitude);
                }
                else
                    Debug.Print("Waiting for valid GPS data");
                Thread.Sleep(1000);
            }

        }

    }
}



#19

Thank you! I do not know if I can try it out today, but I will absolutely try it out tomorrow! Thanks! :dance:


#20

I have tyco GPS that ouputs each 4 seconds the following string (4800)

$GPGSV,3,1,12,20,00,000,10,00,000,31,00,000,27,00,000,7C
$GPGSV,3,2,12,19,00,000,07,00,000,04,00,000,24,00,000,76
$GPGSV,3,3,12,16,00,000,28,00,000,26,00,000,29,00,000,78
$GPRMC,000401.063,V,010407,N
4F
$GPGGA,000402.076,0,00,M,0.0,M,0000
51
$GPGSA,A,1,1E
$GPRMC,000402.076,V,010407,N
48
$GPGGA,000403.064,0,00,M,0.0,M,0000
53
$GPGSA,A,1,*1E

Will your CLASS read it ?

Miguel