Windows Mobile下GPS管理软件NavsGo之GPS监控功能的开发

前言

这个软件,前前后后断断续续做了两个星期,现在做成一个基本的版本,把当前功能记录下来,作为进一步开发的基础。我取名为NavsGo等等原因主要是Nav表示Navigator的简称,是导航器的意思。Go是出发的意思。NavsGo,有点像Let's Go的谐音,一起走的意思, 一方面表示这个软件作为GPS管理软件,可以协助多个导航软件一起运作,另一方面也希望该软件和使用者“一起走”。

背景

当前几乎所有Windows Mobile机型都自带GPS接收器,GPS渐渐成为我们生活中必不可少的一部分,可是在Windows Mobile直接下使用GPS导航不是一件容易的事情,需要配置硬件端口,软件端口,配置GPS Intermediate Driver, 配置AGPS,有时候甚至要配置GPRS上网(某些导航软件需要连接网络,例如Google Maps),更有些软件需要一个分离端口(splitted port)。因此做一个GPS管理软件,统一管理各项配置功能,调用现有的各种GPS导航软件实现导航。

关于

本文讲述一个GPS管理软件的开发,当前版本软件功能包括调用Google Maps,Tomtom,Garmin,iGO8,Route66等导航软件,调用GPS端口配置,AGP,split port等配置功能,基于GPS.net 3.0开发GPS诊断功能和GPS监控功能。界面上实现类iPhone菜单界面。在这篇文章中主要讲述GPS监控功能的实现。

功能

先展现一下系统的功能截图,有个整体的感性认识。

NavsGo1

图1
这是系统的整体导航菜单,系统会自动检测已经安装的导航软件,如上图检测到Google Maps,Tomtom,Garmin,iGO8,Route66等导航软件。

NavsGo2 NavsGo3
图2
Google Maps,我心目中的NO.1,哪怕在室内也可以根据基站信息定位,是其他离线软件没有实现的。同时支持街景(Stree View)功能,交通状况功能,大大方便寻找,还有定位你自己的功能,把用户当前的位置信息通知朋友,甚至可以直接显示到facebook上 。反正功能多到超出想象,在无线网络日益完善的未来,Google Maps这类在线导航软件必定成为霸主,MS live map也属于这一类软件,大家拭目以待。缺点是需要上网费用,有些地图位置定位不够准确。

NavsGo4 NavsGo5
图3
Tomtom,平常一直在用,导航功能很不错,地图数据也比较准确,界面和易用性方面需要提高。

NavsGo6 NavsGo7
图4
iGO8,界面和易用性一流,可是导航地图信息有时候有点问题,导致我老是U-turn。
 NavsGo9  

图5
Garmin,老牌GPS厂商,本人做的工业GPS产品一直使用Garmin的硬件,Garmin做产品有保障。关于Garmin的导航软件,我感觉易用性没有iGO8好,导航又比不上Tomtom。

NavsGo10
图6
Route66,用的不多,不好评价。

NavsGo12

图7
Port Splitter,一个免费软件,有些导航软件需要开了Port Splitter才能找到GPS receiver(GPS 硬件接收器)。以后版本打算把这个功能加入到NavsGo中,使用virtual com port的开发。

NavsGo19
图8
端口管理和GPS Intermediate Driver管理,以后版本打算把把这个功能加入到NavsGo中。

NavsGo11
图9
AGPS管理,以后版本打算把把这个功能加入到NavsGo中。

NavsGo13NavsGo18
图10
GPS监控功能,后面讲述如何实现。
NavsGo20

图11
GPS诊断功能,后面讲述如何实现。

以上各个导航软件的评价只是个人使用感觉,不代表各个导航软件的真实情况。

实现

GPS诊断功能和GPS监控功能是基于GPS.net 3.0实现的,关于GPS.net 3.0可以参考 GPS.NET 和 GeoFramework开源了。我觉得GPS.net是目前最好的基于.NET开发的GPS开源软件,所以我使用GPS.net作为NavsGo的基础。GPS.net 3.0比GPS.net 2.0做了很大改动,比如说把类结构进行大规模的调整,把NMEA分析功能实行简化,使用了泛型(Generic)封装异常处理等等。也导致了部分功能在2.0可用,到了3.0却不可以用,例如卫星控件。可能是我使用不当,也可能是源代码的问题。下面讲述如何使用GPS.net 3.0做一个GPS监控功能。

GPS设备检测功能

           /* GPS.NET provides the ability to quickly discover GPS devices on the
* local system. This feature is known as "Automatic Device Discovery"
* and greatly simplifies the task of finding a GPS device to communicate
* with. Developers can simply call the "Start" method of an interpreter,
* and GPS.NET will automatically locate the best GPS Device to work with.
*
* During Device Discovery, several events are raised to indicate the
* progress of detection. This example hooks into almost all of the events
* to give users feedback.
*/
GeoFramework.Gps.IO.Devices.DeviceDetectionStarted += new EventHandler(Devices_DeviceDetectionStarted);
GeoFramework.Gps.IO.Devices.DeviceDetected += new EventHandler<GeoFramework.Gps.IO.DeviceEventArgs>(Devices_DeviceDetected);
GeoFramework.Gps.IO.Devices.DeviceDetectionCompleted += new EventHandler(Devices_DeviceDetectionCompleted);
GeoFramework.Gps.IO.Devices.DeviceDetectionAttempted += new EventHandler<GeoFramework.Gps.IO.DeviceEventArgs>(Devices_DeviceDetectionAttempted);
GeoFramework.Gps.IO.Devices.DeviceDetectionAttemptFailed += new EventHandler<GeoFramework.Gps.IO.DeviceDetectionExceptionEventArgs>(Devices_DeviceDetectionAttemptFailed);

GPS.net可以检测当前设备上的所有端口,这些端口包括GPS Intermediate Driver端口,普通串口,蓝牙串口等等,这个过程是使用多线程在后台运行的,为了处理检测结果,需要注册相关的事件,上面为注册过程,下面是处理函数。关于GPS Intermediate Driver可以参考 30 Days of .NET [Windows Mobile Applications] - Day 03: GPS Compass(GPS指南针)

       #region GPS Device Detection Events
private void SearchButton_Click(object sender, EventArgs e)
{
// Start the GPS device detection process. This operation will complete
// in the background, on a separate thread. This method can be called again
// to attempt to locate new devices.
GeoFramework.Gps.IO.Devices.BeginDetection();
}

private void Devices_DeviceDetectionStarted(object sender, EventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Detecting GPS devices...";
SearchButton.Enabled = false;
}));
}

private void Devices_DeviceFailure(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Not Responding: " + e.Device.ToString();
}));
}

private void Devices_DeviceDetectionAttemptFailed(object sender, GeoFramework.Gps.IO.DeviceDetectionExceptionEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Error: " + e.Exception.ToString();
}));
}

private void Devices_DeviceDetectionCompleted(object sender, EventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Device detection completed.";
SearchButton.Enabled = true;
StartButton.Enabled = true;
}));
}

private void Devices_DeviceDetectionAttempted(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Detecting " + e.Device.ToString();
}));
}

private void Devices_DeviceDetected(object sender, GeoFramework.Gps.IO.DeviceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
StatusBar1.Text = "Found Device: " + e.Device.ToString();

// Add it to the list box
ListViewItem DeviceItem = new ListViewItem(e.Device.ToString());
// Associate the detected device with this list view item, so we can connect
// to the device later on
DeviceItem.Tag = e.Device;
// Set the image of the new item
DeviceItem.ImageIndex = 0;
DevicesListView.Items.Add(DeviceItem);
}));
}
#endregion


当Devices_DeviceDetected被回调的时候表示有设备被检查出来,并把之加入列表框供用户选择。
由于使用多线程的关系,所有的界面更新需要使用Delegate,该deleagate定义如下:

     /* The GPS.NET device detection process is multithreaded.  As a result, we must
* use the Invoke and BeginInvoke methods to ensure that any updates to the form
* occur on the form's own thread. This delegate is used by such methods.
*/
public delegate void MethodInvoker();

监视GPS端口运行情况

检测当前设备上所有可用GPS端口后,可以绑定监视某个端口,使用NMEA解释器分析该端口的输出。下面是启动和停止监视某个端口的代码。

         private void StartButton_Click(object sender, EventArgs e)
{
if (DevicesListView.SelectedIndices.Count == 0)
{
MessageBox.Show("Please Select the device to start.");
return;
}
// Find out which item is selected in the list view
ListViewItem SelectedItem = DevicesListView.Items[DevicesListView.SelectedIndices[0]];

// The "tag" property holds the device that was detected
Device selectedDevice = (Device)SelectedItem.Tag;

try
{
// Finally, start the interpreter using the newly-assigned Stream
Interpreter.Start(selectedDevice);

selectedDevice.Connecting += new EventHandler(Device_Connecting);
selectedDevice.Connected += new EventHandler(Device_Connected);
selectedDevice.Disconnecting += new EventHandler(Device_Disconnecting);
selectedDevice.Disconnected += new EventHandler(Device_Disconnected);

StartButton.Enabled = false;
// Enable the disconnect button
StopButton.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Unable to Connect", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
}
}

private void StopButton_Click(object sender, EventArgs e)
{
// Stop any GPS communications
Interpreter.Stop();

StartButton.Enabled = true;
// And disable the disconnect button
StopButton.Enabled = false;
StatusBar1.Text = "Disconnected from GPS device.";
}

通过Interpreter.Start(selectedDevice);把选择的GPS设备端口绑定到NMEA解释器上,这样解释器就会分析该选定端口输出的数据了。同时注册该GPS设备建立链接和端口链接的事件。
当NMEA解释器对指定端口的数据进行分析的时候,可以订阅相关的事件对关心的数据进行处理,下面为NMEA解释器事件的订阅代码。

            Interpreter.SpeedChanged += new System.EventHandler<GeoFramework.SpeedEventArgs>(Interpreter_SpeedChanged);
Interpreter.FixAcquired += new System.EventHandler(Interpreter_FixAcquired);
Interpreter.FixLost += new System.EventHandler(Interpreter_FixLost);
Interpreter.Resumed += new EventHandler(Interpreter_Resumed);
Interpreter.FixModeChanged += new System.EventHandler<GeoFramework.Gps.FixModeEventArgs>(Interpreter_FixModeChanged);
Interpreter.FixQualityChanged += new System.EventHandler<GeoFramework.Gps.FixQualityEventArgs>(Interpreter_FixQualityChanged);
Interpreter.HorizontalDilutionOfPrecisionChanged += new System.EventHandler<GeoFramework.Gps.DilutionOfPrecisionEventArgs>(Interpreter_HorizontalDilutionOfPrecisionChanged);
Interpreter.SentenceReceived += new System.EventHandler<GeoFramework.Gps.Nmea.NmeaSentenceEventArgs>(Interpreter_SentenceReceived);
Interpreter.AltitudeChanged += new System.EventHandler<GeoFramework.DistanceEventArgs>(Interpreter_AltitudeChanged);
Interpreter.PositionChanged += new System.EventHandler<GeoFramework.PositionEventArgs>(Interpreter_PositionChanged);
Interpreter.UtcDateTimeChanged += new System.EventHandler<GeoFramework.DateTimeEventArgs>(Interpreter_UtcDateTimeChanged);
Interpreter.Paused += new EventHandler(Interpreter_Paused);
Interpreter.BearingChanged += new System.EventHandler<GeoFramework.AzimuthEventArgs>(Interpreter_BearingChanged);
Interpreter.VerticalDilutionOfPrecisionChanged += new System.EventHandler<GeoFramework.Gps.DilutionOfPrecisionEventArgs>(Interpreter_VerticalDilutionOfPrecisionChanged);
Interpreter.SatellitesChanged += new EventHandler<SatelliteListEventArgs>(Interpreter_SatellitesChanged);

            
下面为部分事件处理代码.

        void Interpreter_SentenceReceived(object sender, GeoFramework.Gps.Nmea.NmeaSentenceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Keep the list box from getting too big
if (RawDataListBox.Items.Count > 50)
RawDataListBox.Items.RemoveAt(0);

// Add the sentence to the raw data list box
RawDataListBox.Items.Add(e.Sentence.ToString());
RawDataListBox.SelectedIndex = RawDataListBox.Items.Count - 1;
}));
}

当NMEA解释器从指定端口中接收到Raw NMEA data时,会回调Interpreter_SentenceReceived,系统会把这些Raw NMEA data打印到列表框中。

时间变化

void Interpreter_UtcDateTimeChanged(object sender, GeoFramework.DateTimeEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Update the current satellite-derived time
SatelliteTime.Text = e.DateTime.ToLocalTime().ToString();
}));
}

时间发生变化时进行回调,一般来说,设备会在每一秒或者每两秒钟输出NMEA data,重要的NMEA句子都会包含GPS时间的字段,关于NMEA可以参考.NET Compact Framework下的GPS NMEA data数据分析。这个输入时间的间隔和硬件有关,是可以设置的。事实上,GPS时间和UTC时间是有时间差的,目前(2009年)GPS时间比UTC时间快15秒,但是做应用开发一般不需要处理这个时间差,GPS硬件厂商的固件(Firmware)在输出NMEA data的时候已经进行调整,可以认为NMEA data的时间是UTC时间。

经度和纬度变化

void Interpreter_PositionChanged(object sender, GeoFramework.PositionEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
// Update the latitude and longitude
Latitude.Text = e.Position.Latitude.ToString();
Longitude.Text = e.Position.Longitude.ToString();
StatusBar1.Text = "Position has changed.";
}));
}

经度和纬度信息发生变化时调用。

  NavsGo14   

海拔变化

void Interpreter_AltitudeChanged(object sender, GeoFramework.DistanceEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
altimeter1.Value = e.Distance;
// Update the current altitude
Altitude.Text = e.Distance.ToLocalUnitType().ToString();
StatusBar1.Text = "Altitude has changed.";
}));
}

海拔发生变化时调用,同时更新海拔计控件的信息。

NavsGo17

方位变化

void Interpreter_BearingChanged(object sender, GeoFramework.AzimuthEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
compass1.Value = e.Azimuth;

// Output the current bearing as degrees (i.e. 012°)
Bearing.Text = e.Azimuth.ToString();
// Output the current bearing as a compass direction (i.e. Southwest)
Direction.Text = e.Azimuth.ToString("c");
StatusBar1.Text = "Bearing has changed.";
}));
}

方位发生变化时调用,同时更新指南针控件的信息。

NavsGo15

速度变化

void Interpreter_SpeedChanged(object sender, GeoFramework.SpeedEventArgs e)
{
BeginInvoke(new MethodInvoker(delegate()
{
speedometer1.Value = e.Speed;

// Update the current speed
Speed.Text = e.Speed.ToLocalUnitType().ToString();
StatusBar1.Text = "Speed has changed.";
}));
}

速度发生变化时调用,同时更新速度表控件的信息。

NavsGo16

控件的使用

GPS.net 3.0提供了速度计,海拔计,指南针,卫星方位图和卫星信号图等控件。在上面的图可见,我使用了 速度计,海拔计,指南针等控件,可是我在使用卫星方位图和卫星信号图时发现些问题,我正在联系原作者帮助,可能是我使用的不对,也可能是GPS.net 3.0的bug,我会在下一个版本解决。

使用GPS.net 3.0的控件十分简单,只要把控件拖拉到winform里面,同时修改控件的IsPaintingOnSeparateThread属性为false。

compass1.IsPaintingOnSeparateThread = false;
altimeter1.IsPaintingOnSeparateThread = false;
speedometer1.IsPaintingOnSeparateThread = false;
//satelliteViewer1.IsPaintingOnSeparateThread = false;
//satelliteSignalBar1.IsPaintingOnSeparateThread = false;

在目前版本,如果不把IsPaintingOnSeparateThread修改为false会抛出一个多线程的异常信息。

at Microsoft.AGL.Common.MISC.HandleAr(PAL_ERROR ar)
at System.Windows.Forms.Control.get_Visible()
at GeoFramework.Drawing.DoubleBufferedControl.Repaint()
at GeoFramework.Drawing.DoubleBufferedControl.PaintingThreadProc()

这个问题我正在处理。
 

To-Do-List

当前只是第一个版本,有些功能需要继续不断完善,待完善功能如下:
1.实现端口管理功能。
2.实现AGPS功能。
3.实现检查飞行模式功能,因为飞行模式下GPS不能使用。
4.实现管理GPRS,3G网络链接功能。
5.在GPS监控模块增加对卫星的监控。
6.实现保存导航信息到Google maps及其他地图格式的功能。
7.优化UI处理。
  ......

关于项目的发展

我把项目host到codeplex了,打算不断改进。项目主页链接如下:

NavsGo - GPS management software

下载当前版本的源代码链接如下

 

http://navsgo.codeplex.com/SourceControl/ListDownloadableCommits.aspx#DownloadLatest

 

检查和下载最新版本链接如下

http://navsgo.codeplex.com/SourceControl/ListDownloadableCommits.aspx

安装包  NavsGo.zip 由于博客园不能直接上传cab文件,请解压成 NavsGo.cab,然后拷贝到Windows Mobile设备上安装,谢谢。

下篇文章会讲一下GPS诊断功能的开发,然后讲类iPhone界面的开发。

posted @ 2009-08-19 07:19 Jake Lin 阅读(...) 评论(...) 编辑 收藏