Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Store and forward GPS data for Smart Phones

0.00/5 (No votes)
28 Jul 2009 1  
How to deliver GPS data asynchronously to the server over the internet without queues.

mobstoreandforwardgpsdata/PhoneApp.jpg

Introduction

Store and Forward file based delivery for GPS data over internet for Smart Phones (Windows Mobile 5/6), without databases or queues. There are lots of good articles on how to receive and manipulate GPS data with a mobile device. In this article, I want to share how I send data from my Smart Phone to a server. This article is about client site code for a client - server application where the mobile device is just a tool to record GPS data and pass it to the server via the internet HTTP protocol.

Background

The server application will do whatever you need it to do with your GPS data. In my case, I simply display the phone location on the map and show the route for the past 24 hours. The client application for the Smart Phone can be downloaded at http://vvx.webhop.net/gps/gps.CAB or http://vvx.webhop.net/gps/index.htm. To view your device on the map, you can go to http://vvx.webhop.net/gps/map2.aspx.

Enter your device id (phone number) to see your mobile device on the map. Phone ID or phone number will be populated automatically and can be seen Configuration screen.

Initially, I was passing the data in a synchronous way to the server, trying to get the location close to real time (1-2 seconds delay based on connectivity). While doing so, I had many difficulties to pass the data to the server, especially when my signal was very low.

To fix this issue, I took another approach and wrote my code as "Store and Forward". All of the information from the GPS was written to a file, and another thread would send the data to the server. Prior to sending the data, the file was renamed and copied so nothing else would change it.

Using the code

Settings

mobstoreandforwardgpsdata/ConfigScreen.jpg

You can see the ErrorRead for ID. This screenshot is taken from a simulator and the code cannot recognize the SIM card, and an error is displayed.

This code is written to connect to the Bluetooth enabled GPS antenna. The COM port setting is stored in the configurations file as well as sent to the server in an interval in seconds, the default is 20 secs. On application start, I scan the phone for all available COM ports and load that data into the drop down box.

private void LoadConfig() {
    //discover the COM ports
    string[] sp = System.IO.Ports.SerialPort.GetPortNames();
    foreach (string s in sp) {
        ddlComPort.Items.Add(s.ToString());
    }
    ReadConfig();
}

The high level idea is to write all of the incoming data to a file and have another thread send the data asynchronously. If the data is sent successfully, the data will be deleted from the device drive. In order to make sure data is not modified, we need to make a copy of the data file before we send it.

Here is an example of how I build data delivery while the internet connection is unavailable.

Code from MainFrm.cs
delegate void WriteOutputDel(object txt);

public partial class MainFrm : Form {
    int buffersize = 1024;
    string comPort = "COM5";
    SerialPort sp = new SerialPort();
    StreamWriter swLogFile = null;
    Thread t;
    VVXGps.GPSWebService gpsSvc = null; //= new MobileGps.VVXGps.GPSWebService();
    string id = string.Empty;
    string ver = string.Empty;
    string msg = string.Empty;
    int sendInterval = 1000 * 10; //10sec

    public MainFrm() {
        try {
            InitializeComponent();
            Version v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
            ver = string.Format("Version:{0}.{1}.{2}.{3}\r\n", 
                                v.Major, v.Minor, v.Build, v.Revision);
            //msg += "Init\r\n";
            ReadConfig();
            //msg += "ReadConfig done\r\n";
            string logFile = Util.GetLogFile();
            msg += "log file directory:" + logFile + "\r\n";
            try {
                swLogFile = File.AppendText(logFile);
            } catch (Exception ioe) { msg += ioe.Message; }

                //msg += "Get LogFile Stream\r\n";
                gpsSvc = new MobileGps.VVXGps.GPSWebService();
               
                //msg += "Init gps service\r\n";
                lblOutput.Text = ver + msg;
               
            } catch (Exception ex) {
                lblOutput.Text = ver + "\r\n" + msg + ex.Message;
        }
    }

    private void ReadConfig() {
        try {
            XmlDocument xDoc = new XmlDocument();
            string fname = Util.GetConfigFile();
            if (string.IsNullOrEmpty(fname)) {
                Util.Reset();
                fname = Util.GetConfigFile();
            }
            xDoc.Load(fname);
            XmlNode comNode = xDoc.SelectSingleNode("//COM");
            if (comNode != null) {
                comPort = comNode.InnerText;
            }
            XmlNode idNode = xDoc.SelectSingleNode("//ID");
            if (idNode != null) {
                id = idNode.InnerText;
            }
            XmlNode sendNode = xDoc.SelectSingleNode("//SendInterval");
            if (sendNode != null) {
                sendInterval = Int32.Parse(sendNode.InnerText) * 1000;
            }
            //Start process of sending files.
            PickUpFileInput();
        } catch (Exception ex) {
            MessageBox.Show("Read Config error:\r\n" + ex.Message);
        }
    }

    private void PickUpFileInput() {
        Thread t = new Thread(new ThreadStart(GpsFileListner));
        t.Start();
        //Helper h = new Helper();
        //TimerCallback timeCB = new TimerCallback(h.CleanUpOldFiles);
        //System.Threading.Timer t = 
        //   new System.Threading.Timer(timeCB, null, 100, sendInterval);
    }

    private void GpsFileListner() {
        Helper h = new Helper();
        while (true) {
            h.CleanUpOldFiles(null);
            Thread.Sleep(sendInterval);
        }
    }

    private void mnuStart_Click(object sender, EventArgs e) {
        sp = new SerialPort(comPort);
//#if !DEBUG
        Connect(sp);
//#endif
        StartCOMPortListner();
    }

    void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) {
        WriteOutputDel del = new WriteOutputDel(WriteOut);
        Invoke(del, new object[] { sender });
        SerialData sd = e.EventType;
        //if (swLogFile != null)
        //    LogData(string.Format("Sender {0}\r\ne:{1}\r\nSize{2}", 
        //          sender.ToString(), e.ToString(), sd.ToString()));
    }

    private void WriteOut(object obj) {
        if (InvokeRequired) {
            WriteOutputDel del = new WriteOutputDel(WriteOut);
            Invoke(del, obj);
            return;
        }
        string s = obj.ToString();
        lblOutput.Text = s;
    }

    public bool Connect(SerialPort serialport) {
        if (!this.OpenSerialPort(serialport)) return false; //open a serial port           
        return true;
    }

    public bool OpenSerialPort(System.IO.Ports.SerialPort serialPort1) {
        try {
            if (serialPort1.IsOpen) serialPort1.Close();
            serialPort1.ReadBufferSize = buffersize;
            serialPort1.Open();
            return true;
        } catch (Exception ex) {
            MessageBox.Show("Cann't open serial port." + ex.Message);
            return false;
        }
    }

    private void StartCOMPortListner() {
        t = new Thread(new ThreadStart(Start));
        t.Start();
    }

    private void Start() {
        string line;
        WriteOutputDel del = new WriteOutputDel(WriteOut);
        while (true) {
            try {
                Thread.Sleep(500);
               line = sp.ReadLine();

                if (line != null) {
                    try {
                        if (line.StartsWith("$GPRMC")) {
                            Util.SaveToBatchSend(line);
                            Invoke(del, line);
                        }
                    } catch (Exception ex) {
                        Invoke(del, "Error in Start function:\n" + ex.Message);
                    }                      
                }
            } catch (Exception ex) {
                Invoke(del, "Read COM Port input Error");
                Thread.Sleep(1000);
            }
        }
    }
.......

The Helper class queries the Web Service asynchronously and deletes the file if data was delivered successfully.

 public class Helper {
    static VVXGps.GPSWebService gpsSvc = 
           new MobileGps.VVXGps.GPSWebService();
    private static object lockObj = new object();
    Thread t = null;

    internal void CleanUpOldFiles(object state) {
        try {
            lock (lockObj) {
                string batchfile = Util.GetBatchFile();
                long tick = DateTime.Now.Ticks;
                string tmpFile = batchfile + "." + tick + ".vvx";

                try {
                    File.Copy(batchfile, tmpFile, false);
                    try { File.Delete(batchfile); } catch { }
                    File.CreateText(batchfile);
                } catch { }

                string dir = Path.GetDirectoryName(
                  Assembly.GetExecutingAssembly().GetName().CodeBase);
                MobileGps.Config cnf = new MobileGps.Config();
                string id = cnf.GetPhoneId();
                AsyncCallback callBack = new AsyncCallback(ProcessServiceInformation);
                string[] files = Directory.GetFiles(dir);

                foreach (string s in files) {
                    if (s.EndsWith(".vvx")) {
                        string[] data = new string[] { };

                        using (StreamReader sr = File.OpenText(s)) {
                            data = sr.ReadToEnd().Split(new char[] { '\n', '\r' });
                            sr.Close();
                        }
                        try {
                            //Make sure we send it
                            IAsyncResult arg = 
                              gpsSvc.BeginRecordBatchData(data, id, callBack, s);
                            while (!arg.IsCompleted) {
                                Thread.Sleep(10);
                            }
                        } catch {
                            return;
                        }
                    }
                }
            }
        } catch (Exception ex) {
            Util.SaveGPSLog(ex.Message + "\n\r" + ex.StackTrace);
        }
    }


    void ProcessServiceInformation(IAsyncResult status) {
        try {
            if (status.IsCompleted) {
                try {
                    gpsSvc.EndRecordBatchData(status);
                    File.Delete((string)status.AsyncState);
                } catch (Exception x) {
                    //Util.SaveGPSLog("Error deleting file:" +
                    //        status.AsyncState + " \n" + x.Message);
                }
            }
            t.Abort();
        } catch (Exception ex) {

        }
    }
}

The function ProcessServiceInformation will throw an exception:

gpsSvc.EndRecordBatchData(status);

If the service call cannot be completed, in this case, our file will not be deleted and will be retried again.

History

The first time this code was written was over two years ago. This is the second version. The server side code will be published soon.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here