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
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() {
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; string id = string.Empty;
string ver = string.Empty;
string msg = string.Empty;
int sendInterval = 1000 * 10;
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);
ReadConfig();
string logFile = Util.GetLogFile();
msg += "log file directory:" + logFile + "\r\n";
try {
swLogFile = File.AppendText(logFile);
} catch (Exception ioe) { msg += ioe.Message; }
gpsSvc = new MobileGps.VVXGps.GPSWebService();
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;
}
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();
}
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);
Connect(sp);
StartCOMPortListner();
}
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) {
WriteOutputDel del = new WriteOutputDel(WriteOut);
Invoke(del, new object[] { sender });
SerialData sd = e.EventType;
}
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; 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 {
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) {
}
}
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.